From fa574e8309d1cac5c7103a8750edb83d9f222f47 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 21 Nov 2024 12:47:20 -0600 Subject: [PATCH 01/18] Add support for the color mode attribute --- .../SmartThings/matter-switch/src/init.lua | 45 ++++++++++++++++--- .../test/test_light_illuminance_motion.lua | 1 + .../src/test/test_matter_switch.lua | 1 + 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index b37390d995..61ae5e3562 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -60,6 +60,7 @@ local ENERGY_MANAGEMENT_ENDPOINT = "__energy_management_endpoint" local IS_PARENT_CHILD_DEVICE = "__is_parent_child_device" local COLOR_TEMP_BOUND_RECEIVED_KELVIN = "__colorTemp_bound_received_kelvin" local COLOR_TEMP_BOUND_RECEIVED_MIRED = "__colorTemp_bound_received_mired" +local COLOR_MODE = "__color_mode" local COLOR_TEMP_MIN = "__color_temp_min" local COLOR_TEMP_MAX = "__color_temp_max" local LEVEL_BOUND_RECEIVED = "__level_bound_received" @@ -881,14 +882,26 @@ end local function hue_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) + local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) + -- don't send capability events if the color of the device is being determined by CurrentX and CurrentY (1) or ColorTemperatureMireds (2) + if current_color_mode == 1 or current_color_mode == 2 then + log.info_with({hub_logs=true}, string.format("CurrentHue and CurrentSaturation are not currently determining the color of the device. ColorMode is %d", current_color_mode)) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) + end end end local function sat_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) + local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) + -- don't send capability events if the color of the device is being determined by CurrentX and CurrentY (1) or ColorTemperatureMireds (2) + if current_color_mode == 1 or current_color_mode == 2 then + log.info_with({hub_logs=true}, string.format("CurrentHue and CurrentSaturation are not currently determining the color of the device. ColorMode is %d", current_color_mode)) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) + end end end @@ -999,8 +1012,14 @@ local function x_attr_handler(driver, device, ib, response) else local x = ib.data.value local h, s, _ = color_utils.safe_xy_to_hsv(x, y) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) + local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) + -- don't send capability events if the color of the device is being determined by CurrentHue and CurrentSaturation (0) or ColorTemperatureMireds (2) + if current_color_mode == 0 or current_color_mode == 2 then + log.info_with({hub_logs=true}, string.format("CurrentX and CurrentY are not currently determining the color of the device. ColorMode is %d", current_color_mode)) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) + end device:set_field(RECEIVED_Y, nil) end end @@ -1012,12 +1031,24 @@ local function y_attr_handler(driver, device, ib, response) else local y = ib.data.value local h, s, _ = color_utils.safe_xy_to_hsv(x, y) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) + local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) + -- don't send capability events if the color of the device is being determined by CurrentHue and CurrentSaturation (0) or ColorTemperatureMireds (2) + if current_color_mode == 0 or current_color_mode == 2 then + log.info_with({hub_logs=true}, string.format("CurrentX and CurrentY are not currently determining the color of the device. ColorMode is %d", current_color_mode)) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) + end device:set_field(RECEIVED_X, nil) end end +local function color_mode_attr_handler(driver, device, ib, response) + if ib.data.value ~= nil then + set_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id, tonumber(ib.data.value), {persist = true}) + end +end + --TODO setup configure handler to read this attribute. local function color_cap_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then @@ -1308,6 +1339,7 @@ local matter_driver_template = { [clusters.ColorControl.attributes.ColorTemperatureMireds.ID] = temp_attr_handler, [clusters.ColorControl.attributes.CurrentX.ID] = x_attr_handler, [clusters.ColorControl.attributes.CurrentY.ID] = y_attr_handler, + [clusters.ColorControl.attributes.ColorMode.ID] = color_mode_attr_handler, [clusters.ColorControl.attributes.ColorCapabilities.ID] = color_cap_attr_handler, [clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds.ID] = mired_bounds_handler_factory(COLOR_TEMP_MIN), -- max mireds = min kelvin [clusters.ColorControl.attributes.ColorTempPhysicalMinMireds.ID] = mired_bounds_handler_factory(COLOR_TEMP_MAX), -- min mireds = max kelvin @@ -1370,6 +1402,7 @@ local matter_driver_template = { clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, }, [capabilities.colorTemperature.ID] = { clusters.ColorControl.attributes.ColorTemperatureMireds, 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 index 335a6b86c8..72f7a5f6a2 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -85,6 +85,7 @@ local function test_init() clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, clusters.ColorControl.attributes.ColorTemperatureMireds, clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index 676e0928fc..eb48e9ca87 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -86,6 +86,7 @@ local cluster_subscribe_list = { clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorMode, clusters.ColorControl.attributes.ColorTemperatureMireds, clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, From fbd57b8ec69edbba4f0a2b5b99f3c75e1c4964b5 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 21 Nov 2024 15:30:53 -0600 Subject: [PATCH 02/18] Adding test cases --- .../src/test/test_matter_switch.lua | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index eb48e9ca87..7d977a67dc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -905,4 +905,116 @@ test.register_message_test( } ) +test.register_message_test( + "colorControl capability sent based on CurrentHue and CurrentSaturation but not CurrentX and CurrentY due to ColorMode", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0xFE) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(100)) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0xFE) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(100)) + }, + { + 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) + } + }, + } +) + +test.register_message_test( + "colorControl capability sent based on CurrentX and CurrentY but not CurrentHue and CurrentSaturation due to ColorMode", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, 1) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0xFE) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0xFE) + } + }, + { + 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.run_registered_tests() From e7fb55836004eaabd03ffe93f814bd960ebdbc59 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 5 Dec 2024 15:35:30 -0600 Subject: [PATCH 03/18] Add handling in case color mode is not received before hue/sat or x/y values --- .../SmartThings/matter-switch/src/init.lua | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 61ae5e3562..9f72cfb871 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -63,6 +63,8 @@ local COLOR_TEMP_BOUND_RECEIVED_MIRED = "__colorTemp_bound_received_mired" local COLOR_MODE = "__color_mode" local COLOR_TEMP_MIN = "__color_temp_min" local COLOR_TEMP_MAX = "__color_temp_max" +local CURRENT_HUE = "__current_hue" +local CURRENT_SAT = "__current_sat" local LEVEL_BOUND_RECEIVED = "__level_bound_received" local LEVEL_MIN = "__level_min" local LEVEL_MAX = "__level_max" @@ -883,10 +885,10 @@ local function hue_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - -- don't send capability events if the color of the device is being determined by CurrentX and CurrentY (1) or ColorTemperatureMireds (2) - if current_color_mode == 1 or current_color_mode == 2 then - log.info_with({hub_logs=true}, string.format("CurrentHue and CurrentSaturation are not currently determining the color of the device. ColorMode is %d", current_color_mode)) - else + -- don't send capability events if the color of the device is being determined by CurrentX and CurrentY + -- (ColorMode 1), store the value for now in case the ColorMode was changed. + set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, hue, {persist = true}) + if current_color_mode ~= 1 then device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) end end @@ -896,10 +898,10 @@ local function sat_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - -- don't send capability events if the color of the device is being determined by CurrentX and CurrentY (1) or ColorTemperatureMireds (2) - if current_color_mode == 1 or current_color_mode == 2 then - log.info_with({hub_logs=true}, string.format("CurrentHue and CurrentSaturation are not currently determining the color of the device. ColorMode is %d", current_color_mode)) - else + -- don't send capability events if the color of the device is being determined by CurrentX and CurrentY + -- (ColorMode 1), store the value for now in case the ColorMode was changed. + set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, sat, {persist = true}) + if current_color_mode ~= 1 then device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) end end @@ -1013,10 +1015,11 @@ local function x_attr_handler(driver, device, ib, response) local x = ib.data.value local h, s, _ = color_utils.safe_xy_to_hsv(x, y) local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - -- don't send capability events if the color of the device is being determined by CurrentHue and CurrentSaturation (0) or ColorTemperatureMireds (2) - if current_color_mode == 0 or current_color_mode == 2 then - log.info_with({hub_logs=true}, string.format("CurrentX and CurrentY are not currently determining the color of the device. ColorMode is %d", current_color_mode)) - else + -- don't send capability events if the color of the device is being determined by CurrentHue and CurrentSaturation + -- (ColorMode 0), store the values for now in case the ColorMode was changed. + set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, h, {persist = true}) + set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, s, {persist = true}) + if current_color_mode ~= 0 then device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) end @@ -1032,10 +1035,11 @@ local function y_attr_handler(driver, device, ib, response) local y = ib.data.value local h, s, _ = color_utils.safe_xy_to_hsv(x, y) local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - -- don't send capability events if the color of the device is being determined by CurrentHue and CurrentSaturation (0) or ColorTemperatureMireds (2) - if current_color_mode == 0 or current_color_mode == 2 then - log.info_with({hub_logs=true}, string.format("CurrentX and CurrentY are not currently determining the color of the device. ColorMode is %d", current_color_mode)) - else + -- don't send capability events if the color of the device is being determined by CurrentHue and CurrentSaturation + -- (ColorMode 0), store the values for now in case the ColorMode was changed. + set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, h, {persist = true}) + set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, s, {persist = true}) + if current_color_mode ~= 0 then device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) end @@ -1045,7 +1049,29 @@ end local function color_mode_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then - set_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id, tonumber(ib.data.value), {persist = true}) + local previous_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) + local hue = get_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id) + local sat = get_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id) + if ib.data.value ~= previous_color_mode then + if ib.data.value == 0 then + if hue ~= nil then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) + set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, nil, {persist = true}) + end + if sat ~= nil then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) + set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, nil, {persist = true}) + end + elseif ib.data.value == 1 then + if hue ~= nil and sat ~= nil then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) + set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, nil, {persist = true}) + set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, nil, {persist = true}) + end + end + end + set_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id, ib.data.value, {persist = true}) end end From 21d45d5e97b2aa6899fddf4a180196a18199d4f4 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Tue, 18 Feb 2025 16:38:27 -0600 Subject: [PATCH 04/18] Update color mode handler to read attributes Read the ColorMode during init, and then when the handler runs, read the corresponding attributes based on the color mode. --- .../SmartThings/matter-switch/src/init.lua | 83 ++++++------------- .../test/test_light_illuminance_motion.lua | 1 - .../src/test/test_matter_switch.lua | 1 - 3 files changed, 24 insertions(+), 61 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 9f72cfb871..3b44517a7d 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -15,6 +15,7 @@ local capabilities = require "st.capabilities" local log = require "log" local clusters = require "st.matter.clusters" +local im = require "st.matter.interaction_model" local MatterDriver = require "st.matter.driver" local lua_socket = require "socket" local utils = require "st.utils" @@ -63,8 +64,6 @@ local COLOR_TEMP_BOUND_RECEIVED_MIRED = "__colorTemp_bound_received_mired" local COLOR_MODE = "__color_mode" local COLOR_TEMP_MIN = "__color_temp_min" local COLOR_TEMP_MAX = "__color_temp_max" -local CURRENT_HUE = "__current_hue" -local CURRENT_SAT = "__current_sat" local LEVEL_BOUND_RECEIVED = "__level_bound_received" local LEVEL_MIN = "__level_min" local LEVEL_MAX = "__level_max" @@ -731,6 +730,7 @@ local function device_init(driver, device) end end device:subscribe() + device:send(clusters.ColorControl.attributes.ColorMode:read(device)) end local function device_removed(driver, device) @@ -884,26 +884,14 @@ end local function hue_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) - local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - -- don't send capability events if the color of the device is being determined by CurrentX and CurrentY - -- (ColorMode 1), store the value for now in case the ColorMode was changed. - set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, hue, {persist = true}) - if current_color_mode ~= 1 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) - end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) end end local function sat_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) - local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - -- don't send capability events if the color of the device is being determined by CurrentX and CurrentY - -- (ColorMode 1), store the value for now in case the ColorMode was changed. - set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, sat, {persist = true}) - if current_color_mode ~= 1 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) - end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) end end @@ -1014,15 +1002,8 @@ local function x_attr_handler(driver, device, ib, response) else local x = ib.data.value local h, s, _ = color_utils.safe_xy_to_hsv(x, y) - local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - -- don't send capability events if the color of the device is being determined by CurrentHue and CurrentSaturation - -- (ColorMode 0), store the values for now in case the ColorMode was changed. - set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, h, {persist = true}) - set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, s, {persist = true}) - if current_color_mode ~= 0 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) - end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) device:set_field(RECEIVED_Y, nil) end end @@ -1034,44 +1015,29 @@ local function y_attr_handler(driver, device, ib, response) else local y = ib.data.value local h, s, _ = color_utils.safe_xy_to_hsv(x, y) - local current_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - -- don't send capability events if the color of the device is being determined by CurrentHue and CurrentSaturation - -- (ColorMode 0), store the values for now in case the ColorMode was changed. - set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, h, {persist = true}) - set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, s, {persist = true}) - if current_color_mode ~= 0 then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) - end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(h)) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(s)) device:set_field(RECEIVED_X, nil) end end local function color_mode_attr_handler(driver, device, ib, response) - if ib.data.value ~= nil then - local previous_color_mode = get_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id) - local hue = get_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id) - local sat = get_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id) - if ib.data.value ~= previous_color_mode then - if ib.data.value == 0 then - if hue ~= nil then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) - set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, nil, {persist = true}) - end - if sat ~= nil then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) - set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, nil, {persist = true}) - end - elseif ib.data.value == 1 then - if hue ~= nil and sat ~= nil then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) - set_field_for_endpoint(device, CURRENT_HUE, ib.endpoint_id, nil, {persist = true}) - set_field_for_endpoint(device, CURRENT_SAT, ib.endpoint_id, nil, {persist = true}) - end - end - end - set_field_for_endpoint(device, COLOR_MODE, ib.endpoint_id, ib.data.value, {persist = true}) + if ib.data.value == nil then + return + end + local color_mode = ib.data.value + local read_req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) + if color_mode == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then + read_req:merge(clusters.ColorControl.attributes.CurrentHue:read(device)) + read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read(device)) + device:send(read_req) + elseif color_mode == clusters.ColorControl.types.ColorMode.CURRENT_X_AND_CURRENT_Y then + read_req:merge(clusters.ColorControl.attributes.CurrentX:read(device)) + read_req:merge(clusters.ColorControl.attributes.CurrentY:read(device)) + device:send(read_req) + else -- color_mode = clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE + read_req:merge(clusters.ColorControl.attributes.ColorTemperatureMireds:read(device)) + device:send(read_req) end end @@ -1428,7 +1394,6 @@ local matter_driver_template = { clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, - clusters.ColorControl.attributes.ColorMode, }, [capabilities.colorTemperature.ID] = { clusters.ColorControl.attributes.ColorTemperatureMireds, 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 index 72f7a5f6a2..335a6b86c8 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -85,7 +85,6 @@ local function test_init() clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, - clusters.ColorControl.attributes.ColorMode, clusters.ColorControl.attributes.ColorTemperatureMireds, clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index 7d977a67dc..7d2958c364 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -86,7 +86,6 @@ local cluster_subscribe_list = { clusters.ColorControl.attributes.CurrentSaturation, clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY, - clusters.ColorControl.attributes.ColorMode, clusters.ColorControl.attributes.ColorTemperatureMireds, clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, From 194b4b34f4dae284dadd251b5452047d7ea891a1 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 19 Feb 2025 15:52:03 -0600 Subject: [PATCH 05/18] Refactor color mode attribute handler --- drivers/SmartThings/matter-switch/src/init.lua | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 3b44517a7d..97f67c2992 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -1022,21 +1022,17 @@ local function y_attr_handler(driver, device, ib, response) end local function color_mode_attr_handler(driver, device, ib, response) - if ib.data.value == nil then - return - end - local color_mode = ib.data.value local read_req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - if color_mode == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then + if ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then read_req:merge(clusters.ColorControl.attributes.CurrentHue:read(device)) read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read(device)) - device:send(read_req) - elseif color_mode == clusters.ColorControl.types.ColorMode.CURRENT_X_AND_CURRENT_Y then + elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_X_AND_CURRENT_Y then read_req:merge(clusters.ColorControl.attributes.CurrentX:read(device)) read_req:merge(clusters.ColorControl.attributes.CurrentY:read(device)) - device:send(read_req) - else -- color_mode = clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE + elseif ib.data.value == clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE then read_req:merge(clusters.ColorControl.attributes.ColorTemperatureMireds:read(device)) + end + if #read_req.info_blocks ~= 0 then device:send(read_req) end end From 8679b60d50dfdf8e2af05d0f1a84567c7bfd06d4 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 19 Feb 2025 16:30:13 -0600 Subject: [PATCH 06/18] Update test cases to account for new attribute reads --- .../SmartThings/matter-switch/src/init.lua | 1 + .../src/test/test_electrical_sensor.lua | 7 +++++++ .../test/test_light_illuminance_motion.lua | 2 ++ .../src/test/test_matter_bridge.lua | 2 ++ .../src/test/test_matter_button.lua | 4 +++- .../src/test/test_matter_multi_button.lua | 3 +++ .../test_matter_multi_button_switch_mcd.lua | 6 ++++++ .../src/test/test_matter_switch.lua | 3 +++ .../test/test_matter_switch_device_types.lua | 20 +++++++++++++++++++ .../src/test/test_matter_water_valve.lua | 2 ++ .../src/test/test_multi_switch_mcd.lua | 6 ++++++ .../test_multi_switch_parent_child_lights.lua | 6 ++++++ .../test_multi_switch_parent_child_plugs.lua | 6 ++++-- 13 files changed, 65 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 97f67c2992..f9e9d3b0be 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -1233,6 +1233,7 @@ end local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then device:subscribe() + device:send(clusters.ColorControl.attributes.ColorMode:read(device)) if device:get_field(DEFERRED_CONFIGURE) and device.network_type ~= device_lib.NETWORK_TYPE_CHILD then -- profile has changed, and we deferred setting up our buttons, so do that now configure_buttons(device) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua index 9faa5d117b..899710ae70 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua @@ -139,6 +139,8 @@ local function test_init() end end test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) -- to test powerConsumptionReport test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") @@ -153,6 +155,8 @@ local function test_init_periodic() end end test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_periodic.id, read_color_mode}) test.mock_device.add_test_device(mock_device_periodic) -- to test powerConsumptionReport test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") @@ -167,6 +171,9 @@ test.register_coroutine_test( subscribe_request:merge(cluster:subscribe(mock_device)) end end + test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 0.0, unit = "W" })) ) 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 index 335a6b86c8..e7876f0821 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -99,6 +99,8 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua index 54eb21dc88..9d5d637a1a 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -82,6 +82,8 @@ local function test_init_mock_bridge() end end test.socket.matter:__expect_send({mock_bridge.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_bridge.id, read_color_mode}) test.mock_device.add_test_device(mock_bridge) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua index b45d774c39..85885067ad 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -59,7 +59,8 @@ local function test_init() if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) @@ -69,6 +70,7 @@ local function test_init() local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) 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", button_attr.pushed({state_change = false}))) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua index a420fc933b..2ccd3444e4 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua @@ -111,11 +111,14 @@ local function test_init() local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local device_info_copy = utils.deep_copy(mock_device.raw_st_data) device_info_copy.profile.id = "5-buttons-battery" local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) 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", button_attr.pushed({state_change = false}))) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua index 0d71c8fa07..1da318ccf9 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua @@ -187,6 +187,8 @@ local function test_init() if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) test.mock_device.add_test_device(mock_child) mock_device:expect_device_create({ @@ -198,12 +200,14 @@ local function test_init() }) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) mock_device:expect_metadata_update({ profile = "light-level-3-button" }) local device_info_copy = utils.deep_copy(mock_device.raw_st_data) device_info_copy.profile.id = "3-button" local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.socket.capability:__expect_send(mock_device:generate_test_message("button1", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(mock_device:generate_test_message("button1", button_attr.pushed({state_change = false}))) @@ -239,6 +243,8 @@ local function test_init_mcd_unsupported_switch_device_type() }) test.mock_device.add_test_device(mock_device_mcd_unsupported_switch_device_type) test.socket.matter:__expect_send({mock_device_mcd_unsupported_switch_device_type.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_mcd_unsupported_switch_device_type.id, read_color_mode}) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index 7d2958c364..32d249b7c6 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -100,6 +100,8 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) @@ -109,6 +111,7 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device_no_hue_sat.id, read_color_mode}) test.mock_device.add_test_device(mock_device_no_hue_sat) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua index d896607284..5fd129f7cd 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua @@ -305,6 +305,8 @@ local mock_device_parent_child_unsupported_device_type = test.mock_device.build_ local function test_init_parent_child_switch_types() local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_parent_child_switch_types) test.socket.matter:__expect_send({mock_device_parent_child_switch_types.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_parent_child_switch_types.id, read_color_mode}) test.mock_device.add_test_device(mock_device_parent_child_switch_types) mock_device_parent_child_switch_types:expect_metadata_update({ profile = "switch-level" }) @@ -319,33 +321,45 @@ end local function test_init_onoff() test.mock_device.add_test_device(mock_device_onoff) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_onoff.id, read_color_mode}) mock_device_onoff:expect_metadata_update({ profile = "switch-binary" }) end local function test_init_onoff_client() test.mock_device.add_test_device(mock_device_onoff_client) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_onoff_client.id, read_color_mode}) end local function test_init_parent_client_child_server() local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_parent_client_child_server) test.socket.matter:__expect_send({mock_device_parent_client_child_server.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_parent_client_child_server.id, read_color_mode}) test.mock_device.add_test_device(mock_device_parent_client_child_server) mock_device_parent_client_child_server:expect_metadata_update({ profile = "switch-binary" }) end local function test_init_dimmer() test.mock_device.add_test_device(mock_device_dimmer) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_dimmer.id, read_color_mode}) mock_device_dimmer:expect_metadata_update({ profile = "switch-level" }) end local function test_init_color_dimmer() test.mock_device.add_test_device(mock_device_color_dimmer) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_color_dimmer.id, read_color_mode}) mock_device_color_dimmer:expect_metadata_update({ profile = "switch-color-level" }) end local function test_init_water_valve() test.mock_device.add_test_device(mock_device_water_valve) test.socket.device_lifecycle:__queue_receive({ mock_device_water_valve.id, "doConfigure" }) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_water_valve.id, read_color_mode}) mock_device_water_valve:expect_metadata_update({ profile = "water-valve-level" }) mock_device_water_valve:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -372,6 +386,9 @@ local function test_init_parent_child_different_types() end test.socket.matter:__expect_send({mock_device_parent_child_different_types.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_parent_child_different_types.id, read_color_mode}) + test.mock_device.add_test_device(mock_device_parent_child_different_types) mock_device_parent_child_different_types:expect_device_create({ @@ -387,6 +404,9 @@ local function test_init_parent_child_unsupported_device_type() test.mock_device.add_test_device(mock_device_parent_child_unsupported_device_type) mock_device_parent_child_unsupported_device_type:expect_metadata_update({ profile = "switch-binary" }) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_parent_child_unsupported_device_type.id, read_color_mode}) + mock_device_parent_child_unsupported_device_type:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 2", diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua index d4ead24458..c3a89cf334 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua @@ -64,6 +64,8 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua index 8680a57740..29ce7b25d9 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua @@ -157,6 +157,8 @@ local function test_init_mock_3switch() test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch) test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_3switch.id, read_color_mode}) test.mock_device.add_test_device(mock_3switch) end @@ -169,6 +171,8 @@ local function test_init_mock_2switch() test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_2switch) test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_2switch.id, read_color_mode}) test.mock_device.add_test_device(mock_2switch) end @@ -181,6 +185,8 @@ local function test_init_mock_3switch_non_sequential() test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch_non_sequential) test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_3switch_non_sequential.id, read_color_mode}) test.mock_device.add_test_device(mock_3switch_non_sequential) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua index 065b364998..9ce06f7018 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua @@ -180,6 +180,9 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) + test.mock_device.add_test_device(mock_device) for _, child in pairs(mock_children) do test.mock_device.add_test_device(child) @@ -243,6 +246,9 @@ local function test_init_parent_child_endpoints_non_sequential() end test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, read_color_mode}) + test.mock_device.add_test_device(mock_device_parent_child_endpoints_non_sequential) for _, child in pairs(mock_children_non_sequential) do test.mock_device.add_test_device(child) diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua index 5b2e080713..497f5326a0 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua @@ -137,7 +137,8 @@ local function test_init() } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) for _, child in pairs(mock_children) do test.mock_device.add_test_device(child) @@ -178,7 +179,8 @@ local function test_init_child_profile_override() } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_child_profile_override) test.socket.matter:__expect_send({mock_device_child_profile_override.id, subscribe_request}) - + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device_child_profile_override.id, read_color_mode}) test.mock_device.add_test_device(mock_device_child_profile_override) for _, child in pairs(mock_children_child_profile_override) do test.mock_device.add_test_device(child) From 10bb3d1efaa6adee21c26f48bb9cb1191884593a Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 20 Feb 2025 16:19:57 -0600 Subject: [PATCH 07/18] Fix and clean up test cases after refactoring previous changes --- .../SmartThings/matter-switch/src/init.lua | 2 +- .../test/test_aqara_climate_sensor_w100.lua | 4 + .../src/test/test_aqara_light_switch_h2.lua | 4 + .../src/test/test_electrical_sensor.lua | 1 - .../src/test/test_matter_button.lua | 2 + .../src/test/test_matter_multi_button.lua | 3 +- .../src/test/test_matter_switch.lua | 187 +++++++++--------- 7 files changed, 108 insertions(+), 95 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index f9e9d3b0be..7ae1c64f89 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -1026,7 +1026,7 @@ local function color_mode_attr_handler(driver, device, ib, response) if ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then read_req:merge(clusters.ColorControl.attributes.CurrentHue:read(device)) read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read(device)) - elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_X_AND_CURRENT_Y then + elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY then read_req:merge(clusters.ColorControl.attributes.CurrentX:read(device)) read_req:merge(clusters.ColorControl.attributes.CurrentY:read(device)) elseif ib.data.value == clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE then diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua index 54a18d2d06..040aa8a63d 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua @@ -142,11 +142,14 @@ local function test_init() end test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.mock_device.add_test_device(aqara_mock_device) test.set_rpc_version(5) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "added" }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.mock_devices_api._expected_device_updates[aqara_mock_device.device_id] = "00000000-1111-2222-3333-000000000001" test.mock_devices_api._expected_device_updates[1] = {device_id = "00000000-1111-2222-3333-000000000001"} test.mock_devices_api._expected_device_updates[1].metadata = {deviceId="00000000-1111-2222-3333-000000000001", profileReference="3-button-battery-temperature-humidity"} @@ -157,6 +160,7 @@ local function test_init() local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) read_request = cluster_read_list[1]:read(aqara_mock_device, 3) read_request:merge(cluster_read_list[1]:subscribe(aqara_mock_device)) diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua index ec72b9a19b..7f2ee0cb1d 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua @@ -163,6 +163,8 @@ local function test_init() end end test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.mock_device.add_test_device(aqara_mock_device) -- to test powerConsumptionReport test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") @@ -189,6 +191,7 @@ local function test_init() test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "added" }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.mock_devices_api._expected_device_updates[aqara_mock_device.device_id] = "00000000-1111-2222-3333-000000000001" test.mock_devices_api._expected_device_updates[1] = {device_id = "00000000-1111-2222-3333-000000000001"} test.mock_devices_api._expected_device_updates[1].metadata = {deviceId="00000000-1111-2222-3333-000000000001", profileReference="4-button"} @@ -199,6 +202,7 @@ local function test_init() local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("main", button_attr.pushed({state_change = false}))) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua index 899710ae70..3cf79b2ef3 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua @@ -181,7 +181,6 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) ) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.wait_for_events() end ) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua index 85885067ad..46c66f18bb 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -65,6 +65,8 @@ local function test_init() local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local device_info_copy = utils.deep_copy(mock_device.raw_st_data) device_info_copy.profile.id = "buttons-battery" local device_info_json = dkjson.encode(device_info_copy) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua index 2ccd3444e4..ee5d7b28bc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua @@ -108,10 +108,11 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local device_info_copy = utils.deep_copy(mock_device.raw_st_data) device_info_copy.profile.id = "5-buttons-battery" diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index 32d249b7c6..3e4c24b823 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -15,6 +15,7 @@ local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" +local uint32 = require "st.matter.data_types.Uint32" local clusters = require "st.matter.clusters" local TRANSITION_TIME = 0 @@ -907,116 +908,118 @@ test.register_message_test( } ) -test.register_message_test( - "colorControl capability sent based on CurrentHue and CurrentSaturation but not CurrentX and CurrentY due to ColorMode", - { - { - channel = "matter", - direction = "receive", - message = { +test.register_coroutine_test( + "colorControl capability sent based on CurrentHue and CurrentSaturation due to ColorMode", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) + } + ) + local read_hue_sat = clusters.ColorControl.attributes.CurrentHue:read() + read_hue_sat:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) + test.socket.matter:__expect_send( + { + mock_device.id, + read_hue_sat + } + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0xFE), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorControl.hue(100) + ) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0xFE), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorControl.saturation(100) + ) + ) + end +) + +test.register_coroutine_test( + "colorControl capability sent based on CurrentX and CurrentY due to ColorMode", + function() + test.socket.matter:__queue_receive( + { mock_device.id, - clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, 0) + clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + local read_x_y = clusters.ColorControl.attributes.CurrentX:read() + read_x_y:merge(clusters.ColorControl.attributes.CurrentY:read()) + test.socket.matter:__expect_send( + { mock_device.id, - clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0xFE) + read_x_y } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorControl.hue(100)) - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0xFE) - } - }, - { - channel = "capability", - direction = "send", - message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(100)) - }, - { - channel = "matter", - direction = "receive", - message = { + ) + test.socket.matter:__queue_receive( + { mock_device.id, clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + test.socket.matter:__queue_receive( + { mock_device.id, clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) } - }, - } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorControl.hue(50) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorControl.saturation(72) + ) + ) + end ) -test.register_message_test( - "colorControl capability sent based on CurrentX and CurrentY but not CurrentHue and CurrentSaturation due to ColorMode", - { - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, 1) - } - }, - { - channel = "matter", - direction = "receive", - message = { +test.register_coroutine_test( + "colorControl capability sent based on Color Temperature due to ColorMode", + function() + test.socket.matter:__queue_receive( + { mock_device.id, - clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0xFE) + clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE) } - }, - { - channel = "matter", - direction = "receive", - message = { - mock_device.id, - clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0xFE) - } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + local read_color_temp = clusters.ColorControl.attributes.ColorTemperatureMireds:read() + test.socket.matter:__expect_send( + { mock_device.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) + read_color_temp } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + test.socket.matter:__queue_receive( + { mock_device.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 305) } - }, - { - 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.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.colorTemperature.colorTemperature(3279) + ) + ) + end ) test.run_registered_tests() From b6ba5d7bd57da0ac3ecb28835f18eba90a5ce478 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Fri, 21 Feb 2025 15:15:46 -0600 Subject: [PATCH 08/18] Fix luacheck issues --- drivers/SmartThings/matter-switch/src/init.lua | 2 -- .../SmartThings/matter-switch/src/test/test_matter_switch.lua | 1 - 2 files changed, 3 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 7ae1c64f89..e3ec0c2563 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -20,7 +20,6 @@ local MatterDriver = require "st.matter.driver" local lua_socket = require "socket" local utils = require "st.utils" local device_lib = require "st.device" -local im = require "st.matter.interaction_model" local embedded_cluster_utils = require "embedded-cluster-utils" -- Include driver-side definitions when lua libs api version is < 11 local version = require "version" @@ -61,7 +60,6 @@ local ENERGY_MANAGEMENT_ENDPOINT = "__energy_management_endpoint" local IS_PARENT_CHILD_DEVICE = "__is_parent_child_device" local COLOR_TEMP_BOUND_RECEIVED_KELVIN = "__colorTemp_bound_received_kelvin" local COLOR_TEMP_BOUND_RECEIVED_MIRED = "__colorTemp_bound_received_mired" -local COLOR_MODE = "__color_mode" local COLOR_TEMP_MIN = "__color_temp_min" local COLOR_TEMP_MAX = "__color_temp_max" local LEVEL_BOUND_RECEIVED = "__level_bound_received" diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index 3e4c24b823..a8212ac690 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -15,7 +15,6 @@ local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" -local uint32 = require "st.matter.data_types.Uint32" local clusters = require "st.matter.clusters" local TRANSITION_TIME = 0 From a5c2d4455a9a7d13dac6c5d426d232b5c16e3851 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Tue, 25 Feb 2025 12:31:43 -0600 Subject: [PATCH 09/18] Change method of determining color mode Set the color mode according to the most recent attribute handler that runs and check this after each device init rather than reading the ColorMode from the device. The original issue is due to subscribing to multiple attributes that can individually control the color of the device without considering that there is not a guaranteed order that the attribute handlers will run in. The changes in this commit address this by having special behavior to check the color mode following driver init. --- .../SmartThings/matter-switch/src/init.lua | 82 +++++++++++++------ .../test/test_aqara_climate_sensor_w100.lua | 4 - .../src/test/test_aqara_light_switch_h2.lua | 4 - .../src/test/test_electrical_sensor.lua | 8 +- .../test/test_light_illuminance_motion.lua | 2 - .../src/test/test_matter_bridge.lua | 2 - .../src/test/test_matter_button.lua | 4 - .../src/test/test_matter_multi_button.lua | 4 - .../test_matter_multi_button_switch_mcd.lua | 6 -- .../src/test/test_matter_switch.lua | 3 - .../test/test_matter_switch_device_types.lua | 20 ----- .../src/test/test_matter_water_valve.lua | 2 - .../src/test/test_multi_switch_mcd.lua | 6 -- .../test_multi_switch_parent_child_lights.lua | 6 -- .../test_multi_switch_parent_child_plugs.lua | 6 +- 15 files changed, 59 insertions(+), 100 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index e3ec0c2563..f130580411 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -42,6 +42,12 @@ local COLOR_TEMPERATURE_MIRED_MIN = MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPE local SWITCH_LEVEL_LIGHTING_MIN = 1 local CURRENT_HUESAT_ATTR_MIN = 0 local CURRENT_HUESAT_ATTR_MAX = 254 +local INIT_HUE = "__init_hue" +local INIT_SAT = "__init_sat" +local INIT_X = "__init_x" +local INIT_Y = "__init_y" +local INIT_TEMP = "__init_temp" +local COLOR_MODE = "__color_mode" local SWITCH_INITIALIZED = "__switch_intialized" -- COMPONENT_TO_ENDPOINT_MAP is here only to preserve the endpoint mapping for @@ -368,6 +374,31 @@ local function mired_to_kelvin(value, minOrMax) end end +--- set_color_mode helper function used to set the current color mode from the +--- attribute handlers for hue, saturation, x, y, and color temp. +--- +--- If the init_type field is set, this is the first time the caller has ran +--- since init. In this case we set this field to nil and then check if the +--- COLOR_MODE field has been set. If it has, and different attribute is +--- currently controlling the color of the device, return false to alert the +--- caller to do an early return. +--- +--- If the init_type field is not set, that means this is at least the second +--- time that the caller has ran since the driver started. In that case, we +--- set the COLOR_MODE field because we know that the handler is running +--- following a device report rather than the initial subscription report. +local function set_color_mode(device, init_type, current_color_mode) + if device:get_field(init_type) then + device:set_field(init_type, nil) + if device:get_field(COLOR_MODE) ~= nil and device:get_field(COLOR_MODE) ~= current_color_mode then + return false + end + else + device:set_field(COLOR_MODE, current_color_mode, {persist = true}) + end + return true +end + --- device_type_supports_button_switch_combination helper function used to check --- whether the device type for an endpoint is currently supported by a profile for --- combination button/switch devices. @@ -727,8 +758,12 @@ local function device_init(driver, device) end end end + device:set_field(INIT_HUE, true) + device:set_field(INIT_SAT, true) + device:set_field(INIT_X, true) + device:set_field(INIT_Y, true) + device:set_field(INIT_TEMP, true) device:subscribe() - device:send(clusters.ColorControl.attributes.ColorMode:read(device)) end local function device_removed(driver, device) @@ -880,22 +915,24 @@ local function level_attr_handler(driver, device, ib, response) end local function hue_attr_handler(driver, device, ib, response) - if ib.data.value ~= nil then - local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) + if ib.data.value == nil or not set_color_mode(device, INIT_HUE, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) then + return end + local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) end local function sat_attr_handler(driver, device, ib, response) - if ib.data.value ~= nil then - local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) + if ib.data.value == nil or not set_color_mode(device, INIT_SAT, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) then + return end + local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) end local function temp_attr_handler(driver, device, ib, response) local temp_in_mired = ib.data.value - if temp_in_mired == nil then + if temp_in_mired == nil or not set_color_mode(device, INIT_TEMP, clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE) then return end if (temp_in_mired < COLOR_TEMPERATURE_MIRED_MIN or temp_in_mired > COLOR_TEMPERATURE_MIRED_MAX) then @@ -992,6 +1029,9 @@ end local color_utils = require "color_utils" local function x_attr_handler(driver, device, ib, response) + if not set_color_mode(device, INIT_X, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) then + return + end local y = device:get_field(RECEIVED_Y) --TODO it is likely that both x and y attributes are in the response (not guaranteed though) -- if they are we can avoid setting fields on the device. @@ -1007,6 +1047,9 @@ local function x_attr_handler(driver, device, ib, response) end local function y_attr_handler(driver, device, ib, response) + if not set_color_mode(device, INIT_Y, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) then + return + end local x = device:get_field(RECEIVED_X) if x == nil then device:set_field(RECEIVED_Y, ib.data.value) @@ -1019,22 +1062,6 @@ local function y_attr_handler(driver, device, ib, response) end end -local function color_mode_attr_handler(driver, device, ib, response) - local read_req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - if ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then - read_req:merge(clusters.ColorControl.attributes.CurrentHue:read(device)) - read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read(device)) - elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY then - read_req:merge(clusters.ColorControl.attributes.CurrentX:read(device)) - read_req:merge(clusters.ColorControl.attributes.CurrentY:read(device)) - elseif ib.data.value == clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE then - read_req:merge(clusters.ColorControl.attributes.ColorTemperatureMireds:read(device)) - end - if #read_req.info_blocks ~= 0 then - device:send(read_req) - end -end - --TODO setup configure handler to read this attribute. local function color_cap_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then @@ -1230,8 +1257,12 @@ end local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then + device:set_field(INIT_HUE, true) + device:set_field(INIT_SAT, true) + device:set_field(INIT_X, true) + device:set_field(INIT_Y, true) + device:set_field(INIT_TEMP, true) device:subscribe() - device:send(clusters.ColorControl.attributes.ColorMode:read(device)) if device:get_field(DEFERRED_CONFIGURE) and device.network_type ~= device_lib.NETWORK_TYPE_CHILD then -- profile has changed, and we deferred setting up our buttons, so do that now configure_buttons(device) @@ -1326,7 +1357,6 @@ local matter_driver_template = { [clusters.ColorControl.attributes.ColorTemperatureMireds.ID] = temp_attr_handler, [clusters.ColorControl.attributes.CurrentX.ID] = x_attr_handler, [clusters.ColorControl.attributes.CurrentY.ID] = y_attr_handler, - [clusters.ColorControl.attributes.ColorMode.ID] = color_mode_attr_handler, [clusters.ColorControl.attributes.ColorCapabilities.ID] = color_cap_attr_handler, [clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds.ID] = mired_bounds_handler_factory(COLOR_TEMP_MIN), -- max mireds = min kelvin [clusters.ColorControl.attributes.ColorTempPhysicalMinMireds.ID] = mired_bounds_handler_factory(COLOR_TEMP_MAX), -- min mireds = max kelvin diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua index 040aa8a63d..54a18d2d06 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua @@ -142,14 +142,11 @@ local function test_init() end test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.mock_device.add_test_device(aqara_mock_device) test.set_rpc_version(5) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "added" }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.mock_devices_api._expected_device_updates[aqara_mock_device.device_id] = "00000000-1111-2222-3333-000000000001" test.mock_devices_api._expected_device_updates[1] = {device_id = "00000000-1111-2222-3333-000000000001"} test.mock_devices_api._expected_device_updates[1].metadata = {deviceId="00000000-1111-2222-3333-000000000001", profileReference="3-button-battery-temperature-humidity"} @@ -160,7 +157,6 @@ local function test_init() local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) read_request = cluster_read_list[1]:read(aqara_mock_device, 3) read_request:merge(cluster_read_list[1]:subscribe(aqara_mock_device)) diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua index 7f2ee0cb1d..ec72b9a19b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua @@ -163,8 +163,6 @@ local function test_init() end end test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.mock_device.add_test_device(aqara_mock_device) -- to test powerConsumptionReport test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") @@ -191,7 +189,6 @@ local function test_init() test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "added" }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.mock_devices_api._expected_device_updates[aqara_mock_device.device_id] = "00000000-1111-2222-3333-000000000001" test.mock_devices_api._expected_device_updates[1] = {device_id = "00000000-1111-2222-3333-000000000001"} test.mock_devices_api._expected_device_updates[1].metadata = {deviceId="00000000-1111-2222-3333-000000000001", profileReference="4-button"} @@ -202,7 +199,6 @@ local function test_init() local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({aqara_mock_device.id, read_color_mode}) test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("main", button_attr.pushed({state_change = false}))) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua index 3cf79b2ef3..9faa5d117b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua @@ -139,8 +139,6 @@ local function test_init() end end test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) -- to test powerConsumptionReport test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") @@ -155,8 +153,6 @@ local function test_init_periodic() end end test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request }) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_periodic.id, read_color_mode}) test.mock_device.add_test_device(mock_device_periodic) -- to test powerConsumptionReport test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") @@ -171,9 +167,6 @@ test.register_coroutine_test( subscribe_request:merge(cluster:subscribe(mock_device)) end end - test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 0.0, unit = "W" })) ) @@ -181,6 +174,7 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) ) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.wait_for_events() end ) 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 index e7876f0821..335a6b86c8 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -99,8 +99,6 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua index 9d5d637a1a..54eb21dc88 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_bridge.lua @@ -82,8 +82,6 @@ local function test_init_mock_bridge() end end test.socket.matter:__expect_send({mock_bridge.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_bridge.id, read_color_mode}) test.mock_device.add_test_device(mock_bridge) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua index 46c66f18bb..9dc16013a8 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_button.lua @@ -59,20 +59,16 @@ local function test_init() if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local device_info_copy = utils.deep_copy(mock_device.raw_st_data) device_info_copy.profile.id = "buttons-battery" local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) 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", button_attr.pushed({state_change = false}))) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua index ee5d7b28bc..a420fc933b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button.lua @@ -108,18 +108,14 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read() test.socket.matter:__expect_send({mock_device.id, read_attribute_list}) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local device_info_copy = utils.deep_copy(mock_device.raw_st_data) device_info_copy.profile.id = "5-buttons-battery" local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) 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", button_attr.pushed({state_change = false}))) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua index 1da318ccf9..0d71c8fa07 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua @@ -187,8 +187,6 @@ local function test_init() if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) test.mock_device.add_test_device(mock_child) mock_device:expect_device_create({ @@ -200,14 +198,12 @@ local function test_init() }) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) mock_device:expect_metadata_update({ profile = "light-level-3-button" }) local device_info_copy = utils.deep_copy(mock_device.raw_st_data) device_info_copy.profile.id = "3-button" local device_info_json = dkjson.encode(device_info_copy) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.socket.capability:__expect_send(mock_device:generate_test_message("button1", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) test.socket.capability:__expect_send(mock_device:generate_test_message("button1", button_attr.pushed({state_change = false}))) @@ -243,8 +239,6 @@ local function test_init_mcd_unsupported_switch_device_type() }) test.mock_device.add_test_device(mock_device_mcd_unsupported_switch_device_type) test.socket.matter:__expect_send({mock_device_mcd_unsupported_switch_device_type.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_mcd_unsupported_switch_device_type.id, read_color_mode}) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index a8212ac690..7ab38d714f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -100,8 +100,6 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) @@ -111,7 +109,6 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) - test.socket.matter:__expect_send({mock_device_no_hue_sat.id, read_color_mode}) test.mock_device.add_test_device(mock_device_no_hue_sat) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua index 5fd129f7cd..d896607284 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua @@ -305,8 +305,6 @@ local mock_device_parent_child_unsupported_device_type = test.mock_device.build_ local function test_init_parent_child_switch_types() local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_parent_child_switch_types) test.socket.matter:__expect_send({mock_device_parent_child_switch_types.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_parent_child_switch_types.id, read_color_mode}) test.mock_device.add_test_device(mock_device_parent_child_switch_types) mock_device_parent_child_switch_types:expect_metadata_update({ profile = "switch-level" }) @@ -321,45 +319,33 @@ end local function test_init_onoff() test.mock_device.add_test_device(mock_device_onoff) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_onoff.id, read_color_mode}) mock_device_onoff:expect_metadata_update({ profile = "switch-binary" }) end local function test_init_onoff_client() test.mock_device.add_test_device(mock_device_onoff_client) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_onoff_client.id, read_color_mode}) end local function test_init_parent_client_child_server() local subscribe_request = clusters.OnOff.attributes.OnOff:subscribe(mock_device_parent_client_child_server) test.socket.matter:__expect_send({mock_device_parent_client_child_server.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_parent_client_child_server.id, read_color_mode}) test.mock_device.add_test_device(mock_device_parent_client_child_server) mock_device_parent_client_child_server:expect_metadata_update({ profile = "switch-binary" }) end local function test_init_dimmer() test.mock_device.add_test_device(mock_device_dimmer) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_dimmer.id, read_color_mode}) mock_device_dimmer:expect_metadata_update({ profile = "switch-level" }) end local function test_init_color_dimmer() test.mock_device.add_test_device(mock_device_color_dimmer) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_color_dimmer.id, read_color_mode}) mock_device_color_dimmer:expect_metadata_update({ profile = "switch-color-level" }) end local function test_init_water_valve() test.mock_device.add_test_device(mock_device_water_valve) test.socket.device_lifecycle:__queue_receive({ mock_device_water_valve.id, "doConfigure" }) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_water_valve.id, read_color_mode}) mock_device_water_valve:expect_metadata_update({ profile = "water-valve-level" }) mock_device_water_valve:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -386,9 +372,6 @@ local function test_init_parent_child_different_types() end test.socket.matter:__expect_send({mock_device_parent_child_different_types.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_parent_child_different_types.id, read_color_mode}) - test.mock_device.add_test_device(mock_device_parent_child_different_types) mock_device_parent_child_different_types:expect_device_create({ @@ -404,9 +387,6 @@ local function test_init_parent_child_unsupported_device_type() test.mock_device.add_test_device(mock_device_parent_child_unsupported_device_type) mock_device_parent_child_unsupported_device_type:expect_metadata_update({ profile = "switch-binary" }) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_parent_child_unsupported_device_type.id, read_color_mode}) - mock_device_parent_child_unsupported_device_type:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 2", diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua index c3a89cf334..d4ead24458 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua @@ -64,8 +64,6 @@ local function test_init() end end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) test.mock_device.add_test_device(mock_device) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua index 29ce7b25d9..8680a57740 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_mcd.lua @@ -157,8 +157,6 @@ local function test_init_mock_3switch() test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch) test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_3switch.id, read_color_mode}) test.mock_device.add_test_device(mock_3switch) end @@ -171,8 +169,6 @@ local function test_init_mock_2switch() test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_2switch) test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_2switch.id, read_color_mode}) test.mock_device.add_test_device(mock_2switch) end @@ -185,8 +181,6 @@ local function test_init_mock_3switch_non_sequential() test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch_non_sequential) test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_3switch_non_sequential.id, read_color_mode}) test.mock_device.add_test_device(mock_3switch_non_sequential) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua index 9ce06f7018..065b364998 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua @@ -180,9 +180,6 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) - test.mock_device.add_test_device(mock_device) for _, child in pairs(mock_children) do test.mock_device.add_test_device(child) @@ -246,9 +243,6 @@ local function test_init_parent_child_endpoints_non_sequential() end test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, read_color_mode}) - test.mock_device.add_test_device(mock_device_parent_child_endpoints_non_sequential) for _, child in pairs(mock_children_non_sequential) do test.mock_device.add_test_device(child) diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua index 497f5326a0..5b2e080713 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_plugs.lua @@ -137,8 +137,7 @@ local function test_init() } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device.id, read_color_mode}) + test.mock_device.add_test_device(mock_device) for _, child in pairs(mock_children) do test.mock_device.add_test_device(child) @@ -179,8 +178,7 @@ local function test_init_child_profile_override() } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_child_profile_override) test.socket.matter:__expect_send({mock_device_child_profile_override.id, subscribe_request}) - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() - test.socket.matter:__expect_send({mock_device_child_profile_override.id, read_color_mode}) + test.mock_device.add_test_device(mock_device_child_profile_override) for _, child in pairs(mock_children_child_profile_override) do test.mock_device.add_test_device(child) From e183124298d10578a088bcd449ad4d3979e3ab77 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 26 Feb 2025 12:03:22 -0600 Subject: [PATCH 10/18] Subsribe to subset of color control attributes Subscribe to a subset of CurrentHue, CurrentSaturation, CurrentX, and CurrentY depending on the initial color mode of the device. --- .../SmartThings/matter-switch/src/init.lua | 211 ++++++++---------- .../src/test/test_matter_switch.lua | 12 + 2 files changed, 110 insertions(+), 113 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index f130580411..82c5afa9fb 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -42,12 +42,6 @@ local COLOR_TEMPERATURE_MIRED_MIN = MIRED_KELVIN_CONVERSION_CONSTANT/COLOR_TEMPE local SWITCH_LEVEL_LIGHTING_MIN = 1 local CURRENT_HUESAT_ATTR_MIN = 0 local CURRENT_HUESAT_ATTR_MAX = 254 -local INIT_HUE = "__init_hue" -local INIT_SAT = "__init_sat" -local INIT_X = "__init_x" -local INIT_Y = "__init_y" -local INIT_TEMP = "__init_temp" -local COLOR_MODE = "__color_mode" local SWITCH_INITIALIZED = "__switch_intialized" -- COMPONENT_TO_ENDPOINT_MAP is here only to preserve the endpoint mapping for @@ -172,6 +166,54 @@ local device_type_attribute_map = { } } +local subscribed_attributes = { + [capabilities.switch.ID] = { + clusters.OnOff.attributes.OnOff + }, + [capabilities.switchLevel.ID] = { + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + }, + [capabilities.colorTemperature.ID] = { + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + }, + [capabilities.illuminanceMeasurement.ID] = { + clusters.IlluminanceMeasurement.attributes.MeasuredValue + }, + [capabilities.motionSensor.ID] = { + clusters.OccupancySensing.attributes.Occupancy + }, + [capabilities.valve.ID] = { + clusters.ValveConfigurationAndControl.attributes.CurrentState + }, + [capabilities.level.ID] = { + clusters.ValveConfigurationAndControl.attributes.CurrentLevel + }, + [capabilities.battery.ID] = { + clusters.PowerSource.attributes.BatPercentRemaining, + }, + [capabilities.batteryLevel.ID] = { + clusters.PowerSource.attributes.BatChargeLevel, + }, + [capabilities.energyMeter.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported + }, + [capabilities.powerMeter.ID] = { + clusters.ElectricalPowerMeasurement.attributes.ActivePower + }, + [capabilities.relativeHumidityMeasurement.ID] = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + }, + [capabilities.temperatureMeasurement.ID] = { + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue + } +} + local child_device_profile_overrides = { { vendor_id = 0x1321, product_id = 0x000C, target_profile = "switch-binary", initial_profile = "plug-binary" }, { vendor_id = 0x1321, product_id = 0x000D, target_profile = "switch-binary", initial_profile = "plug-binary" }, @@ -374,31 +416,6 @@ local function mired_to_kelvin(value, minOrMax) end end ---- set_color_mode helper function used to set the current color mode from the ---- attribute handlers for hue, saturation, x, y, and color temp. ---- ---- If the init_type field is set, this is the first time the caller has ran ---- since init. In this case we set this field to nil and then check if the ---- COLOR_MODE field has been set. If it has, and different attribute is ---- currently controlling the color of the device, return false to alert the ---- caller to do an early return. ---- ---- If the init_type field is not set, that means this is at least the second ---- time that the caller has ran since the driver started. In that case, we ---- set the COLOR_MODE field because we know that the handler is running ---- following a device report rather than the initial subscription report. -local function set_color_mode(device, init_type, current_color_mode) - if device:get_field(init_type) then - device:set_field(init_type, nil) - if device:get_field(COLOR_MODE) ~= nil and device:get_field(COLOR_MODE) ~= current_color_mode then - return false - end - else - device:set_field(COLOR_MODE, current_color_mode, {persist = true}) - end - return true -end - --- device_type_supports_button_switch_combination helper function used to check --- whether the device type for an endpoint is currently supported by a profile for --- combination button/switch devices. @@ -758,12 +775,11 @@ local function device_init(driver, device) end end end - device:set_field(INIT_HUE, true) - device:set_field(INIT_SAT, true) - device:set_field(INIT_X, true) - device:set_field(INIT_Y, true) - device:set_field(INIT_TEMP, true) - device:subscribe() + if device:supports_capability(capabilities.colorControl) then + device:send(clusters.ColorControl.attributes.ColorMode:read()) + else + device:subscribe() + end end local function device_removed(driver, device) @@ -915,26 +931,24 @@ local function level_attr_handler(driver, device, ib, response) end local function hue_attr_handler(driver, device, ib, response) - if ib.data.value == nil or not set_color_mode(device, INIT_HUE, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) then - return + if ib.data.value ~= nil then + local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) end - local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) end local function sat_attr_handler(driver, device, ib, response) - if ib.data.value == nil or not set_color_mode(device, INIT_SAT, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) then - return + if ib.data.value ~= nil then + local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) end - local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) end local function temp_attr_handler(driver, device, ib, response) - local temp_in_mired = ib.data.value - if temp_in_mired == nil or not set_color_mode(device, INIT_TEMP, clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE) then + if ib.data.value == nil then return end + local temp_in_mired = ib.data.value if (temp_in_mired < COLOR_TEMPERATURE_MIRED_MIN or temp_in_mired > COLOR_TEMPERATURE_MIRED_MAX) then device.log.warn_with({hub_logs = true}, string.format("Device reported color temperature %d mired outside of sane range of %.2f-%.2f", temp_in_mired, COLOR_TEMPERATURE_MIRED_MIN, COLOR_TEMPERATURE_MIRED_MAX)) return @@ -1029,9 +1043,6 @@ end local color_utils = require "color_utils" local function x_attr_handler(driver, device, ib, response) - if not set_color_mode(device, INIT_X, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) then - return - end local y = device:get_field(RECEIVED_Y) --TODO it is likely that both x and y attributes are in the response (not guaranteed though) -- if they are we can avoid setting fields on the device. @@ -1047,9 +1058,6 @@ local function x_attr_handler(driver, device, ib, response) end local function y_attr_handler(driver, device, ib, response) - if not set_color_mode(device, INIT_Y, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) then - return - end local x = device:get_field(RECEIVED_X) if x == nil then device:set_field(RECEIVED_Y, ib.data.value) @@ -1062,6 +1070,36 @@ local function y_attr_handler(driver, device, ib, response) end end +local function color_mode_attr_handler(driver, device, ib, response) + if ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then + subscribed_attributes[capabilities.colorControl.ID] = { + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation + } + elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY then + subscribed_attributes[capabilities.colorControl.ID] = { + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY + } + elseif ib.data.value == clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE then + subscribed_attributes[capabilities.colorTemperature.ID] = { + clusters.ColorControl.attributes.ColorTemperatureMireds + } + else + device.log.debug_with({hub_logs=true}, "Invalid color mode received from device.") + subscribed_attributes[capabilities.colorControl.ID] = { + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY + } + subscribed_attributes[capabilities.colorTemperature.ID] = { + clusters.ColorControl.attributes.ColorTemperatureMireds + } + end + device:subscribe() +end + --TODO setup configure handler to read this attribute. local function color_cap_attr_handler(driver, device, ib, response) if ib.data.value ~= nil then @@ -1257,12 +1295,11 @@ end local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then - device:set_field(INIT_HUE, true) - device:set_field(INIT_SAT, true) - device:set_field(INIT_X, true) - device:set_field(INIT_Y, true) - device:set_field(INIT_TEMP, true) - device:subscribe() + if device:supports_capability(capabilities.colorControl) then + device:send(clusters.ColorControl.attributes.ColorMode:read()) + else + device:subscribe() + end if device:get_field(DEFERRED_CONFIGURE) and device.network_type ~= device_lib.NETWORK_TYPE_CHILD then -- profile has changed, and we deferred setting up our buttons, so do that now configure_buttons(device) @@ -1357,6 +1394,7 @@ local matter_driver_template = { [clusters.ColorControl.attributes.ColorTemperatureMireds.ID] = temp_attr_handler, [clusters.ColorControl.attributes.CurrentX.ID] = x_attr_handler, [clusters.ColorControl.attributes.CurrentY.ID] = y_attr_handler, + [clusters.ColorControl.attributes.ColorMode.ID] = color_mode_attr_handler, [clusters.ColorControl.attributes.ColorCapabilities.ID] = color_cap_attr_handler, [clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds.ID] = mired_bounds_handler_factory(COLOR_TEMP_MIN), -- max mireds = min kelvin [clusters.ColorControl.attributes.ColorTempPhysicalMinMireds.ID] = mired_bounds_handler_factory(COLOR_TEMP_MAX), -- min mireds = max kelvin @@ -1405,60 +1443,7 @@ local matter_driver_template = { }, fallback = matter_handler, }, - subscribed_attributes = { - [capabilities.switch.ID] = { - clusters.OnOff.attributes.OnOff - }, - [capabilities.switchLevel.ID] = { - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - }, - [capabilities.colorControl.ID] = { - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation, - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY, - }, - [capabilities.colorTemperature.ID] = { - clusters.ColorControl.attributes.ColorTemperatureMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, - }, - [capabilities.illuminanceMeasurement.ID] = { - clusters.IlluminanceMeasurement.attributes.MeasuredValue - }, - [capabilities.motionSensor.ID] = { - clusters.OccupancySensing.attributes.Occupancy - }, - [capabilities.valve.ID] = { - clusters.ValveConfigurationAndControl.attributes.CurrentState - }, - [capabilities.level.ID] = { - clusters.ValveConfigurationAndControl.attributes.CurrentLevel - }, - [capabilities.battery.ID] = { - clusters.PowerSource.attributes.BatPercentRemaining, - }, - [capabilities.batteryLevel.ID] = { - clusters.PowerSource.attributes.BatChargeLevel, - }, - [capabilities.energyMeter.ID] = { - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, - clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported - }, - [capabilities.powerMeter.ID] = { - clusters.ElectricalPowerMeasurement.attributes.ActivePower - }, - [capabilities.relativeHumidityMeasurement.ID] = { - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue - }, - [capabilities.temperatureMeasurement.ID] = { - clusters.TemperatureMeasurement.attributes.MeasuredValue, - clusters.TemperatureMeasurement.attributes.MinMeasuredValue, - clusters.TemperatureMeasurement.attributes.MaxMeasuredValue - } - }, + subscribed_attributes = subscribed_attributes, subscribed_events = { [capabilities.button.ID] = { clusters.Switch.events.InitialPress, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index 7ab38d714f..30304ad25c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -93,6 +93,16 @@ local cluster_subscribe_list = { local function test_init() test.socket.matter:__set_channel_ordering("relaxed") + + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) + + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ColorControl.attributes.ColorMode:build_test_report_data( + mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) + }) + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then @@ -102,6 +112,8 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) + test.socket.matter:__expect_send({mock_device_no_hue_sat.id, read_color_mode}) + subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then From 8d77e2bf7cf26c208c0b5ae4b061944cd8654760 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 26 Feb 2025 14:28:53 -0600 Subject: [PATCH 11/18] Read proper attributes after reading color mode During init, read the color mode before subscribing. In the color mode attribute handler, send out read requests for the corresponding attributes. Then, in the handlers for those attributes, ignore the first value and wait until the attribute handler runs again before emitting the colorControl capability. This ensures that we only emit this capability based on the correct values. --- .../SmartThings/matter-switch/src/init.lua | 187 +++++++++--------- .../test/test_light_illuminance_motion.lua | 10 + 2 files changed, 109 insertions(+), 88 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 82c5afa9fb..7d7a194a99 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -65,6 +65,12 @@ local COLOR_TEMP_MAX = "__color_temp_max" local LEVEL_BOUND_RECEIVED = "__level_bound_received" local LEVEL_MIN = "__level_min" local LEVEL_MAX = "__level_max" +local IGNORE_INITIAL_COLOR_ATTRS = "__ignore_initial_color_attrs" +local IGNORE_INITIAL_HUE = 0x0001 +local IGNORE_INITIAL_SAT = 0x0002 +local IGNORE_INITIAL_X = 0x0004 +local IGNORE_INITIAL_Y = 0x0008 + local AGGREGATOR_DEVICE_TYPE_ID = 0x000E local ON_OFF_LIGHT_DEVICE_TYPE_ID = 0x0100 local DIMMABLE_LIGHT_DEVICE_TYPE_ID = 0x0101 @@ -166,54 +172,6 @@ local device_type_attribute_map = { } } -local subscribed_attributes = { - [capabilities.switch.ID] = { - clusters.OnOff.attributes.OnOff - }, - [capabilities.switchLevel.ID] = { - clusters.LevelControl.attributes.CurrentLevel, - clusters.LevelControl.attributes.MaxLevel, - clusters.LevelControl.attributes.MinLevel, - }, - [capabilities.colorTemperature.ID] = { - clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, - clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, - }, - [capabilities.illuminanceMeasurement.ID] = { - clusters.IlluminanceMeasurement.attributes.MeasuredValue - }, - [capabilities.motionSensor.ID] = { - clusters.OccupancySensing.attributes.Occupancy - }, - [capabilities.valve.ID] = { - clusters.ValveConfigurationAndControl.attributes.CurrentState - }, - [capabilities.level.ID] = { - clusters.ValveConfigurationAndControl.attributes.CurrentLevel - }, - [capabilities.battery.ID] = { - clusters.PowerSource.attributes.BatPercentRemaining, - }, - [capabilities.batteryLevel.ID] = { - clusters.PowerSource.attributes.BatChargeLevel, - }, - [capabilities.energyMeter.ID] = { - clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, - clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported - }, - [capabilities.powerMeter.ID] = { - clusters.ElectricalPowerMeasurement.attributes.ActivePower - }, - [capabilities.relativeHumidityMeasurement.ID] = { - clusters.RelativeHumidityMeasurement.attributes.MeasuredValue - }, - [capabilities.temperatureMeasurement.ID] = { - clusters.TemperatureMeasurement.attributes.MeasuredValue, - clusters.TemperatureMeasurement.attributes.MinMeasuredValue, - clusters.TemperatureMeasurement.attributes.MaxMeasuredValue - } -} - local child_device_profile_overrides = { { vendor_id = 0x1321, product_id = 0x000C, target_profile = "switch-binary", initial_profile = "plug-binary" }, { vendor_id = 0x1321, product_id = 0x000D, target_profile = "switch-binary", initial_profile = "plug-binary" }, @@ -775,11 +733,12 @@ local function device_init(driver, device) end end end + if device:supports_capability(capabilities.colorControl) then + device:set_field(IGNORE_INITIAL_COLOR_ATTRS, IGNORE_INITIAL_HUE | IGNORE_INITIAL_SAT | IGNORE_INITIAL_X | IGNORE_INITIAL_Y) device:send(clusters.ColorControl.attributes.ColorMode:read()) - else - device:subscribe() end + device:subscribe() end local function device_removed(driver, device) @@ -806,7 +765,6 @@ local function handle_switch_off(driver, device, cmd) device:send(req) end - local function handle_set_switch_level(driver, device, cmd) if type(device.register_native_capability_cmd_handler) == "function" then device:register_native_capability_cmd_handler(cmd.capability, cmd.command) @@ -931,24 +889,30 @@ local function level_attr_handler(driver, device, ib, response) end local function hue_attr_handler(driver, device, ib, response) - if ib.data.value ~= nil then - local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) + local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) + if ib.data.value == nil or ignore_initial_color_attrs & IGNORE_INITIAL_HUE == 0 then + device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_HUE) + return end + local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.hue(hue)) end local function sat_attr_handler(driver, device, ib, response) - if ib.data.value ~= nil then - local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) + local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) + if ib.data.value == nil or ignore_initial_color_attrs & IGNORE_INITIAL_SAT == 0 then + device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_SAT) + return end + local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.colorControl.saturation(sat)) end local function temp_attr_handler(driver, device, ib, response) - if ib.data.value == nil then + local temp_in_mired = ib.data.value + if temp_in_mired == nil then return end - local temp_in_mired = ib.data.value if (temp_in_mired < COLOR_TEMPERATURE_MIRED_MIN or temp_in_mired > COLOR_TEMPERATURE_MIRED_MAX) then device.log.warn_with({hub_logs = true}, string.format("Device reported color temperature %d mired outside of sane range of %.2f-%.2f", temp_in_mired, COLOR_TEMPERATURE_MIRED_MIN, COLOR_TEMPERATURE_MIRED_MAX)) return @@ -1043,6 +1007,11 @@ end local color_utils = require "color_utils" local function x_attr_handler(driver, device, ib, response) + local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) + if ignore_initial_color_attrs & IGNORE_INITIAL_X == 0 then + device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_X) + return + end local y = device:get_field(RECEIVED_Y) --TODO it is likely that both x and y attributes are in the response (not guaranteed though) -- if they are we can avoid setting fields on the device. @@ -1058,6 +1027,11 @@ local function x_attr_handler(driver, device, ib, response) end local function y_attr_handler(driver, device, ib, response) + local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) + if ignore_initial_color_attrs & IGNORE_INITIAL_Y == 0 then + device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_Y) + return + end local x = device:get_field(RECEIVED_X) if x == nil then device:set_field(RECEIVED_Y, ib.data.value) @@ -1071,33 +1045,17 @@ local function y_attr_handler(driver, device, ib, response) end local function color_mode_attr_handler(driver, device, ib, response) - if ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then - subscribed_attributes[capabilities.colorControl.ID] = { - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation - } - elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY then - subscribed_attributes[capabilities.colorControl.ID] = { - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY - } - elseif ib.data.value == clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE then - subscribed_attributes[capabilities.colorTemperature.ID] = { - clusters.ColorControl.attributes.ColorTemperatureMireds - } - else - device.log.debug_with({hub_logs=true}, "Invalid color mode received from device.") - subscribed_attributes[capabilities.colorControl.ID] = { - clusters.ColorControl.attributes.CurrentHue, - clusters.ColorControl.attributes.CurrentSaturation, - clusters.ColorControl.attributes.CurrentX, - clusters.ColorControl.attributes.CurrentY - } - subscribed_attributes[capabilities.colorTemperature.ID] = { - clusters.ColorControl.attributes.ColorTemperatureMireds - } + if ib.data.value == clusters.ColorControl.attributes.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then + local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) + req:merge(clusters.ColorControl.attributes.CurrentHue:read()) + req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) + device:send(req) + elseif ib.data.value == clusters.ColorControl.attributes.ColorMode.CURRENTX_AND_CURRENTY then + local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) + req:merge(clusters.ColorControl.attributes.CurrentX:read()) + req:merge(clusters.ColorControl.attributes.CurrentY:read()) + device:send(req) end - device:subscribe() end --TODO setup configure handler to read this attribute. @@ -1296,10 +1254,10 @@ end local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then if device:supports_capability(capabilities.colorControl) then + device:set_field(IGNORE_INITIAL_COLOR_ATTRS, IGNORE_INITIAL_HUE | IGNORE_INITIAL_SAT | IGNORE_INITIAL_X | IGNORE_INITIAL_Y) device:send(clusters.ColorControl.attributes.ColorMode:read()) - else - device:subscribe() end + device:subscribe() if device:get_field(DEFERRED_CONFIGURE) and device.network_type ~= device_lib.NETWORK_TYPE_CHILD then -- profile has changed, and we deferred setting up our buttons, so do that now configure_buttons(device) @@ -1443,7 +1401,60 @@ local matter_driver_template = { }, fallback = matter_handler, }, - subscribed_attributes = subscribed_attributes, + subscribed_attributes = { + [capabilities.switch.ID] = { + clusters.OnOff.attributes.OnOff + }, + [capabilities.switchLevel.ID] = { + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + }, + [capabilities.colorControl.ID] = { + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + }, + [capabilities.colorTemperature.ID] = { + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + }, + [capabilities.illuminanceMeasurement.ID] = { + clusters.IlluminanceMeasurement.attributes.MeasuredValue + }, + [capabilities.motionSensor.ID] = { + clusters.OccupancySensing.attributes.Occupancy + }, + [capabilities.valve.ID] = { + clusters.ValveConfigurationAndControl.attributes.CurrentState + }, + [capabilities.level.ID] = { + clusters.ValveConfigurationAndControl.attributes.CurrentLevel + }, + [capabilities.battery.ID] = { + clusters.PowerSource.attributes.BatPercentRemaining, + }, + [capabilities.batteryLevel.ID] = { + clusters.PowerSource.attributes.BatChargeLevel, + }, + [capabilities.energyMeter.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported + }, + [capabilities.powerMeter.ID] = { + clusters.ElectricalPowerMeasurement.attributes.ActivePower + }, + [capabilities.relativeHumidityMeasurement.ID] = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + }, + [capabilities.temperatureMeasurement.ID] = { + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue + } + }, subscribed_events = { [capabilities.button.ID] = { clusters.Switch.events.InitialPress, 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 index 335a6b86c8..0e2a5412e0 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -92,6 +92,8 @@ local function test_init() clusters.OccupancySensing.attributes.Occupancy } test.socket.matter:__set_channel_ordering("relaxed") + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() + test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then @@ -100,6 +102,14 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ColorControl.attributes.ColorMode:build_test_report_data( + mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) + }) + local read_req = clusters.ColorControl.attributes.CurrentHue:read() + read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) + test.socket.matter:__expect_send({mock_device.id, read_req}) end test.set_test_init_function(test_init) From 16b41216dceeb60e3a27840d29b1a149cd7571cc Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Wed, 26 Feb 2025 15:23:24 -0600 Subject: [PATCH 12/18] Fix bug with bitwise operations and update test cases --- .../SmartThings/matter-switch/src/init.lua | 14 ++--- .../src/test/test_matter_switch.lua | 54 ++++++------------- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 7d7a194a99..dd4f6a0747 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -890,7 +890,7 @@ end local function hue_attr_handler(driver, device, ib, response) local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) - if ib.data.value == nil or ignore_initial_color_attrs & IGNORE_INITIAL_HUE == 0 then + if ib.data.value == nil or (ignore_initial_color_attrs ~= nil and ignore_initial_color_attrs & IGNORE_INITIAL_HUE == 0) then device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_HUE) return end @@ -900,7 +900,7 @@ end local function sat_attr_handler(driver, device, ib, response) local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) - if ib.data.value == nil or ignore_initial_color_attrs & IGNORE_INITIAL_SAT == 0 then + if ib.data.value == nil or (ignore_initial_color_attrs ~= nil and ignore_initial_color_attrs & IGNORE_INITIAL_SAT == 0) then device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_SAT) return end @@ -1008,7 +1008,7 @@ local color_utils = require "color_utils" local function x_attr_handler(driver, device, ib, response) local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) - if ignore_initial_color_attrs & IGNORE_INITIAL_X == 0 then + if (ignore_initial_color_attrs ~= nil and ignore_initial_color_attrs & IGNORE_INITIAL_X == 0) then device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_X) return end @@ -1028,7 +1028,7 @@ end local function y_attr_handler(driver, device, ib, response) local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) - if ignore_initial_color_attrs & IGNORE_INITIAL_Y == 0 then + if (ignore_initial_color_attrs ~= nil and ignore_initial_color_attrs & IGNORE_INITIAL_Y == 0) then device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_Y) return end @@ -1045,15 +1045,15 @@ local function y_attr_handler(driver, device, ib, response) end local function color_mode_attr_handler(driver, device, ib, response) + local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) if ib.data.value == clusters.ColorControl.attributes.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then - local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) req:merge(clusters.ColorControl.attributes.CurrentHue:read()) req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) - device:send(req) elseif ib.data.value == clusters.ColorControl.attributes.ColorMode.CURRENTX_AND_CURRENTY then - local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) req:merge(clusters.ColorControl.attributes.CurrentX:read()) req:merge(clusters.ColorControl.attributes.CurrentY:read()) + end + if #req.info_blocks ~= 0 then device:send(req) end end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index 30304ad25c..bedf1fdf8c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -97,12 +97,6 @@ local function test_init() local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() test.socket.matter:__expect_send({mock_device.id, read_color_mode}) - test.socket.matter:__queue_receive({ - mock_device.id, - clusters.ColorControl.attributes.ColorMode:build_test_report_data( - mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) - }) - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then @@ -111,9 +105,16 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ColorControl.attributes.ColorMode:build_test_report_data( + mock_device, 1, clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION) + }) + local read_req = clusters.ColorControl.attributes.CurrentHue:read() + read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) + test.socket.matter:__expect_send({mock_device.id, read_req}) test.socket.matter:__expect_send({mock_device_no_hue_sat.id, read_color_mode}) - subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then @@ -122,6 +123,15 @@ local function test_init() end test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) test.mock_device.add_test_device(mock_device_no_hue_sat) + + test.socket.matter:__queue_receive({ + mock_device_no_hue_sat.id, + clusters.ColorControl.attributes.ColorMode:build_test_report_data( + mock_device_no_hue_sat, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) + }) + read_req = clusters.ColorControl.attributes.CurrentX:read() + read_req:merge(clusters.ColorControl.attributes.CurrentY:read()) + test.socket.matter:__expect_send({mock_device_no_hue_sat.id, read_req}) end test.set_test_init_function(test_init) @@ -1000,34 +1010,4 @@ test.register_coroutine_test( end ) -test.register_coroutine_test( - "colorControl capability sent based on Color Temperature due to ColorMode", - function() - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.ColorMode:build_test_report_data(mock_device, 1, clusters.ColorControl.types.ColorMode.COLOR_TEMPERATURE) - } - ) - local read_color_temp = clusters.ColorControl.attributes.ColorTemperatureMireds:read() - test.socket.matter:__expect_send( - { - mock_device.id, - read_color_temp - } - ) - test.socket.matter:__queue_receive( - { - mock_device.id, - clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 305) - } - ) - test.socket.capability:__expect_send( - mock_device:generate_test_message( - "main", capabilities.colorTemperature.colorTemperature(3279) - ) - ) - end -) - test.run_registered_tests() From 21d424da4002a8fe52a5f6c9f3ade505e4384920 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 27 Feb 2025 09:59:10 -0600 Subject: [PATCH 13/18] Addressing review feedback --- .../SmartThings/matter-switch/src/init.lua | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index dd4f6a0747..e93fd74f08 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -65,11 +65,11 @@ local COLOR_TEMP_MAX = "__color_temp_max" local LEVEL_BOUND_RECEIVED = "__level_bound_received" local LEVEL_MIN = "__level_min" local LEVEL_MAX = "__level_max" -local IGNORE_INITIAL_COLOR_ATTRS = "__ignore_initial_color_attrs" -local IGNORE_INITIAL_HUE = 0x0001 -local IGNORE_INITIAL_SAT = 0x0002 -local IGNORE_INITIAL_X = 0x0004 -local IGNORE_INITIAL_Y = 0x0008 +local COLOR_MODE_ATTRS_BITMAP = "__color_mode_attrs_bitmap" +local HUE_ATTR_BIT = 0x01 +local SAT_ATTR_BIT = 0x02 +local X_ATTR_BIT = 0x04 +local Y_ATTR_BIT = 0x08 local AGGREGATOR_DEVICE_TYPE_ID = 0x000E local ON_OFF_LIGHT_DEVICE_TYPE_ID = 0x0100 @@ -374,6 +374,15 @@ local function mired_to_kelvin(value, minOrMax) end end +local function ignore_initial_color_read(device, attr_bit) + local color_attr_bitmap = device:get_field(COLOR_MODE_ATTRS_BITMAP) + if color_attr_bitmap ~= nil and color_attr_bitmap & attr_bit > 0 then + device:set_field(COLOR_MODE_ATTRS_BITMAP, color_attr_bitmap & ~attr_bit) + return true + end + return false +end + --- device_type_supports_button_switch_combination helper function used to check --- whether the device type for an endpoint is currently supported by a profile for --- combination button/switch devices. @@ -735,7 +744,7 @@ local function device_init(driver, device) end if device:supports_capability(capabilities.colorControl) then - device:set_field(IGNORE_INITIAL_COLOR_ATTRS, IGNORE_INITIAL_HUE | IGNORE_INITIAL_SAT | IGNORE_INITIAL_X | IGNORE_INITIAL_Y) + device:set_field(COLOR_MODE_ATTRS_BITMAP, HUE_ATTR_BIT | SAT_ATTR_BIT | X_ATTR_BIT | Y_ATTR_BIT) device:send(clusters.ColorControl.attributes.ColorMode:read()) end device:subscribe() @@ -889,9 +898,7 @@ local function level_attr_handler(driver, device, ib, response) end local function hue_attr_handler(driver, device, ib, response) - local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) - if ib.data.value == nil or (ignore_initial_color_attrs ~= nil and ignore_initial_color_attrs & IGNORE_INITIAL_HUE == 0) then - device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_HUE) + if ib.data.value == nil or ignore_initial_color_read(device, HUE_ATTR_BIT) then return end local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) @@ -899,9 +906,7 @@ local function hue_attr_handler(driver, device, ib, response) end local function sat_attr_handler(driver, device, ib, response) - local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) - if ib.data.value == nil or (ignore_initial_color_attrs ~= nil and ignore_initial_color_attrs & IGNORE_INITIAL_SAT == 0) then - device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_SAT) + if ib.data.value == nil or ignore_initial_color_read(device, SAT_ATTR_BIT) then return end local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) @@ -1007,9 +1012,7 @@ end local color_utils = require "color_utils" local function x_attr_handler(driver, device, ib, response) - local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) - if (ignore_initial_color_attrs ~= nil and ignore_initial_color_attrs & IGNORE_INITIAL_X == 0) then - device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_X) + if ignore_initial_color_read(device, X_ATTR_BIT) then return end local y = device:get_field(RECEIVED_Y) @@ -1027,9 +1030,7 @@ local function x_attr_handler(driver, device, ib, response) end local function y_attr_handler(driver, device, ib, response) - local ignore_initial_color_attrs = device:get_field(IGNORE_INITIAL_COLOR_ATTRS) - if (ignore_initial_color_attrs ~= nil and ignore_initial_color_attrs & IGNORE_INITIAL_Y == 0) then - device:set_field(IGNORE_INITIAL_COLOR_ATTRS, ignore_initial_color_attrs & ~IGNORE_INITIAL_Y) + if ignore_initial_color_read(device, Y_ATTR_BIT) then return end local x = device:get_field(RECEIVED_X) @@ -1053,7 +1054,7 @@ local function color_mode_attr_handler(driver, device, ib, response) req:merge(clusters.ColorControl.attributes.CurrentX:read()) req:merge(clusters.ColorControl.attributes.CurrentY:read()) end - if #req.info_blocks ~= 0 then + if #req.info_blocks > 0 then device:send(req) end end @@ -1254,7 +1255,7 @@ end local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then if device:supports_capability(capabilities.colorControl) then - device:set_field(IGNORE_INITIAL_COLOR_ATTRS, IGNORE_INITIAL_HUE | IGNORE_INITIAL_SAT | IGNORE_INITIAL_X | IGNORE_INITIAL_Y) + device:set_field(COLOR_MODE_ATTRS_BITMAP, HUE_ATTR_BIT | SAT_ATTR_BIT | X_ATTR_BIT | Y_ATTR_BIT) device:send(clusters.ColorControl.attributes.ColorMode:read()) end device:subscribe() From 5882d24f89aee1ef26b69ef07b7b57a46d695f28 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 27 Feb 2025 10:38:19 -0600 Subject: [PATCH 14/18] Update field names --- .../SmartThings/matter-switch/src/init.lua | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index e93fd74f08..cce6d66081 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -66,10 +66,12 @@ local LEVEL_BOUND_RECEIVED = "__level_bound_received" local LEVEL_MIN = "__level_min" local LEVEL_MAX = "__level_max" local COLOR_MODE_ATTRS_BITMAP = "__color_mode_attrs_bitmap" -local HUE_ATTR_BIT = 0x01 -local SAT_ATTR_BIT = 0x02 -local X_ATTR_BIT = 0x04 -local Y_ATTR_BIT = 0x08 +local color_mode_attr_bits = { + HUE = 0x01, -- CurrentHue + SAT = 0x02, -- CurrentSaturation + X = 0x04, -- CurrentX + Y = 0x08 -- CurrentY +} local AGGREGATOR_DEVICE_TYPE_ID = 0x000E local ON_OFF_LIGHT_DEVICE_TYPE_ID = 0x0100 @@ -744,7 +746,7 @@ local function device_init(driver, device) end if device:supports_capability(capabilities.colorControl) then - device:set_field(COLOR_MODE_ATTRS_BITMAP, HUE_ATTR_BIT | SAT_ATTR_BIT | X_ATTR_BIT | Y_ATTR_BIT) + device:set_field(COLOR_MODE_ATTRS_BITMAP, 0x0F) -- all bits enabled: Hue (0x01), Saturation (0x02), X (0x04), and Y (0x08) device:send(clusters.ColorControl.attributes.ColorMode:read()) end device:subscribe() @@ -898,7 +900,7 @@ local function level_attr_handler(driver, device, ib, response) end local function hue_attr_handler(driver, device, ib, response) - if ib.data.value == nil or ignore_initial_color_read(device, HUE_ATTR_BIT) then + if ib.data.value == nil or ignore_initial_color_read(device, color_mode_attr_bits.HUE) then return end local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) @@ -906,7 +908,7 @@ local function hue_attr_handler(driver, device, ib, response) end local function sat_attr_handler(driver, device, ib, response) - if ib.data.value == nil or ignore_initial_color_read(device, SAT_ATTR_BIT) then + if ib.data.value == nil or ignore_initial_color_read(device, color_mode_attr_bits.SAT) then return end local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) @@ -1012,7 +1014,7 @@ end local color_utils = require "color_utils" local function x_attr_handler(driver, device, ib, response) - if ignore_initial_color_read(device, X_ATTR_BIT) then + if ignore_initial_color_read(device, color_mode_attr_bits.X) then return end local y = device:get_field(RECEIVED_Y) @@ -1030,7 +1032,7 @@ local function x_attr_handler(driver, device, ib, response) end local function y_attr_handler(driver, device, ib, response) - if ignore_initial_color_read(device, Y_ATTR_BIT) then + if ignore_initial_color_read(device, color_mode_attr_bits.Y) then return end local x = device:get_field(RECEIVED_X) @@ -1255,7 +1257,7 @@ end local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then if device:supports_capability(capabilities.colorControl) then - device:set_field(COLOR_MODE_ATTRS_BITMAP, HUE_ATTR_BIT | SAT_ATTR_BIT | X_ATTR_BIT | Y_ATTR_BIT) + device:set_field(COLOR_MODE_ATTRS_BITMAP, 0x0F) -- all bits enabled: Hue (0x01), Saturation (0x02), X (0x04), and Y (0x08) device:send(clusters.ColorControl.attributes.ColorMode:read()) end device:subscribe() From 22d2b2baa47f62aea3aa612ca6e481d6edfa2261 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 27 Feb 2025 13:16:48 -0600 Subject: [PATCH 15/18] Fix broken test cases --- .../test/test_light_illuminance_motion.lua | 5 +- .../src/test/test_matter_switch.lua | 92 +++++++++---------- 2 files changed, 48 insertions(+), 49 deletions(-) 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 index 0e2a5412e0..6a6e84ee0e 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -91,7 +91,6 @@ local function test_init() clusters.IlluminanceMeasurement.attributes.MeasuredValue, clusters.OccupancySensing.attributes.Occupancy } - test.socket.matter:__set_channel_ordering("relaxed") local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() test.socket.matter:__expect_send({mock_device.id, read_color_mode}) local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) @@ -102,6 +101,10 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) + test.socket.matter:__queue_receive({mock_device.id, clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0)}) + test.socket.matter:__queue_receive({mock_device.id, clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0)}) + test.socket.matter:__queue_receive({mock_device.id, clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 0)}) + test.socket.matter:__queue_receive({mock_device.id, clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 0)}) test.socket.matter:__queue_receive({ mock_device.id, clusters.ColorControl.attributes.ColorMode:build_test_report_data( diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index bedf1fdf8c..9eda0b1616 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -92,8 +92,6 @@ local cluster_subscribe_list = { } local function test_init() - test.socket.matter:__set_channel_ordering("relaxed") - local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() test.socket.matter:__expect_send({mock_device.id, read_color_mode}) @@ -105,6 +103,10 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) + test.socket.matter:__queue_receive({mock_device.id, clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, 0)}) + test.socket.matter:__queue_receive({mock_device.id, clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, 0)}) + test.socket.matter:__queue_receive({mock_device.id, clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 0)}) + test.socket.matter:__queue_receive({mock_device.id, clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 0)}) test.socket.matter:__queue_receive({ mock_device.id, clusters.ColorControl.attributes.ColorMode:build_test_report_data( @@ -113,9 +115,13 @@ local function test_init() local read_req = clusters.ColorControl.attributes.CurrentHue:read() read_req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) test.socket.matter:__expect_send({mock_device.id, read_req}) +end +test.set_test_init_function(test_init) +local function test_init_no_hue_sat() + local read_color_mode = clusters.ColorControl.attributes.ColorMode:read() test.socket.matter:__expect_send({mock_device_no_hue_sat.id, read_color_mode}) - subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then subscribe_request:merge(cluster:subscribe(mock_device_no_hue_sat)) @@ -123,17 +129,17 @@ local function test_init() end test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) test.mock_device.add_test_device(mock_device_no_hue_sat) - + test.socket.matter:__queue_receive({mock_device_no_hue_sat.id, clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device_no_hue_sat, 1, 0)}) + test.socket.matter:__queue_receive({mock_device_no_hue_sat.id, clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device_no_hue_sat, 1, 0)}) test.socket.matter:__queue_receive({ mock_device_no_hue_sat.id, clusters.ColorControl.attributes.ColorMode:build_test_report_data( mock_device_no_hue_sat, 1, clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) }) - read_req = clusters.ColorControl.attributes.CurrentX:read() + local read_req = clusters.ColorControl.attributes.CurrentX:read() read_req:merge(clusters.ColorControl.attributes.CurrentY:read()) test.socket.matter:__expect_send({mock_device_no_hue_sat.id, read_req}) end -test.set_test_init_function(test_init) test.register_message_test( "On command should send the appropriate commands", @@ -278,60 +284,50 @@ test.register_message_test( } ) -test.register_message_test( - "Set color command should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { +test.register_coroutine_test( + "Set color command should send the appropriate commands", function() + test.socket.capability:__queue_receive( + { mock_device_no_hue_sat.id, - { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 72 } } } + { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 72 } }}, } - }, - { - channel = "matter", - direction = "send", - message = { + ) + test.socket.matter:__expect_send( + { mock_device_no_hue_sat.id, clusters.ColorControl.server.commands.MoveToColor(mock_device_no_hue_sat, 1, 15182, 21547, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + test.socket.matter:__queue_receive( + { mock_device_no_hue_sat.id, clusters.ColorControl.server.commands.MoveToColor:build_test_command_response(mock_device_no_hue_sat, 1) } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + test.socket.matter:__queue_receive( + { mock_device_no_hue_sat.id, - clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device_no_hue_sat, 1, 15091) } - }, - { - channel = "matter", - direction = "receive", - message = { + ) + test.socket.matter:__queue_receive( + { mock_device_no_hue_sat.id, - clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device_no_hue_sat, 1, 21547) } - }, - { - channel = "capability", - direction = "send", - message = mock_device_no_hue_sat:generate_test_message("main", capabilities.colorControl.hue(50)) - }, - { - channel = "capability", - direction = "send", - message = mock_device_no_hue_sat:generate_test_message("main", capabilities.colorControl.saturation(72)) - } - } + ) + test.socket.capability:__expect_send( + mock_device_no_hue_sat:generate_test_message( + "main", capabilities.colorControl.hue(50) + ) + ) + test.socket.capability:__expect_send( + mock_device_no_hue_sat:generate_test_message( + "main", capabilities.colorControl.saturation(72) + ) + ) + end, + { test_init = test_init_no_hue_sat } ) local hue = math.floor((50 * 0xFE) / 100.0 + 0.5) From ba23a45428706eda5d1f73cc02afd9fd31b71ac9 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 27 Feb 2025 14:39:25 -0600 Subject: [PATCH 16/18] Add explanatory comment for ignore_initial_color_read --- drivers/SmartThings/matter-switch/src/init.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 80d18a4bcf..e95d748821 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -379,6 +379,12 @@ local function mired_to_kelvin(value, minOrMax) end end +--- ignore_initial_color_read helper function used to ensure that we do not +--- process the CurrentHue, CurrentSaturation, CurrentX, and CurrentY attributes +--- from the initial subscription report, but instead wait until the current +--- ColorMode is known. Otherwise, attributes that are not currently controlling +--- the color of the device can override the colorControl capability with the +--- wrong hue and saturation values when subscribe is called during init. local function ignore_initial_color_read(device, attr_bit) local color_attr_bitmap = device:get_field(COLOR_MODE_ATTRS_BITMAP) if color_attr_bitmap ~= nil and color_attr_bitmap & attr_bit > 0 then From 57a0b0804723481edc1f507957c0d2145649dfa2 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Fri, 28 Feb 2025 14:28:17 -0600 Subject: [PATCH 17/18] Swap order of checks in hue and sat handlers --- drivers/SmartThings/matter-switch/src/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index e95d748821..17db59ea46 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -909,7 +909,7 @@ local function level_attr_handler(driver, device, ib, response) end local function hue_attr_handler(driver, device, ib, response) - if ib.data.value == nil or ignore_initial_color_read(device, color_mode_attr_bits.HUE) then + if ignore_initial_color_read(device, color_mode_attr_bits.HUE) or ib.data.value == nil then return end local hue = math.floor((ib.data.value / 0xFE * 100) + 0.5) @@ -917,7 +917,7 @@ local function hue_attr_handler(driver, device, ib, response) end local function sat_attr_handler(driver, device, ib, response) - if ib.data.value == nil or ignore_initial_color_read(device, color_mode_attr_bits.SAT) then + if ignore_initial_color_read(device, color_mode_attr_bits.SAT) or ib.data.value == nil then return end local sat = math.floor((ib.data.value / 0xFE * 100) + 0.5) From 9156e47fc8370e8f8a7bf2b9712566ed82e3614e Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Mon, 3 Mar 2025 13:14:17 -0600 Subject: [PATCH 18/18] Fix broken unit tests --- drivers/SmartThings/matter-switch/src/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 17db59ea46..b47d2d7295 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -1058,10 +1058,10 @@ end local function color_mode_attr_handler(driver, device, ib, response) local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) - if ib.data.value == clusters.ColorControl.attributes.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then + if ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then req:merge(clusters.ColorControl.attributes.CurrentHue:read()) req:merge(clusters.ColorControl.attributes.CurrentSaturation:read()) - elseif ib.data.value == clusters.ColorControl.attributes.ColorMode.CURRENTX_AND_CURRENTY then + elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY then req:merge(clusters.ColorControl.attributes.CurrentX:read()) req:merge(clusters.ColorControl.attributes.CurrentY:read()) end