diff --git a/.github/workflows/create-channel.yml b/.github/workflows/create-channel.yml index e86e4fc6d5..f3a287748c 100644 --- a/.github/workflows/create-channel.yml +++ b/.github/workflows/create-channel.yml @@ -14,7 +14,7 @@ jobs: head_ref: ${{ steps.head_ref.outputs.head_ref }} steps: - name: Download artifacts - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v6 with: workflow: upload-driver-packages.yml run_id: ${{ github.event.workflow_run.id }} @@ -90,7 +90,7 @@ jobs: driver: ${{ fromJSON(needs.download-artifacts.outputs.drivers) }} steps: - name: Download artifacts - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v6 with: workflow: upload-driver-packages.yml name: ${{ matrix.driver }} diff --git a/.github/workflows/duplicate-profiles-comment.yml b/.github/workflows/duplicate-profiles-comment.yml index 9d1ed8aea3..478b4cbd15 100644 --- a/.github/workflows/duplicate-profiles-comment.yml +++ b/.github/workflows/duplicate-profiles-comment.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download comment artifact - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v6 with: workflow: duplicate-profiles.yml run_id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml index 02d4da0005..57c8812913 100644 --- a/.github/workflows/publish-test-results.yml +++ b/.github/workflows/publish-test-results.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download artifacts - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v6 with: workflow: run-tests.yml run_id: ${{ github.event.workflow_run.id }} diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index bb19b6a5fd..3fab77fa3a 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -151,6 +151,7 @@ local device_type_attribute_map = { } local child_device_profile_overrides = { + { vendor_id = 0x1321, product_id = 0x000C, child_profile = "switch-binary" }, { vendor_id = 0x1321, product_id = 0x000D, child_profile = "switch-binary" }, } diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 94b51d1c76..59aa93e896 100644 --- a/drivers/SmartThings/matter-window-covering/fingerprints.yml +++ b/drivers/SmartThings/matter-window-covering/fingerprints.yml @@ -26,6 +26,11 @@ matterManufacturer: vendorId: 0x139C productId: 0xFF15 deviceProfileName: window-covering + - id: "5020/64017" + deviceLabel: Zemismart ZM25C Smart Curtain + vendorId: 0x139C + productId: 0xFA11 + deviceProfileName: window-covering matterGeneric: - id: "windowcovering" deviceLabel: Matter Window Covering diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua index 1ee1a1e2b2..9e45b9dbad 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/init.lua @@ -15,6 +15,20 @@ local capabilities = require "st.capabilities" local ZigbeeDriver = require "st.zigbee" local defaults = require "st.zigbee.defaults" +local zcl_clusters = require "st.zigbee.zcl.clusters" + +local battery_attribute_configuration = { + cluster = zcl_clusters.PowerConfiguration.ID, + attribute = zcl_clusters.PowerConfiguration.attributes.BatteryPercentageRemaining.ID, + minimum_interval = 30, + maximum_interval = 14300, -- ~4hrs + data_type = zcl_clusters.PowerConfiguration.attributes.BatteryPercentageRemaining.base_type, + reportable_change = 1 +} + +local function device_init(driver, device) + device:add_configured_attribute(battery_attribute_configuration) +end local zigbee_dimmer_remote_driver_template = { supported_capabilities = { @@ -23,7 +37,11 @@ local zigbee_dimmer_remote_driver_template = { capabilities.switch, capabilities.switchLevel }, - sub_drivers = { require("zigbee-accessory-dimmer"), require("zigbee-battery-accessory-dimmer")} + lifecycle_handlers = { + init = device_init, + }, + sub_drivers = { require("zigbee-accessory-dimmer"), require("zigbee-battery-accessory-dimmer")}, + health_check = false, } defaults.register_for_default_handlers(zigbee_dimmer_remote_driver_template, zigbee_dimmer_remote_driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua index bcb7ea5b98..b16fe8cd11 100644 Binary files a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua and b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua differ diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/init.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/init.lua index 85afaa3b40..0734bf57e8 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/init.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/zigbee-battery-accessory-dimmer/CentraliteSystems/init.lua @@ -87,6 +87,24 @@ local is_centralite_systems = function(opts, driver, device) return false end +local voltage_configuration = { + cluster = zcl_clusters.PowerConfiguration.ID, + attribute = zcl_clusters.PowerConfiguration.attributes.BatteryVoltage.ID, + minimum_interval = 30, + maximum_interval = 14300, + data_type = zcl_clusters.PowerConfiguration.attributes.BatteryVoltage.base_type, + reportable_change = 1 +} + +local function device_init(driver, device) + device:add_configured_attribute(voltage_configuration) + device:add_monitored_attribute(voltage_configuration) + device:remove_monitored_attribute(zcl_clusters.PowerConfiguration.ID, zcl_clusters.PowerConfiguration.attributes.BatteryPercentageRemaining.ID) + device:remove_configured_attribute(zcl_clusters.PowerConfiguration.ID, zcl_clusters.PowerConfiguration.attributes.BatteryPercentageRemaining.ID) + device:set_field(battery_defaults.DEVICE_MIN_VOLTAGE_KEY, 2.3) + device:set_field(battery_defaults.DEVICE_MAX_VOLTAGE_KEY, 3.0) +end + local centralite_systems = { NAME = "centralite systems", zigbee_handlers = { @@ -98,7 +116,7 @@ local centralite_systems = { } }, lifecycle_handlers = { - init = battery_defaults.build_linear_voltage_init(2.3, 3.0), + init = device_init, doConfigure = do_configure }, can_handle = is_centralite_systems diff --git a/drivers/SmartThings/zigbee-sound-sensor/src/init.lua b/drivers/SmartThings/zigbee-sound-sensor/src/init.lua index fb972bd5b5..f5b4d0d07f 100644 --- a/drivers/SmartThings/zigbee-sound-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-sound-sensor/src/init.lua @@ -126,7 +126,8 @@ local zigbee_sound_sensor_driver_template = { added = added_handler, doConfigure = do_configure, init = battery_defaults.build_linear_voltage_init(2.2, 3.0) - } + }, + health_check = false, } defaults.register_for_default_handlers(zigbee_sound_sensor_driver_template, zigbee_sound_sensor_driver_template.supported_capabilities) diff --git a/drivers/SmartThings/zigbee-valve/src/ezex/init.lua b/drivers/SmartThings/zigbee-valve/src/ezex/init.lua new file mode 100644 index 0000000000..53eddb5a1c --- /dev/null +++ b/drivers/SmartThings/zigbee-valve/src/ezex/init.lua @@ -0,0 +1,80 @@ +-- 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 IASZone = clusters.IASZone +local Basic = clusters.Basic +local OnOff = clusters.OnOff + +local configuration = { + { + cluster = IASZone.ID, + attribute = IASZone.attributes.ZoneStatus.ID, + minimum_interval = 0, + maximum_interval = 3600, + data_type = IASZone.attributes.ZoneStatus.base_type, + reportable_change = 1 + }, + { + cluster = Basic.ID, + attribute = Basic.attributes.PowerSource.ID, + minimum_interval = 30, + maximum_interval = 21600, + data_type = Basic.attributes.PowerSource.base_type, + }, + { + cluster = OnOff.ID, + attribute = OnOff.attributes.OnOff.ID, + minimum_interval = 0, + maximum_interval = 600, + data_type = OnOff.attributes.OnOff.base_type + } +} + +local function ias_zone_status_attr_handler(driver, device, zone_status, zb_rx) + -- this is cribbed from the DTH + if zone_status:is_battery_low_set() then + device:emit_event(capabilities.battery.battery(5)) + else + device:emit_event(capabilities.battery.battery(50)) + end +end + +local function device_init(driver, device) + for _, attribute in ipairs(configuration) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end +end + +local ezex_valve = { + NAME = "Ezex Valve", + zigbee_handlers = { + attr = { + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = ias_zone_status_attr_handler + }, + } + }, + lifecycle_handlers = { + init = device_init + }, + can_handle = function(opts, driver, device, ...) + return device:get_model() == "E253-KR0B0ZX-HA" and not device:supports_server_cluster(clusters.PowerConfiguration.ID) + end +} + +return ezex_valve diff --git a/drivers/SmartThings/zigbee-valve/src/init.lua b/drivers/SmartThings/zigbee-valve/src/init.lua index 693a0cf311..bc2059522d 100644 --- a/drivers/SmartThings/zigbee-valve/src/init.lua +++ b/drivers/SmartThings/zigbee-valve/src/init.lua @@ -52,7 +52,8 @@ local zigbee_valve_driver_template = { added = device_added }, sub_drivers = { - require("sinope") + require("sinope"), + require("ezex") } } diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua new file mode 100644 index 0000000000..60db8c2535 --- /dev/null +++ b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua @@ -0,0 +1,321 @@ +-- 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 clusters = require "st.zigbee.zcl.clusters" +local Basic = clusters.Basic +local OnOff = clusters.OnOff +local IASZone = clusters.IASZone +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" + +local mock_device = test.mock_device.build_test_zigbee_device( + { profile = t_utils.get_profile_definition("valve-battery-powerSource.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "", + model = "E253-KR0B0ZX-HA", + server_clusters = {0x0000, 0x0006, 0x0500} + } + } + } +) + +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_message_test( + "OnOff(on) reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, + true) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) + } + } +) + + +test.register_message_test( + "OnOff(off) reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_device, + false) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) + } + } +) + +test.register_message_test( + "Battery percentage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0008) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(5)) + } + } +) + +test.register_message_test( + "PowerSource(unknown) reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Basic.attributes.PowerSource:build_test_attr_report(mock_device, + 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.unknown()) + } + } +) + +test.register_message_test( + "PowerSource(mains) reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Basic.attributes.PowerSource:build_test_attr_report(mock_device, + 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + } + } +) + +test.register_message_test( + "PowerSource(battery) reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Basic.attributes.PowerSource:build_test_attr_report(mock_device, + 0x03) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + } + } +) + +test.register_message_test( + "PowerSource(dc) reporting should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Basic.attributes.PowerSource:build_test_attr_report(mock_device, + 0x04) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.dc()) + } + } +) + +test.register_message_test( + "Capability(valve) command(open) on should be handled", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "valve", component = "main", command = "open", args = { } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.On(mock_device) } + } + } +) + +test.register_message_test( + "Capability(valve) command(off) on should be handled", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "valve", component = "main", command = "close", args = { } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.Off(mock_device) } + } + } +) + +test.register_coroutine_test( + "doConfigure lifecycle should configure device", + function () + -- test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Basic.attributes.PowerSource:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOff.attributes.OnOff:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Basic.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Basic.attributes.PowerSource:configure_reporting(mock_device, 30, 21600) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, OnOff.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 600, 0) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, IASZone.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.ZoneStatus:configure_reporting(mock_device, 0, 3600, 1) + }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_message_test( + "Refresh should read all necessary attributes", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Basic.attributes.PowerSource:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + OnOff.attributes.OnOff:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.register_message_test( + "Device added event should refresh device states", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_device.id, "added" }, + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + Basic.attributes.PowerSource:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + OnOff.attributes.OnOff:read(mock_device) + } + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device.id, + IASZone.attributes.ZoneStatus:read(mock_device) + } + } + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-valve/src/init.lua b/drivers/SmartThings/zwave-valve/src/init.lua index 7392f1cbe1..36d6c65ea8 100644 --- a/drivers/SmartThings/zwave-valve/src/init.lua +++ b/drivers/SmartThings/zwave-valve/src/init.lua @@ -29,7 +29,8 @@ local driver_template = { sub_drivers = { -- Fortrezz and Zooz valves treat open as "off" and close as "on" require("inverse_valve") - } + }, + health_check = false, } defaults.register_for_default_handlers(driver_template, driver_template.supported_capabilities) diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 87378d7013..2f51db2f6e 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -79,3 +79,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "WallHero Switch (8 Way)",八位智能开关/场景面板 "WallHero Outlet",智能墙面五孔插座 "WallHero Remote Control (4-Inch)",语音场景(4寸)面板 +"Zemismart ZM25C Smart Curtain",Zemismart ZM25C 智能窗帘