From 6c3399293950ae09363b0a4a23e392f579425b5b Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 1 Dec 2025 11:31:49 -0800 Subject: [PATCH 001/277] 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 ff4ea0d58ac1b76a8a637b858d7b013c161081d9 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:19 -0600 Subject: [PATCH 002/277] 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 003/277] 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 58de43e1941183e03db917df39c6dbf78107d208 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:09 -0600 Subject: [PATCH 004/277] 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 005/277] 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 006/277] 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 71e5828e7d37d8cdd87239eff51599f9e8610fe1 Mon Sep 17 00:00:00 2001 From: Alec Lorimer Date: Mon, 17 Nov 2025 15:25:04 -0600 Subject: [PATCH 007/277] 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 314b6627c61e3ff6ca07d4ffb0848ae946565a71 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 24 Feb 2026 13:52:12 -0800 Subject: [PATCH 008/277] 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 009/277] 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 010/277] 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 011/277] 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 48451c814f036d0c718d0ab865ed21e5e299d7af Mon Sep 17 00:00:00 2001 From: cjswedes Date: Tue, 24 Feb 2026 15:53:44 -0600 Subject: [PATCH 012/277] 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 013/277] 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 014/277] 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 015/277] 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 016/277] 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 017/277] 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 018/277] 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 019/277] 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 020/277] 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 021/277] 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 022/277] 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 023/277] 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 024/277] 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 025/277] 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 026/277] 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 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 027/277] 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 028/277] 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 029/277] 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 030/277] 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 031/277] 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 032/277] 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 033/277] 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 034/277] 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 035/277] 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 036/277] 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 037/277] 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 038/277] 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 039/277] 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 040/277] 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 041/277] 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 042/277] 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 043/277] [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 044/277] 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 045/277] 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 046/277] 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 047/277] 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 e9f7098d9804278b15115da035dd4c7a5b31d245 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Tue, 3 Mar 2026 11:13:57 -0800 Subject: [PATCH 048/277] 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 049/277] 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 050/277] 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 051/277] 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 052/277] 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 053/277] 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 054/277] 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 055/277] 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 056/277] 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 057/277] 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 058/277] 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 059/277] 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 060/277] 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 061/277] [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 062/277] 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 063/277] 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 064/277] 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 065/277] 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 066/277] 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 067/277] 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 068/277] 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 069/277] 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 070/277] 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 071/277] 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 072/277] 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 073/277] 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 074/277] 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 075/277] 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 076/277] 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 077/277] 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 078/277] 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 079/277] 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 080/277] 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 081/277] 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 082/277] 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 083/277] 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 084/277] 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 085/277] 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 086/277] 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 087/277] 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 088/277] 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 089/277] 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 090/277] 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 091/277] 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 092/277] 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 093/277] 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 094/277] 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 095/277] 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 096/277] 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 097/277] 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 098/277] 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 099/277] 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 100/277] 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 101/277] 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 102/277] 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 103/277] 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 104/277] 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 105/277] 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 106/277] 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 107/277] 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 108/277] 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 109/277] 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 110/277] 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 111/277] 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 112/277] 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 113/277] 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 114/277] 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 115/277] 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 116/277] 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 117/277] 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 118/277] 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 119/277] 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 120/277] 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 121/277] 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 122/277] 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 123/277] 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 124/277] 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 125/277] 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 126/277] 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 127/277] 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 128/277] 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 129/277] 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 130/277] 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 131/277] 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 132/277] 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 133/277] 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 134/277] 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 135/277] 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 136/277] 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 137/277] 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 138/277] 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 139/277] 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 140/277] 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 141/277] 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 142/277] 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 143/277] 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 144/277] 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 145/277] 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 146/277] 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 147/277] 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 148/277] 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 149/277] 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 150/277] 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 151/277] 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 152/277] 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 153/277] 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 154/277] 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 155/277] 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 156/277] 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 157/277] 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 158/277] 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 159/277] 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 160/277] 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 161/277] 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 162/277] 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 163/277] 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 164/277] 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 165/277] 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 166/277] 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 167/277] 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 168/277] 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 169/277] 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 170/277] 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 171/277] 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 172/277] 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 173/277] 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 174/277] 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 175/277] 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 176/277] 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 177/277] 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 178/277] 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 179/277] 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 180/277] 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 181/277] 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 182/277] 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 183/277] 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 184/277] 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 185/277] 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 186/277] 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 187/277] 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 188/277] 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 189/277] 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 190/277] 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 191/277] 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 192/277] 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 193/277] 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 194/277] 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 195/277] 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 196/277] 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 197/277] 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 198/277] 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 199/277] 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 200/277] 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 201/277] 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 202/277] 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 203/277] 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 204/277] 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 205/277] 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 206/277] 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 207/277] 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 208/277] 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 209/277] 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 210/277] 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 211/277] 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 212/277] 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 213/277] 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 214/277] 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 215/277] 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 216/277] 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 217/277] 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 218/277] 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 219/277] 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 220/277] 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 221/277] 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 222/277] 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 223/277] 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 224/277] 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 225/277] 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 226/277] 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 227/277] 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 228/277] 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 229/277] 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 230/277] 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 231/277] 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 232/277] 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 233/277] 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 234/277] 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 235/277] 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 236/277] [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 237/277] 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 238/277] 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 239/277] 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 240/277] 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 241/277] 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 242/277] 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 243/277] 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 244/277] 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 245/277] 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 246/277] 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 247/277] 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 248/277] 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 249/277] 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 250/277] 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 251/277] 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 252/277] 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 253/277] 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 254/277] 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 255/277] 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 256/277] 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 257/277] 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 258/277] 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 259/277] 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 260/277] 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 261/277] 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 262/277] 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 263/277] 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 264/277] 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 265/277] 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 266/277] 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 267/277] 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 268/277] 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 269/277] 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 270/277] 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 271/277] 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 272/277] 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 273/277] 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 274/277] 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 275/277] 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 276/277] 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 408c459e8ee797d754c07eb8ecdb8171ec97ee57 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 277/277] 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"