diff --git a/drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml b/drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml index 69972f1223..19dbc88e37 100644 --- a/drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml +++ b/drivers/SmartThings/matter-thermostat/profiles/thermostat-modular.yml @@ -6,6 +6,9 @@ components: version: 1 - id: thermostatMode version: 1 + - id: fanMode + version: 1 + optional: true - id: thermostatFanMode version: 1 optional: true diff --git a/drivers/SmartThings/matter-thermostat/src/init.lua b/drivers/SmartThings/matter-thermostat/src/init.lua index 3cc1910ca1..e5525c8284 100644 --- a/drivers/SmartThings/matter-thermostat/src/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/init.lua @@ -189,12 +189,17 @@ local subscribed_attributes = { clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit }, [capabilities.airConditionerFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, [capabilities.airPurifierFanMode.ID] = { clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, + [capabilities.fanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode + }, [capabilities.fanSpeedPercent.ID] = { clusters.FanControl.attributes.PercentCurrent }, @@ -998,7 +1003,7 @@ local function match_modular_profile_thermostat(driver, device) local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) if #fan_eps > 0 then - table.insert(main_component_capabilities, capabilities.thermostatFanMode.ID) + table.insert(main_component_capabilities, capabilities.fanMode.ID) end if #rock_eps > 0 then table.insert(main_component_capabilities, capabilities.fanOscillationMode.ID) @@ -1450,7 +1455,7 @@ local function running_state_handler(driver, device, ib, response) end local function 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. + -- 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(OPTIONAL_THERMOSTAT_MODES_SEEN) == nil then @@ -1481,7 +1486,7 @@ local function sequence_of_operation_handler(driver, device, ib, response) table.insert(modes_for_inclusion, capabilities.thermostatMode.thermostatMode.heat.NAME) end - -- check whether the Auto Mode should be supported in SystemMode, though this is unrelated to ControlSequenceofOperation + -- check whether the Auto Mode should be supported in SystemMode, though this is unrelated to ControlSequenceOfOperation 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) @@ -1520,150 +1525,81 @@ local function min_deadband_limit_handler(driver, device, ib, response) end local function fan_mode_handler(driver, device, ib, response) - if device:supports_capability_by_id(capabilities.airConditionerFanMode.ID) then - -- Room Air Conditioner - if ib.data.value == clusters.FanControl.attributes.FanMode.OFF then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airConditionerFanMode.fanMode("off")) - elseif ib.data.value == clusters.FanControl.attributes.FanMode.LOW then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airConditionerFanMode.fanMode("low")) - elseif ib.data.value == clusters.FanControl.attributes.FanMode.MEDIUM then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airConditionerFanMode.fanMode("medium")) - elseif ib.data.value == clusters.FanControl.attributes.FanMode.HIGH then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airConditionerFanMode.fanMode("high")) - else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airConditionerFanMode.fanMode("auto")) - end - elseif device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) then - if ib.data.value == clusters.FanControl.attributes.FanMode.OFF then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airPurifierFanMode.airPurifierFanMode.off()) - elseif ib.data.value == clusters.FanControl.attributes.FanMode.LOW then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airPurifierFanMode.airPurifierFanMode.low()) - elseif ib.data.value == clusters.FanControl.attributes.FanMode.MEDIUM then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airPurifierFanMode.airPurifierFanMode.medium()) - elseif ib.data.value == clusters.FanControl.attributes.FanMode.HIGH then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airPurifierFanMode.airPurifierFanMode.high()) - else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airPurifierFanMode.airPurifierFanMode.auto()) - end + local fan_mode_event = { + [clusters.FanControl.attributes.FanMode.OFF] = { capabilities.fanMode.fanMode.off(), + capabilities.airConditionerFanMode.fanMode("off"), + capabilities.airPurifierFanMode.airPurifierFanMode.off(), + nil }, -- 'OFF' is not supported by thermostatFanMode + [clusters.FanControl.attributes.FanMode.LOW] = { capabilities.fanMode.fanMode.low(), + capabilities.airConditionerFanMode.fanMode("low"), + capabilities.airPurifierFanMode.airPurifierFanMode.low(), + capabilities.thermostatFanMode.thermostatFanMode.on() }, + [clusters.FanControl.attributes.FanMode.MEDIUM] = { capabilities.fanMode.fanMode.medium(), + capabilities.airConditionerFanMode.fanMode("medium"), + capabilities.airPurifierFanMode.airPurifierFanMode.medium(), + capabilities.thermostatFanMode.thermostatFanMode.on() }, + [clusters.FanControl.attributes.FanMode.HIGH] = { capabilities.fanMode.fanMode.high(), + capabilities.airConditionerFanMode.fanMode("high"), + capabilities.airPurifierFanMode.airPurifierFanMode.high(), + capabilities.thermostatFanMode.thermostatFanMode.on() }, + [clusters.FanControl.attributes.FanMode.ON] = { capabilities.fanMode.fanMode.auto(), + capabilities.airConditionerFanMode.fanMode("auto"), + capabilities.airPurifierFanMode.airPurifierFanMode.auto(), + capabilities.thermostatFanMode.thermostatFanMode.on() }, + [clusters.FanControl.attributes.FanMode.AUTO] = { capabilities.fanMode.fanMode.auto(), + capabilities.airConditionerFanMode.fanMode("auto"), + capabilities.airPurifierFanMode.airPurifierFanMode.auto(), + capabilities.thermostatFanMode.thermostatFanMode.auto() }, + [clusters.FanControl.attributes.FanMode.SMART] = { capabilities.fanMode.fanMode.auto(), + capabilities.airConditionerFanMode.fanMode("auto"), + capabilities.airPurifierFanMode.airPurifierFanMode.auto(), + capabilities.thermostatFanMode.thermostatFanMode.auto() } + } + local fan_mode_idx = device:supports_capability_by_id(capabilities.fanMode.ID) and 1 or + device:supports_capability_by_id(capabilities.airConditionerFanMode.ID) and 2 or + device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) and 3 or + device:supports_capability_by_id(capabilities.thermostatFanMode.ID) and 4 + if fan_mode_idx and fan_mode_idx >= 1 and fan_mode_idx <= 4 and fan_mode_event[ib.data.value][fan_mode_idx] then + device:emit_event_for_endpoint(ib.endpoint_id, fan_mode_event[ib.data.value][fan_mode_idx]) else - -- Thermostat - if ib.data.value == clusters.FanControl.attributes.FanMode.AUTO or - ib.data.value == clusters.FanControl.attributes.FanMode.SMART then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatFanMode.thermostatFanMode.auto()) - elseif ib.data.value ~= clusters.FanControl.attributes.FanMode.OFF then -- we don't have an "off" value - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatFanMode.thermostatFanMode.on()) - end + log.warn(string.format("Invalid Fan Mode (%s)", ib.data.value)) end end local function fan_mode_sequence_handler(driver, device, ib, response) - if device:supports_capability_by_id(capabilities.airConditionerFanMode.ID) then - -- Room Air Conditioner - local supportedAcFanModes - if ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH then - supportedAcFanModes = { - "off", - "low", - "medium", - "high" - } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH then - supportedAcFanModes = { - "off", - "low", - "high" - } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO then - supportedAcFanModes = { - "off", - "low", - "medium", - "high", - "auto" - } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO then - supportedAcFanModes = { - "off", - "low", - "high", - "auto" - } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_ON_AUTO then - supportedAcFanModes = { - "off", - "high", - "auto" - } + local supported_fan_modes_attribute, supportedFanModes + if device:supports_capability_by_id(capabilities.thermostatFanMode.ID) then + supported_fan_modes_attribute = capabilities.thermostatFanMode.supportedThermostatFanModes + -- Our thermostat fan mode control is not granular enough to handle all of the supported modes + if ib.data.value >= clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO and + ib.data.value <= clusters.FanControl.attributes.FanModeSequence.OFF_ON_AUTO then + supportedFanModes = { "auto", "on" } else - supportedAcFanModes = { - "off", - "high" - } - end - local event = capabilities.airConditionerFanMode.supportedAcFanModes(supportedAcFanModes, {visibility = {displayed = false}}) - device:emit_event_for_endpoint(ib.endpoint_id, event) - elseif device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) then - -- Air Purifier - local supportedAirPurifierFanModes + supportedFanModes = { "on" } + end + else + supported_fan_modes_attribute = capabilities.fanMode.supportedFanModes + if device:supports_capability_by_id(capabilities.airPurifierFanMode.ID) then + supported_fan_modes_attribute = capabilities.airPurifierFanMode.supportedAirPurifierFanModes + elseif device:supports_capability_by_id(capabilities.airConditionerFanMode.ID) then + supported_fan_modes_attribute = capabilities.airConditionerFanMode.supportedAcFanModes + end if ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH then - supportedAirPurifierFanModes = { - capabilities.airPurifierFanMode.airPurifierFanMode.off.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.low.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.medium.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME - } + supportedFanModes = { "off", "low", "medium", "high" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH then - supportedAirPurifierFanModes = { - capabilities.airPurifierFanMode.airPurifierFanMode.off.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.low.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME - } + supportedFanModes = { "off", "low", "high" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO then - supportedAirPurifierFanModes = { - capabilities.airPurifierFanMode.airPurifierFanMode.off.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.low.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.medium.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.auto.NAME - } + supportedFanModes = { "off", "low", "medium", "high", "auto" } elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO then - supportedAirPurifierFanModes = { - capabilities.airPurifierFanMode.airPurifierFanMode.off.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.low.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.auto.NAME - } - elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_ON_AUTO then - supportedAirPurifierFanModes = { - capabilities.airPurifierFanMode.airPurifierFanMode.off.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.auto.NAME - } - else - supportedAirPurifierFanModes = { - capabilities.airPurifierFanMode.airPurifierFanMode.off.NAME, - capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME - } - end - local event = capabilities.airPurifierFanMode.supportedAirPurifierFanModes(supportedAirPurifierFanModes, {visibility = {displayed = false}}) - device:emit_event_for_endpoint(ib.endpoint_id, event) - else - -- Thermostat - -- Our thermostat fan mode control is probably not granular enough to handle the supported modes here well - -- definitely meant for actual fans and not HVAC fans - if ib.data.value >= clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO and - ib.data.value <= clusters.FanControl.attributes.FanModeSequence.OFF_ON_AUTO then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatFanMode.supportedThermostatFanModes( - {capabilities.thermostatFanMode.thermostatFanMode.auto.NAME, capabilities.thermostatFanMode.thermostatFanMode.on.NAME}, - {visibility = {displayed = false}} - )) + supportedFanModes = { "off", "low", "high", "auto" } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_HIGH_AUTO then + supportedFanModes = { "off", "high", "auto" } else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.thermostatFanMode.supportedThermostatFanModes( - {capabilities.thermostatFanMode.thermostatFanMode.on.NAME}, - {visibility = {displayed = false}} - )) + supportedFanModes = { "off", "high" } end end + local event = supported_fan_modes_attribute(supportedFanModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) end local function fan_speed_percent_attr_handler(driver, device, ib, response) @@ -1921,65 +1857,42 @@ local cooling_setpoint_limit_handler_factory = function(minOrMax) end end -local function set_thermostat_fan_mode(driver, device, cmd) - local fan_mode_id = nil - if cmd.args.mode == capabilities.thermostatFanMode.thermostatFanMode.auto.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.AUTO - elseif cmd.args.mode == capabilities.thermostatFanMode.thermostatFanMode.on.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.ON +local function set_fan_mode(device, cmd, fan_mode_capability) + local command_argument = cmd.args.fanMode + if fan_mode_capability == capabilities.airPurifierFanMode then + command_argument = cmd.args.airPurifierFanMode + elseif fan_mode_capability == capabilities.thermostatFanMode then + command_argument = cmd.args.mode end - if fan_mode_id then - device:send(clusters.FanControl.attributes.FanMode:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) - end -end - -local function thermostat_fan_mode_setter(mode_name) - return function(driver, device, cmd) - return set_thermostat_fan_mode(driver, device, {component = cmd.component, args = {mode = mode_name}}) - end -end - -local function set_fan_mode(driver, device, cmd) local fan_mode_id - if cmd.args.fanMode == "off" then + if command_argument == "off" then fan_mode_id = clusters.FanControl.attributes.FanMode.OFF - elseif cmd.args.fanMode == "low" then - fan_mode_id = clusters.FanControl.attributes.FanMode.LOW - elseif cmd.args.fanMode == "medium" then - fan_mode_id = clusters.FanControl.attributes.FanMode.MEDIUM - elseif cmd.args.fanMode == "high" then - fan_mode_id = clusters.FanControl.attributes.FanMode.HIGH - elseif cmd.args.fanMode == "auto" then + elseif command_argument == "on" then + fan_mode_id = clusters.FanControl.attributes.FanMode.ON + elseif command_argument == "auto" then fan_mode_id = clusters.FanControl.attributes.FanMode.AUTO + elseif command_argument == "high" then + fan_mode_id = clusters.FanControl.attributes.FanMode.HIGH + elseif command_argument == "medium" then + fan_mode_id = clusters.FanControl.attributes.FanMode.MEDIUM + elseif tbl_contains({ "low", "sleep", "quiet", "windFree" }, command_argument) then + fan_mode_id = clusters.FanControl.attributes.FanMode.LOW else - fan_mode_id = clusters.FanControl.attributes.FanMode.OFF - end - if fan_mode_id then - device:send(clusters.FanControl.attributes.FanMode:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) + device.log.warn(string.format("Invalid Fan Mode (%s) received from capability command", command_argument)) + return end + device:send(clusters.FanControl.attributes.FanMode:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) end -local function set_air_purifier_fan_mode(driver, device, cmd) - local fan_mode_id - if cmd.args.airPurifierFanMode == capabilities.airPurifierFanMode.airPurifierFanMode.low.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.LOW - elseif cmd.args.airPurifierFanMode == capabilities.airPurifierFanMode.airPurifierFanMode.sleep.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.LOW - elseif cmd.args.airPurifierFanMode == capabilities.airPurifierFanMode.airPurifierFanMode.quiet.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.LOW - elseif cmd.args.airPurifierFanMode == capabilities.airPurifierFanMode.airPurifierFanMode.windFree.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.LOW - elseif cmd.args.airPurifierFanMode == capabilities.airPurifierFanMode.airPurifierFanMode.medium.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.MEDIUM - elseif cmd.args.airPurifierFanMode == capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.HIGH - elseif cmd.args.airPurifierFanMode == capabilities.airPurifierFanMode.airPurifierFanMode.auto.NAME then - fan_mode_id = clusters.FanControl.attributes.FanMode.AUTO - else - fan_mode_id = clusters.FanControl.attributes.FanMode.OFF +local set_fan_mode_factory = function(fan_mode_capability) + return function(driver, device, cmd) + set_fan_mode(device, cmd, fan_mode_capability) end - if fan_mode_id then - device:send(clusters.FanControl.attributes.FanMode:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) +end + +local function thermostat_fan_mode_setter(mode_name) + return function(driver, device, cmd) + set_fan_mode(device, {component = cmd.component, args = {mode = mode_name}}, capabilities.thermostatFanMode) end end @@ -2306,7 +2219,7 @@ local matter_driver_template = { [capabilities.thermostatMode.commands.emergencyHeat.NAME] = thermostat_mode_setter(capabilities.thermostatMode.thermostatMode.emergency_heat.NAME) }, [capabilities.thermostatFanMode.ID] = { - [capabilities.thermostatFanMode.commands.setThermostatFanMode.NAME] = set_thermostat_fan_mode, + [capabilities.thermostatFanMode.commands.setThermostatFanMode.NAME] = set_fan_mode_factory(capabilities.thermostatFanMode), [capabilities.thermostatFanMode.commands.fanAuto.NAME] = thermostat_fan_mode_setter(capabilities.thermostatFanMode.thermostatFanMode.auto.NAME), [capabilities.thermostatFanMode.commands.fanOn.NAME] = thermostat_fan_mode_setter(capabilities.thermostatFanMode.thermostatFanMode.on.NAME) }, @@ -2317,10 +2230,13 @@ local matter_driver_template = { [capabilities.thermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = set_setpoint(clusters.Thermostat.attributes.OccupiedHeatingSetpoint) }, [capabilities.airConditionerFanMode.ID] = { - [capabilities.airConditionerFanMode.commands.setFanMode.NAME] = set_fan_mode, + [capabilities.airConditionerFanMode.commands.setFanMode.NAME] = set_fan_mode_factory(capabilities.airConditionerFanMode) }, [capabilities.airPurifierFanMode.ID] = { - [capabilities.airPurifierFanMode.commands.setAirPurifierFanMode.NAME] = set_air_purifier_fan_mode + [capabilities.airPurifierFanMode.commands.setAirPurifierFanMode.NAME] = set_fan_mode_factory(capabilities.airPurifierFanMode) + }, + [capabilities.fanMode.ID] = { + [capabilities.fanMode.commands.setFanMode.NAME] = set_fan_mode_factory(capabilities.fanMode) }, [capabilities.fanSpeedPercent.ID] = { [capabilities.fanSpeedPercent.commands.setPercent.NAME] = set_fan_speed_percent, @@ -2345,6 +2261,7 @@ local matter_driver_template = { capabilities.thermostatFanMode, capabilities.thermostatOperatingState, capabilities.airConditionerFanMode, + capabilities.fanMode, capabilities.fanSpeedPercent, capabilities.airPurifierFanMode, capabilities.windMode, diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua index 86b58de240..f05c5ee175 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier.lua @@ -78,13 +78,13 @@ local mock_device_rock = test.mock_device.build_test_matter_device({ } }, { - endpoint_id = 1, - clusters = { - {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER"}, - {cluster_id = clusters.HepaFilterMonitoring.ID, cluster_type = "SERVER"}, - {cluster_id = clusters.ActivatedCarbonFilterMonitoring.ID, cluster_type = "SERVER"}, - } + endpoint_id = 1, + clusters = { + {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.HepaFilterMonitoring.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.ActivatedCarbonFilterMonitoring.ID, cluster_type = "SERVER"}, } + } } }) diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua index 009cdc700b..c21de741e2 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_modular.lua @@ -1,4 +1,4 @@ --- Copyright 2024 SmartThings +-- Copyright 2025 SmartThings -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -12,28 +12,29 @@ -- See the License for the specific language governing permissions and -- limitations under the License. local test = require "integration_test" +test.set_rpc_version(8) local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local utils = require "st.utils" local dkjson = require "dkjson" - local clusters = require "st.matter.clusters" +local version = require "version" -clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" -clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" -clusters.AirQuality = require "AirQuality" -clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" -clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" -clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" -clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" -clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" -clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" -clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" -clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" -clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" -clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" - -test.set_rpc_version(8) +if version.api < 10 then + clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" + clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" + clusters.AirQuality = require "AirQuality" + clusters.CarbonMonoxideConcentrationMeasurement = require "CarbonMonoxideConcentrationMeasurement" + clusters.CarbonDioxideConcentrationMeasurement = require "CarbonDioxideConcentrationMeasurement" + clusters.FormaldehydeConcentrationMeasurement = require "FormaldehydeConcentrationMeasurement" + clusters.NitrogenDioxideConcentrationMeasurement = require "NitrogenDioxideConcentrationMeasurement" + clusters.OzoneConcentrationMeasurement = require "OzoneConcentrationMeasurement" + clusters.Pm1ConcentrationMeasurement = require "Pm1ConcentrationMeasurement" + clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" + clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" + clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" +end local mock_device_basic = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("air-purifier-hepa-ac-wind.yml"), @@ -162,7 +163,7 @@ local cluster_subscribe_list_configured = { clusters.Thermostat.attributes.SystemMode, clusters.Thermostat.attributes.ControlSequenceOfOperation }, - [capabilities.thermostatFanMode.ID] = { + [capabilities.fanMode.ID] = { clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua index 4c853c45d3..4a94a9a7e9 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_fan.lua @@ -1,4 +1,4 @@ --- Copyright 2024 SmartThings +-- Copyright 2025 SmartThings -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -74,6 +74,7 @@ local mock_device_generic = test.mock_device.build_test_matter_device({ local cluster_subscribe_list = { clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.PercentCurrent, clusters.FanControl.attributes.WindSupport, clusters.FanControl.attributes.WindSetting, @@ -83,6 +84,7 @@ local cluster_subscribe_list = { local cluster_subscribe_list_generic = { clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.PercentCurrent, } 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 b57901f219..6c30fae787 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 @@ -142,6 +142,7 @@ local function test_init() clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit }, [capabilities.airConditionerFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, [capabilities.fanSpeedPercent.ID] = { @@ -199,6 +200,7 @@ local function test_init_configure() clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit }, [capabilities.airConditionerFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, [capabilities.fanSpeedPercent.ID] = { @@ -253,6 +255,7 @@ local function test_init_nostate() clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit }, [capabilities.airConditionerFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, [capabilities.fanSpeedPercent.ID] = { @@ -401,4 +404,82 @@ test.register_message_test( } ) +test.register_message_test( + "Test fan mode handler", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanMode.OFF) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.airConditionerFanMode.fanMode("off")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanMode.LOW) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.airConditionerFanMode.fanMode("low")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanMode.HIGH) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.airConditionerFanMode.fanMode("high")) + } + } +) + +local FanModeSequence = clusters.FanControl.attributes.FanModeSequence +test.register_message_test( + "Room AC fan mode sequence reports should generate the appropriate supported modes", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + FanModeSequence:build_test_report_data(mock_device, 1, FanModeSequence.OFF_ON) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.airConditionerFanMode.supportedAcFanModes({"off", "high"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + FanModeSequence:build_test_report_data(mock_device, 1, FanModeSequence.OFF_LOW_MED_HIGH_AUTO) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.airConditionerFanMode.supportedAcFanModes({"off", "low", "medium", "high", "auto"}, {visibility={displayed=false}})) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac_modular.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac_modular.lua index d4258bbdb3..096ce71221 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac_modular.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_room_ac_modular.lua @@ -134,6 +134,7 @@ local function test_init_basic() clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit }, [capabilities.airConditionerFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, [capabilities.fanSpeedPercent.ID] = { @@ -180,6 +181,7 @@ local subscribed_attributes_no_state = { clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit }, [capabilities.airConditionerFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, [capabilities.fanSpeedPercent.ID] = { @@ -235,6 +237,7 @@ local function test_init_no_state() clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit }, [capabilities.airConditionerFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode }, [capabilities.fanSpeedPercent.ID] = { diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_modular.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_modular.lua index 31e15216d6..ee2d27c001 100644 --- a/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_modular.lua +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_thermostat_modular.lua @@ -1,4 +1,4 @@ --- Copyright 2023 SmartThings +-- Copyright 2025 SmartThings -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -110,7 +110,7 @@ local expected_metadata = { "main", { "relativeHumidityMeasurement", - "thermostatFanMode", + "fanMode", "thermostatHeatingSetpoint", "thermostatCoolingSetpoint" },