diff --git a/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml b/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml index 6c0f009564..f568aaac1d 100644 --- a/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml +++ b/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml @@ -35,14 +35,6 @@ components: version: 1 - id: tvocHealthConcern version: 1 - - id: thermostatOperatingState - version: 1 - config: - values: - - key: "thermostatOperatingState.value" - enabledValues: - - idle - - heating - id: firmwareUpdate version: 1 - id: refresh diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/init.lua b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/init.lua index c7057f82e4..3fa5f37100 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/init.lua @@ -1,5 +1,6 @@ local cluster_base = require "st.matter.cluster_base" local ActivatedCarbonFilterMonitoringServerAttributes = require "ActivatedCarbonFilterMonitoring.server.attributes" +local ActivatedCarbonFilterMonitoringServerCommands = require "ActivatedCarbonFilterMonitoring.server.commands" local ActivatedCarbonFilterMonitoringTypes = require "ActivatedCarbonFilterMonitoring.types" local ActivatedCarbonFilterMonitoring = {} @@ -9,6 +10,7 @@ ActivatedCarbonFilterMonitoring.NAME = "ActivatedCarbonFilterMonitoring" ActivatedCarbonFilterMonitoring.server = {} ActivatedCarbonFilterMonitoring.client = {} ActivatedCarbonFilterMonitoring.server.attributes = ActivatedCarbonFilterMonitoringServerAttributes:set_parent_cluster(ActivatedCarbonFilterMonitoring) +ActivatedCarbonFilterMonitoring.server.commands = ActivatedCarbonFilterMonitoringServerCommands:set_parent_cluster(ActivatedCarbonFilterMonitoring) ActivatedCarbonFilterMonitoring.types = ActivatedCarbonFilterMonitoringTypes function ActivatedCarbonFilterMonitoring:get_attribute_by_id(attr_id) @@ -52,6 +54,9 @@ ActivatedCarbonFilterMonitoring.attribute_direction_map = { ["AttributeList"] = "server", } +ActivatedCarbonFilterMonitoring.command_direction_map = { + ["ResetCondition"] = "server", +} ActivatedCarbonFilterMonitoring.FeatureMap = ActivatedCarbonFilterMonitoring.types.Feature @@ -73,6 +78,24 @@ end ActivatedCarbonFilterMonitoring.attributes = {} setmetatable(ActivatedCarbonFilterMonitoring.attributes, attribute_helper_mt) +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = ActivatedCarbonFilterMonitoring.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, ActivatedCarbonFilterMonitoring.NAME)) + end + return ActivatedCarbonFilterMonitoring[direction].commands[key] +end +ActivatedCarbonFilterMonitoring.commands = {} +setmetatable(ActivatedCarbonFilterMonitoring.commands, command_helper_mt) + +local event_helper_mt = {} +event_helper_mt.__index = function(self, key) + return ActivatedCarbonFilterMonitoring.server.events[key] +end +ActivatedCarbonFilterMonitoring.events = {} +setmetatable(ActivatedCarbonFilterMonitoring.events, event_helper_mt) + setmetatable(ActivatedCarbonFilterMonitoring, {__index = cluster_base}) return ActivatedCarbonFilterMonitoring diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua index f367bff0fe..320826dacc 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua @@ -1,30 +1,7 @@ --- Copyright 2022 SmartThings --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. - --- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. - local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" ---- @class st.matter.clusters.ActivatedCarbonFilterMonitoring.AttributeList ---- @alias AttributeList ---- ---- @field public ID number 0xFFFB the ID of this attribute ---- @field public NAME string "AttributeList" the name of this attribute ---- @field public data_type st.matter.data_types.Array the data type of this attribute - local AttributeList = { ID = 0xFFFB, NAME = "AttributeList", @@ -32,31 +9,18 @@ local AttributeList = { element_type = require "st.matter.data_types.Uint32", } ---- Add additional functionality to the base type object ---- ---- @param base_type_obj st.matter.data_types.Array the base data type object to add functionality to function AttributeList:augment_type(data_type_obj) for i, v in ipairs(data_type_obj.elements) do data_type_obj.elements[i] = data_types.validate_or_build_type(v, AttributeList.element_type) end end ---- Create a Array object of this attribute with any additional features provided for the attribute ---- This is also usable with the AttributeList(...) syntax ---- ---- @vararg vararg the values needed to construct a Array ---- @return st.matter.data_types.Array function AttributeList:new_value(...) local o = self.base_type(table.unpack({...})) return o end ---- Constructs an st.matter.interaction_model.InteractionRequest to read ---- this attribute from a device ---- @param device st.matter.Device ---- @param endpoint_id number|nil ---- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request function AttributeList:read(device, endpoint_id) return cluster_base.read( device, @@ -68,13 +32,6 @@ function AttributeList:read(device, endpoint_id) end ---- Reporting policy: AttributeList => true => mandatory - ---- Sets up a Subscribe Interaction ---- ---- @param device any ---- @param endpoint_id number|nil ---- @return any function AttributeList:subscribe(device, endpoint_id) return cluster_base.subscribe( device, @@ -90,13 +47,6 @@ function AttributeList:set_parent_cluster(cluster) return self end ---- Builds an AttributeList test attribute reponse for the driver integration testing framework ---- ---- @param device st.matter.Device the device to build this message for ---- @param endpoint_id number|nil ---- @param value any ---- @param status string Interaction status associated with the path ---- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA function AttributeList:build_test_report_data( device, endpoint_id, diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua index 5e08fd9991..4980356485 100644 --- a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua +++ b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua @@ -1,52 +1,19 @@ --- Copyright 2022 SmartThings --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. - --- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. - local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" ---- @class st.matter.clusters.ActivatedCarbonFilterMonitoring.ChangeIndication ---- @alias ChangeIndication ---- ---- @field public ID number 0x0002 the ID of this attribute ---- @field public NAME string "ChangeIndication" the name of this attribute ---- @field public data_type ActivatedCarbonFilterMonitoring.types.ChangeIndicationEnum the data type of this attribute - local ChangeIndication = { ID = 0x0002, NAME = "ChangeIndication", base_type = require "ActivatedCarbonFilterMonitoring.types.ChangeIndicationEnum", } ---- Create a ChangeIndicationEnum object of this attribute with any additional features provided for the attribute ---- This is also usable with the ChangeIndication(...) syntax ---- ---- @vararg vararg the values needed to construct a ChangeIndicationEnum ---- @return ActivatedCarbonFilterMonitoring.types.ChangeIndicationEnum function ChangeIndication:new_value(...) local o = self.base_type(table.unpack({...})) self:augment_type(o) return o end ---- Constructs an st.matter.interaction_model.InteractionRequest to read ---- this attribute from a device ---- @param device st.matter.Device ---- @param endpoint_id number|nil ---- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request function ChangeIndication:read(device, endpoint_id) return cluster_base.read( device, @@ -57,14 +24,6 @@ function ChangeIndication:read(device, endpoint_id) ) end - ---- Reporting policy: ChangeIndication => true => mandatory - ---- Sets up a Subscribe Interaction ---- ---- @param device any ---- @param endpoint_id number|nil ---- @return any function ChangeIndication:subscribe(device, endpoint_id) return cluster_base.subscribe( device, @@ -80,13 +39,6 @@ function ChangeIndication:set_parent_cluster(cluster) return self end ---- Builds an ChangeIndication test attribute reponse for the driver integration testing framework ---- ---- @param device st.matter.Device the device to build this message for ---- @param endpoint_id number|nil ---- @param value any ---- @param status string Interaction status associated with the path ---- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA function ChangeIndication:build_test_report_data( device, endpoint_id, diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua new file mode 100644 index 0000000000..040ce653b4 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/ResetCondition.lua @@ -0,0 +1,91 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ResetCondition = {} + +ResetCondition.NAME = "ResetCondition" +ResetCondition.ID = 0x0000 +ResetCondition.field_defs = { +} + +function ResetCondition:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function ResetCondition:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = ResetCondition, + __tostring = ResetCondition.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function ResetCondition:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ResetCondition:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + base_type_obj.elements = elems +end + +function ResetCondition:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(ResetCondition, {__call = ResetCondition.init}) + +return ResetCondition + diff --git a/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/init.lua b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/init.lua new file mode 100644 index 0000000000..7f641481d4 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/ActivatedCarbonFilterMonitoring/server/commands/init.lua @@ -0,0 +1,23 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("ActivatedCarbonFilterMonitoring.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local ActivatedCarbonFilterMonitoringServerCommands = {} + +function ActivatedCarbonFilterMonitoringServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ActivatedCarbonFilterMonitoringServerCommands, command_mt) + +return ActivatedCarbonFilterMonitoringServerCommands + diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/init.lua b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/init.lua index 21795104b7..84400d7833 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/init.lua @@ -1,5 +1,6 @@ local cluster_base = require "st.matter.cluster_base" local HepaFilterMonitoringServerAttributes = require "HepaFilterMonitoring.server.attributes" +local HepaFilterMonitoringServerCommands = require "HepaFilterMonitoring.server.commands" local HepaFilterMonitoringTypes = require "HepaFilterMonitoring.types" local HepaFilterMonitoring = {} @@ -9,6 +10,7 @@ HepaFilterMonitoring.NAME = "HepaFilterMonitoring" HepaFilterMonitoring.server = {} HepaFilterMonitoring.client = {} HepaFilterMonitoring.server.attributes = HepaFilterMonitoringServerAttributes:set_parent_cluster(HepaFilterMonitoring) +HepaFilterMonitoring.server.commands = HepaFilterMonitoringServerCommands:set_parent_cluster(HepaFilterMonitoring) HepaFilterMonitoring.types = HepaFilterMonitoringTypes function HepaFilterMonitoring:get_attribute_by_id(attr_id) @@ -52,6 +54,9 @@ HepaFilterMonitoring.attribute_direction_map = { ["AttributeList"] = "server", } +HepaFilterMonitoring.command_direction_map = { + ["ResetCondition"] = "server", +} HepaFilterMonitoring.FeatureMap = HepaFilterMonitoring.types.Feature @@ -73,6 +78,24 @@ end HepaFilterMonitoring.attributes = {} setmetatable(HepaFilterMonitoring.attributes, attribute_helper_mt) +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = HepaFilterMonitoring.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, HepaFilterMonitoring.NAME)) + end + return HepaFilterMonitoring[direction].commands[key] +end +HepaFilterMonitoring.commands = {} +setmetatable(HepaFilterMonitoring.commands, command_helper_mt) + +local event_helper_mt = {} +event_helper_mt.__index = function(self, key) + return HepaFilterMonitoring.server.events[key] +end +HepaFilterMonitoring.events = {} +setmetatable(HepaFilterMonitoring.events, event_helper_mt) + setmetatable(HepaFilterMonitoring, {__index = cluster_base}) return HepaFilterMonitoring diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/AttributeList.lua index 6ee5d9d091..f2ca149f03 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/AttributeList.lua @@ -1,30 +1,7 @@ --- Copyright 2022 SmartThings --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. - --- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. - local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" ---- @class st.matter.clusters.HepaFilterMonitoring.AttributeList ---- @alias AttributeList ---- ---- @field public ID number 0xFFFB the ID of this attribute ---- @field public NAME string "AttributeList" the name of this attribute ---- @field public data_type st.matter.data_types.Array the data type of this attribute - local AttributeList = { ID = 0xFFFB, NAME = "AttributeList", @@ -32,31 +9,18 @@ local AttributeList = { element_type = require "st.matter.data_types.Uint32", } ---- Add additional functionality to the base type object ---- ---- @param base_type_obj st.matter.data_types.Array the base data type object to add functionality to function AttributeList:augment_type(data_type_obj) for i, v in ipairs(data_type_obj.elements) do data_type_obj.elements[i] = data_types.validate_or_build_type(v, AttributeList.element_type) end end ---- Create a Array object of this attribute with any additional features provided for the attribute ---- This is also usable with the AttributeList(...) syntax ---- ---- @vararg vararg the values needed to construct a Array ---- @return st.matter.data_types.Array function AttributeList:new_value(...) local o = self.base_type(table.unpack({...})) return o end ---- Constructs an st.matter.interaction_model.InteractionRequest to read ---- this attribute from a device ---- @param device st.matter.Device ---- @param endpoint_id number|nil ---- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request function AttributeList:read(device, endpoint_id) return cluster_base.read( device, @@ -67,14 +31,6 @@ function AttributeList:read(device, endpoint_id) ) end - ---- Reporting policy: AttributeList => true => mandatory - ---- Sets up a Subscribe Interaction ---- ---- @param device any ---- @param endpoint_id number|nil ---- @return any function AttributeList:subscribe(device, endpoint_id) return cluster_base.subscribe( device, @@ -90,13 +46,6 @@ function AttributeList:set_parent_cluster(cluster) return self end ---- Builds an AttributeList test attribute reponse for the driver integration testing framework ---- ---- @param device st.matter.Device the device to build this message for ---- @param endpoint_id number|nil ---- @param value any ---- @param status string Interaction status associated with the path ---- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA function AttributeList:build_test_report_data( device, endpoint_id, diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua index 44642e0a8f..955b89eb88 100644 --- a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua +++ b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua @@ -1,52 +1,19 @@ --- Copyright 2022 SmartThings --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. - --- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. - local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" ---- @class st.matter.clusters.HepaFilterMonitoring.ChangeIndication ---- @alias ChangeIndication ---- ---- @field public ID number 0x0002 the ID of this attribute ---- @field public NAME string "ChangeIndication" the name of this attribute ---- @field public data_type HepaFilterMonitoring.types.ChangeIndicationEnum the data type of this attribute - local ChangeIndication = { ID = 0x0002, NAME = "ChangeIndication", base_type = require "HepaFilterMonitoring.types.ChangeIndicationEnum", } ---- Create a ChangeIndicationEnum object of this attribute with any additional features provided for the attribute ---- This is also usable with the ChangeIndication(...) syntax ---- ---- @vararg vararg the values needed to construct a ChangeIndicationEnum ---- @return HepaFilterMonitoring.types.ChangeIndicationEnum function ChangeIndication:new_value(...) local o = self.base_type(table.unpack({...})) self:augment_type(o) return o end ---- Constructs an st.matter.interaction_model.InteractionRequest to read ---- this attribute from a device ---- @param device st.matter.Device ---- @param endpoint_id number|nil ---- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request function ChangeIndication:read(device, endpoint_id) return cluster_base.read( device, @@ -57,14 +24,6 @@ function ChangeIndication:read(device, endpoint_id) ) end - ---- Reporting policy: ChangeIndication => true => mandatory - ---- Sets up a Subscribe Interaction ---- ---- @param device any ---- @param endpoint_id number|nil ---- @return any function ChangeIndication:subscribe(device, endpoint_id) return cluster_base.subscribe( device, @@ -80,13 +39,6 @@ function ChangeIndication:set_parent_cluster(cluster) return self end ---- Builds an ChangeIndication test attribute reponse for the driver integration testing framework ---- ---- @param device st.matter.Device the device to build this message for ---- @param endpoint_id number|nil ---- @param value any ---- @param status string Interaction status associated with the path ---- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA function ChangeIndication:build_test_report_data( device, endpoint_id, diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/ResetCondition.lua b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/ResetCondition.lua new file mode 100644 index 0000000000..040ce653b4 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/ResetCondition.lua @@ -0,0 +1,91 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ResetCondition = {} + +ResetCondition.NAME = "ResetCondition" +ResetCondition.ID = 0x0000 +ResetCondition.field_defs = { +} + +function ResetCondition:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function ResetCondition:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = ResetCondition, + __tostring = ResetCondition.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function ResetCondition:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ResetCondition:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + base_type_obj.elements = elems +end + +function ResetCondition:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(ResetCondition, {__call = ResetCondition.init}) + +return ResetCondition + diff --git a/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/init.lua b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/init.lua new file mode 100644 index 0000000000..55a4ea7a30 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/HepaFilterMonitoring/server/commands/init.lua @@ -0,0 +1,23 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("HepaFilterMonitoring.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local HepaFilterMonitoringServerCommands = {} + +function HepaFilterMonitoringServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(HepaFilterMonitoringServerCommands, command_mt) + +return HepaFilterMonitoringServerCommands + diff --git a/drivers/SmartThings/matter-thermostat/src/init.lua b/drivers/SmartThings/matter-thermostat/src/init.lua index 69adcc3c7d..f563303c73 100644 --- a/drivers/SmartThings/matter-thermostat/src/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/init.lua @@ -404,20 +404,15 @@ local function find_default_endpoint(device, cluster) return res end -local function component_to_endpoint(device, component_name) +local function component_to_endpoint(device, component_name, cluster_id) -- Use the find_default_endpoint function to return the first endpoint that -- supports a given cluster. local component_to_endpoint_map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) if component_to_endpoint_map ~= nil and component_to_endpoint_map[component_name] ~= nil then return component_to_endpoint_map[component_name] end - if device:supports_capability(capabilities.airPurifierFanMode) then - -- Fan Control is mandatory for the Air Purifier device type - return find_default_endpoint(device, clusters.FanControl.ID) - else - -- Thermostat is mandatory for Thermostat and Room AC device type - return find_default_endpoint(device, clusters.Thermostat.ID) - end + if not cluster_id then return device.MATTER_DEFAULT_ENDPOINT end + return find_default_endpoint(device, cluster_id) end local endpoint_to_component = function (device, endpoint_id) @@ -1415,13 +1410,13 @@ local function activated_carbon_filter_change_indication_handler(driver, device, end local function handle_switch_on(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) + local endpoint_id = component_to_endpoint(device, cmd.component, clusters.OnOff.ID) local req = clusters.OnOff.server.commands.On(device, endpoint_id) device:send(req) end local function handle_switch_off(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) + local endpoint_id = component_to_endpoint(device, cmd.component, clusters.OnOff.ID) local req = clusters.OnOff.server.commands.Off(device, endpoint_id) device:send(req) end @@ -1435,7 +1430,7 @@ local function set_thermostat_mode(driver, device, cmd) end end if mode_id then - device:send(clusters.Thermostat.attributes.SystemMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) + device:send(clusters.Thermostat.attributes.SystemMode:write(device, component_to_endpoint(device, cmd.component, clusters.Thermostat.ID), mode_id)) end end @@ -1447,7 +1442,7 @@ end local function set_setpoint(setpoint) return function(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) + local endpoint_id = component_to_endpoint(device, cmd.component, clusters.Thermostat.ID) local MAX_TEMP_IN_C = THERMOSTAT_MAX_TEMP_IN_C local MIN_TEMP_IN_C = THERMOSTAT_MIN_TEMP_IN_C local is_water_heater_device = get_device_type(driver, device) == WATER_HEATER_DEVICE_TYPE_ID @@ -1526,7 +1521,7 @@ local function set_setpoint(setpoint) return end end - device:send(setpoint:write(device, device:component_to_endpoint(cmd.component), utils.round(value * 100.0))) + device:send(setpoint:write(device, component_to_endpoint(device, cmd.component, clusters.Thermostat.ID), utils.round(value * 100.0))) end end @@ -1593,7 +1588,7 @@ local function set_thermostat_fan_mode(driver, device, cmd) fan_mode_id = clusters.FanControl.attributes.FanMode.ON end if fan_mode_id then - device:send(clusters.FanControl.attributes.FanMode:write(device, device:component_to_endpoint(cmd.component), fan_mode_id)) + device:send(clusters.FanControl.attributes.FanMode:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) end end @@ -1619,7 +1614,7 @@ local function set_fan_mode(driver, device, cmd) fan_mode_id = clusters.FanControl.attributes.FanMode.OFF end if fan_mode_id then - device:send(clusters.FanControl.attributes.FanMode:write(device, device:component_to_endpoint(cmd.component), fan_mode_id)) + device:send(clusters.FanControl.attributes.FanMode:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) end end @@ -1643,13 +1638,13 @@ local function set_air_purifier_fan_mode(driver, device, cmd) fan_mode_id = clusters.FanControl.attributes.FanMode.OFF end if fan_mode_id then - device:send(clusters.FanControl.attributes.FanMode:write(device, device:component_to_endpoint(cmd.component), fan_mode_id)) + device:send(clusters.FanControl.attributes.FanMode:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), fan_mode_id)) end end local function set_fan_speed_percent(driver, device, cmd) local speed = math.floor(cmd.args.percent) - device:send(clusters.FanControl.attributes.PercentSetting:write(device, device:component_to_endpoint(cmd.component), speed)) + device:send(clusters.FanControl.attributes.PercentSetting:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), speed)) end local function set_wind_mode(driver, device, cmd) @@ -1659,7 +1654,7 @@ local function set_wind_mode(driver, device, cmd) elseif cmd.args.windMode == capabilities.windMode.windMode.naturalWind.NAME then wind_mode = clusters.FanControl.types.WindSupportMask.NATURAL_WIND end - device:send(clusters.FanControl.attributes.WindSetting:write(device, device:component_to_endpoint(cmd.component), wind_mode)) + device:send(clusters.FanControl.attributes.WindSetting:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), wind_mode)) end local function set_rock_mode(driver, device, cmd) @@ -1671,12 +1666,12 @@ local function set_rock_mode(driver, device, cmd) elseif cmd.args.fanOscillationMode == capabilities.fanOscillationMode.fanOscillationMode.swing.NAME then rock_mode = clusters.FanControl.types.RockSupportMask.ROCK_ROUND end - device:send(clusters.FanControl.attributes.RockSetting:write(device, device:component_to_endpoint(cmd.component), rock_mode)) + device:send(clusters.FanControl.attributes.RockSetting:write(device, component_to_endpoint(device, cmd.component, clusters.FanControl.ID), rock_mode)) end local function set_water_heater_mode(driver, device, cmd) device.log.info(string.format("set_water_heater_mode mode: %s", cmd.args.mode)) - local endpoint_id = device:component_to_endpoint(cmd.component) + local endpoint_id = component_to_endpoint(device, cmd.component, clusters.Thermostat.ID) local supportedWaterHeaterModesWithIdx = device:get_field(SUPPORTED_WATER_HEATER_MODES_WITH_IDX) or {} for i, mode in ipairs(supportedWaterHeaterModesWithIdx) do if cmd.args.mode == mode[2] then @@ -1686,6 +1681,15 @@ local function set_water_heater_mode(driver, device, cmd) end end +local function reset_filter_state(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + if cmd.component == "hepaFilter" then + device:send(clusters.HepaFilterMonitoring.server.commands.ResetCondition(device, endpoint_id)) + else + device:send(clusters.ActivatedCarbonFilterMonitoring.server.commands.ResetCondition(device, endpoint_id)) + end +end + local function battery_percent_remaining_attr_handler(driver, device, ib, response) if ib.data.value then device:emit_event(capabilities.battery.battery(math.floor(ib.data.value / 2.0 + 0.5))) @@ -1987,6 +1991,9 @@ local matter_driver_template = { }, [capabilities.mode.ID] = { [capabilities.mode.commands.setMode.NAME] = set_water_heater_mode, + }, + [capabilities.filterState.ID] = { + [capabilities.filterState.commands.resetFilter.NAME] = reset_filter_state, } }, supported_capabilities = { 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 be2c66483b..86b58de240 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 @@ -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. @@ -15,22 +15,24 @@ local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local SinglePrecisionFloat = require "st.matter.data_types.SinglePrecisionFloat" - 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" +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 = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("air-purifier-hepa-ac-wind.yml"), @@ -49,13 +51,13 @@ local mock_device = 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"}, } + } } }) @@ -310,9 +312,6 @@ local cluster_subscribe_list_configured = { clusters.Thermostat.attributes.SystemMode, clusters.Thermostat.attributes.ControlSequenceOfOperation }, - [capabilities.thermostatOperatingState.ID] = { - clusters.Thermostat.attributes.ThermostatRunningState - }, [capabilities.thermostatFanMode.ID] = { clusters.FanControl.attributes.FanModeSequence, clusters.FanControl.attributes.FanMode @@ -851,4 +850,42 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Test set heating setpoint on thermostat endpoint", + function() + test.socket.capability:__queue_receive({ + mock_device_ap_thermo_aqs_preconfigured.id, + { capability = "thermostatHeatingSetpoint", component = "main", command = "setHeatingSetpoint", args = { 21 } } + }) + test.socket.matter:__expect_send({ + mock_device_ap_thermo_aqs_preconfigured.id, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device_ap_thermo_aqs_preconfigured, 7, 2100) + }) + end, + { test_init = test_init_ap_thermo_aqs_preconfigured } +) + +test.register_coroutine_test( + "Test filter state reset command", + function() + test.socket.capability:__queue_receive({ + mock_device_ap_thermo_aqs_preconfigured.id, + { capability = "filterState", component = "hepaFilter", command = "resetFilter", args = { } } + }) + test.socket.matter:__expect_send({ + mock_device_ap_thermo_aqs_preconfigured.id, + clusters.HepaFilterMonitoring.server.commands.ResetCondition(mock_device_ap_thermo_aqs_preconfigured, 1) + }) + test.socket.capability:__queue_receive({ + mock_device_ap_thermo_aqs_preconfigured.id, + { capability = "filterState", component = "activatedCarbonFilter", command = "resetFilter", args = { } } + }) + test.socket.matter:__expect_send({ + mock_device_ap_thermo_aqs_preconfigured.id, + clusters.ActivatedCarbonFilterMonitoring.server.commands.ResetCondition(mock_device_ap_thermo_aqs_preconfigured, 1) + }) + end, + { test_init = test_init_ap_thermo_aqs_preconfigured } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_api9.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_api9.lua new file mode 100644 index 0000000000..871693d5a6 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_air_purifier_api9.lua @@ -0,0 +1,894 @@ +-- 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. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +local test = require "integration_test" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local SinglePrecisionFloat = require "st.matter.data_types.SinglePrecisionFloat" +local t_utils = require "integration_test.utils" +local version = require "version" + +version.api = 9 + +-- include driver-side cluster definitions to test embedded clusters on lower api versions +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" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("air-purifier-hepa-ac-wind.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000 + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"} + }, + device_types = { + device_type_id = 0x0016, device_type_revision = 1 -- RootNode + } + }, + { + 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"} + } + } + } +}) + +local mock_device_rock = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("air-purifier-hepa-ac-rock-wind.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000 + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + device_type_id = 0x0016, device_type_revision = 1, -- RootNode + } + }, + { + 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"} + } + } + } +}) + +local mock_device_ap_aqs = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("air-purifier-hepa-ac-wind.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000 + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"} + }, + device_types = { + device_type_id = 0x0016, device_type_revision = 1 -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 0}, + {cluster_id = clusters.HepaFilterMonitoring.ID, cluster_type = "SERVER", feature_map = 0}, + {cluster_id = clusters.ActivatedCarbonFilterMonitoring.ID, cluster_type = "SERVER", feature_map = 0} + }, + device_types = { + {device_type_id = 0x002D, device_type_revision = 1} -- AP + } + }, + { + endpoint_id = 3, + clusters = { + {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.CarbonDioxideConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 3}, + {cluster_id = clusters.RadonConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 2}, + {cluster_id = clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 1} + }, + device_types = { + {device_type_id = 0x002C, device_type_revision = 1} -- AQS + } + } + } +}) + +local mock_device_ap_thermo_aqs = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("air-purifier-hepa-ac-wind.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000 + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"} + }, + device_types = { + device_type_id = 0x0016, device_type_revision = 1 -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 63}, + {cluster_id = clusters.HepaFilterMonitoring.ID, cluster_type = "SERVER", feature_map = 7}, + {cluster_id = clusters.ActivatedCarbonFilterMonitoring.ID, cluster_type = "SERVER", feature_map = 7} + }, + device_types = { + {device_type_id = 0x002D, device_type_revision = 1} -- AP + } + }, + { + endpoint_id = 3, + clusters = { + {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.NitrogenDioxideConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 14}, + {cluster_id = clusters.Pm25ConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 15}, + {cluster_id = clusters.FormaldehydeConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 15}, + {cluster_id = clusters.Pm10ConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 15}, + {cluster_id = clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 14} + }, + device_types = { + {device_type_id = 0x002C, device_type_revision = 1} -- AQS + } + }, + { + endpoint_id = 4, + clusters = { + {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER", feature_map = 0} + }, + device_types = { + {device_type_id = 0x0302, device_type_revision = 1} -- Temperature Sensor + } + }, + { + endpoint_id = 6, + clusters = { + {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER", feature_map = 0} + }, + device_types = { + {device_type_id = 0x0307, device_type_revision = 1} -- Humidity Sensor + } + }, + { + endpoint_id = 7, + clusters = { + {cluster_id = clusters.Thermostat.ID, cluster_type = "SERVER", feature_map = 1} + }, + device_types = { + {device_type_id = 0x0301, device_type_revision = 1} -- Thermostat + } + } + } +}) + +local mock_device_ap_thermo_aqs_preconfigured = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000 + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"} + }, + device_types = { + device_type_id = 0x0016, device_type_revision = 1 -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 63}, + {cluster_id = clusters.HepaFilterMonitoring.ID, cluster_type = "SERVER", feature_map = 7}, + {cluster_id = clusters.ActivatedCarbonFilterMonitoring.ID, cluster_type = "SERVER", feature_map = 7} + }, + device_types = { + {device_type_id = 0x002D, device_type_revision = 1} -- AP + } + }, + { + endpoint_id = 3, + clusters = { + {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.NitrogenDioxideConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 14}, + {cluster_id = clusters.Pm25ConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 15}, + {cluster_id = clusters.FormaldehydeConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 15}, + {cluster_id = clusters.Pm10ConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 15}, + {cluster_id = clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 14} + }, + device_types = { + {device_type_id = 0x002C, device_type_revision = 1} -- AQS + } + }, + { + endpoint_id = 4, + clusters = { + {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER", feature_map = 0}, + }, + device_types = { + {device_type_id = 0x0302, device_type_revision = 1} -- Temperature Sensor + } + }, + { + endpoint_id = 6, + clusters = { + {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER", feature_map = 0}, + }, + device_types = { + {device_type_id = 0x0307, device_type_revision = 1} -- Humidity Sensor + } + }, + { + endpoint_id = 7, + clusters = { + {cluster_id = clusters.Thermostat.ID, cluster_type = "SERVER", feature_map = 1}, + }, + device_types = { + {device_type_id = 0x0301, device_type_revision = 1} -- Thermostat + } + } + } +}) + +local cluster_subscribe_list = { + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.PercentCurrent, + clusters.FanControl.attributes.WindSupport, + clusters.FanControl.attributes.WindSetting, + clusters.HepaFilterMonitoring.attributes.ChangeIndication, + clusters.HepaFilterMonitoring.attributes.Condition, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication, + clusters.ActivatedCarbonFilterMonitoring.attributes.Condition +} + +local cluster_subscribe_list_rock = { + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode, + clusters.FanControl.attributes.PercentCurrent, + clusters.FanControl.attributes.WindSupport, + clusters.FanControl.attributes.WindSetting, + clusters.FanControl.attributes.RockSupport, + clusters.FanControl.attributes.RockSetting, + clusters.HepaFilterMonitoring.attributes.ChangeIndication, + clusters.HepaFilterMonitoring.attributes.Condition, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication, + clusters.ActivatedCarbonFilterMonitoring.attributes.Condition +} + +local cluster_subscribe_list_configured = { + [capabilities.temperatureMeasurement.ID] = { + clusters.Thermostat.attributes.LocalTemperature, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue + }, + [capabilities.relativeHumidityMeasurement.ID] = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + }, + [capabilities.thermostatMode.ID] = { + clusters.Thermostat.attributes.SystemMode, + clusters.Thermostat.attributes.ControlSequenceOfOperation + }, + [capabilities.thermostatFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode + }, + [capabilities.thermostatHeatingSetpoint.ID] = { + clusters.Thermostat.attributes.OccupiedHeatingSetpoint, + clusters.Thermostat.attributes.AbsMinHeatSetpointLimit, + clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit + }, + [capabilities.airPurifierFanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode + }, + [capabilities.fanSpeedPercent.ID] = { + clusters.FanControl.attributes.PercentCurrent + }, + [capabilities.windMode.ID] = { + clusters.FanControl.attributes.WindSupport, + clusters.FanControl.attributes.WindSetting, + clusters.FanControl.attributes.RockSupport, + clusters.FanControl.attributes.RockSetting + }, + [capabilities.filterState.ID] = { + clusters.HepaFilterMonitoring.attributes.Condition, + clusters.ActivatedCarbonFilterMonitoring.attributes.Condition + }, + [capabilities.filterStatus.ID] = { + clusters.HepaFilterMonitoring.attributes.ChangeIndication, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication + }, + [capabilities.airQualityHealthConcern.ID] = { + clusters.AirQuality.attributes.AirQuality + }, + [capabilities.nitrogenDioxideHealthConcern.ID] = { + clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue + }, + [capabilities.formaldehydeMeasurement.ID] = { + clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue, + clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit + }, + [capabilities.formaldehydeHealthConcern.ID] = { + clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue + }, + [capabilities.fineDustHealthConcern.ID] = { + clusters.Pm25ConcentrationMeasurement.attributes.LevelValue + }, + [capabilities.dustSensor.ID] = { + clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit, + clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue, + clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit + }, + [capabilities.dustHealthConcern.ID] = { + clusters.Pm10ConcentrationMeasurement.attributes.LevelValue + }, + [capabilities.tvocHealthConcern.ID] = { + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue + } +} + +local function test_init() + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device)) + end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.mock_device.add_test_device(mock_device) + + subscribe_request = cluster_subscribe_list_rock[1]:subscribe(mock_device_rock) + for i, cluster in ipairs(cluster_subscribe_list_rock) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_rock)) + end + end + test.socket.matter:__expect_send({mock_device_rock.id, subscribe_request}) + test.mock_device.add_test_device(mock_device_rock) +end +test.set_test_init_function(test_init) + +local function test_init_ap_aqs() + local subscribe_request_ap_aqs = cluster_subscribe_list[1]:subscribe(mock_device_ap_aqs) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request_ap_aqs:merge(cluster:subscribe(mock_device_ap_aqs)) + end + end + test.socket.matter:__expect_send({mock_device_ap_aqs.id, subscribe_request_ap_aqs}) + test.mock_device.add_test_device(mock_device_ap_aqs) +end + +local function test_init_ap_thermo_aqs_preconfigured() + local subscribe_request = nil + for _, attributes in pairs(cluster_subscribe_list_configured) do + for _, attribute in ipairs(attributes) do + if subscribe_request == nil then + subscribe_request = attribute:subscribe(mock_device) + else + subscribe_request:merge(attribute:subscribe(mock_device)) + end + end + end + test.socket.matter:__expect_send({mock_device_ap_thermo_aqs_preconfigured.id, subscribe_request}) + test.mock_device.add_test_device(mock_device_ap_thermo_aqs_preconfigured) +end + +local function test_init_ap_thermo_aqs() + local subscribe_request_ap_aqs = cluster_subscribe_list[1]:subscribe(mock_device_ap_thermo_aqs) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request_ap_aqs:merge(cluster:subscribe(mock_device_ap_thermo_aqs)) + end + end + test.socket.matter:__expect_send({mock_device_ap_thermo_aqs.id, subscribe_request_ap_aqs}) + test.mock_device.add_test_device(mock_device_ap_thermo_aqs) +end + +test.register_coroutine_test( + "Test profile change on init for AP and AQS combined device type", + function() + mock_device_ap_aqs:set_field("__BATTERY_SUPPORT", "NO_BATTERY") -- since we're assuming this would have happened during device_added in this case. + mock_device_ap_aqs:set_field("__THERMOSTAT_RUNNING_STATE_SUPPORT", false) -- since we're assuming this would have happened during device_added in this case. + test.socket.device_lifecycle:__queue_receive({ mock_device_ap_aqs.id, "doConfigure" }) + mock_device_ap_aqs:expect_metadata_update({ profile = "air-purifier-hepa-ac-aqs-co2-tvoc-meas-co2-radon-level" }) + mock_device_ap_aqs:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { test_init = test_init_ap_aqs } +) + +test.register_coroutine_test( + "Test profile change on init for AP and Thermo and AQS combined device type", + function() + mock_device_ap_thermo_aqs:set_field("__BATTERY_SUPPORT", "NO_BATTERY") -- since we're assuming this would have happened during device_added in this case. + mock_device_ap_thermo_aqs:set_field("__THERMOSTAT_RUNNING_STATE_SUPPORT", false) -- since we're assuming this would have happened during device_added in this case. + test.socket.device_lifecycle:__queue_receive({ mock_device_ap_thermo_aqs.id, "doConfigure" }) + mock_device_ap_thermo_aqs:expect_metadata_update({ profile = "air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level" }) + mock_device_ap_thermo_aqs:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + print(mock_device_ap_thermo_aqs.profile) + end, + { test_init = test_init_ap_thermo_aqs } +) + +test.register_coroutine_test( + "Molecular weight conversion should be handled appropriately in unit_conversion", + function () + test.socket.matter:__queue_receive({ + mock_device_ap_thermo_aqs_preconfigured.id, + clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit:build_test_report_data( + mock_device_ap_thermo_aqs_preconfigured, 1, clusters.FormaldehydeConcentrationMeasurement.types.MeasurementUnitEnum.MGM3 + ) + }) + test.socket.matter:__queue_receive({ + mock_device_ap_thermo_aqs_preconfigured.id, + clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue:build_test_report_data( + mock_device_ap_thermo_aqs_preconfigured, 1, SinglePrecisionFloat(0, 4, .11187500) + ) + }) + test.socket.capability:__expect_send( + mock_device_ap_thermo_aqs_preconfigured:generate_test_message("main", capabilities.formaldehydeMeasurement.formaldehydeLevel({value = 14, unit = "ppm"})) + ) + end, + { test_init = test_init_ap_thermo_aqs_preconfigured } +) + +test.register_message_test( + "setAirPurifierFanMode command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "airPurifierFanMode", component = "main", command = "setAirPurifierFanMode", args = { "low" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.attributes.FanMode.LOW) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "airPurifierFanMode", component = "main", command = "setAirPurifierFanMode", args = { "sleep" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.attributes.FanMode.LOW) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "airPurifierFanMode", component = "main", command = "setAirPurifierFanMode", args = { "auto" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.attributes.FanMode.AUTO) + } + } + } +) + +test.register_message_test( + "FanModeSequence send the appropriate commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanModeSequence:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.airPurifierFanMode.supportedAirPurifierFanModes({ + capabilities.airPurifierFanMode.airPurifierFanMode.off.NAME, + capabilities.airPurifierFanMode.airPurifierFanMode.low.NAME, + capabilities.airPurifierFanMode.airPurifierFanMode.medium.NAME, + capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME + }, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanModeSequence:build_test_report_data(mock_device, 1, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.airPurifierFanMode.supportedAirPurifierFanModes({ + capabilities.airPurifierFanMode.airPurifierFanMode.off.NAME, + capabilities.airPurifierFanMode.airPurifierFanMode.low.NAME, + capabilities.airPurifierFanMode.airPurifierFanMode.high.NAME + }, {visibility={displayed=false}})) + }, + } +) + +test.register_message_test( + "Test fan speed commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 10) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanSpeedPercent.percent(10)) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "fanSpeedPercent", component = "main", command = "setPercent", args = { 50 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.PercentSetting:write(mock_device, 1, 50) + } + } + } +) + +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.airPurifierFanMode.airPurifierFanMode.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.airPurifierFanMode.airPurifierFanMode.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.airPurifierFanMode.airPurifierFanMode.high()) + } + } +) + +test.register_message_test( + "Test filter status for HEPA and Activated Carbon filters", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.HepaFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.HepaFilterMonitoring.attributes.ChangeIndication.OK) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("hepaFilter", capabilities.filterStatus.filterStatus.normal()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.HepaFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.HepaFilterMonitoring.attributes.ChangeIndication.CRITICAL) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("hepaFilter", capabilities.filterStatus.filterStatus.replace()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.OK) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterStatus.filterStatus.normal()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.CRITICAL) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterStatus.filterStatus.replace()) + } + } +) + +local supportedFanWind = { + capabilities.windMode.windMode.noWind.NAME, + capabilities.windMode.windMode.sleepWind.NAME, + capabilities.windMode.windMode.naturalWind.NAME +} + +test.register_message_test( + "Test wind mode", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.WindSupport:build_test_report_data(mock_device, 1, 0x03) -- NoWind, SleepWind (0x0001), and NaturalWind (0x0002) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windMode.supportedWindModes(supportedFanWind, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.WindSetting:build_test_report_data(mock_device, 1, clusters.FanControl.types.WindSettingMask.SLEEP_WIND) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windMode.windMode.sleepWind()) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "windMode", component = "main", command = "setWindMode", args = { "naturalWind" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.WindSetting:write(mock_device, 1, clusters.FanControl.types.WindSettingMask.NATURAL_WIND) + } + } + } +) + +test.register_message_test( + "Set percent command should clamp invalid percentage values", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 255) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanSpeedPercent.percent(100)) + } + } +) + + +local supportedFanRock = { + capabilities.fanOscillationMode.fanOscillationMode.off.NAME, + capabilities.fanOscillationMode.fanOscillationMode.horizontal.NAME, + capabilities.fanOscillationMode.fanOscillationMode.vertical.NAME, + capabilities.fanOscillationMode.fanOscillationMode.swing.NAME +} + +test.register_message_test( + "Test rock mode", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device_rock.id, + clusters.FanControl.attributes.RockSupport:build_test_report_data(mock_device_rock, 1, 0x07) -- off, RockLeftRight (0x01), RockUpDown (0x02), and RockRound (0x04) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_rock:generate_test_message("main", capabilities.fanOscillationMode.supportedFanOscillationModes(supportedFanRock, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_rock.id, + clusters.FanControl.attributes.RockSetting:build_test_report_data(mock_device_rock, 1, clusters.FanControl.types.RockBitmap.ROCK_UP_DOWN) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_rock:generate_test_message("main", capabilities.fanOscillationMode.fanOscillationMode.vertical()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_rock.id, + clusters.FanControl.attributes.RockSetting:build_test_report_data(mock_device_rock, 1, clusters.FanControl.types.RockBitmap.ROCK_LEFT_RIGHT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_rock:generate_test_message("main", capabilities.fanOscillationMode.fanOscillationMode.horizontal()) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device_rock.id, + { capability = "fanOscillationMode", component = "main", command = "setFanOscillationMode", args = { "vertical" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device_rock.id, + clusters.FanControl.attributes.RockSetting:write(mock_device_rock, 1, clusters.FanControl.types.RockBitmap.ROCK_UP_DOWN) + } + } + } +) + +test.register_coroutine_test( + "Test set heating setpoint on thermostat endpoint", + function() + test.socket.capability:__queue_receive({ + mock_device_ap_thermo_aqs_preconfigured.id, + { capability = "thermostatHeatingSetpoint", component = "main", command = "setHeatingSetpoint", args = { 21 } } + }) + test.socket.matter:__expect_send({ + mock_device_ap_thermo_aqs_preconfigured.id, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device_ap_thermo_aqs_preconfigured, 7, 2100) + }) + end, + { test_init = test_init_ap_thermo_aqs_preconfigured } +) + +test.register_coroutine_test( + "Test filter state reset command", + function() + test.socket.capability:__queue_receive({ + mock_device_ap_thermo_aqs_preconfigured.id, + { capability = "filterState", component = "hepaFilter", command = "resetFilter", args = { } } + }) + test.socket.matter:__expect_send({ + mock_device_ap_thermo_aqs_preconfigured.id, + clusters.HepaFilterMonitoring.server.commands.ResetCondition(mock_device_ap_thermo_aqs_preconfigured, 1) + }) + test.socket.capability:__queue_receive({ + mock_device_ap_thermo_aqs_preconfigured.id, + { capability = "filterState", component = "activatedCarbonFilter", command = "resetFilter", args = { } } + }) + test.socket.matter:__expect_send({ + mock_device_ap_thermo_aqs_preconfigured.id, + clusters.ActivatedCarbonFilterMonitoring.server.commands.ResetCondition(mock_device_ap_thermo_aqs_preconfigured, 1) + }) + end, + { test_init = test_init_ap_thermo_aqs_preconfigured } +) + +test.run_registered_tests()