Skip to content

Commit 5cba9d4

Browse files
Don't include Off for supportedFanModes for thermostats by default
Off is an optional enum value for SystemMode and setting fanMode to Off may have no effect if the thermostat mode is set to heating or cooling. This commit removes this value from supportedFanModes attribute unless Off is reported by the SystemMode attribute at some point.
1 parent f34c7dd commit 5cba9d4

File tree

3 files changed

+304
-27
lines changed

3 files changed

+304
-27
lines changed

drivers/SmartThings/matter-thermostat/src/init.lua

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ end
4848
local SAVED_SYSTEM_MODE_IB = "__saved_system_mode_ib"
4949
local DISALLOWED_THERMOSTAT_MODES = "__DISALLOWED_CONTROL_OPERATIONS"
5050
local OPTIONAL_THERMOSTAT_MODES_SEEN = "__OPTIONAL_THERMOSTAT_MODES_SEEN"
51+
local SEQUENCE_OF_OPERATIONS_PROCESSED = "__SEQUENCE_OF_OPERATIONS_PROCESSED"
5152

5253
if version.api < 11 then
5354
clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement"
@@ -1422,13 +1423,37 @@ local function humidity_attr_handler(driver, device, ib, response)
14221423
end
14231424

14241425
local function system_mode_handler(driver, device, ib, response)
1425-
if device:get_field(OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then -- this being nil means the sequence_of_operation_handler hasn't run.
1426+
if device:get_field(SEQUENCE_OF_OPERATIONS_PROCESSED) == nil then -- this being nil means the sequence_of_operation_handler hasn't run.
14261427
device.log.info_with({hub_logs = true}, "In the SystemMode handler: ControlSequenceOfOperation has not run yet. Exiting early.")
14271428
device:set_field(SAVED_SYSTEM_MODE_IB, ib)
14281429
return
14291430
end
14301431

1431-
local supported_modes = device:get_latest_state(device:endpoint_to_component(ib.endpoint_id), capabilities.thermostatMode.ID, capabilities.thermostatMode.supportedThermostatModes.NAME) or {}
1432+
if device:supports_capability_by_id(capabilities.fanMode.ID) and
1433+
ib.data.value == clusters.Thermostat.types.ThermostatSystemMode.OFF and
1434+
get_device_type(device) == THERMOSTAT_DEVICE_TYPE_ID then
1435+
local supported_fan_modes = device:get_latest_state(
1436+
device:endpoint_to_component(ib.endpoint_id),
1437+
capabilities.fanMode.ID,
1438+
capabilities.fanMode.supportedFanModes.NAME
1439+
) or {}
1440+
if not tbl_contains(supported_fan_modes, "off") then
1441+
local new_supported_fan_modes = {"off"}
1442+
for _, fan_mode in ipairs(supported_fan_modes) do
1443+
table.insert(new_supported_fan_modes, fan_mode)
1444+
end
1445+
device:emit_event_for_endpoint(
1446+
ib.endpoint_id,
1447+
capabilities.fanMode.supportedFanModes(new_supported_fan_modes, {visibility = {displayed = false}})
1448+
)
1449+
end
1450+
end
1451+
1452+
local supported_modes = device:get_latest_state(
1453+
device:endpoint_to_component(ib.endpoint_id),
1454+
capabilities.thermostatMode.ID,
1455+
capabilities.thermostatMode.supportedThermostatModes.NAME
1456+
) or {}
14321457
-- check that the given mode was in the supported modes list
14331458
if tbl_contains(supported_modes, THERMOSTAT_MODE_MAP[ib.data.value].NAME) then
14341459
device:emit_event_for_endpoint(ib.endpoint_id, THERMOSTAT_MODE_MAP[ib.data.value]())
@@ -1464,11 +1489,14 @@ end
14641489
local function sequence_of_operation_handler(driver, device, ib, response)
14651490
-- The ControlSequenceOfOperation attribute only directly specifies what can't be operated by the operating environment, not what can.
14661491
-- However, we assert here that a Cooling enum value implies that SystemMode supports cooling, and the same for a Heating enum.
1467-
-- We also assert that Off is supported, though per spec this is optional.
1492+
-- We also assert that Off is supported for non-thermostat device types, though per spec this is optional.
14681493
if device:get_field(OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then
1469-
device:set_field(OPTIONAL_THERMOSTAT_MODES_SEEN, {capabilities.thermostatMode.thermostatMode.off.NAME}, {persist=true})
1494+
if get_device_type(device) ~= THERMOSTAT_DEVICE_TYPE_ID then
1495+
device:set_field(OPTIONAL_THERMOSTAT_MODES_SEEN, {capabilities.thermostatMode.thermostatMode.off.NAME}, {persist=true})
1496+
end
1497+
device:set_field(SEQUENCE_OF_OPERATIONS_PROCESSED, true)
14701498
end
1471-
local supported_modes = utils.deep_copy(device:get_field(OPTIONAL_THERMOSTAT_MODES_SEEN))
1499+
local supported_modes = utils.deep_copy(device:get_field(OPTIONAL_THERMOSTAT_MODES_SEEN) or {})
14721500
local disallowed_mode_operations = {}
14731501

14741502
local modes_for_inclusion = {}
@@ -1576,17 +1604,20 @@ end
15761604
local function fan_mode_sequence_handler(driver, device, ib, response)
15771605
local supportedFanModes, supported_fan_modes_attribute
15781606
if ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH then
1579-
supportedFanModes = { "off", "low", "medium", "high" }
1607+
supportedFanModes = { "low", "medium", "high" }
15801608
elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH then
1581-
supportedFanModes = { "off", "low", "high" }
1609+
supportedFanModes = { "low", "high" }
15821610
elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO then
1583-
supportedFanModes = { "off", "low", "medium", "high", "auto" }
1611+
supportedFanModes = { "low", "medium", "high", "auto" }
15841612
elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO then
1585-
supportedFanModes = { "off", "low", "high", "auto" }
1613+
supportedFanModes = { "low", "high", "auto" }
15861614
elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_HIGH_AUTO then
1587-
supportedFanModes = { "off", "high", "auto" }
1615+
supportedFanModes = { "high", "auto" }
15881616
else
1589-
supportedFanModes = { "off", "high" }
1617+
supportedFanModes = { "high" }
1618+
end
1619+
if get_device_type(device) ~= THERMOSTAT_DEVICE_TYPE_ID then
1620+
table.insert(supportedFanModes, 1, "off")
15901621
end
15911622

15921623
if device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) then

drivers/SmartThings/matter-thermostat/src/test/test_matter_heat_pump.lua

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ test.register_message_test(
347347
{
348348
channel = "capability",
349349
direction = "send",
350-
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}}))
350+
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}}))
351351
},
352352
{
353353
channel = "matter",
@@ -360,7 +360,7 @@ test.register_message_test(
360360
{
361361
channel = "capability",
362362
direction = "send",
363-
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}}))
363+
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}}))
364364
},
365365
{
366366
channel = "matter",
@@ -373,7 +373,7 @@ test.register_message_test(
373373
{
374374
channel = "capability",
375375
direction = "send",
376-
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "emergency heat"}, {visibility={displayed=false}}))
376+
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat", "emergency heat"}, {visibility={displayed=false}}))
377377
},
378378
{
379379
channel = "capability",
@@ -411,7 +411,7 @@ test.register_message_test(
411411
{
412412
channel = "capability",
413413
direction = "send",
414-
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}}))
414+
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}}))
415415
},
416416
{
417417
channel = "matter",
@@ -424,7 +424,7 @@ test.register_message_test(
424424
{
425425
channel = "capability",
426426
direction = "send",
427-
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "heat"}, {visibility={displayed=false}}))
427+
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"heat"}, {visibility={displayed=false}}))
428428
},
429429
{
430430
channel = "matter",
@@ -437,7 +437,7 @@ test.register_message_test(
437437
{
438438
channel = "capability",
439439
direction = "send",
440-
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool"}, {visibility={displayed=false}}))
440+
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool"}, {visibility={displayed=false}}))
441441
},
442442
{
443443
channel = "matter",
@@ -450,7 +450,7 @@ test.register_message_test(
450450
{
451451
channel = "capability",
452452
direction = "send",
453-
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}}))
453+
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}}))
454454
},
455455
{
456456
channel = "matter",
@@ -463,7 +463,7 @@ test.register_message_test(
463463
{
464464
channel = "capability",
465465
direction = "send",
466-
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "heat"}, {visibility={displayed=false}}))
466+
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"heat"}, {visibility={displayed=false}}))
467467
},
468468
{
469469
channel = "matter",
@@ -476,7 +476,7 @@ test.register_message_test(
476476
{
477477
channel = "capability",
478478
direction = "send",
479-
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool"}, {visibility={displayed=false}}))
479+
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"cool"}, {visibility={displayed=false}}))
480480
},
481481
}
482482
)
@@ -495,7 +495,7 @@ test.register_message_test(
495495
{
496496
channel = "capability",
497497
direction = "send",
498-
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}}))
498+
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}}))
499499
},
500500
{
501501
channel = "matter",
@@ -508,7 +508,7 @@ test.register_message_test(
508508
{
509509
channel = "capability",
510510
direction = "send",
511-
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}}))
511+
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat"}, {visibility={displayed=false}}))
512512
},
513513
{
514514
channel = "matter",
@@ -521,7 +521,7 @@ test.register_message_test(
521521
{
522522
channel = "capability",
523523
direction = "send",
524-
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "emergency heat"}, {visibility={displayed=false}}))
524+
message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat", "emergency heat"}, {visibility={displayed=false}}))
525525
},
526526
{
527527
channel = "capability",
@@ -539,7 +539,7 @@ test.register_message_test(
539539
{
540540
channel = "capability",
541541
direction = "send",
542-
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "emergency heat"}, {visibility={displayed=false}}))
542+
message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat", "emergency heat"}, {visibility={displayed=false}}))
543543
},
544544
{
545545
channel = "capability",
@@ -563,7 +563,7 @@ test.register_message_test(
563563
{
564564
channel = "capability",
565565
direction = "send",
566-
message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "auto"}, {visibility={displayed=false}}))
566+
message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat", "auto"}, {visibility={displayed=false}}))
567567
},
568568
{
569569
channel = "matter",
@@ -576,13 +576,31 @@ test.register_message_test(
576576
{
577577
channel = "capability",
578578
direction = "send",
579-
message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "auto", "emergency heat"}, {visibility={displayed=false}}))
579+
message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat", "auto", "emergency heat"}, {visibility={displayed=false}}))
580580
},
581581
{
582582
channel = "capability",
583583
direction = "send",
584584
message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.thermostatMode.emergency_heat())
585585
},
586+
{
587+
channel = "matter",
588+
direction = "receive",
589+
message = {
590+
mock_device_with_auto.id,
591+
clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device_with_auto, THERMOSTAT_ONE_EP, 0)
592+
}
593+
},
594+
{
595+
channel = "capability",
596+
direction = "send",
597+
message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"cool", "heat", "auto", "emergency heat", "off"}, {visibility={displayed=false}}))
598+
},
599+
{
600+
channel = "capability",
601+
direction = "send",
602+
message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.thermostatMode.off())
603+
},
586604
{
587605
channel = "matter",
588606
direction = "receive",
@@ -594,7 +612,7 @@ test.register_message_test(
594612
{
595613
channel = "capability",
596614
direction = "send",
597-
message = mock_device_with_auto:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "emergency heat", "cool", "heat", "auto"}, {visibility={displayed=false}}))
615+
message = mock_device_with_auto:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"emergency heat", "off", "cool", "heat", "auto"}, {visibility={displayed=false}}))
598616
},
599617
{
600618
channel = "matter",

0 commit comments

Comments
 (0)