From 7b38f82ddafed1134a8e31fb255f34d607536687 Mon Sep 17 00:00:00 2001 From: seungkwan-choi <136560804+seungkwan-choi@users.noreply.github.com> Date: Thu, 14 Mar 2024 23:09:42 +0900 Subject: [PATCH 1/5] Create to Zigbee fan for SLED (#1115) * Add Zigbee fan Add Zigbee fan * Update configurations.lua change date * Delete drivers/SmartThings/zigbee-fan/README.md Delete README file * Update config.yml generic in this config * Update init.lua change code * Update config.yml dfd * Update config.yml Modify config file to eneric * Update itm-fan-light.yml Changing profile * Update fingerprints.yml changing fingerprints * Update init.lua changing init file * Update configurations.lua change year * Update init.lua change year * Update init.lua change year * Create test_fan_light.lua Added the test code for the fan light * Update test_fan_light.lua update test code * Update init.lua update init code * Update init.lua update init code * Update test_fan_light.lua update test fan light code * Update init.lua update init code * Update and rename itm-fan-light.yml to fan-light.yml update profiles code(revise using multi-component) * Delete drivers/SmartThings/zigbee-fan/profiles/switch-level.yml Delete child device * Update fingerprints.yml update fingerprint(name change) * Update test_fan_light.lua update test code * Update init.lua Update init file(name change) * Update and rename init.lua to init.lua Update and rename init file (Revise using multi-component) * Update init.lua update init (update change in fan speed status value * Update test_fan_light.lua update test code (changes in test code due to changes in init code) * Update init.lua I have modified contents as per your request. Please review it. * Update test_fan_light.lua I have modified the test code to match init. * Update fan-light.yml I have modified the speed level to 3. --------- Co-authored-by: lelandblue <79465613+lelandblue@users.noreply.github.com> --- drivers/SmartThings/zigbee-fan/config.yml | 7 + .../SmartThings/zigbee-fan/fingerprints.yml | 7 + .../zigbee-fan/profiles/fan-light.yml | 34 ++ .../zigbee-fan/src/configurations.lua | 66 +++ .../zigbee-fan/src/fan-light/init.lua | 119 +++++ drivers/SmartThings/zigbee-fan/src/init.lua | 47 ++ .../zigbee-fan/src/test/test_fan_light.lua | 447 ++++++++++++++++++ 7 files changed, 727 insertions(+) create mode 100644 drivers/SmartThings/zigbee-fan/config.yml create mode 100644 drivers/SmartThings/zigbee-fan/fingerprints.yml create mode 100644 drivers/SmartThings/zigbee-fan/profiles/fan-light.yml create mode 100644 drivers/SmartThings/zigbee-fan/src/configurations.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/fan-light/init.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/init.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua diff --git a/drivers/SmartThings/zigbee-fan/config.yml b/drivers/SmartThings/zigbee-fan/config.yml new file mode 100644 index 0000000000..14d5bac020 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/config.yml @@ -0,0 +1,7 @@ +name: 'Zigbee Fan' +packageKey: 'zigbee-fan' +permissions: + zigbee: {} +description: "SmartThings driver for zigbee fan devices" +vendorSupportInformation: "https://support.smartthings.com" + diff --git a/drivers/SmartThings/zigbee-fan/fingerprints.yml b/drivers/SmartThings/zigbee-fan/fingerprints.yml new file mode 100644 index 0000000000..64b64c6258 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/fingerprints.yml @@ -0,0 +1,7 @@ +zigbeeManufacturer: + # FAN LIGHT + - id: "Samsung/SAMSUNG-ITM-Z-003" + deviceLabel: Fan light + manufacturer: Samsung Electronics + model: SAMSUNG-ITM-Z-003 + deviceProfileName: fan-light diff --git a/drivers/SmartThings/zigbee-fan/profiles/fan-light.yml b/drivers/SmartThings/zigbee-fan/profiles/fan-light.yml new file mode 100644 index 0000000000..259a0f7dbb --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/profiles/fan-light.yml @@ -0,0 +1,34 @@ +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 diff --git a/drivers/SmartThings/zigbee-fan/src/configurations.lua b/drivers/SmartThings/zigbee-fan/src/configurations.lua new file mode 100644 index 0000000000..c2cbb57de0 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/configurations.lua @@ -0,0 +1,66 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local clusters = require "st.zigbee.zcl.clusters" + +local OnOff = clusters.OnOff +local Level = clusters.Level +local FanControl = clusters.FanControl + +local devices = { + ITM_FAN_LIGHT = { + FINGERPRINTS = { + { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-003" }, + }, + CONFIGURATION = { + { + cluster = OnOff.ID, + attribute = OnOff.attributes.OnOff.ID, + minimum_interval = 0, + maximum_interval = 600, + data_type = OnOff.attributes.OnOff.base_type + }, + { + cluster = Level.ID, + attribute = Level.attributes.CurrentLevel.ID, + minimum_interval = 1, + maximum_interval = 600, + data_type = Level.attributes.CurrentLevel.base_type, + reportable_change = 1 + }, + { + cluster = FanControl.ID, + attribute = FanControl.attributes.FanMode.ID, + minimum_interval = 1, + maximum_interval = 600, + data_type = FanControl.attributes.FanMode.base_type + } + } + }, +} + +local configurations = {} + +configurations.get_device_configuration = function(zigbee_device) + for _, device in pairs(devices) do + for _, fingerprint in pairs(device.FINGERPRINTS) do + if zigbee_device:get_manufacturer() == fingerprint.mfr and zigbee_device:get_model() == fingerprint.model then + return device.CONFIGURATION + end + end + end + return nil +end + +return configurations diff --git a/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua b/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua new file mode 100644 index 0000000000..95329c1ddf --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua @@ -0,0 +1,119 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local FanControl = clusters.FanControl +local Level = clusters.Level +local OnOff = clusters.OnOff + +local FINGERPRINTS = { + { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-003" }, +} + +local function can_handle_itm_fanlight(opts, driver, device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +-- CAPABILITY HANDLERS + +local function on_handler(driver, device, command) + if command.component == 'light' then + device:send(OnOff.server.commands.On(device)) + else + local speed = device:get_field('LAST_FAN_SPD') or 1 + device:send(FanControl.attributes.FanMode:write(device, speed)) + end + device:send(FanControl.attributes.FanMode:read(device)) +end + +local function off_handler(driver, device, command) + if command.component == 'light' then + device:send(OnOff.server.commands.Off(device)) + else + device:send(FanControl.attributes.FanMode:write(device, FanControl.attributes.FanMode.OFF)) + end + device:send(FanControl.attributes.FanMode:read(device)) +end + +local function switch_level_handler(driver, device, command) + local level = math.floor(command.args.level/100.0 * 254) + device:send(Level.server.commands.MoveToLevelWithOnOff(device, level, command.args.rate or 0xFFFF)) +end + +local function fan_speed_handler(driver, device, command) + device:send(FanControl.attributes.FanMode:write(device, command.args.speed)) + device:send(FanControl.attributes.FanMode:read(device)) +end + +-- ZIGBEE HANDLERS + +local function zb_fan_control_handler(driver, device, value, zb_rx) + device:emit_event(capabilities.fanSpeed.fanSpeed(value.value)) + local evt = capabilities.switch.switch(value.value > 0 and 'on' or 'off', { visibility = { displayed = true } }) + device:emit_component_event(device.profile.components.main, evt) + device:emit_component_event(device.profile.components.main, capabilities.fanSpeed.fanSpeed(value.value)) + if value.value > 0 then + device:set_field('LAST_FAN_SPD', value.value, {persist = true}) + end +end + +local function zb_level_handler(driver, device, value, zb_rx) + local evt = capabilities.switchLevel.level(math.floor((value.value / 254.0 * 100) + 0.5)) + device:emit_component_event(device.profile.components.light, evt) +end + +local function zb_onoff_handler(driver, device, value, zb_rx) + local attr = capabilities.switch.switch + local evt = value.value and attr.on() or attr.off() + device:emit_component_event(device.profile.components.light, evt) +end + +local itm_fan_light = { + NAME = "ITM Fan Light", + zigbee_handlers = { + attr = { + [FanControl.ID] = { + [FanControl.attributes.FanMode.ID] = zb_fan_control_handler + }, + [Level.ID] = { + [Level.attributes.CurrentLevel.ID] = zb_level_handler + }, + [OnOff.ID] = { + [OnOff.attributes.OnOff.ID] = zb_onoff_handler + } + } + }, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = on_handler, + [capabilities.switch.commands.off.NAME] = off_handler, + }, + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_handler + }, + [capabilities.fanSpeed.ID] = { + [capabilities.fanSpeed.commands.setFanSpeed.NAME] = fan_speed_handler + } + }, + can_handle = can_handle_itm_fanlight +} + +return itm_fan_light + + diff --git a/drivers/SmartThings/zigbee-fan/src/init.lua b/drivers/SmartThings/zigbee-fan/src/init.lua new file mode 100644 index 0000000000..eff62434ef --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/init.lua @@ -0,0 +1,47 @@ +-- 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 ZigbeeDriver = require "st.zigbee" +local defaults = require "st.zigbee.defaults" +local configurationMap = require "configurations" + +local device_init = function(self, device) + local configuration = configurationMap.get_device_configuration(device) + if configuration ~= nil then + for _, attribute in ipairs(configuration) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end + end +end + +local zigbee_fan_driver = { + supported_capabilities = { + capabilities.switch, + capabilities.switchLevel, + capabilities.fanspeed + }, + sub_drivers = { + require("fan-light") + }, + lifecycle_handlers = { + init = device_init + } +} + +defaults.register_for_default_handlers(zigbee_fan_driver,zigbee_fan_driver.supported_capabilities) +local fan = ZigbeeDriver("zigbee-fan", zigbee_fan_driver) +fan:run() + diff --git a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua new file mode 100644 index 0000000000..f83a33074b --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua @@ -0,0 +1,447 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +local test = require "integration_test" +local 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 FanControl = clusters.FanControl +local OnOff = clusters.OnOff +local Level = clusters.Level + +-- create test device (Multifunction) +local mock_base_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("fan-light.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Samsung Electronics", + model = "SAMSUNG-ITM-Z-003", + server_clusters = { 0x0000, 0x0003, 0x0006, 0x0008, 0x0202, 0x0300 } + } + }, + fingerprinted_endpoint_id = 0x01 + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_base_device) + zigbee_test_utils.init_noop_health_check_timer() +end +test.set_test_init_function(test_init) + +-- create test commands +test.register_message_test( + " Light switchLevel command : 100% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switchLevel", component = "light", + command = "setLevel", args = { 100, 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff + (mock_base_device, 254, 0) } + } + } +) + +test.register_message_test( + " Light switchLevel command : 50% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switchLevel", component = "light", + command = "setLevel", args = { 50, 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff + (mock_base_device, 127, 0) } + } + } +) + +test.register_message_test( + " Light switchLevel command : 50% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switchLevel", component = "light", + command = "setLevel", args = { 50, 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff + (mock_base_device, 127, 0) } + } + } +) + +test.register_message_test( + " Light switchLevel command : 0% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switchLevel", component = "light", + command = "setLevel", args = { 0, 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff + (mock_base_device, 0, 0) } + } + } +) + +test.register_message_test( + " Light OnOff command : Off ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switch", component = "light", command = "off", + args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, OnOff.server.commands.Off(mock_base_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " Light OnOff command : On ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switch", component = "light", command = "on", + args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, OnOff.server.commands.On(mock_base_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " Light switch attribute : on ", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, OnOff.attributes.OnOff: + build_test_attr_report(mock_base_device, true):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switch.switch.on()) + } + } +) + +test.register_message_test( + " Light switch attribute : off ", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, OnOff.attributes.OnOff: + build_test_attr_report(mock_base_device, false):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switch.switch.off()) + } + } +) + +test.register_message_test( + " Light level attribute : 100% ", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, Level.attributes.CurrentLevel: + build_test_attr_report(mock_base_device, 254):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(100)) + } + } +) + +test.register_message_test( + " Light level attribute : 50% ", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, Level.attributes.CurrentLevel: + build_test_attr_report(mock_base_device, 127):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(50)) + } + } +) + +test.register_message_test( + " Light level attribute : 0%", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, Level.attributes.CurrentLevel: + build_test_attr_report(mock_base_device, 0):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(0)) + } + } +) + +test.register_message_test( + " FanSpeed command : 0% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "fanSpeed", command = "setFanSpeed", args = { 0 } } } + }, + { + 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.register_message_test( + " 'FanSpeed command : Low ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "fanSpeed", command = "setFanSpeed", args = { 1 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 0x01) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " 'FanSpeed command : High ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "fanSpeed", command = "setFanSpeed", args = { 3 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 0x03) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " FanSpeed attribute : LOW", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 1):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.on({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(1)) + } + } +) + +test.register_message_test( + " FanSpeed attribute : Middle", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 2):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(2)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.on({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(2)) + } + } +) + +test.register_message_test( + " FanSpeed attribute : High", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 3):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(3)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.on({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(3)) + } + } +) + +test.register_message_test( + " Fan control attribute : On", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 4):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(4)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.on({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(4)) + } + } +) + +test.register_message_test( + " Fan control attribute : Off", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 0):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.off({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0)) + } + } +) + +test.run_registered_tests() From ae399f05a1ea7541bd2720f71cb6dc593297c3cb Mon Sep 17 00:00:00 2001 From: Hongming6 <150672162+Hongming6@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:14:51 +0800 Subject: [PATCH 2/5] Aqara Smart Natural Gas Detector (#1160) * Aqara Smart Natural Gas Detector * Fix formatting issues * Example Delete the configurations that support self-check * Adding CN translation for Aqara Smart Natural Gas Detector * Update the self-check code of the device * Update self-check * Fixed repeated setting sensitivity device does not respond, refer to -Aqara zigbee motion sensor * Update PR * Update correct device information * Update the configuration of unmute and metadata * Solve formatting problems * Fix formatting issues * Fix known issues * Update Test Code * Adding the correct CN translation for Aqara Smart Natural Gas Detector * Remove duplicate definition * Update Test Code * Delete .yml --------- Co-authored-by: lelandblue <79465613+lelandblue@users.noreply.github.com> --- .../capabilities/lifeTimeReport.yaml | 22 ++ .../capabilities/sensitivityAdjustment.yaml | 55 ++++ .../zigbee-smoke-detector/fingerprints.yml | 5 + .../profiles/gas-lifetime-selfcheck-aqara.yml | 21 ++ .../src/aqara-gas/init.lua | 189 ++++++++++++++ .../zigbee-smoke-detector/src/init.lua | 2 + .../src/test/test_aqara_gas_detector.lua | 239 ++++++++++++++++++ 7 files changed, 533 insertions(+) create mode 100644 drivers/SmartThings/zigbee-smoke-detector/capabilities/lifeTimeReport.yaml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/capabilities/sensitivityAdjustment.yaml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/profiles/gas-lifetime-selfcheck-aqara.yml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua diff --git a/drivers/SmartThings/zigbee-smoke-detector/capabilities/lifeTimeReport.yaml b/drivers/SmartThings/zigbee-smoke-detector/capabilities/lifeTimeReport.yaml new file mode 100644 index 0000000000..2d893adf5b --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/capabilities/lifeTimeReport.yaml @@ -0,0 +1,22 @@ +id: stse.lifeTimeReport +version: 1 +status: proposed +name: Life Time Report +ephemeral: false +attributes: + lifeTimeState: + schema: + type: object + properties: + value: + type: string + enum: + - normal + - beAboutToEnd + - endOfLife + additionalProperties: false + required: + - value + enumCommands: [] +commands: { +} diff --git a/drivers/SmartThings/zigbee-smoke-detector/capabilities/sensitivityAdjustment.yaml b/drivers/SmartThings/zigbee-smoke-detector/capabilities/sensitivityAdjustment.yaml new file mode 100644 index 0000000000..c6e973a502 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/capabilities/sensitivityAdjustment.yaml @@ -0,0 +1,55 @@ +id: stse.sensitivityAdjustment +version: 1 +status: proposed +name: Sensitivity Adjustment +ephemeral: false +attributes: + sensitivityAdjustment: + schema: + type: object + properties: + value: + type: string + enum: + - High + - Medium + - Low + - Desc + additionalProperties: false + required: + - value + setter: setSensitivityAdjustment + enumCommands: + - command: High + value: High + - command: Medium + value: Medium + - command: Low + value: Low + - command: Desc + value: Desc +commands: + High: + name: High + arguments: [] + Desc: + name: Desc + arguments: [] + setSensitivityAdjustment: + name: setSensitivityAdjustment + arguments: + - name: sensitivity + optional: false + schema: + type: string + enum: + - High + - Medium + - Low + - Desc + Low: + name: Low + arguments: [] + Medium: + name: Medium + arguments: [] diff --git a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml index fe6ac2f884..a8384b886c 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml @@ -1,4 +1,9 @@ zigbeeManufacturer: + - id: "LUMI/lumi.sensor_gas.acn02" + deviceLabel: Aqara Smart Natural Gas Detector + manufacturer: LUMI + model: lumi.sensor_gas.acn02 + deviceProfileName: gas-lifetime-selfcheck-aqara - id: "LUMI/lumi.sensor_smoke.acn03" deviceLabel: Aqara Smart Smoke Detector manufacturer: LUMI diff --git a/drivers/SmartThings/zigbee-smoke-detector/profiles/gas-lifetime-selfcheck-aqara.yml b/drivers/SmartThings/zigbee-smoke-detector/profiles/gas-lifetime-selfcheck-aqara.yml new file mode 100644 index 0000000000..f6f482067f --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/profiles/gas-lifetime-selfcheck-aqara.yml @@ -0,0 +1,21 @@ +name: gas-lifetime-selfcheck-aqara +components: +- id: main + capabilities: + - id: gasDetector + version: 1 + - id: refresh + version: 1 + - id: audioMute + version: 1 + - id: stse.selfCheck + version: 1 + - id: stse.lifeTimeReport + version: 1 + - id: stse.sensitivityAdjustment + version: 1 +categories: + - name: SmokeDetector +metadata: + mnmn: SmartThingsCommunity + vid: 407e7b4f-4e9c-3cb3-9849-bd0d57f9b40a diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua new file mode 100644 index 0000000000..3b17912add --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua @@ -0,0 +1,189 @@ +-- 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 data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" +local capabilities = require "st.capabilities" + +local sensitivityAdjustment = capabilities["stse.sensitivityAdjustment"] +local sensitivityAdjustmentCommandName = "setSensitivityAdjustment" +local selfCheck = capabilities["stse.selfCheck"] +local startSelfCheckCommandName = "startSelfCheck" +local lifeTimeReport = capabilities["stse.lifeTimeReport"] + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID = 0x010C +local PRIVATE_MUTE_ATTRIBUTE_ID = 0x0126 +local PRIVATE_SELF_CHECK_ATTRIBUTE_ID = 0x0127 +local PRIVATE_LIFE_TIME_ATTRIBUTE_ID = 0x0128 +local PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID = 0x013A + + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.sensor_gas.acn02" } +} + + +local CONFIGURATIONS = { + { + cluster = PRIVATE_CLUSTER_ID, + attribute = PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID, + minimum_interval = 1, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 1 + } +} + +local function gas_zone_status_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event(capabilities.gasDetector.gas.detected()) + elseif value.value == 0 then + device:emit_event(capabilities.gasDetector.gas.clear()) + end +end + +local function buzzer_status_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event(capabilities.audioMute.mute.muted()) + elseif value.value == 0 then + device:emit_event(capabilities.audioMute.mute.unmuted()) + end +end + +local function lifetime_status_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event(lifeTimeReport.lifeTimeState.endOfLife()) + elseif value.value == 0 then + device:emit_event(lifeTimeReport.lifeTimeState.normal()) + end +end + +local function selfcheck_status_handler(driver, device, value, zb_rx) + if value.value == 0 then + device:emit_event(selfCheck.selfCheckState.idle()) + elseif value.value == 1 then + device:emit_event(selfCheck.selfCheckState.selfCheckCompleted()) + end +end + +local function sensitivity_adjustment_handler(driver, device, value, zb_rx) + if value.value == 0x01 then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.Low()) + elseif value.value == 0x02 then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + end +end + +local function mute_handler(driver, device, cmd) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1)) +end + +local function unmute_handler(driver, device, cmd) + -- device:send(cluster_base.write_manufacturer_specific_attribute(device, + -- PRIVATE_CLUSTER_ID, PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0)) + device:emit_event(capabilities.audioMute.mute.muted()) +end + +local function sensitivity_adjustment_capability_handler(driver, device, command) + local sensitivity = command.args.sensitivity + local pre_sensitivity_value = device:get_latest_state("main", sensitivityAdjustment.ID, sensitivityAdjustment.sensitivityAdjustment.NAME) + + if pre_sensitivity_value ~= sensitivity then + if sensitivity == 'High' then + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x02)) + elseif sensitivity == 'Low' then + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) + end + else + if sensitivity == 'High' then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + elseif sensitivity == 'Low' then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.Low()) + end + end +end + +local function self_check_attr_handler(self, device, zone_status, zb_rx) + device:emit_event(selfCheck.selfCheckState.selfChecking()) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true)) +end + +local function is_aqara_products(opts, driver, device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local function device_init(driver, device) + if CONFIGURATIONS ~= nil then + for _, attribute in ipairs(CONFIGURATIONS) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end + end +end + +local function device_added(driver, device) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) + device:emit_event(capabilities.gasDetector.gas.clear()) + device:emit_event(capabilities.audioMute.mute.unmuted()) + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + device:emit_event(selfCheck.selfCheckState.idle()) + device:emit_event(lifeTimeReport.lifeTimeState.normal()) +end + +local aqara_gas_detector_handler = { + NAME = "Aqara Gas Detector Handler", + lifecycle_handlers = { + init = device_init, + added = device_added + }, + zigbee_handlers = { + attr = { + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID] = gas_zone_status_handler, + [PRIVATE_MUTE_ATTRIBUTE_ID] = buzzer_status_handler, + [PRIVATE_LIFE_TIME_ATTRIBUTE_ID] = lifetime_status_handler, + [PRIVATE_SELF_CHECK_ATTRIBUTE_ID] = selfcheck_status_handler, + [PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID] = sensitivity_adjustment_handler + }, + } + }, + capability_handlers = { + [capabilities.audioMute.ID] = { + [capabilities.audioMute.commands.mute.NAME] = mute_handler, + [capabilities.audioMute.commands.unmute.NAME] = unmute_handler + }, + [sensitivityAdjustment.ID] = { + [sensitivityAdjustmentCommandName] = sensitivity_adjustment_capability_handler + }, + [selfCheck.ID] = { + [startSelfCheckCommandName] = self_check_attr_handler + }, + }, + can_handle = is_aqara_products +} + +return aqara_gas_detector_handler + diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua index 1a704222f1..9ac3026208 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua @@ -23,6 +23,8 @@ local zigbee_smoke_driver_template = { capabilities.battery }, sub_drivers = { + require("frient"), + require("aqara-gas") require("aqara"), require("frient") }, 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 new file mode 100644 index 0000000000..c24b46f172 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua @@ -0,0 +1,239 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + +local selfCheck = capabilities["stse.selfCheck"] +local lifeTimeReport = capabilities["stse.lifeTimeReport"] +local sensitivityAdjustment = capabilities["stse.sensitivityAdjustment"] + +local sensitivityAdjustmentId = "stse.sensitivityAdjustment" +local selfCheckId = "stse.selfCheck" + +test.add_package_capability("lifeTimeReport.yaml") +test.add_package_capability("selfCheck.yaml") +test.add_package_capability("sensitivityAdjustment.yaml") + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID = 0x010C +local PRIVATE_MUTE_ATTRIBUTE_ID = 0x0126 +local PRIVATE_SELF_CHECK_ATTRIBUTE_ID = 0x0127 +local PRIVATE_LIFE_TIME_ATTRIBUTE_ID = 0x0128 +local PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID = 0x013A + + + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("gas-lifetime-selfcheck-aqara.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.sensor_gas.acn02", + server_clusters = { 0x0001, 0xFCC0 } + } + } + } +) + + + +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.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, 0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.gasDetector.gas.clear())) + 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", 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 +) + + + +test.register_coroutine_test( + "gasDetector report should be handled", + function() + local attr_report_data = { + { PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID, 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) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.gasDetector.gas.detected())) + end +) + + + +test.register_coroutine_test( + "audioMute report should be handled", + function() + local attr_report_data = { + { PRIVATE_MUTE_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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.muted())) + end +) + + + +test.register_coroutine_test( + "Capability on command should be handled : device mute", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "audioMute", component = "main", command = "mute", args = {} } }) + 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 +) + +test.register_coroutine_test( + "Capability on command should not be handled : device unmute", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "audioMute", component = "main", command = "unmute", args = {} } }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.audioMute.mute.muted())) + end +) + + + +test.register_coroutine_test( + "selfCheck report should be handled", + function() + local attr_report_data = { + { PRIVATE_SELF_CHECK_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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.selfCheckCompleted())) + end +) + + + +test.register_coroutine_test( + "Capability on command should be handled : device selfCheck", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = selfCheckId, component = "main", command = "startSelfCheck", args = {state = "selfCheckCompleted"} } }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main",selfCheck.selfCheckState.selfChecking())) + 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 +) + + + +test.register_coroutine_test( + "lifetime report should be handled", + function() + local attr_report_data = { + { PRIVATE_LIFE_TIME_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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", + lifeTimeReport.lifeTimeState.endOfLife())) + end +) + + + +test.register_coroutine_test( + "sensitivityAdjustment report should be handled", + function() + local attr_report_data = { + { PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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.Low())) + end +) + + + +test.register_coroutine_test( + "Capability on command should be handled : setSensitivityAdjustment Low", + function() + local attr_report_data = { + { PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + test.socket.capability:__queue_receive({ mock_device.id, + { capability = sensitivityAdjustmentId, component = "main", command = "setSensitivityAdjustment", args = {"Low"}} + }) + 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, 0x01) + }) + 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.Low()) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = sensitivityAdjustmentId, component = "main", command = "setSensitivityAdjustment", args = {"Low"}} + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + sensitivityAdjustment.sensitivityAdjustment.Low()) + ) + end +) + + +test.run_registered_tests() From 04b780b6fd888133922b1bdd3f5ccfb0358f57ea Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Thu, 14 Mar 2024 09:38:05 -0500 Subject: [PATCH 3/5] Fix missing comma in subdriver list --- drivers/SmartThings/zigbee-smoke-detector/src/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua index 9ac3026208..2d46a5dc8c 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua @@ -24,7 +24,7 @@ local zigbee_smoke_driver_template = { }, sub_drivers = { require("frient"), - require("aqara-gas") + require("aqara-gas"), require("aqara"), require("frient") }, From 05b2f3f80ef2183edd47f7269b37f9cb8adb8201 Mon Sep 17 00:00:00 2001 From: Raj Kumar <108387779+raj-1219@users.noreply.github.com> Date: Fri, 15 Mar 2024 21:04:31 +0530 Subject: [PATCH 4/5] audio TTS fix on samsung speakers (#1268) * audio TTS fix on samsung speakers --------- Co-authored-by: Carter Swedal --- drivers/SmartThings/samsung-audio/README.md | 15 ++++++++ .../SmartThings/samsung-audio/src/command.lua | 38 +------------------ .../samsung-audio/src/handlers.lua | 6 ++- 3 files changed, 21 insertions(+), 38 deletions(-) create mode 100644 drivers/SmartThings/samsung-audio/README.md diff --git a/drivers/SmartThings/samsung-audio/README.md b/drivers/SmartThings/samsung-audio/README.md new file mode 100644 index 0000000000..5a8b6e06ae --- /dev/null +++ b/drivers/SmartThings/samsung-audio/README.md @@ -0,0 +1,15 @@ +# Samsung-Audio integration documentation + +The purpose of this readme is to document the behavior of various samsung lan speaker devices, +since during the development of the driver there have been some weird behavior. +The document will also explain any mitigations the driver makes to provide a +consistent integration despite the weird behavior. + +## Device testing notes + +Audio Speaker Device shows some weird behavior while responding to various commands. Due to non uniform response by devices, device state mismatch can be +observed sometimes in the ST app. + +## Samsung Audio TTS Notification + +Currently, Audio TTS notification is played with certain non expected behaviors. For example, when the speakers are in paused state, the notification plays but the queued music resumes playing. Similarly, when speakers are in mute state, it will unmute and play the notification but the speakers will remain unmuted after this. diff --git a/drivers/SmartThings/samsung-audio/src/command.lua b/drivers/SmartThings/samsung-audio/src/command.lua index 38bf2ff2e7..deca7c4416 100644 --- a/drivers/SmartThings/samsung-audio/src/command.lua +++ b/drivers/SmartThings/samsung-audio/src/command.lua @@ -349,27 +349,6 @@ function Command.getPlayStatus(ip) return response_map end ---- Return err code from xml handler or nil if it doesn't exist -local get_resp = function(response_map) - if response_map ~= nil and - response_map.handler_res~= nil and - response_map.handler_res.root ~= nil and - response_map.handler_res.root.UIC ~= nil and - response_map.handler_res.root.UIC.response ~=nil then - return { - method = response_map.handler_res.root.UIC.method, - err_code= response_map.handler_res.root.UIC.response.errCode - } - else - log.trace("errorCode not found") - return { - method = nil, - err_code = nil - } - end - -end - local format_streaming_path = function(uri) return "/UIC?cmd=%3Cpwron%3Eon%3C/pwron%3E%3Cname%3ESetUrlPlayback%3C/name%3E%3Cp%20type=%22cdata%22%20name=%22url%22%20val=%22empty%22%3E%3C![CDATA[" .. uri .. "]]%3E%3C/p%3E%3Cp%20type=%22dec%22%20name=%22buffersize%22%20val=%220%22/%3E%3Cp%20type=%22dec%22%20name=%22seektime%22%20val=%220%22/%3E%3Cp%20type=%22dec%22%20name=%22resume%22%20val=%221%22/%3E" end @@ -388,22 +367,7 @@ function Command.play_streaming_uri(ip, uri) log.trace("Triggering UPnP Command Request for [Audio Notification -> SetUrlPlayback]") local response_map = nil if ip then - local path = format_streaming_path(uri) - local url = format_url(ip, path) - log.trace(string.format("Final Notification Command URL for making Audio Notification http request = %s", url)) - response_map = handle_http_request(ip, url) - --- Adding extra debug logs in case some issue comes in future - log.debug("Response Map table with https: ", utils.stringify_table(response_map)) - local resp = get_resp(response_map) - log.debug("resp method and error code : ", utils.stringify_table(resp)) - if resp.method == "ErrorEvent" then - log.info(string.format("Recieved error code %s",resp.err_code)) - response_map= fallback_to_http(ip,uri) - end - if resp.method == "PausePlaybackEvent" then - log.info(string.format("Recieved error code %s",resp.err_code)) - response_map= fallback_to_http(ip,uri) - end + response_map = fallback_to_http(ip, uri) end return response_map end diff --git a/drivers/SmartThings/samsung-audio/src/handlers.lua b/drivers/SmartThings/samsung-audio/src/handlers.lua index 8ff2722757..6233a86326 100644 --- a/drivers/SmartThings/samsung-audio/src/handlers.lua +++ b/drivers/SmartThings/samsung-audio/src/handlers.lua @@ -136,7 +136,11 @@ end function CapabilityHandlers.handle_audio_notification(driver, device, cmd) local ip = device:get_field("ip") - -- Audio Notification working fine when checked through routine. Need to confirm later for any issue whether ST app expect any emit_event here + local mute_status = command.getMute(ip) + if mute_status.muted ~= "off" then + --unmute before playig notification + command.unmute(ip) + end command.play_streaming_uri(ip, cmd.args.uri) end From 561aa74bfac73d0971d5ae318c3a6a379b737a4f Mon Sep 17 00:00:00 2001 From: ThirdReality Support <117250319+3reality-support@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:14:03 +0800 Subject: [PATCH 5/5] Add ThirdReality Driver for Matter Night Light (#1048) * Add ThirdReality Driver for Matter Night Light * move third reality nl support to subdriver format * rename profile file name -nl to illuminance-motion clean third-reality/init useless code * Add illuminance and motion to base matter-switch driver * Commit:Add colorTemperature range 1000-15000k support in profile * Commit: add new profile loight-color-level-illuminance-motion-1000-15000k;update the fingerptint * Commit: update the file name to be light-color-level-illuminance-motion-1000K-15000K * Commit: update device label by writing out "NL" to "Night Light" * fix unit tests for illiminance motion light --------- Co-authored-by: Zhangyi Co-authored-by: xdong Co-authored-by: Cooper Towns Co-authored-by: lelandblue <79465613+lelandblue@users.noreply.github.com> --- .../matter-switch/fingerprints.yml | 7 + ...-level-illuminance-motion-1000K-15000K.yml | 27 + .../light-color-level-illuminance-motion.yml | 23 + .../SmartThings/matter-switch/src/init.lua | 26 +- .../test/test_light_illuminance_motion.lua | 537 ++++++++++++++++++ 5 files changed, 619 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml create mode 100644 drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 34c1fc5447..180584609b 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1,4 +1,11 @@ matterManufacturer: +#ThirdReality + - id: "ThirdReality/WiFi" + deviceLabel: THIRDREALITY Night Light + vendorId: 0x1407 + productId: 0x1088 + deviceProfileName: light-color-level-illuminance-motion-1000K-15000K + #Eve - id: "Eve/Energy/US" deviceLabel: Eve Energy diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml new file mode 100644 index 0000000000..0b815134b3 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml @@ -0,0 +1,27 @@ +name: light-color-level-illuminance-motion-1000K-15000K +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: colorTemperature + version: 1 + config: + values: + - key: "colorTemperature.value" + range: [ 1000, 15000 ] + - id: colorControl + version: 1 + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light + diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml new file mode 100644 index 0000000000..e2d33f6929 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml @@ -0,0 +1,23 @@ +name: light-color-level-illuminance-motion +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: colorTemperature + version: 1 + - id: colorControl + version: 1 + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light + diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 27823095a8..b4cac166f3 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -338,6 +338,16 @@ local function color_cap_attr_handler(driver, device, ib, response) end end + +local function illuminance_attr_handler(driver, device, ib, response) + local lux = math.floor(10 ^ ((ib.data.value - 1) / 10000)) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.illuminanceMeasurement.illuminance(lux)) +end + +local function occupancy_attr_handler(driver, device, ib, response) + device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) +end + local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then device:subscribe() @@ -374,6 +384,12 @@ local matter_driver_template = { [clusters.ColorControl.attributes.CurrentX.ID] = x_attr_handler, [clusters.ColorControl.attributes.CurrentY.ID] = y_attr_handler, [clusters.ColorControl.attributes.ColorCapabilities.ID] = color_cap_attr_handler, + }, + [clusters.IlluminanceMeasurement.ID] = { + [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = illuminance_attr_handler + }, + [clusters.OccupancySensing.ID] = { + [clusters.OccupancySensing.attributes.Occupancy.ID] = occupancy_attr_handler, } }, fallback = matter_handler, @@ -394,6 +410,12 @@ local matter_driver_template = { [capabilities.colorTemperature.ID] = { clusters.ColorControl.attributes.ColorTemperatureMireds, }, + [capabilities.illuminanceMeasurement.ID] = { + clusters.IlluminanceMeasurement.attributes.MeasuredValue + }, + [capabilities.motionSensor.ID] = { + clusters.OccupancySensing.attributes.Occupancy + } }, capability_handlers = { [capabilities.switch.ID] = { @@ -420,9 +442,11 @@ local matter_driver_template = { capabilities.switchLevel, capabilities.colorControl, capabilities.colorTemperature, + capabilities.motionSensor, + capabilities.illuminanceMeasurement }, sub_drivers = { - require("eve-energy") + require("eve-energy"), } } 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 new file mode 100644 index 0000000000..96be10bf6a --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -0,0 +1,537 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT 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 TRANSITION_TIME = 0 +local OPTIONS_MASK = 0x01 +local OPTIONS_OVERRIDE = 0x01 + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("light-color-level-illuminance-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_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"} + }, + device_types = { + {device_type_id = 0x010C, device_type_revision = 1} -- ColorTemperatureLight + } + }, + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.IlluminanceMeasurement.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0106, device_type_revision = 1} -- LightSensor + } + }, + { + endpoint_id = 3, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- OccupancySensor + } + } + } +}) + + +local function test_init() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.IlluminanceMeasurement.attributes.MeasuredValue, + clusters.OccupancySensing.attributes.Occupancy + } + test.socket.matter:__set_channel_ordering("relaxed") + 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) +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 = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, 1) + } + } + } +) + +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 = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.Off(mock_device, 1) + } + } + } +) + +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 = "matter", + direction = "send", + message = { + mock_device.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 1, math.floor(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 = "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()) + } + } +) + +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))) + }, + } +) + +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, OPTIONS_OVERRIDE) + } + }, + { + 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 = "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)) + } + } +) + +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, OPTIONS_OVERRIDE) + } + }, + } +) + +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, OPTIONS_OVERRIDE) + } + }, + } +) + +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, OPTIONS_OVERRIDE) + } + }, + { + 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)) + }, + } +) + +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, 1, 15091) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(72)) + } + } +) + +test.register_message_test( + "X and Y color values have 0 value", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(33)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(100)) + } + } +) + + +test.register_message_test( + "Y and X color values should report hue and saturation once both have been received", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(72)) + } + } +) + +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.register_message_test( + "Illuminance reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.IlluminanceMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 2, 21370) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) + } + } +) + +test.register_message_test( + "Occupancy reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 3, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 3, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) + } + } +) + +test.run_registered_tests()