From aa2ccde4c69ffcc8f51a909b7d121e6c84afb185 Mon Sep 17 00:00:00 2001 From: Mariano Sciacco Date: Fri, 7 Mar 2025 20:08:32 +0100 Subject: [PATCH] Add zigbee-button for Vimar xx591 (Wall-mounted Remote Control) (#1508) * add vimar remote control (zigbee-button) * docs: update copyright year in vimar zigbee button test --- .../zigbee-button/fingerprints.yml | 5 + .../profiles/two-buttons-no-fw-update.yml | 22 ++ .../src/test/test_vimar_button.lua | 215 ++++++++++++++++++ .../src/zigbee-multi-button/init.lua | 6 +- .../zigbee-multi-button/supported_values.lua | 7 + .../src/zigbee-multi-button/vimar/init.lua | 93 ++++++++ 6 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 drivers/SmartThings/zigbee-button/profiles/two-buttons-no-fw-update.yml create mode 100644 drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua create mode 100644 drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/init.lua diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index 2e7b3fbe57..48c16ce41e 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -1,4 +1,9 @@ zigbeeManufacturer: + - id: "Vimar/xx591-remote-control" + deviceLabel: Vimar Remote Control + manufacturer: Vimar + model: "RemoteControl_v1.0" + deviceProfileName: two-buttons-no-fw-update - id: "LUMI/lumi.remote.b1acn02" deviceLabel: Aqara Wireless Mini Switch T1 manufacturer: LUMI diff --git a/drivers/SmartThings/zigbee-button/profiles/two-buttons-no-fw-update.yml b/drivers/SmartThings/zigbee-button/profiles/two-buttons-no-fw-update.yml new file mode 100644 index 0000000000..c11b3915eb --- /dev/null +++ b/drivers/SmartThings/zigbee-button/profiles/two-buttons-no-fw-update.yml @@ -0,0 +1,22 @@ +name: two-buttons-no-fw-update +components: + - id: main + capabilities: + - id: button + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController + - id: button1 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua new file mode 100644 index 0000000000..3bb8b94fff --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/test/test_vimar_button.lua @@ -0,0 +1,215 @@ +-- 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. + +-- Mock out globals +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local OnOff = clusters.OnOff +local LevelControl = clusters.Level + +local button_attr = capabilities.button.button + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("two-buttons-no-fw-update.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Vimar", + model = "RemoteControl_v1.0", + server_clusters = {0x0000, 0x0003}, + client_clusters = {0x0006, 0x0008} + } + } + } +) + +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( + "Remote Control should be handled in 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", "down_hold", "up" }, { visibility = { displayed = false } }) + ) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", + capabilities.button.numberOfButtons({ value = 2 }, { visibility = { displayed = false } }) + ) + ) + + for button_number = 1, 2 do + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button" .. button_number, + capabilities.button.supportedButtonValues({ "pushed", "down_hold", "up" }, { visibility = { displayed = false } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "button" .. button_number, + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }) + ) + ) + end + test.socket.capability:__expect_send({ + mock_device.id, + { + capability_id = "button", component_id = "main", + attribute_id = "button", state = { value = "pushed" } + } + }) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Button UP (button1) should handle pushed event", + function() + test.wait_for_events() + + 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("button1", button_attr.pushed({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) + ) + end +) + +test.register_coroutine_test( + "Button DOWN (button2) should handle pushed event", + function() + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.server.commands.Off.build_test_rx(mock_device) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button2", button_attr.pushed({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", button_attr.pushed({ state_change = true })) + ) + end +) + +test.register_coroutine_test( + "Button UP (button1) should handle down hold and up event", + function() + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, LevelControl.server.commands.Move.build_test_rx(mock_device, LevelControl.types.MoveStepMode.UP, 255) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button1", button_attr.down_hold({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", button_attr.down_hold({ state_change = true })) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, LevelControl.server.commands.Stop.build_test_rx(mock_device) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button1", button_attr.up({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", button_attr.up({ state_change = true })) + ) + end +) + +test.register_coroutine_test( + "Button DOWN (button2) should handle down hold and up", + function() + test.wait_for_events() + + test.socket.zigbee:__queue_receive({ mock_device.id, LevelControl.server.commands.Move.build_test_rx(mock_device, LevelControl.types.MoveStepMode.DOWN, 255) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button2", button_attr.down_hold({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", button_attr.down_hold({ state_change = true })) + ) + + test.socket.zigbee:__queue_receive({ mock_device.id, LevelControl.server.commands.Stop.build_test_rx(mock_device) }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("button2", button_attr.up({ state_change = true })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", button_attr.up({ state_change = true })) + ) + end +) + +test.register_coroutine_test( + "Remote Control driver should handle configuration lifecycle", + function() + test.wait_for_events() + + 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, + LevelControl.ID) + } + ) + + for _, component in pairs(mock_device.profile.components) do + local number_of_buttons = component.id == "main" and 2 or 1 + test.socket.capability:__expect_send( + mock_device:generate_test_message( + component.id, + capabilities.button.supportedButtonValues({ "pushed", "down_hold", "up" }, { visibility = { displayed = true } }) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + component.id, + capabilities.button.numberOfButtons({ value = number_of_buttons }, { visibility = { displayed = true } }) + ) + ) + end + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() 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 f572fd79e4..05d1a62de9 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/init.lua @@ -41,7 +41,8 @@ local ZIGBEE_MULTI_BUTTON_FINGERPRINTS = { { mfr = "ROBB smarrt", model = "ROB_200-007-0" }, { mfr = "ROBB smarrt", model = "ROB_200-008-0" }, { mfr = "WALL HERO", model = "ACL-401SCA4" }, - { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-005" } + { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-005" }, + { mfr = "Vimar", model = "RemoteControl_v1.0" } } local function can_handle_zigbee_multi_button(opts, driver, device, ...) @@ -88,7 +89,8 @@ local zigbee_multi_button = { require("zigbee-multi-button.shinasystems"), require("zigbee-multi-button.robb"), require("zigbee-multi-button.wallhero"), - require("zigbee-multi-button.SLED") + require("zigbee-multi-button.SLED"), + require("zigbee-multi-button.vimar") } } diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua index fabaa7da6f..9b7ffc3f91 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua @@ -115,6 +115,13 @@ local devices = { }, SUPPORTED_BUTTON_VALUES = { "pushed", "held", "double" }, NUMBER_OF_BUTTONS = 4 + }, + BUTTON_PUSH_DOWN_HOLD_UP_VIMAR_2 = { + MATCHING_MATRIX = { + { mfr = "Vimar", model = "RemoteControl_v1.0" } + }, + SUPPORTED_BUTTON_VALUES = { "pushed", "down_hold", "up" }, + NUMBER_OF_BUTTONS = 2 } } diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/init.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/init.lua new file mode 100644 index 0000000000..3df8cc4d71 --- /dev/null +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/vimar/init.lua @@ -0,0 +1,93 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local device_management = require "st.zigbee.device_management" +local OnOff = clusters.OnOff +local LevelControl = clusters.Level +local log = require "log" + +local VIMAR_HOLD_BUTTON = "Vimar_Holding_Button" + +local emit_button_event = function(button_name, device, event) + local comp = device.profile.components[button_name] + if comp ~= nil then + device:emit_component_event(comp, event) + device:emit_event(event) + else + log.warn("Attempted to emit button event for unknown button: " .. button_name) + end +end + +local function vimar_up_button_pushed(driver, device, zb_rx) + emit_button_event("button1", device, capabilities.button.button.pushed({state_change = true})) +end + +local function vimar_down_button_pushed(driver, device, zb_rx) + emit_button_event("button2", device, capabilities.button.button.pushed({state_change = true})) +end + +local function vimar_button_hold(driver, device, zb_rx) + if zb_rx.body.zcl_body.move_mode.value == LevelControl.types.MoveStepMode.UP then + device:set_field(VIMAR_HOLD_BUTTON, "button1") + emit_button_event("button1", device, capabilities.button.button.down_hold({state_change = true})) + elseif zb_rx.body.zcl_body.move_mode.value == LevelControl.types.MoveStepMode.DOWN then + device:set_field(VIMAR_HOLD_BUTTON, "button2") + emit_button_event("button2", device, capabilities.button.button.down_hold({state_change = true})) + else + log.warn("MoveStepMode value not supported") + end +end + +local function vimar_release_button_hold(driver, device, zb_rx) + local hold_button = device:get_field(VIMAR_HOLD_BUTTON) + emit_button_event(hold_button, device, capabilities.button.button.up({state_change = true})) +end + +local do_configure = function(self, device) + device:send(device_management.build_bind_request(device, OnOff.ID, self.environment_info.hub_zigbee_eui)) + device:send(device_management.build_bind_request(device, LevelControl.ID, self.environment_info.hub_zigbee_eui)) + for _, component in pairs(device.profile.components) do + local number_of_buttons = component.id == "main" and 2 or 1 + device:emit_component_event(component, + capabilities.button.supportedButtonValues({ "pushed", "down_hold", "up" }, { visibility = { displayed = true } })) + device:emit_component_event(component, + capabilities.button.numberOfButtons({ value = number_of_buttons }, { visibility = { displayed = true } })) + end +end + +local vimar_remote_control = { + NAME = "Vimar Remote Control", + zigbee_handlers = { + cluster = { + [OnOff.ID] = { + [OnOff.server.commands.Off.ID] = vimar_down_button_pushed, + [OnOff.server.commands.On.ID] = vimar_up_button_pushed + }, + [LevelControl.ID] = { + [LevelControl.server.commands.Move.ID] = vimar_button_hold, + [LevelControl.server.commands.Stop.ID] = vimar_release_button_hold + } + } + }, + lifecycle_handlers = { + doConfigure = do_configure + }, + can_handle = function(opts, driver, device, ...) + return device:get_manufacturer() == "Vimar" and device:get_model() == "RemoteControl_v1.0" + end +} + +return vimar_remote_control