From 6ebbe8e3305527d0039cc5826941d1d52a468fdc Mon Sep 17 00:00:00 2001 From: Hongming6 <150672162+Hongming6@users.noreply.github.com> Date: Sat, 18 May 2024 00:49:04 +0800 Subject: [PATCH] New Device(s) (Zigbee Switch): Aqara Smart Wall Switch H1 (#1290) * Aqara Smart Wall Switch H1 * Fix formatting * Optimize code * Correct devicelabel * Add the Chinese name of the product * Add parentheses to name --------- Co-authored-by: lelandblue <79465613+lelandblue@users.noreply.github.com> --- .../zigbee-switch/fingerprints.yml | 20 ++ .../profiles/aqara-switch-no-power.yml | 19 ++ .../zigbee-switch/src/aqara/init.lua | 6 +- .../src/aqara/multi-switch/init.lua | 4 +- .../zigbee-switch/src/aqara/version/init.lua | 6 +- .../src/test/test_aqara_switch_no_power.lua | 245 ++++++++++++++++++ tools/localizations/cn.csv | 6 +- 7 files changed, 301 insertions(+), 5 deletions(-) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/aqara-switch-no-power.yml create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_no_power.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index d55ab3e55c..e4ea88650b 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -116,6 +116,26 @@ zigbeeManufacturer: manufacturer: Aqara model: lumi.light.acn014 deviceProfileName: aqara-led-bulb + - id: "LUMI/lumi.switch.n1aeu1" + deviceLabel: Aqara Smart Wall Switch H1 EU (With Neutral, Single Rocker) + manufacturer: LUMI + model: lumi.switch.n1aeu1 + deviceProfileName: aqara-switch-power + - id: "LUMI/lumi.switch.n2aeu1" + deviceLabel: Aqara Smart Wall Switch H1 EU (With Neutral, Double Rocker) + manufacturer: LUMI + model: lumi.switch.n2aeu1 + deviceProfileName: aqara-switch-power + - id: "LUMI/lumi.switch.l1aeu1" + deviceLabel: Aqara Smart Wall Switch H1 EU (No Neutral, Single Rocker) + manufacturer: LUMI + model: lumi.switch.l1aeu1 + deviceProfileName: aqara-switch-no-power + - id: "LUMI/lumi.switch.l2aeu1" + deviceLabel: Aqara Smart Wall Switch H1 EU (No Neutral, Double Rocker) + manufacturer: LUMI + model: lumi.switch.l2aeu1 + deviceProfileName: aqara-switch-no-power # VIMAR - id: "Vimar/xx592-2-way-smart-switch" deviceLabel: Vimar 2-way Smart Switch diff --git a/drivers/SmartThings/zigbee-switch/profiles/aqara-switch-no-power.yml b/drivers/SmartThings/zigbee-switch/profiles/aqara-switch-no-power.yml new file mode 100644 index 0000000000..54ff6b731c --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/aqara-switch-no-power.yml @@ -0,0 +1,19 @@ +name: aqara-switch-no-power +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +preferences: + - preferenceId: stse.restorePowerState + explicit: true + - preferenceId: stse.changeToWirelessSwitch + explicit: true \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara/init.lua index 7e49b2b395..a45f8d254c 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/init.lua @@ -32,7 +32,11 @@ local FINGERPRINTS = { { mfr = "LUMI", model = "lumi.switch.n1acn1" }, { mfr = "LUMI", model = "lumi.switch.n2acn1" }, { mfr = "LUMI", model = "lumi.switch.n3acn1" }, - { mfr = "LUMI", model = "lumi.switch.b2laus01" } + { mfr = "LUMI", model = "lumi.switch.b2laus01" }, + { mfr = "LUMI", model = "lumi.switch.n1aeu1" }, + { mfr = "LUMI", model = "lumi.switch.n2aeu1" }, + { mfr = "LUMI", model = "lumi.switch.l1aeu1" }, + { mfr = "LUMI", model = "lumi.switch.l2aeu1" } } local preference_map = { 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 40de476c0e..5e3d976184 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua @@ -11,7 +11,9 @@ local FINGERPRINTS = { { mfr = "LUMI", model = "lumi.switch.n1acn1", children = 1, child_profile = "" }, { mfr = "LUMI", model = "lumi.switch.n2acn1", children = 2, child_profile = "aqara-switch-child" }, { mfr = "LUMI", model = "lumi.switch.n3acn1", children = 3, child_profile = "aqara-switch-child" }, - { mfr = "LUMI", model = "lumi.switch.b2laus01", children = 2, child_profile = "aqara-switch-child" } + { mfr = "LUMI", model = "lumi.switch.b2laus01", children = 2, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.l2aeu1", children = 2, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.n2aeu1", children = 2, child_profile = "aqara-switch-child" } } local function is_aqara_products(opts, driver, device) diff --git a/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua index 41776ab308..1e80c23ea0 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua @@ -68,8 +68,10 @@ end local function do_refresh(self, device) device:send(OnOff.attributes.OnOff:read(device)) - device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(POWER_METER_ENDPOINT)) - device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(ENERGY_METER_ENDPOINT)) + if (device:supports_capability_by_id(capabilities.powerMeter.ID)) then + device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(POWER_METER_ENDPOINT)) + device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(ENERGY_METER_ENDPOINT)) + end end local aqara_switch_version_handler = { 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 new file mode 100644 index 0000000000..543f08d152 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_switch_no_power.lua @@ -0,0 +1,245 @@ +-- 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 cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local OnOff = clusters.OnOff + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local RESTORE_POWER_STATE_ATTRIBUTE_ID = 0x0201 +local CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID = 0x0200 +local WIRELESS_SWITCH_CLUSTER_ID = 0x0012 +local WIRELESS_SWITCH_ATTRIBUTE_ID = 0x0055 +local WIRELESS_SWITCH_PUSHED_VALUE = 1 +local BUTTON_1_ENDPOINT = 0x29 +local BUTTON_2_ENDPOINT = 0x2A + +local PRIVATE_MODE = "PRIVATE_MODE" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-switch-no-power.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.switch.l2aeu1", + server_clusters = { 0x0006 } + } + } + } +) + +local mock_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("aqara-switch-child.yml"), + device_network_id = string.format("%04X:%02X", mock_device:get_short_address(), 2), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%02X", 2) +}) + + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_child) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Lifecycle - added test", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.numberOfButtons({ value = 2 }, + { visibility = { displayed = false } }))) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, + data_types.Uint8, 1) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) + + end +) + +test.register_coroutine_test( + "Lifecycle - added test", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_child.id, "added" }) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.numberOfButtons({ value = 1 }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.button.button.pushed({ state_change = false }))) + end +) + +test.register_coroutine_test( + "Refresh device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + 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) }) + end +) + +test.register_coroutine_test( + "Reported on status should be handled : parent device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + 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 +) + +test.register_coroutine_test( + "Reported on status should be handled : child device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + 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 +) + +test.register_coroutine_test( + "Reported off status should be handled by parent device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + 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 +) + +test.register_coroutine_test( + "Reported off status should be handled by child device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + 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 +) + +test.register_coroutine_test( + "Capability on command should be handled : parent device", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.On(mock_device) }) + end +) + +test.register_coroutine_test( + "Capability on command should be handled : child device", + function() + test.socket.capability:__queue_receive({ mock_child.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) + end +) + +test.register_coroutine_test( + "Capability off command should be handled : parent device", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "off", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.Off(mock_device) }) + end +) + +test.register_coroutine_test( + "Capability off command should be handled : child device", + function() + test.socket.capability:__queue_receive({ mock_child.id, + { capability = "switch", component = "main", command = "off", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) + end +) + +test.register_coroutine_test( + "Wireless button pushed report should be correctly handled : parent device", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, WIRELESS_SWITCH_CLUSTER_ID, { + { WIRELESS_SWITCH_ATTRIBUTE_ID, data_types.Uint16.ID, WIRELESS_SWITCH_PUSHED_VALUE } + }, MFG_CODE):from_endpoint(BUTTON_1_ENDPOINT) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end +) + +test.register_coroutine_test( + "Wireless button pushed report should be correctly handled : child device", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, WIRELESS_SWITCH_CLUSTER_ID, { + { WIRELESS_SWITCH_ATTRIBUTE_ID, data_types.Uint16.ID, WIRELESS_SWITCH_PUSHED_VALUE } + }, MFG_CODE):from_endpoint(BUTTON_2_ENDPOINT) + }) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end +) + + + +test.register_coroutine_test( + "Handle restorePowerState in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.restorePowerState"] = true } + })) + 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 +) + +test.register_coroutine_test( + "Handle changeToWirelessSwitch in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.changeToWirelessSwitch"] = true } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) + end +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 9c45f636ef..5b059a6508 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -56,4 +56,8 @@ Aqara Smart Smoke Detector,Aqara烟雾报警器 Aqara Smart Plug T1,Aqara 智能插座 T1 (国标) Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "Aqara Wireless Remote Switch E1 (Single Rocker)",Aqara 无线开关 E1 (贴墙式单键版) -"Aqara LED Light Bulb T1 (Tunable White)",Aqara LED灯泡 T1 (可调色温) \ No newline at end of file +"Aqara LED Light Bulb T1 (Tunable White)",Aqara LED灯泡 T1 (可调色温) +"Aqara Smart Wall Switch H1 EU (With Neutral, Single Rocker)",Aqara智能墙壁开关H1 EU (零火线单键版) +"Aqara Smart Wall Switch H1 EU (With Neutral, Double Rocker)",Aqara智能墙壁开关H1 EU (零火线双键版) +"Aqara Smart Wall Switch H1 EU (No Neutral, Single Rocker)",Aqara智能墙壁开关H1 EU (单火线单键版) +"Aqara Smart Wall Switch H1 EU (No Neutral, Double Rocker)",Aqara智能墙壁开关H1 EU (单火线双键版) \ No newline at end of file