diff --git a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua index a4c58a3646..5452b287ac 100644 --- a/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua +++ b/drivers/SmartThings/matter-switch/src/embedded_clusters/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua @@ -42,6 +42,20 @@ EnergyMeasurementStruct.field_defs = { is_optional = true, data_type = require "st.matter.data_types.Uint64", }, + { + name = "apparent_energy", + field_id = 5, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Int64", + }, + { + name = "reactive_energy", + field_id = 6, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Int64", + }, } EnergyMeasurementStruct.init = function(cls, tbl) @@ -49,10 +63,10 @@ EnergyMeasurementStruct.init = function(cls, tbl) o.elements = {} o.num_elements = 0 setmetatable(o, new_mt) - for idx, field_def in ipairs(cls.field_defs) do + for _idx, field_def in ipairs(cls.field_defs) do 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) - else + 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 @@ -71,7 +85,7 @@ new_mt.__index.serialize = EnergyMeasurementStruct.serialize EnergyMeasurementStruct.augment_type = function(self, val) local elems = {} local num_elements = 0 - for _, v in pairs(val.elements) do + for _, v in pairs(val.elements or {}) do for _, field_def in ipairs(self.field_defs) do if field_def.field_id == v.field_id and field_def.is_nullable and @@ -79,11 +93,11 @@ EnergyMeasurementStruct.augment_type = function(self, val) 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) then + (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 + for i, e in ipairs(elems[field_def.name].elements or {}) do elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) end end diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 9d4e22fb15..adebbb0ec1 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -29,6 +29,11 @@ if version.api < 16 then clusters.Descriptor = require "embedded_clusters.Descriptor" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local SwitchLifecycleHandlers = {} function SwitchLifecycleHandlers.device_added(driver, device) diff --git a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua index b7feed6344..753c76992e 100644 --- a/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua @@ -18,6 +18,11 @@ if version.api < 11 then clusters.PowerTopology = require "embedded_clusters.PowerTopology" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local AttributeHandlers = {} -- [[ ON OFF CLUSTER ATTRIBUTES ]] -- diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 9c5235d1e2..130fd1c1e4 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -15,6 +15,11 @@ if version.api < 11 then clusters.ValveConfigurationAndControl = require "embedded_clusters.ValveConfigurationAndControl" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local DeviceConfiguration = {} local ChildConfiguration = {} local SwitchDeviceConfiguration = {} @@ -87,7 +92,9 @@ function SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, server_on local generic_profile = fields.device_type_profile_map[primary_dt_id] local static_electrical_tags = switch_utils.get_field_for_endpoint(device, fields.ELECTRICAL_TAGS, server_onoff_ep_id) - if static_electrical_tags ~= nil then + if type(static_electrical_tags) == "string" then + -- if no associated profile is found for the device type and static electrical tags are available, use "plug-binary" as a fallback + generic_profile = generic_profile or "plug-binary" -- profiles like 'light-binary' and 'plug-binary' should drop the '-binary' and become 'light-power', 'plug-energy-powerConsumption', etc. generic_profile = string.gsub(generic_profile, "-binary", "") .. static_electrical_tags end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index d2457bfb4d..1d12f41781 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -21,6 +21,11 @@ if version.api < 16 then clusters.Descriptor = require "embedded_clusters.Descriptor" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local utils = {} function utils.tbl_contains(array, value) @@ -545,16 +550,14 @@ function utils.subscribe(device) devices_seen[checked_device.id] = true -- only loop through any device once end end - -- The refresh capability command handler in the lua libs uses this key to determine which attributes to read. Note - -- that only attributes_seen needs to be saved here, and not events_seen, since the refresh handler only checks - -- attributes and not events. - device:set_field(fields.SUBSCRIBED_ATTRIBUTES_KEY, attributes_seen) -- If the type of battery support has not yet been determined, add the PowerSource AttributeList to the list of -- subscribed attributes in order to determine which if any battery capability should be used. if device:get_field(fields.profiling_data.BATTERY_SUPPORT) == nil then local ib = im.InteractionInfoBlock(nil, clusters.PowerSource.ID, clusters.PowerSource.attributes.AttributeList.ID) subscribe_request:with_info_block(ib) + attributes_seen[clusters.PowerSource.ID] = attributes_seen[clusters.PowerSource.ID] or {} + attributes_seen[clusters.PowerSource.ID][clusters.PowerSource.attributes.AttributeList.ID] = ib end -- If the power topology of the device has not yet been determined, add the AvailableEndpoints (for SET topology) @@ -566,12 +569,21 @@ function utils.subscribe(device) if clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.SET_TOPOLOGY, endpoint_power_topology_cluster.feature_map or 0) then local ib = im.InteractionInfoBlock(nil, clusters.PowerTopology.ID, clusters.PowerTopology.attributes.AvailableEndpoints.ID) subscribe_request:with_info_block(ib) + attributes_seen[clusters.PowerTopology.ID] = attributes_seen[clusters.PowerTopology.ID] or {} + attributes_seen[clusters.PowerTopology.ID][clusters.PowerTopology.attributes.AvailableEndpoints.ID] = ib elseif clusters.PowerTopology.are_features_supported(clusters.PowerTopology.types.Feature.TREE_TOPOLOGY, endpoint_power_topology_cluster.feature_map or 0) then local ib = im.InteractionInfoBlock(nil, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID) subscribe_request:with_info_block(ib) + attributes_seen[clusters.Descriptor.ID] = attributes_seen[clusters.Descriptor.ID] or {} + attributes_seen[clusters.Descriptor.ID][clusters.Descriptor.attributes.PartsList.ID] = ib end end + -- The refresh capability command handler in the lua libs uses this key to determine which attributes to read. Note + -- that only attributes_seen needs to be saved here, and not events_seen, since the refresh handler only checks + -- attributes and not events. + device:set_field(fields.SUBSCRIBED_ATTRIBUTES_KEY, attributes_seen) + if #subscribe_request.info_blocks > 0 then device:send(subscribe_request) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua index f763cc769f..468232cbc3 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_light_switch_h2.lua @@ -15,6 +15,11 @@ if version.api < 11 then clusters.PowerTopology = require "embedded_clusters.PowerTopology" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local aqara_parent_ep = 4 local aqara_child1_ep = 1 local aqara_child2_ep = 2 diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua index dae6b09a6c..18c44f7b9c 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_set.lua @@ -15,6 +15,11 @@ if version.api < 11 then clusters.PowerTopology = require "embedded_clusters.PowerTopology" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("plug-level-power-energy-powerConsumption.yml"), manufacturer_info = { diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua index d691f2af27..2abfe6e9c5 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor_tree.lua @@ -19,6 +19,11 @@ if version.api < 16 then clusters.Descriptor = require "embedded_clusters.Descriptor" end +-- Catch nil elements errors gracefully without receiving a coroutine error +if version.api < 21 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct = require "embedded_clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct" +end + local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("plug-level-power-energy-powerConsumption.yml"), manufacturer_info = {