diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua index 80c1bca52d..fb83ed49c2 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/server/commands/MoveTo.lua @@ -25,7 +25,7 @@ MoveTo.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua index 41af9d66db..cf4aaa91df 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallCurrentStateStruct.lua @@ -24,7 +24,7 @@ OverallCurrentStateStruct.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, { name = "secure_state", diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua index eac6492815..f52fff4d1c 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureControl/types/OverallTargetStateStruct.lua @@ -24,7 +24,7 @@ OverallTargetStateStruct.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua index d2f4a51b78..c14a550f16 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/SetTarget.lua @@ -25,7 +25,7 @@ SetTarget.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua index ce70c7e680..597d2ddd74 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/server/commands/Step.lua @@ -25,7 +25,7 @@ Step.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua index 56958b2020..ecf718cbf3 100644 --- a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/ClosureDimension/types/DimensionStateStruct.lua @@ -24,7 +24,7 @@ DimensionStateStruct.field_defs = { field_id = 2, is_nullable = false, is_optional = true, - data_type = require "st.matter.generated.zap_clusters.Global.types.ThreeLevelAutoEnum", + data_type = require "embedded_clusters.Global.types.ThreeLevelAutoEnum", }, } diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/init.lua new file mode 100644 index 0000000000..23af682506 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/init.lua @@ -0,0 +1,55 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local DescriptorServerAttributes = require "embedded_clusters.Descriptor.server.attributes" + +local Descriptor = {} + +Descriptor.ID = 0x001D +Descriptor.NAME = "Descriptor" +Descriptor.server = {} +Descriptor.client = {} +Descriptor.server.attributes = DescriptorServerAttributes:set_parent_cluster(Descriptor) + +function Descriptor:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0003] = "PartsList", + [0x0004] = "TagList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function Descriptor:get_server_command_by_id(command_id) + local server_id_map = { + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +Descriptor.attribute_direction_map = { + ["PartsList"] = "server", + ["TagList"] = "server", +} + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = Descriptor.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, Descriptor.NAME)) + end + return Descriptor[direction].attributes[key] +end +Descriptor.attributes = {} +setmetatable(Descriptor.attributes, attribute_helper_mt) + +setmetatable(Descriptor, {__index = cluster_base}) + +return Descriptor + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua new file mode 100644 index 0000000000..7c5b24d28d --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/PartsList.lua @@ -0,0 +1,78 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local PartsList = { + ID = 0x0003, + NAME = "PartsList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint16", +} + +function PartsList: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, PartsList.element_type) + end +end + +function PartsList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function PartsList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PartsList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PartsList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function PartsList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function PartsList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(PartsList, {__call = PartsList.new_value, __index = PartsList.base_type}) +return PartsList + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/TagList.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/TagList.lua new file mode 100644 index 0000000000..6bc889d407 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/TagList.lua @@ -0,0 +1,80 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local TagList = { + ID = 0x0004, + NAME = "TagList", + _cluster = require "embedded_clusters.Descriptor", + base_type = require "st.matter.data_types.Array", + element_type = require "embedded_clusters.Global.types.SemanticTagStruct", +} + +function TagList: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, TagList.element_type) + end +end + +function TagList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function TagList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + + +function TagList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function TagList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function TagList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function TagList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(TagList, {__call = TagList.new_value, __index = TagList.base_type}) +return TagList + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/init.lua new file mode 100644 index 0000000000..8a3d9ea3c2 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/server/attributes/init.lua @@ -0,0 +1,27 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("embedded_clusters.Descriptor.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local DescriptorServerAttributes = {} + +function DescriptorServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(DescriptorServerAttributes, attr_mt) + +return DescriptorServerAttributes + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/Feature.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/Feature.lua new file mode 100644 index 0000000000..90c03f67b0 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/Feature.lua @@ -0,0 +1,56 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.TAG_LIST = 0x0001 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + TAG_LIST = 0x0001, +} + +Feature.is_tag_list_set = function(self) + return (self.value & self.TAG_LIST) ~= 0 +end + +Feature.set_tag_list = function(self) + if self.value ~= nil then + self.value = self.value | self.TAG_LIST + else + self.value = self.TAG_LIST + end +end + +Feature.unset_tag_list = function(self) + self.value = self.value & (~self.TAG_LIST & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.TAG_LIST + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_tag_list_set = Feature.is_tag_list_set, + set_tag_list = Feature.set_tag_list, + unset_tag_list = Feature.unset_tag_list, +} + +Feature.augment_type = function(_cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/init.lua new file mode 100644 index 0000000000..e76712b2af --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Descriptor/types/init.lua @@ -0,0 +1,9 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local lazy_loader = require "st.utils.lazy_loader" + +local DescriptorTypes = lazy_loader:new { prefix = "embedded_clusters.Descriptor.types." } + +return DescriptorTypes + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/init.lua new file mode 100644 index 0000000000..5c0c673c40 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/init.lua @@ -0,0 +1,8 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local GlobalTypes = require "embedded_clusters.Global.types" + +local Global = {} +Global.types = GlobalTypes +return Global diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/SemanticTagStruct.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/SemanticTagStruct.lua new file mode 100644 index 0000000000..c0e6f8caa7 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/SemanticTagStruct.lua @@ -0,0 +1,94 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local SemanticTagStruct = {} +local new_mt = StructureABC.new_mt({NAME = "SemanticTagStruct", ID = data_types.name_to_id_map["Structure"]}) + +SemanticTagStruct.field_defs = { + { + name = "mfg_code", + field_id = 0, + is_nullable = true, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "namespace_id", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "tag", + field_id = 2, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "label", + field_id = 3, + is_nullable = true, + is_optional = true, + data_type = require "st.matter.data_types.UTF8String1", + }, +} + +SemanticTagStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for _idx, field_def in ipairs(cls.field_defs) do -- Note: _idx is 1 when field_id is 0 + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif not (field_def.is_optional and tbl[field_def.name] == nil) then + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +SemanticTagStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = SemanticTagStruct.init +new_mt.__index.serialize = SemanticTagStruct.serialize + +SemanticTagStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.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) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + 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 + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(SemanticTagStruct, new_mt) + +return SemanticTagStruct + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/ThreeLevelAutoEnum.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/ThreeLevelAutoEnum.lua new file mode 100644 index 0000000000..a203c9b505 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/ThreeLevelAutoEnum.lua @@ -0,0 +1,38 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ThreeLevelAutoEnum = {} +local new_mt = UintABC.new_mt({NAME = "ThreeLevelAutoEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.AUTO] = "AUTO", + [self.LOW] = "LOW", + [self.MEDIUM] = "MEDIUM", + [self.HIGH] = "HIGH", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.AUTO = 0x00 +new_mt.__index.LOW = 0x01 +new_mt.__index.MEDIUM = 0x02 +new_mt.__index.HIGH = 0x03 + +ThreeLevelAutoEnum.AUTO = 0x00 +ThreeLevelAutoEnum.LOW = 0x01 +ThreeLevelAutoEnum.MEDIUM = 0x02 +ThreeLevelAutoEnum.HIGH = 0x03 + +ThreeLevelAutoEnum.augment_type = function(_cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ThreeLevelAutoEnum, new_mt) + + +return ThreeLevelAutoEnum + diff --git a/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/init.lua b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/init.lua new file mode 100644 index 0000000000..f4b0f9b225 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/embedded_clusters/Global/types/init.lua @@ -0,0 +1,18 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("embedded_clusters.Global.types." .. key) + end + return types_mt.__types_cache[key] +end + +local GlobalTypes = {} + +setmetatable(GlobalTypes, types_mt) + + +return GlobalTypes diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua index a643fa909e..0ca3579b75 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/can_handle.lua @@ -4,7 +4,8 @@ local CLOSURE_CONTROL_CLUSTER_ID = 0x0104 return function(opts, driver, device) - if #device:get_endpoints(CLOSURE_CONTROL_CLUSTER_ID) > 0 then + local embedded_cluster_utils = require "sub_drivers.closure.closure_utils.embedded_cluster_utils" + if #embedded_cluster_utils.get_endpoints(device, CLOSURE_CONTROL_CLUSTER_ID) > 0 then return true, require("sub_drivers.closure") end return false diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua index c574991c82..24faac339b 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_handlers/attribute_handlers.lua @@ -8,6 +8,7 @@ local version = require "version" if version.api < 20 then clusters.ClosureControl = require "embedded_clusters.ClosureControl" clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" + clusters.Global = require "embedded_clusters.Global" end local fields = require "sub_drivers.closure.closure_utils.fields" @@ -63,6 +64,9 @@ end function ClosureAttrHandlers.tag_list_handler(driver, device, ib, response) if not ib.data.elements then return end local tag_value + if version.api < 16 then + clusters.Global.types.SemanticTagStruct:augment_type(ib.data) + end for _, v in ipairs(ib.data.elements) do local tag = v.elements if tag and tag.namespace_id and tag.namespace_id.value == 0x44 then diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/embedded_cluster_utils.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/embedded_cluster_utils.lua new file mode 100644 index 0000000000..dbbd41cc85 --- /dev/null +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/embedded_cluster_utils.lua @@ -0,0 +1,62 @@ +-- Copyright © 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local clusters = require "st.matter.clusters" +local utils = require "st.utils" +local version = require "version" + +if version.api < 20 then + clusters.ClosureControl = require "embedded_clusters.ClosureControl" + clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" +end + +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + +local embedded_cluster_utils = {} + +local embedded_clusters_16 = { + [clusters.Descriptor.ID] = clusters.Descriptor +} + +local embedded_clusters_20 = { + [clusters.ClosureControl.ID] = clusters.ClosureControl, + [clusters.ClosureDimension.ID] = clusters.ClosureDimension +} + +function embedded_cluster_utils.get_endpoints(device, cluster_id, opts) + -- If using older lua libs and need to check for an embedded cluster feature, + -- we must use the embedded cluster definitions here + if (version.api < 16 and embedded_clusters_16[cluster_id] ~= nil) + or (version.api < 20 and embedded_clusters_20[cluster_id] ~= nil) then + local embedded_cluster = embedded_clusters_16[cluster_id] or embedded_clusters_20[cluster_id] + local opts = opts or {} + if utils.table_size(opts) > 1 then + device.log.warn_with({hub_logs = true}, "Invalid options for get_endpoints") + return + end + local clus_has_features = function(clus, feature_bitmap) + if not feature_bitmap or not clus then return false end + return embedded_cluster.are_features_supported(feature_bitmap, clus.feature_map) + end + local eps = {} + for _, ep in ipairs(device.endpoints) do + for _, clus in ipairs(ep.clusters) do + if ((clus.cluster_id == cluster_id) + and (opts.feature_bitmap == nil or clus_has_features(clus, opts.feature_bitmap)) + and ((opts.cluster_type == nil and clus.cluster_type == "SERVER" or clus.cluster_type == "BOTH") + or (opts.cluster_type == clus.cluster_type)) + or (cluster_id == nil)) then + table.insert(eps, ep.endpoint_id) + if cluster_id == nil then break end + end + end + end + return eps + else + return device:get_endpoints(cluster_id, opts) + end + end + + return embedded_cluster_utils \ No newline at end of file diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua index df6dc9015c..8597d029f5 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/closure_utils/utils.lua @@ -13,12 +13,13 @@ if version.api < 20 then end local fields = require "sub_drivers.closure.closure_utils.fields" +local embedded_cluster_utils = require "sub_drivers.closure.closure_utils.embedded_cluster_utils" local utils = {} function utils.find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT - local eps = device:get_endpoints(cluster) + local eps = embedded_cluster_utils.get_endpoints(device, cluster) table.sort(eps) for _, v in ipairs(eps) do if v ~= 0 then -- 0 is the Matter RootNode endpoint @@ -33,7 +34,7 @@ function utils.find_default_endpoint(device, cluster) end function utils.get_closure_dimension_eps(device) - local eps = device:get_endpoints(clusters.ClosureDimension.ID) or {} + local eps = embedded_cluster_utils.get_endpoints(device, clusters.ClosureDimension.ID) or {} table.sort(eps) local result = {} for _, ep in ipairs(eps) do diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua index 638f561415..397167d3df 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/closure/init.lua @@ -5,12 +5,19 @@ local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local log = require "log" local version = require "version" +local embedded_cluster_utils = require "sub_drivers.closure.closure_utils.embedded_cluster_utils" if version.api < 20 then clusters.ClosureControl = require "embedded_clusters.ClosureControl" clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" + clusters.Global = require "embedded_clusters.Global" end +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + + local fields = require "sub_drivers.closure.closure_utils.fields" local closure_utils = require "sub_drivers.closure.closure_utils.utils" local attribute_handlers = require "sub_drivers.closure.closure_handlers.attribute_handlers" @@ -55,7 +62,7 @@ function ClosureLifecycleHandlers.device_added(driver, device) end function ClosureLifecycleHandlers.do_configure(driver, device) - if #device:get_endpoints(clusters.Descriptor.ID) == 0 then + if #embedded_cluster_utils.get_endpoints(device, clusters.Descriptor.ID) == 0 then log.warn( "Descriptor cluster not implemented on ClosureControl endpoint, " .. "cannot read TagList to determine closure type" diff --git a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua index 56407c6667..5db95ba28c 100644 --- a/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/sub_drivers/matter-window-covering-position-updates-while-moving/init.lua @@ -119,7 +119,7 @@ local matter_window_covering_position_updates_while_moving_handler = { }, capability_handlers = { }, - can_handle = require("matter-window-covering-position-updates-while-moving.can_handle"), + can_handle = require("sub_drivers.matter-window-covering-position-updates-while-moving.can_handle"), shared_device_thread_enabled = true, } diff --git a/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua b/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua index 95a4c6f112..e39cb428c8 100644 --- a/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua +++ b/drivers/SmartThings/matter-window-covering/src/test/test_matter_closure.lua @@ -11,6 +11,11 @@ local version = require "version" if version.api < 20 then clusters.ClosureControl = require "embedded_clusters.ClosureControl" clusters.ClosureDimension = require "embedded_clusters.ClosureDimension" + clusters.Global = require "embedded_clusters.Global" +end + +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" end -- --------------------------------------------------------------------------- diff --git a/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua b/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua index 9f273037f3..cf2b9fd681 100644 --- a/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua +++ b/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua @@ -8,6 +8,11 @@ local t_utils = require "integration_test.utils" local uint32 = require "st.matter.data_types.Uint32" local clusters = require "st.matter.clusters" +local version = require "version" +if version.api < 16 then + clusters.Descriptor = require "embedded_clusters.Descriptor" +end + local WindowCovering = clusters.WindowCovering test.disable_startup_messages()