Repository navigation
Matter Thermostat mode discovery state and Auto feature detection are not endpoint-specific #3265
Description
Summary
For devices with multiple Thermostat endpoints, SystemMode / ControlSequenceOfOperation discovery state is cached at device scope, and Auto support is detected by asking whether any thermostat endpoint on the device has the Auto feature. This can cause one endpoint's modes to affect another endpoint's supportedThermostatModes.
Validated against main commit 43f47501b217336d17ed2fc92b64d08971f305ce.
Affected code
attribute_handlers.lua lines 44-71:
if device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then
...
device:set_field(fields.SAVED_SYSTEM_MODE_IB, ib)
return
end
...
local disallowed_thermostat_modes = device:get_field(fields.DISALLOWED_THERMOSTAT_MODES) or {}
...
local optional_modes_seen = st_utils.deep_copy(device:get_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN)) or {}
...
device:set_field(fields.OPTIONAL_THERMOSTAT_MODES_SEEN, optional_modes_seen, {persist=true})attribute_handlers.lua around the Auto check:
local auto = device:get_endpoints(
clusters.Thermostat.ID,
{feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE}
)
if #auto > 0 then
table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.auto.NAME)
else
table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.auto.NAME)
endThe endpoint that generated the ControlSequenceOfOperation report is not checked.
Failure mechanism
OPTIONAL_THERMOSTAT_MODES_SEEN, DISALLOWED_THERMOSTAT_MODES, and SAVED_SYSTEM_MODE_IB are properties of an individual thermostat server endpoint but are stored once per SmartThings device. In addition, Auto support is considered true for every thermostat endpoint when any endpoint has the feature bit.
Reproduction case
Use a composed Matter device with two Thermostat server endpoints:
EP 2: Auto feature set
EP 3: Auto feature not set
Send a ControlSequenceOfOperation report from EP 3. The current handler calls device:get_endpoints(...AUTOMODE) and sees EP 2, so it adds auto to EP 3's supportedThermostatModes even though EP 3 does not advertise the feature.
A second variant is to let one endpoint report an optional SystemMode such as Emergency Heat; the globally cached OPTIONAL_THERMOSTAT_MODES_SEEN can then influence later recomputation for the other endpoint.
Expected behavior
Each thermostat endpoint should maintain its own discovered/allowed modes, and Auto should be exposed only for an endpoint whose Thermostat FeatureMap contains the Auto feature.
Proposed fix
Cache OPTIONAL_THERMOSTAT_MODES_SEEN, DISALLOWED_THERMOSTAT_MODES, and SAVED_SYSTEM_MODE_IB per Thermostat endpoint, and test the Auto feature against the endpoint being processed.
For example, in system_mode_handler():
local optional_modes_seen = thermostat_utils.get_field_for_endpoint(
device, fields.OPTIONAL_THERMOSTAT_MODES_SEEN, ib.endpoint_id
)
if optional_modes_seen == nil then
device.log.info_with(
{hub_logs = true},
"In the SystemMode handler: ControlSequenceOfOperation has not run yet. Exiting early."
)
thermostat_utils.set_field_for_endpoint(
device, fields.SAVED_SYSTEM_MODE_IB, ib.endpoint_id, ib
)
return
end
local disallowed_thermostat_modes = thermostat_utils.get_field_for_endpoint(
device, fields.DISALLOWED_THERMOSTAT_MODES, ib.endpoint_id
) or {}When an optional mode is learned:
thermostat_utils.set_field_for_endpoint(
device,
fields.OPTIONAL_THERMOSTAT_MODES_SEEN,
ib.endpoint_id,
optional_modes_seen,
{ persist = true }
)In control_sequence_of_operation_handler(), determine Auto support for the reporting endpoint, not for the device as a whole:
local auto_eps = device:get_endpoints(
clusters.Thermostat.ID,
{feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE}
)
local auto_supported = thermostat_utils.tbl_contains(auto_eps, ib.endpoint_id)
if auto_supported then
table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.auto.NAME)
else
table.insert(disallowed_mode_operations, capabilities.thermostatMode.thermostatMode.auto.NAME)
endStore the resulting disallowed list per endpoint and retrieve any saved SystemMode IB from that same endpoint:
thermostat_utils.set_field_for_endpoint(
device,
fields.DISALLOWED_THERMOSTAT_MODES,
ib.endpoint_id,
disallowed_mode_operations
)
local saved_system_mode_ib = thermostat_utils.get_field_for_endpoint(
device, fields.SAVED_SYSTEM_MODE_IB, ib.endpoint_id
)The same endpoint-specific Auto check is also required in thermostat_set_setpoint_factory(). The current code treats the device as Auto-capable if any Thermostat endpoint has the feature:
local auto_eps = device:get_endpoints(
clusters.Thermostat.ID,
{feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE}
)
local is_auto_capable = thermostat_utils.tbl_contains(auto_eps, endpoint_id)Without this final change, an endpoint that does not support Auto can still be subjected to the Auto-mode deadband validation because another endpoint supports it.
Suggested regression test
Add a two-endpoint thermostat/heat-pump test where only one Thermostat endpoint has the Auto feature. Verify auto appears only in that component's supportedThermostatModes, and verify an optional mode observed on one endpoint does not appear on the other.
Activity