From 0e2b5c95d9aec495fe7325d266e6802c7fcd2394 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Thu, 5 Feb 2026 15:07:22 -0600 Subject: [PATCH 1/3] conditionally add 'off' to thermostatMode by default if switch is not supported --- .../src/thermostat_handlers/attribute_handlers.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua index 78f5ee3bf2..c2bee684f1 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua @@ -84,8 +84,8 @@ end function AttributeHandlers.control_sequence_of_operation_handler(driver, device, ib, response) -- The ControlSequenceOfOperation attribute only directly specifies what can't be operated by the operating environment, not what can. -- However, we assert here that a Cooling enum value implies that SystemMode supports cooling, and the same for a Heating enum. - -- We also assert that Off is supported, though per spec this is optional. - if device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then + -- We also assert that Off is supported if the switch capability is not supported, though per spec this is optional. + if device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN) == nil and device:supports_capability(capabilities.switch) == false then device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, {capabilities.thermostatMode.thermostatMode.off.NAME}, {persist=true}) end local supported_modes = st_utils.deep_copy(device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN)) From dd6196473c3fd7f63958ccd72a40b5e531e69d66 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 10 Feb 2026 10:35:37 -0600 Subject: [PATCH 2/3] set OPTIONAL_THERMOSTAT_MODES_SEEN in all cases --- .../src/thermostat_handlers/attribute_handlers.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua index c2bee684f1..f0a210bc16 100644 --- a/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-thermostat/src/thermostat_handlers/attribute_handlers.lua @@ -85,8 +85,12 @@ function AttributeHandlers.control_sequence_of_operation_handler(driver, device, -- The ControlSequenceOfOperation attribute only directly specifies what can't be operated by the operating environment, not what can. -- However, we assert here that a Cooling enum value implies that SystemMode supports cooling, and the same for a Heating enum. -- We also assert that Off is supported if the switch capability is not supported, though per spec this is optional. - if device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN) == nil and device:supports_capability(capabilities.switch) == false then - device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, {capabilities.thermostatMode.thermostatMode.off.NAME}, {persist=true}) + if device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then + if device:supports_capability(capabilities.switch) == false then + device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, {capabilities.thermostatMode.thermostatMode.off.NAME}, {persist=true}) + else + device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, {}, {persist=true}) + end end local supported_modes = st_utils.deep_copy(device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN)) local disallowed_mode_operations = {} From 5cae865b6cc9fb6ad970d387dea4fbad0af31439 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 10 Feb 2026 10:40:06 -0600 Subject: [PATCH 3/3] add tests to cover new cases --- .../src/test/test_matter_room_ac.lua | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac.lua index 633fb3f1c1..bb7b9e6bde 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac.lua @@ -481,4 +481,88 @@ test.register_message_test( } ) +local ControlSequenceOfOperation = clusters.Thermostat.attributes.ControlSequenceOfOperation +test.register_message_test( + "Room AC control sequence reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, 1, ControlSequenceOfOperation.COOLING_AND_HEATING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, 1, ControlSequenceOfOperation.HEATING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, 1, ControlSequenceOfOperation.COOLING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool"}, {visibility={displayed=false}})) + }, + } +) + +test.register_message_test( + "Additional mode reports should extend the supported modes", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.ControlSequenceOfOperation:build_test_report_data(mock_device, 1, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device, 1, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat", "emergency heat"}, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.emergency_heat()) + } + } +) + + test.run_registered_tests()