From 94f3f9e5e22545fe554fcdd0e85d788bfa3f949c Mon Sep 17 00:00:00 2001 From: Juho Date: Wed, 8 Jul 2026 15:31:53 +0900 Subject: [PATCH] zigbee-window-treatment: don't halve battery percentage for IKEA devices IKEA blinds report BatteryPercentageRemaining in 1% units (0-100) rather than the 0.5% units defined by the ZCL spec, so the default handler shows them at half their real charge (a full battery reads ~50% in the app). Add an IKEA subdriver that emits the reported value as-is, the same way the zigbee-button and zigbee-dimmer-remote drivers already handle IKEA battery reports. Fixes #2679 --- .../src/ikea/can_handle.lua | 11 +++++++ .../zigbee-window-treatment/src/ikea/init.lua | 30 +++++++++++++++++++ .../src/sub_drivers.lua | 1 + .../test_zigbee_window_shade_battery_ikea.lua | 30 +++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/ikea/can_handle.lua create mode 100644 drivers/SmartThings/zigbee-window-treatment/src/ikea/init.lua diff --git a/drivers/SmartThings/zigbee-window-treatment/src/ikea/can_handle.lua b/drivers/SmartThings/zigbee-window-treatment/src/ikea/can_handle.lua new file mode 100644 index 0000000000..66490dc9dc --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/ikea/can_handle.lua @@ -0,0 +1,11 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local can_handle_ikea = function(opts, driver, device) + if device:get_manufacturer() == "IKEA of Sweden" then + return true, require("ikea") + end + return false +end + +return can_handle_ikea diff --git a/drivers/SmartThings/zigbee-window-treatment/src/ikea/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/ikea/init.lua new file mode 100644 index 0000000000..ee8fb89f80 --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/ikea/init.lua @@ -0,0 +1,30 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local utils = require "st.utils" +local PowerConfiguration = clusters.PowerConfiguration + +-- IKEA devices report BatteryPercentageRemaining in 1% units (0-100) +-- rather than the 0.5% units (0-200) defined by the ZCL spec, so the +-- default handler would halve the reported percentage. Emit the raw +-- value instead, matching the IKEA handling in the zigbee-button and +-- zigbee-dimmer-remote drivers. +local function battery_perc_attr_handler(driver, device, value, zb_rx) + device:emit_event(capabilities.battery.battery(utils.clamp_value(value.value, 0, 100))) +end + +local ikea_window_treatment = { + NAME = "ikea window treatment", + zigbee_handlers = { + attr = { + [PowerConfiguration.ID] = { + [PowerConfiguration.attributes.BatteryPercentageRemaining.ID] = battery_perc_attr_handler + } + } + }, + can_handle = require("ikea.can_handle"), +} + +return ikea_window_treatment diff --git a/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua b/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua index 3353c87fd4..87c77b6f4b 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/sub_drivers.lua @@ -16,5 +16,6 @@ local sub_drivers = { lazy_load_if_possible("screen-innovations"), lazy_load_if_possible("VIVIDSTORM"), lazy_load_if_possible("HOPOsmart"), + lazy_load_if_possible("ikea"), } return sub_drivers diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua index 26a08b1a84..5f8392f5c1 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua @@ -10,6 +10,7 @@ local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local WindowCovering = clusters.WindowCovering +local PowerConfiguration = clusters.PowerConfiguration local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("window-treatment-battery.yml"), @@ -355,4 +356,33 @@ test.register_coroutine_test( } ) +test.register_message_test( + "Battery percentage reports should not be halved for IKEA devices", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 100) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:build_test_attr_report(mock_device, 55) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(55)) + } + }, + { + min_api_version = 17 + } +) + test.run_registered_tests()