diff --git a/drivers/SmartThings/matter-energy/config.yml b/drivers/SmartThings/matter-energy/config.yml new file mode 100644 index 0000000000..ee64fed4ca --- /dev/null +++ b/drivers/SmartThings/matter-energy/config.yml @@ -0,0 +1,6 @@ +name: 'Matter Energy' +packageKey: 'matter-energy' +permissions: + matter: {} +description: "SmartThings driver for Matter Energy devices" +vendorSupportInformation: "https://support.smartthings.com" diff --git a/drivers/SmartThings/matter-energy/fingerprints.yml b/drivers/SmartThings/matter-energy/fingerprints.yml new file mode 100644 index 0000000000..2e4efb9d24 --- /dev/null +++ b/drivers/SmartThings/matter-energy/fingerprints.yml @@ -0,0 +1,17 @@ +matterGeneric: + - id: "matter/evse" + deviceLabel: Matter EVSE + deviceTypes: + - id: 0x0510 + - id: 0x050C + deviceProfileName: evse + - id: "matter/solar-power" + deviceLabel: Matter Solar Power + deviceTypes: + - id: 0x0017 #Solar Power + deviceProfileName: solar-power + - id: "matter/battery-storage" + deviceLabel: Matter Battery Storage + deviceTypes: + - id: 0x0018 #Battery Storage + deviceProfileName: battery-storage diff --git a/drivers/SmartThings/matter-energy/profiles/battery-storage.yml b/drivers/SmartThings/matter-energy/profiles/battery-storage.yml new file mode 100644 index 0000000000..586c1daf82 --- /dev/null +++ b/drivers/SmartThings/matter-energy/profiles/battery-storage.yml @@ -0,0 +1,30 @@ +name: battery-storage +components: +- id: main + capabilities: + - id: battery + version: 1 + - id: chargingState + version: 1 + - id: powerMeter + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Battery +- id: importedEnergy + label: Imported Energy + capabilities: + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 +- id: exportedEnergy + label: Exported Energy + capabilities: + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 diff --git a/drivers/SmartThings/matter-evse/profiles/evse-energy-meas-energy-mgmt-mode.yml b/drivers/SmartThings/matter-energy/profiles/evse-energy-meas-energy-mgmt-mode.yml similarity index 100% rename from drivers/SmartThings/matter-evse/profiles/evse-energy-meas-energy-mgmt-mode.yml rename to drivers/SmartThings/matter-energy/profiles/evse-energy-meas-energy-mgmt-mode.yml diff --git a/drivers/SmartThings/matter-evse/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml b/drivers/SmartThings/matter-energy/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml similarity index 100% rename from drivers/SmartThings/matter-evse/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml rename to drivers/SmartThings/matter-energy/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml diff --git a/drivers/SmartThings/matter-evse/profiles/evse-energy-meas.yml b/drivers/SmartThings/matter-energy/profiles/evse-energy-meas.yml similarity index 100% rename from drivers/SmartThings/matter-evse/profiles/evse-energy-meas.yml rename to drivers/SmartThings/matter-energy/profiles/evse-energy-meas.yml diff --git a/drivers/SmartThings/matter-evse/profiles/evse-power-meas-energy-mgmt-mode.yml b/drivers/SmartThings/matter-energy/profiles/evse-power-meas-energy-mgmt-mode.yml similarity index 100% rename from drivers/SmartThings/matter-evse/profiles/evse-power-meas-energy-mgmt-mode.yml rename to drivers/SmartThings/matter-energy/profiles/evse-power-meas-energy-mgmt-mode.yml diff --git a/drivers/SmartThings/matter-evse/profiles/evse-power-meas.yml b/drivers/SmartThings/matter-energy/profiles/evse-power-meas.yml similarity index 100% rename from drivers/SmartThings/matter-evse/profiles/evse-power-meas.yml rename to drivers/SmartThings/matter-energy/profiles/evse-power-meas.yml diff --git a/drivers/SmartThings/matter-evse/profiles/evse.yml b/drivers/SmartThings/matter-energy/profiles/evse.yml similarity index 100% rename from drivers/SmartThings/matter-evse/profiles/evse.yml rename to drivers/SmartThings/matter-energy/profiles/evse.yml diff --git a/drivers/SmartThings/matter-energy/profiles/solar-power.yml b/drivers/SmartThings/matter-energy/profiles/solar-power.yml new file mode 100644 index 0000000000..ca5551dd47 --- /dev/null +++ b/drivers/SmartThings/matter-energy/profiles/solar-power.yml @@ -0,0 +1,18 @@ +name: solar-power +components: +- id: main + capabilities: + - id: energyMeter + version: 1 + - id: powerMeter + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SolarPanel +- id: exportedEnergy + capabilities: + - id: powerConsumptionReport + version: 1 diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/init.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/init.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/init.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/init.lua diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AcceptedCommandList.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua index 4c51c12e6a..066732c701 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AcceptedCommandList.lua +++ b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua @@ -2,6 +2,7 @@ local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" + local AcceptedCommandList = { ID = 0xFFF9, NAME = "AcceptedCommandList", @@ -77,4 +78,3 @@ end setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) return AcceptedCommandList - diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/AttributeList.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AttributeList.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/AttributeList.lua diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/CurrentMode.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/CurrentMode.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/CurrentMode.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/CurrentMode.lua diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/SupportedModes.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/SupportedModes.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/SupportedModes.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/SupportedModes.lua diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/init.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/init.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/init.lua index e814310f7e..d00f4242d2 100644 --- a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/init.lua +++ b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/attributes/init.lua @@ -21,4 +21,3 @@ end setmetatable(DeviceEnergyManagementModeServerAttributes, attr_mt) return DeviceEnergyManagementModeServerAttributes - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/ChangeToMode.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/ChangeToMode.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/init.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/commands/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/init.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/commands/init.lua index 0ab178d9aa..c6d2ba9c06 100644 --- a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/init.lua +++ b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/server/commands/init.lua @@ -20,4 +20,3 @@ end setmetatable(DeviceEnergyManagementModeServerCommands, command_mt) return DeviceEnergyManagementModeServerCommands - diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/Feature.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/Feature.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/Feature.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/Feature.lua diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeOptionStruct.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/ModeOptionStruct.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeOptionStruct.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/ModeOptionStruct.lua diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTag.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/ModeTag.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTag.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/ModeTag.lua diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTagStruct.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/ModeTagStruct.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTagStruct.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/ModeTagStruct.lua diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/init.lua b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/init.lua rename to drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/init.lua index 5f2578800c..5962f739be 100644 --- a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/init.lua +++ b/drivers/SmartThings/matter-energy/src/DeviceEnergyManagementMode/types/init.lua @@ -14,4 +14,3 @@ local DeviceEnergyManagementModeTypes = {} setmetatable(DeviceEnergyManagementModeTypes, types_mt) return DeviceEnergyManagementModeTypes - diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/init.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/init.lua similarity index 82% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/init.lua rename to drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/init.lua index e50259f69c..991e2e10de 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/init.lua +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/init.lua @@ -1,28 +1,25 @@ local cluster_base = require "st.matter.cluster_base" local ElectricalEnergyMeasurementServerAttributes = require "ElectricalEnergyMeasurement.server.attributes" local ElectricalEnergyMeasurementTypes = require "ElectricalEnergyMeasurement.types" - local ElectricalEnergyMeasurement = {} ElectricalEnergyMeasurement.ID = 0x0091 ElectricalEnergyMeasurement.NAME = "ElectricalEnergyMeasurement" ElectricalEnergyMeasurement.server = {} +ElectricalEnergyMeasurement.client = {} ElectricalEnergyMeasurement.server.attributes = ElectricalEnergyMeasurementServerAttributes:set_parent_cluster(ElectricalEnergyMeasurement) ElectricalEnergyMeasurement.types = ElectricalEnergyMeasurementTypes -ElectricalEnergyMeasurement.FeatureMap = ElectricalEnergyMeasurement.types.Feature - -function ElectricalEnergyMeasurement.are_features_supported(feature, feature_map) - if (ElectricalEnergyMeasurement.FeatureMap.bits_are_valid(feature)) then - return (feature & feature_map) == feature - end - return false -end function ElectricalEnergyMeasurement:get_attribute_by_id(attr_id) local attr_id_map = { + [0x0000] = "Accuracy", + [0x0001] = "CumulativeEnergyImported", [0x0002] = "CumulativeEnergyExported", + [0x0003] = "PeriodicEnergyImported", [0x0004] = "PeriodicEnergyExported", + [0x0005] = "CumulativeEnergyReset", [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", [0xFFFB] = "AttributeList", } local attr_name = attr_id_map[attr_id] @@ -32,18 +29,27 @@ function ElectricalEnergyMeasurement:get_attribute_by_id(attr_id) return nil end --- Attribute Mapping ElectricalEnergyMeasurement.attribute_direction_map = { + ["Accuracy"] = "server", ["CumulativeEnergyImported"] = "server", + ["CumulativeEnergyExported"] = "server", ["PeriodicEnergyImported"] = "server", + ["PeriodicEnergyExported"] = "server", + ["CumulativeEnergyReset"] = "server", ["AcceptedCommandList"] = "server", + ["EventList"] = "server", ["AttributeList"] = "server", } --- Command Mapping -ElectricalEnergyMeasurement.command_direction_map = {} +ElectricalEnergyMeasurement.FeatureMap = ElectricalEnergyMeasurement.types.Feature + +function ElectricalEnergyMeasurement.are_features_supported(feature, feature_map) + if (ElectricalEnergyMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end --- Cluster Completion local attribute_helper_mt = {} attribute_helper_mt.__index = function(self, key) local direction = ElectricalEnergyMeasurement.attribute_direction_map[key] @@ -57,4 +63,4 @@ setmetatable(ElectricalEnergyMeasurement.attributes, attribute_helper_mt) setmetatable(ElectricalEnergyMeasurement, {__index = cluster_base}) -return ElectricalEnergyMeasurement \ No newline at end of file +return ElectricalEnergyMeasurement diff --git a/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua new file mode 100644 index 0000000000..c6293e369c --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CumulativeEnergyExported = { + ID = 0x0002, + NAME = "CumulativeEnergyExported", + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", +} + +function CumulativeEnergyExported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function CumulativeEnergyExported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CumulativeEnergyExported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CumulativeEnergyExported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CumulativeEnergyExported: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 CumulativeEnergyExported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(CumulativeEnergyExported, {__call = CumulativeEnergyExported.new_value, __index = CumulativeEnergyExported.base_type}) +return CumulativeEnergyExported diff --git a/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua new file mode 100644 index 0000000000..7750255974 --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CumulativeEnergyImported = { + ID = 0x0001, + NAME = "CumulativeEnergyImported", + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", +} + +function CumulativeEnergyImported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function CumulativeEnergyImported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CumulativeEnergyImported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CumulativeEnergyImported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CumulativeEnergyImported: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 CumulativeEnergyImported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(CumulativeEnergyImported, {__call = CumulativeEnergyImported.new_value, __index = CumulativeEnergyImported.base_type}) +return CumulativeEnergyImported diff --git a/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua new file mode 100644 index 0000000000..f2a0f570fa --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local PeriodicEnergyExported = { + ID = 0x0004, + NAME = "PeriodicEnergyExported", + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", +} + +function PeriodicEnergyExported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function PeriodicEnergyExported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PeriodicEnergyExported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PeriodicEnergyExported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function PeriodicEnergyExported: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 PeriodicEnergyExported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(PeriodicEnergyExported, {__call = PeriodicEnergyExported.new_value, __index = PeriodicEnergyExported.base_type}) +return PeriodicEnergyExported diff --git a/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua new file mode 100644 index 0000000000..32c5afe7f2 --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local PeriodicEnergyImported = { + ID = 0x0003, + NAME = "PeriodicEnergyImported", + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", +} + +function PeriodicEnergyImported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function PeriodicEnergyImported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PeriodicEnergyImported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PeriodicEnergyImported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function PeriodicEnergyImported: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 PeriodicEnergyImported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(PeriodicEnergyImported, {__call = PeriodicEnergyImported.new_value, __index = PeriodicEnergyImported.base_type}) +return PeriodicEnergyImported diff --git a/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/init.lua new file mode 100644 index 0000000000..bd08f6841d --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/server/attributes/init.lua @@ -0,0 +1,23 @@ +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("ElectricalEnergyMeasurement.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 ElectricalEnergyMeasurementServerAttributes = {} + +function ElectricalEnergyMeasurementServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ElectricalEnergyMeasurementServerAttributes, attr_mt) + +return ElectricalEnergyMeasurementServerAttributes diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementRangeStruct.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua similarity index 50% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementRangeStruct.lua rename to drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua index d1d7cfd371..576b83d6c2 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementRangeStruct.lua +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua @@ -1,86 +1,53 @@ local data_types = require "st.matter.data_types" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" -local MeasurementTypeEnum = require "ElectricalPowerMeasurement.types.MeasurementTypeEnum" -local MeasurementRangeStruct = {} -local new_mt = StructureABC.new_mt({NAME = "MeasurementRangeStruct", ID = data_types.name_to_id_map["Structure"]}) +local EnergyMeasurementStruct = {} +local new_mt = StructureABC.new_mt({NAME = "EnergyMeasurementStruct", ID = data_types.name_to_id_map["Structure"]}) -MeasurementRangeStruct.field_defs = { +EnergyMeasurementStruct.field_defs = { { - data_type = MeasurementTypeEnum, + name = "energy", field_id = 0, - name = "measurement_type", - }, - { - data_type = data_types.Int64, - field_id = 1, - name = "min", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Int64, - field_id = 2, - name = "max", is_nullable = false, is_optional = false, + data_type = require "st.matter.data_types.Int64", }, { - data_type = data_types.Uint32, - field_id = 3, name = "start_timestamp", + field_id = 1, is_nullable = false, - is_optional = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint32", }, { - data_type = data_types.Uint32, - field_id = 4, name = "end_timestamp", + field_id = 2, is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Uint32, - field_id = 5, - name = "min_timestamp", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Uint32, - field_id = 6, - name = "max_timestamp", - is_nullable = false, - is_optional = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint32", }, { - data_type = data_types.Uint64, - field_id = 7, name = "start_systime", + field_id = 3, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", }, { - data_type = data_types.Uint64, - field_id = 8, name = "end_systime", - }, - { - data_type = data_types.Uint64, - field_id = 9, - name = "min_systime", - }, - { - data_type = data_types.Uint64, - field_id = 10, - name = "max_systime", + field_id = 4, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", }, } -MeasurementRangeStruct.init = function(cls, tbl) +EnergyMeasurementStruct.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 or not field_def.is_nullable) and not tbl[field_def.name] then + 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 o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) @@ -91,36 +58,40 @@ MeasurementRangeStruct.init = function(cls, tbl) return o end -MeasurementRangeStruct.serialize = function(self, buf, include_control, tag) +EnergyMeasurementStruct.serialize = function(self, buf, include_control, tag) return data_types['Structure'].serialize(self.elements, buf, include_control, tag) end -new_mt.__call = MeasurementRangeStruct.init -new_mt.__index.serialize = MeasurementRangeStruct.serialize +new_mt.__call = EnergyMeasurementStruct.init +new_mt.__index.serialize = EnergyMeasurementStruct.serialize -MeasurementRangeStruct.augment_type = function(self, val) +EnergyMeasurementStruct.augment_type = function(self, val) local elems = {} - for _, v in ipairs(val.elements) do + 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) then elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) - if field_def.array_type ~= nil then + 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.array_type) + 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(MeasurementRangeStruct, new_mt) +setmetatable(EnergyMeasurementStruct, new_mt) -return MeasurementRangeStruct \ No newline at end of file +return EnergyMeasurementStruct diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/Feature.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/Feature.lua similarity index 91% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/Feature.lua rename to drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/Feature.lua index f8fa8cb516..ba57387b21 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/Feature.lua +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/Feature.lua @@ -1,6 +1,5 @@ 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) @@ -29,10 +28,10 @@ Feature.set_imported_energy = function(self) self.value = self.IMPORTED_ENERGY end end + Feature.unset_imported_energy = function(self) self.value = self.value & (~self.IMPORTED_ENERGY & self.BASE_MASK) end - Feature.is_exported_energy_set = function(self) return (self.value & self.EXPORTED_ENERGY) ~= 0 end @@ -44,10 +43,10 @@ Feature.set_exported_energy = function(self) self.value = self.EXPORTED_ENERGY end end + Feature.unset_exported_energy = function(self) self.value = self.value & (~self.EXPORTED_ENERGY & self.BASE_MASK) end - Feature.is_cumulative_energy_set = function(self) return (self.value & self.CUMULATIVE_ENERGY) ~= 0 end @@ -63,7 +62,6 @@ end Feature.unset_cumulative_energy = function(self) self.value = self.value & (~self.CUMULATIVE_ENERGY & self.BASE_MASK) end - Feature.is_periodic_energy_set = function(self) return (self.value & self.PERIODIC_ENERGY) ~= 0 end @@ -75,10 +73,24 @@ Feature.set_periodic_energy = function(self) self.value = self.PERIODIC_ENERGY end end + Feature.unset_periodic_energy = function(self) self.value = self.value & (~self.PERIODIC_ENERGY & self.BASE_MASK) end +function Feature.bits_are_valid(feature) + local max = + Feature.IMPORTED_ENERGY | + Feature.EXPORTED_ENERGY | + Feature.CUMULATIVE_ENERGY | + Feature.PERIODIC_ENERGY + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + Feature.mask_methods = { is_imported_energy_set = Feature.is_imported_energy_set, set_imported_energy = Feature.set_imported_energy, diff --git a/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/init.lua b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/init.lua new file mode 100644 index 0000000000..3fe7471234 --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalEnergyMeasurement/types/init.lua @@ -0,0 +1,14 @@ +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("ElectricalEnergyMeasurement.types." .. key) + end + return types_mt.__types_cache[key] +end + +local ElectricalEnergyMeasurementTypes = {} + +setmetatable(ElectricalEnergyMeasurementTypes, types_mt) + +return ElectricalEnergyMeasurementTypes diff --git a/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/init.lua b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/init.lua new file mode 100644 index 0000000000..2b541d111d --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/init.lua @@ -0,0 +1,53 @@ +local cluster_base = require "st.matter.cluster_base" +local ElectricalPowerMeasurementServerAttributes = require "ElectricalPowerMeasurement.server.attributes" +local ElectricalPowerMeasurementTypes = require "ElectricalPowerMeasurement.types" + +local ElectricalPowerMeasurement = {} + +ElectricalPowerMeasurement.ID = 0x0090 +ElectricalPowerMeasurement.NAME = "ElectricalPowerMeasurement" +ElectricalPowerMeasurement.server = {} +ElectricalPowerMeasurement.client = {} +ElectricalPowerMeasurement.server.attributes = ElectricalPowerMeasurementServerAttributes:set_parent_cluster(ElectricalPowerMeasurement) +ElectricalPowerMeasurement.types = ElectricalPowerMeasurementTypes + +function ElectricalPowerMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "PowerMode", + [0x0008] = "ActivePower", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +ElectricalPowerMeasurement.attribute_direction_map = { + ["PowerMode"] = "server", + ["ActivePower"] = "server", +} + +ElectricalPowerMeasurement.FeatureMap = ElectricalPowerMeasurement.types.Feature + +function ElectricalPowerMeasurement.are_features_supported(feature, feature_map) + if (ElectricalPowerMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ElectricalPowerMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ElectricalPowerMeasurement.NAME)) + end + return ElectricalPowerMeasurement[direction].attributes[key] +end +ElectricalPowerMeasurement.attributes = {} +setmetatable(ElectricalPowerMeasurement.attributes, attribute_helper_mt) + +setmetatable(ElectricalPowerMeasurement, {__index = cluster_base}) + +return ElectricalPowerMeasurement diff --git a/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua new file mode 100644 index 0000000000..457f6484af --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ActivePower = { + ID = 0x0008, + NAME = "ActivePower", + base_type = require "st.matter.data_types.Int64", +} + +function ActivePower:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function ActivePower:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function ActivePower:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function ActivePower:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ActivePower: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 ActivePower:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(ActivePower, {__call = ActivePower.new_value, __index = ActivePower.base_type}) +return ActivePower diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua similarity index 68% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua rename to drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua index 4d96c91bd8..294c43d5a1 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua +++ b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua @@ -5,26 +5,9 @@ local TLVParser = require "st.matter.TLV.TLVParser" local PowerMode = { ID = 0x0000, NAME = "PowerMode", - base_type = data_types.Uint8, + base_type = require "ElectricalPowerMeasurement.types.PowerModeEnum", } -PowerMode.UNKNOWN = 0x00 -PowerMode.DC = 0x01 -PowerMode.AC = 0x02 -PowerMode.enum_fields = { - [PowerMode.UNKNOWN] = "UNKNOWN", - [PowerMode.DC] = "DC", - [PowerMode.AC] = "AC", -} - -function PowerMode:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print -end - -function PowerMode.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, PowerMode.enum_fields[value_obj.value]) -end function PowerMode:new_value(...) local o = self.base_type(table.unpack({...})) @@ -81,6 +64,5 @@ function PowerMode:deserialize(tlv_buf) return data end -setmetatable(PowerMode, {__call = PowerMode.new_value}) +setmetatable(PowerMode, {__call = PowerMode.new_value, __index = PowerMode.base_type}) return PowerMode - diff --git a/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/init.lua new file mode 100644 index 0000000000..61dee3f7c5 --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/server/attributes/init.lua @@ -0,0 +1,23 @@ +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("ElectricalPowerMeasurement.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 ElectricalPowerMeasurementServerAttributes = {} + +function ElectricalPowerMeasurementServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ElectricalPowerMeasurementServerAttributes, attr_mt) + +return ElectricalPowerMeasurementServerAttributes diff --git a/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/Feature.lua b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/Feature.lua new file mode 100644 index 0000000000..19ca955acd --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/Feature.lua @@ -0,0 +1,137 @@ +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.DIRECT_CURRENT = 0x0001 +Feature.ALTERNATING_CURRENT = 0x0002 +Feature.POLYPHASE_POWER = 0x0004 +Feature.HARMONICS = 0x0008 +Feature.POWER_QUALITY = 0x0010 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + DIRECT_CURRENT = 0x0001, + ALTERNATING_CURRENT = 0x0002, + POLYPHASE_POWER = 0x0004, + HARMONICS = 0x0008, + POWER_QUALITY = 0x0010, +} + +Feature.is_direct_current_set = function(self) + return (self.value & self.DIRECT_CURRENT) ~= 0 +end + +Feature.set_direct_current = function(self) + if self.value ~= nil then + self.value = self.value | self.DIRECT_CURRENT + else + self.value = self.DIRECT_CURRENT + end +end + +Feature.unset_direct_current = function(self) + self.value = self.value & (~self.DIRECT_CURRENT & self.BASE_MASK) +end +Feature.is_alternating_current_set = function(self) + return (self.value & self.ALTERNATING_CURRENT) ~= 0 +end + +Feature.set_alternating_current = function(self) + if self.value ~= nil then + self.value = self.value | self.ALTERNATING_CURRENT + else + self.value = self.ALTERNATING_CURRENT + end +end + +Feature.unset_alternating_current = function(self) + self.value = self.value & (~self.ALTERNATING_CURRENT & self.BASE_MASK) +end +Feature.is_polyphase_power_set = function(self) + return (self.value & self.POLYPHASE_POWER) ~= 0 +end + +Feature.set_polyphase_power = function(self) + if self.value ~= nil then + self.value = self.value | self.POLYPHASE_POWER + else + self.value = self.POLYPHASE_POWER + end +end + +Feature.unset_polyphase_power = function(self) + self.value = self.value & (~self.POLYPHASE_POWER & self.BASE_MASK) +end +Feature.is_harmonics_set = function(self) + return (self.value & self.HARMONICS) ~= 0 +end + +Feature.set_harmonics = function(self) + if self.value ~= nil then + self.value = self.value | self.HARMONICS + else + self.value = self.HARMONICS + end +end + +Feature.unset_harmonics = function(self) + self.value = self.value & (~self.HARMONICS & self.BASE_MASK) +end +Feature.is_power_quality_set = function(self) + return (self.value & self.POWER_QUALITY) ~= 0 +end + +Feature.set_power_quality = function(self) + if self.value ~= nil then + self.value = self.value | self.POWER_QUALITY + else + self.value = self.POWER_QUALITY + end +end + +Feature.unset_power_quality = function(self) + self.value = self.value & (~self.POWER_QUALITY & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.DIRECT_CURRENT | + Feature.ALTERNATING_CURRENT | + Feature.POLYPHASE_POWER | + Feature.HARMONICS | + Feature.POWER_QUALITY + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_direct_current_set = Feature.is_direct_current_set, + set_direct_current = Feature.set_direct_current, + unset_direct_current = Feature.unset_direct_current, + is_alternating_current_set = Feature.is_alternating_current_set, + set_alternating_current = Feature.set_alternating_current, + unset_alternating_current = Feature.unset_alternating_current, + is_polyphase_power_set = Feature.is_polyphase_power_set, + set_polyphase_power = Feature.set_polyphase_power, + unset_polyphase_power = Feature.unset_polyphase_power, + is_harmonics_set = Feature.is_harmonics_set, + set_harmonics = Feature.set_harmonics, + unset_harmonics = Feature.unset_harmonics, + is_power_quality_set = Feature.is_power_quality_set, + set_power_quality = Feature.set_power_quality, + unset_power_quality = Feature.unset_power_quality, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua similarity index 90% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua rename to drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua index f76370acd7..de87167ead 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua +++ b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua @@ -1,7 +1,6 @@ local data_types = require "st.matter.data_types" local UintABC = require "st.matter.data_types.base_defs.UintABC" - local PowerModeEnum = {} local new_mt = UintABC.new_mt({NAME = "PowerModeEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) new_mt.__index.pretty_print = function(self) @@ -18,6 +17,10 @@ new_mt.__index.UNKNOWN = 0x00 new_mt.__index.DC = 0x01 new_mt.__index.AC = 0x02 +PowerModeEnum.UNKNOWN = 0x00 +PowerModeEnum.DC = 0x01 +PowerModeEnum.AC = 0x02 + PowerModeEnum.augment_type = function(cls, val) setmetatable(val, new_mt) end diff --git a/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/init.lua b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/init.lua new file mode 100644 index 0000000000..1e294dcacd --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/ElectricalPowerMeasurement/types/init.lua @@ -0,0 +1,14 @@ +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("ElectricalPowerMeasurement.types." .. key) + end + return types_mt.__types_cache[key] +end + +local ElectricalPowerMeasurementTypes = {} + +setmetatable(ElectricalPowerMeasurementTypes, types_mt) + +return ElectricalPowerMeasurementTypes diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/init.lua index 85e3ddced2..004626a954 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/init.lua @@ -159,4 +159,4 @@ setmetatable(EnergyEvse.events, event_helper_mt) setmetatable(EnergyEvse, {__index = cluster_base}) -return EnergyEvse \ No newline at end of file +return EnergyEvse diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/AcceptedCommandList.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AcceptedCommandList.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/AcceptedCommandList.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua similarity index 98% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua index 878ebd222b..1be08eb3b2 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua @@ -73,4 +73,4 @@ function ApproximateEVEfficiency:deserialize(tlv_buf) end setmetatable(ApproximateEVEfficiency, {__call = ApproximateEVEfficiency.new_value}) -return ApproximateEVEfficiency \ No newline at end of file +return ApproximateEVEfficiency diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/AttributeList.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AttributeList.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/AttributeList.lua index 0f66af7c79..743ffa4daf 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/AttributeList.lua @@ -78,4 +78,3 @@ end setmetatable(AttributeList, {__call = AttributeList.new_value}) return AttributeList - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/BatteryCapacity.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/BatteryCapacity.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/BatteryCapacity.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/BatteryCapacity.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ChargingEnabledUntil.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/ChargingEnabledUntil.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ChargingEnabledUntil.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/ChargingEnabledUntil.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/CircuitCapacity.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/CircuitCapacity.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/CircuitCapacity.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/CircuitCapacity.lua index 58cc7cc911..31cecdf647 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/CircuitCapacity.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/CircuitCapacity.lua @@ -77,4 +77,3 @@ end setmetatable(CircuitCapacity, {__call = CircuitCapacity.new_value}) return CircuitCapacity - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua similarity index 97% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua index 502dd8b1fb..1d90b0bfd1 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua @@ -61,4 +61,4 @@ function DischargingEnabledUntil:deserialize(tlv_buf) end setmetatable(DischargingEnabledUntil, {__call = DischargingEnabledUntil.new_value}) -return DischargingEnabledUntil \ No newline at end of file +return DischargingEnabledUntil diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/EventList.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/EventList.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/EventList.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/EventList.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/FaultState.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/FaultState.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/FaultState.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/FaultState.lua index c0b4db8f5a..3ce56ca65a 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/FaultState.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/FaultState.lua @@ -111,4 +111,3 @@ end setmetatable(FaultState, {__call = FaultState.new_value}) return FaultState - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua index cbfe635d66..4030d89faa 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua @@ -77,4 +77,3 @@ end setmetatable(MaximumChargeCurrent, {__call = MaximumChargeCurrent.new_value}) return MaximumChargeCurrent - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua index c3866fa9d5..609e9c0372 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua @@ -77,4 +77,3 @@ end setmetatable(MaximumDischargeCurrent, {__call = MaximumDischargeCurrent.new_value}) return MaximumDischargeCurrent - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua index ed9572388e..ba2af6f39d 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua @@ -77,4 +77,3 @@ end setmetatable(MinimumChargeCurrent, {__call = MinimumChargeCurrent.new_value}) return MinimumChargeCurrent - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeRequiredEnergy.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeRequiredEnergy.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeRequiredEnergy.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeRequiredEnergy.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeStartTime.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeStartTime.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeStartTime.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeStartTime.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua similarity index 98% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua index 50cf6db6e0..d25101acfa 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua @@ -61,4 +61,4 @@ function NextChargeTargetSoC:deserialize(tlv_buf) end setmetatable(NextChargeTargetSoC, {__call = NextChargeTargetSoC.new_value}) -return NextChargeTargetSoC \ No newline at end of file +return NextChargeTargetSoC diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua similarity index 97% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua index 6462d42978..93099b9a99 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua @@ -61,4 +61,4 @@ function NextChargeTargetTime:deserialize(tlv_buf) end setmetatable(NextChargeTargetTime, {__call = NextChargeTargetTime.new_value}) -return NextChargeTargetTime \ No newline at end of file +return NextChargeTargetTime diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/RandomizationDelayWindow.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/RandomizationDelayWindow.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/RandomizationDelayWindow.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/RandomizationDelayWindow.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionDuration.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionDuration.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionDuration.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionDuration.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyCharged.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionEnergyCharged.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyCharged.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionEnergyCharged.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyDischarged.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionEnergyDischarged.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyDischarged.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionEnergyDischarged.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionID.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionID.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionID.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionID.lua index f8de5a2a9c..28ab386c08 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionID.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SessionID.lua @@ -62,4 +62,3 @@ end setmetatable(SessionID, {__call = SessionID.new_value}) return SessionID - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/State.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/State.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/State.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/State.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/StateOfCharge.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/StateOfCharge.lua similarity index 98% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/StateOfCharge.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/StateOfCharge.lua index 6ca2b9d789..20734be59a 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/StateOfCharge.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/StateOfCharge.lua @@ -61,4 +61,4 @@ function StateOfCharge:deserialize(tlv_buf) end setmetatable(StateOfCharge, {__call = StateOfCharge.new_value}) -return StateOfCharge \ No newline at end of file +return StateOfCharge diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SupplyState.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SupplyState.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SupplyState.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/SupplyState.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua index 9241457b2d..30da83b49d 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua @@ -90,4 +90,3 @@ end setmetatable(UserMaximumChargeCurrent, {__call = UserMaximumChargeCurrent.new_value}) return UserMaximumChargeCurrent - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/VehicleID.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/VehicleID.lua similarity index 98% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/VehicleID.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/VehicleID.lua index 9e6c7652f0..e1f7ce5d8c 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/VehicleID.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/VehicleID.lua @@ -61,4 +61,4 @@ function VehicleID:deserialize(tlv_buf) end setmetatable(VehicleID, {__call = VehicleID.new_value}) -return VehicleID \ No newline at end of file +return VehicleID diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/init.lua index 859f762e0e..d0a62d54da 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/attributes/init.lua @@ -21,4 +21,3 @@ end setmetatable(EnergyEvseServerAttributes, attr_mt) return EnergyEvseServerAttributes - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/ClearTargets.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/ClearTargets.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/ClearTargets.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/ClearTargets.lua index c6ee9f6fd4..bb80560b06 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/ClearTargets.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/ClearTargets.lua @@ -81,4 +81,4 @@ end setmetatable(ClearTargets, {__call = ClearTargets.init}) -return ClearTargets \ No newline at end of file +return ClearTargets diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/Disable.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/Disable.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/Disable.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/Disable.lua index bbbdc44735..b3b84e6ad2 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/Disable.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/Disable.lua @@ -81,4 +81,4 @@ end setmetatable(Disable, {__call = Disable.init}) -return Disable \ No newline at end of file +return Disable diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableCharging.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/EnableCharging.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableCharging.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/EnableCharging.lua index 328dd34692..e82f8ec2dc 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableCharging.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/EnableCharging.lua @@ -103,4 +103,4 @@ end setmetatable(EnableCharging, {__call = EnableCharging.init}) -return EnableCharging \ No newline at end of file +return EnableCharging diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableDischarging.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/EnableDischarging.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableDischarging.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/EnableDischarging.lua index 74c4f3f30a..01df4e7df1 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableDischarging.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/EnableDischarging.lua @@ -96,4 +96,4 @@ end setmetatable(EnableDischarging, {__call = EnableDischarging.init}) -return EnableDischarging \ No newline at end of file +return EnableDischarging diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/GetTargets.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/GetTargets.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/GetTargets.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/GetTargets.lua index 44c9085f88..f110548447 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/GetTargets.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/GetTargets.lua @@ -69,4 +69,4 @@ end setmetatable(GetTargets, {__call = GetTargets.init}) -return GetTargets \ No newline at end of file +return GetTargets diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/SetTargets.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/SetTargets.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/SetTargets.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/SetTargets.lua index 65895b5053..f3726de104 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/SetTargets.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/SetTargets.lua @@ -90,4 +90,4 @@ end setmetatable(SetTargets, {__call = SetTargets.init}) -return SetTargets \ No newline at end of file +return SetTargets diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/StartDiagnostics.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/StartDiagnostics.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/StartDiagnostics.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/StartDiagnostics.lua index e8c6bd2c5e..6b7623cbb6 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/StartDiagnostics.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/StartDiagnostics.lua @@ -81,4 +81,4 @@ end setmetatable(StartDiagnostics, {__call = StartDiagnostics.init}) -return StartDiagnostics \ No newline at end of file +return StartDiagnostics diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/init.lua index dbe3f22200..34f126c95b 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/commands/init.lua @@ -20,4 +20,3 @@ end setmetatable(EnergyEvseServerCommands, command_mt) return EnergyEvseServerCommands - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVConnected.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EVConnected.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVConnected.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EVConnected.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVNotDetected.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EVNotDetected.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVNotDetected.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EVNotDetected.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStarted.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EnergyTransferStarted.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStarted.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EnergyTransferStarted.lua index dc39ca0ca8..afb67f5a92 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStarted.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EnergyTransferStarted.lua @@ -111,4 +111,4 @@ function EnergyTransferStarted:set_parent_cluster(cluster) return self end -return EnergyTransferStarted \ No newline at end of file +return EnergyTransferStarted diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStopped.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EnergyTransferStopped.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStopped.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EnergyTransferStopped.lua index 884dc90e51..cc4b23c2b2 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStopped.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/EnergyTransferStopped.lua @@ -121,4 +121,3 @@ function EnergyTransferStopped:set_parent_cluster(cluster) end return EnergyTransferStopped - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Fault.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/Fault.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Fault.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/Fault.lua index 86fb42e3f0..76b6918ae3 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Fault.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/Fault.lua @@ -120,4 +120,4 @@ function Fault:set_parent_cluster(cluster) return self end -return Fault \ No newline at end of file +return Fault diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Rfid.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/Rfid.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Rfid.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/Rfid.lua index e5bfb998fd..aa3a85342a 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Rfid.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/Rfid.lua @@ -95,4 +95,3 @@ function Rfid:set_parent_cluster(cluster) end return Rfid - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/init.lua index 12268886f1..aa81eed4b5 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/server/events/init.lua @@ -21,4 +21,3 @@ end setmetatable(EnergyEvseEvents, event_mt) return EnergyEvseEvents - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua similarity index 98% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua index 86a641e1b8..f94d0859b5 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua @@ -68,4 +68,4 @@ end setmetatable(ChargingTargetScheduleStruct, new_mt) -return ChargingTargetScheduleStruct \ No newline at end of file +return ChargingTargetScheduleStruct diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetStruct.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/ChargingTargetStruct.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetStruct.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/ChargingTargetStruct.lua index f9c6f7e8c8..81ecd3e751 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetStruct.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/ChargingTargetStruct.lua @@ -76,4 +76,3 @@ end setmetatable(ChargingTargetStruct, new_mt) return ChargingTargetStruct - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/EnergyTransferStoppedReasonEnum.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/EnergyTransferStoppedReasonEnum.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/EnergyTransferStoppedReasonEnum.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/EnergyTransferStoppedReasonEnum.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/FaultStateEnum.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/FaultStateEnum.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/FaultStateEnum.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/FaultStateEnum.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/Feature.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/Feature.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/Feature.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/Feature.lua index 9e3e10b643..924ea7116c 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/Feature.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/Feature.lua @@ -135,4 +135,3 @@ end setmetatable(Feature, new_mt) return Feature - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/StateEnum.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/StateEnum.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/StateEnum.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/StateEnum.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/SupplyStateEnum.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/SupplyStateEnum.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/SupplyStateEnum.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/SupplyStateEnum.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua index 4a874f721b..4459cf4985 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua @@ -168,4 +168,3 @@ end setmetatable(TargetDayOfWeekBitmap, new_mt) return TargetDayOfWeekBitmap - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvse/types/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvse/types/init.lua index 495c5fa250..325111ebd6 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvse/types/init.lua @@ -14,4 +14,3 @@ local EnergyEvseTypes = {} setmetatable(EnergyEvseTypes, types_mt) return EnergyEvseTypes - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/init.lua index e40b17452a..e643539cb9 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/init.lua @@ -93,4 +93,4 @@ setmetatable(EnergyEvseMode.commands, command_helper_mt) setmetatable(EnergyEvseMode, {__index = cluster_base}) -return EnergyEvseMode \ No newline at end of file +return EnergyEvseMode diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua index ad407e23bf..066732c701 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua @@ -78,4 +78,3 @@ end setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) return AcceptedCommandList - diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/AttributeList.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AttributeList.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/AttributeList.lua index e3a71bab0d..5cdd60da2d 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/AttributeList.lua @@ -20,6 +20,7 @@ end function AttributeList.pretty_print(value_obj) return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AttributeList.enum_fields[value_obj.value]) end + function AttributeList:new_value(...) local o = self.base_type(table.unpack({...})) self:augment_type(o) @@ -77,4 +78,3 @@ end setmetatable(AttributeList, {__call = AttributeList.new_value}) return AttributeList - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/CurrentMode.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/CurrentMode.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/CurrentMode.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/CurrentMode.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/SupportedModes.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/SupportedModes.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/SupportedModes.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/SupportedModes.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/init.lua index 167eda541d..862077d7d6 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/attributes/init.lua @@ -21,4 +21,3 @@ end setmetatable(EnergyEvseModeServerAttributes, attr_mt) return EnergyEvseModeServerAttributes - diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/commands/ChangeToMode.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/commands/ChangeToMode.lua index 488443c91c..7e6ee5f9f5 100644 --- a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/commands/ChangeToMode.lua @@ -76,4 +76,4 @@ end setmetatable(ChangeToMode, {__call = ChangeToMode.init}) -return ChangeToMode \ No newline at end of file +return ChangeToMode diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/commands/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/commands/init.lua index bea9b81a30..2414d88e80 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/server/commands/init.lua @@ -20,4 +20,3 @@ end setmetatable(EnergyEvseModeServerCommands, command_mt) return EnergyEvseModeServerCommands - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/Feature.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/Feature.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/Feature.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/Feature.lua index b542363cf9..6632f45908 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/Feature.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/Feature.lua @@ -53,4 +53,3 @@ end setmetatable(Feature, new_mt) return Feature - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeOptionStruct.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/ModeOptionStruct.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeOptionStruct.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/ModeOptionStruct.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTag.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/ModeTag.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTag.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/ModeTag.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTagStruct.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/ModeTagStruct.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTagStruct.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/ModeTagStruct.lua diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/init.lua b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/init.lua similarity index 99% rename from drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/init.lua rename to drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/init.lua index 4197eaa9a6..a0d575f778 100644 --- a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/init.lua +++ b/drivers/SmartThings/matter-energy/src/EnergyEvseMode/types/init.lua @@ -14,4 +14,3 @@ local EnergyEvseModeTypes = {} setmetatable(EnergyEvseModeTypes, types_mt) return EnergyEvseModeTypes - diff --git a/drivers/SmartThings/matter-evse/src/embedded_cluster_utils.lua b/drivers/SmartThings/matter-energy/src/embedded_cluster_utils.lua similarity index 92% rename from drivers/SmartThings/matter-evse/src/embedded_cluster_utils.lua rename to drivers/SmartThings/matter-energy/src/embedded_cluster_utils.lua index afad08d086..ee274e64a3 100644 --- a/drivers/SmartThings/matter-evse/src/embedded_cluster_utils.lua +++ b/drivers/SmartThings/matter-energy/src/embedded_cluster_utils.lua @@ -9,8 +9,9 @@ if version.api < 11 then clusters.EnergyEvseMode = require "EnergyEvseMode" end ---this cluster is not supported in any releases of the lua libs -clusters.DeviceEnergyManagementMode = require "DeviceEnergyManagementMode" +if version.api < 12 then + clusters.DeviceEnergyManagementMode = require "DeviceEnergyManagementMode" +end local embedded_cluster_utils = {} @@ -56,4 +57,4 @@ function embedded_cluster_utils.get_endpoints(device, cluster_id, opts) end end - return embedded_cluster_utils \ No newline at end of file + return embedded_cluster_utils diff --git a/drivers/SmartThings/matter-evse/src/init.lua b/drivers/SmartThings/matter-energy/src/init.lua similarity index 64% rename from drivers/SmartThings/matter-evse/src/init.lua rename to drivers/SmartThings/matter-energy/src/init.lua index 04b3b6a64f..69c28f8638 100644 --- a/drivers/SmartThings/matter-evse/src/init.lua +++ b/drivers/SmartThings/matter-energy/src/init.lua @@ -29,8 +29,9 @@ if version.api < 11 then clusters.EnergyEvseMode = require "EnergyEvseMode" end ---this cluster is not supported in any releases of the lua libs -clusters.DeviceEnergyManagementMode = require "DeviceEnergyManagementMode" +if version.api < 12 then + clusters.DeviceEnergyManagementMode = require "DeviceEnergyManagementMode" +end local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" local SUPPORTED_EVSE_MODES_MAP = "__supported_evse_modes_map" @@ -42,13 +43,45 @@ local POWER_CONSUMPTION_REPORT_TIME_INTERVAL = "__pcr_time_interval" local DEVICE_REPORTED_TIME_INTERVAL_CONSIDERED = "__timer_interval_considered" -- total in case there are multiple electrical sensors local TOTAL_CUMULATIVE_ENERGY_IMPORTED = "__total_cumulative_energy_imported" +local TOTAL_CUMULATIVE_ENERGY_EXPORTED = "__total_cumulative_energy_exported" +local TOTAL_ACTIVE_POWER = "__total_active_power" local TIMER_REPEAT = (1 * 60) -- 1 minute local REPORT_TIMEOUT = (15 * 60) -- Report the value each 15 minutes +local MAX_REPORT_TIMEOUT = (30 * 60) local MAX_CHARGING_CURRENT_CONSTRAINT = 80000 -- In v1.3 release of stack, this check for 80 A is performed. -local ELECTRICAL_SENSOR_DEVICE_ID = 0x0510 -local DEVICE_ENERGY_MANAGEMENT_DEVICE_ID = 0x050D +local EVSE_DEVICE_TYPE_ID = 0x050C +local SOLAR_POWER_DEVICE_TYPE_ID = 0x0017 +local BATTERY_STORAGE_DEVICE_TYPE_ID = 0x0018 +local ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510 +local DEVICE_ENERGY_MANAGEMENT_DEVICE_TYPE_ID = 0x050D + + +local function get_endpoints_for_dt(device, device_type) + local endpoints = {} + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == device_type then + table.insert(endpoints, ep.endpoint_id) + break + end + end + end + table.sort(endpoints) + return endpoints +end + +local find_default_endpoint = function(device) + local evse_eps = get_endpoints_for_dt(device, EVSE_DEVICE_TYPE_ID) or {} + local solar_power_eps = get_endpoints_for_dt(device, SOLAR_POWER_DEVICE_TYPE_ID) or {} + if #evse_eps > 0 then + return evse_eps[1] + elseif #solar_power_eps > 0 then + return solar_power_eps[1] + end + return device.MATTER_DEFAULT_ENDPOINT +end local function endpoint_to_component(device, ep) local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} @@ -65,24 +98,10 @@ local function component_to_endpoint(device, component) if map[component] then return map[component] else - return device.MATTER_DEFAULT_ENDPOINT + return find_default_endpoint(device) end end -local function get_endpoints_for_dt(device, device_type) - local endpoints = {} - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == device_type then - table.insert(endpoints, ep.endpoint_id) - break - end - end - end - table.sort(endpoints) - return endpoints -end - local function time_zone_offset() return os.difftime(os.time(), os.time(os.date("!*t", os.time()))) end @@ -115,6 +134,20 @@ local function tbl_contains(array, value) return false end +local get_total = function(map) + if type(map) == "table" then + local total_value = 0 + for _, value in pairs(map) do + if type(value) == "number" then + total_value = total_value + value + end + end + return total_value + else + log.debug("get_total: 'map' should be of type table") + end +end + -- MAPS -- local EVSE_STATE_ENUM_MAP = { -- Since PLUGGED_IN_DISCHARGING is not to be supported, it is not checked. @@ -154,13 +187,34 @@ local EVSE_FAULT_STATE_ENUM_MAP = { [clusters.EnergyEvse.types.FaultStateEnum.OTHER] = capabilities.evseState.faultState.other, } -local function read_cumulative_energy_imported(device) - local electrical_energy_meas_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) - if electrical_energy_meas_eps and #electrical_energy_meas_eps > 0 then - local read_req = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(device, electrical_energy_meas_eps[1]) - for i, ep in ipairs(electrical_energy_meas_eps) do +local BATTERY_CHARGING_STATE_MAP = { + [clusters.PowerSource.types.BatChargeStateEnum.IS_CHARGING] = capabilities.chargingState.chargingState.charging, + [clusters.PowerSource.types.BatChargeStateEnum.IS_NOT_CHARGING] = capabilities.chargingState.chargingState.stopped, + [clusters.PowerSource.types.BatChargeStateEnum.IS_AT_FULL_CHARGE] = capabilities.chargingState.chargingState.fullyCharged, +} + +-- Matter Handlers +local function read_cumulative_energy(device) + local cumul_imp_eps = embedded_cluster_utils.get_endpoints( + device, clusters.ElectricalEnergyMeasurement.ID, + { feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.IMPORTED_ENERGY } + ) + if cumul_imp_eps and #cumul_imp_eps > 0 then + local read_req = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(device) + device:send(read_req) + end + + -- read energy exported only in case of Solar Power / Battery Storage device. + local solar_power_eps = get_endpoints_for_dt(device, SOLAR_POWER_DEVICE_TYPE_ID) or {} + local battery_storage_eps = get_endpoints_for_dt(device, BATTERY_STORAGE_DEVICE_TYPE_ID) or {} + local eps_to_read = {} + utils.merge(eps_to_read, battery_storage_eps) + utils.merge(eps_to_read, solar_power_eps) + if #eps_to_read > 0 then + local read_req = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(device, eps_to_read[1]) + for i, ep in ipairs(eps_to_read) do if i > 1 then - read_req:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(device, ep)) + read_req:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(device, ep)) end end device:send(read_req) @@ -173,14 +227,40 @@ local function create_poll_schedule(device) return end - -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy + local cumul_eps = embedded_cluster_utils.get_endpoints(device, + clusters.ElectricalEnergyMeasurement.ID, + { feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY }) or {} + if #cumul_eps == 0 then + return + end + read_cumulative_energy(device) + -- Read cumulative energy imported/exported attributes every minute local timer = device.thread:call_on_schedule(TIMER_REPEAT, function() - read_cumulative_energy_imported(device) + read_cumulative_energy(device) end, "polling_schedule_timer") device:set_field(RECURRING_POLL_TIMER, timer) end +local report_energy_to_app = function(device, comp, energy_map, startTime, endTime) + local component = device.profile.components[comp] + local total_cumulative_energy = get_total(energy_map) or 0 + + -- Calculate the energy consumed between the start and the end time + local previousTotalConsumptionWh = device:get_latest_state( + comp, capabilities.powerConsumptionReport.ID, capabilities.powerConsumptionReport.powerConsumption.NAME + ) or { energy = 0 } + local deltaEnergyWh = math.max(total_cumulative_energy - previousTotalConsumptionWh.energy, 0.0) + + -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' + device:emit_component_event(component, capabilities.powerConsumptionReport.powerConsumption({ + start = startTime, + ["end"] = endTime, + deltaEnergy = deltaEnergyWh, + energy = total_cumulative_energy + })) +end + local function create_poll_report_schedule(device) local polling_schedule_timer = device:get_field(RECURRING_REPORT_POLL_TIMER) if polling_schedule_timer ~= nil then @@ -189,41 +269,35 @@ local function create_poll_report_schedule(device) -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy local pcr_interval = device:get_field(POWER_CONSUMPTION_REPORT_TIME_INTERVAL) or REPORT_TIMEOUT + local timer = device.thread:call_on_schedule(pcr_interval, function() local current_time = os.time() local last_time = device:get_field(LAST_REPORTED_TIME) or 0 - local total_cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED) or {} - local total_energy = 0 - - -- we sum up total cumulative energy across all electrical sensor endpoints - for _, energyWh in pairs(total_cumulative_energy_imported) do - total_energy = total_energy + energyWh - end - + local cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED) + local cumulative_energy_exported = device:get_field(TOTAL_CUMULATIVE_ENERGY_EXPORTED) device:set_field(LAST_REPORTED_TIME, current_time, { persist = true }) - - -- Calculate the energy consumed between the start and the end time - local previousTotalConsumptionWh = device:get_latest_state("main", capabilities.powerConsumptionReport - .ID, - capabilities.powerConsumptionReport.powerConsumption.NAME) or { energy = 0 } - - local deltaEnergyWh = math.max(total_energy - previousTotalConsumptionWh.energy, 0.0) local startTime = epoch_to_iso8601(last_time) local endTime = epoch_to_iso8601(current_time - 1) - -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' - device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ - start = startTime, - ["end"] = endTime, - deltaEnergy = deltaEnergyWh, - energy = total_energy - })) + if cumulative_energy_imported ~= nil then + local evse_eps = get_endpoints_for_dt(device, EVSE_DEVICE_TYPE_ID) or {} + local comp_id = "importedEnergy" + if #evse_eps > 0 then + comp_id = "main" + end + report_energy_to_app(device, comp_id, cumulative_energy_imported, startTime, endTime) + end + + -- If energy exported is set, it must be for Solar Power / Battery Storage Device. + if cumulative_energy_exported ~= nil then + report_energy_to_app(device, "exportedEnergy", cumulative_energy_exported, startTime, endTime) + end end, "polling_report_schedule_timer") device:set_field(RECURRING_REPORT_POLL_TIMER, timer) end -local function create_poll_schedules_for_cumulative_energy_imported(device) +local function create_poll_schedules_for_cumulative_energy_reports(device) if not device:supports_capability(capabilities.powerConsumptionReport) then return end @@ -231,17 +305,21 @@ local function create_poll_schedules_for_cumulative_energy_imported(device) create_poll_report_schedule(device) end +local function delete_reporting_timer(device) + local reporting_poll_timer = device:get_field(RECURRING_REPORT_POLL_TIMER) + if reporting_poll_timer ~= nil then + device.thread:cancel_timer(reporting_poll_timer) + device:set_field(RECURRING_REPORT_POLL_TIMER, nil) + end +end + local function delete_poll_schedules(device) local poll_timer = device:get_field(RECURRING_POLL_TIMER) - local reporting_poll_timer = device:get_field(RECURRING_REPORT_POLL_TIMER) if poll_timer ~= nil then device.thread:cancel_timer(poll_timer) device:set_field(RECURRING_POLL_TIMER, nil) end - if reporting_poll_timer ~= nil then - device.thread:cancel_timer(reporting_poll_timer) - device:set_field(RECURRING_REPORT_POLL_TIMER, nil) - end + delete_reporting_timer(device) end -- Lifecycle Handlers -- @@ -249,7 +327,7 @@ local function device_init(driver, device) device:subscribe() device:set_endpoint_to_component_fn(endpoint_to_component) device:set_component_to_endpoint_fn(component_to_endpoint) - create_poll_schedules_for_cumulative_energy_imported(device) + create_poll_schedules_for_cumulative_energy_reports(device) local current_time = os.time() local current_time_iso8601 = epoch_to_iso8601(current_time) -- emit current time by default @@ -257,37 +335,41 @@ local function device_init(driver, device) end local function device_added(driver, device) - local electrical_sensor_eps = get_endpoints_for_dt(device, ELECTRICAL_SENSOR_DEVICE_ID) or {} - local device_energy_mgmt_eps = get_endpoints_for_dt(device, DEVICE_ENERGY_MANAGEMENT_DEVICE_ID) or {} - local component_to_endpoint_map = { - ["main"] = device.MATTER_DEFAULT_ENDPOINT, - ["electricalSensor"] = electrical_sensor_eps[1], - ["deviceEnergyManagement"] = device_energy_mgmt_eps[1] - } - log.debug("component_to_endpoint_map " .. utils.stringify_table(component_to_endpoint_map)) - device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_to_endpoint_map, { persist = true }) + local evse_eps = get_endpoints_for_dt(device, EVSE_DEVICE_TYPE_ID) or {} + if #evse_eps > 0 then + local electrical_sensor_eps = get_endpoints_for_dt(device, ELECTRICAL_SENSOR_DEVICE_TYPE_ID) or {} + local device_energy_mgmt_eps = get_endpoints_for_dt(device, DEVICE_ENERGY_MANAGEMENT_DEVICE_TYPE_ID) or {} + local component_to_endpoint_map = { + ["electricalSensor"] = electrical_sensor_eps[1], + ["deviceEnergyManagement"] = device_energy_mgmt_eps[1] + } + device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_to_endpoint_map, { persist = true }) + end end local function do_configure(driver, device) - local power_meas_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID) or {} - local energy_meas_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) or {} - local device_energy_mgmt_eps = embedded_cluster_utils.get_endpoints(device, clusters.DeviceEnergyManagementMode) or {} - local profile_name = "evse" + local evse_eps = get_endpoints_for_dt(device, EVSE_DEVICE_TYPE_ID) or {} + if #evse_eps > 0 then + local power_meas_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID) or {} + local energy_meas_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) or {} + local device_energy_mgmt_eps = embedded_cluster_utils.get_endpoints(device, clusters.DeviceEnergyManagementMode) or {} + local profile_name = "evse" + + -- As per spec, at least one of the electrical energy measurement or electrical power measurement clusters are to be supported. + if #energy_meas_eps > 0 then + profile_name = profile_name .. "-energy-meas" + end + if #power_meas_eps > 0 then + profile_name = profile_name .. "-power-meas" + end - -- As per spec, at least one of the electrical energy measurement or electrical power measurement clusters are to be supported. - if #energy_meas_eps > 0 then - profile_name = profile_name .. "-energy-meas" - end - if #power_meas_eps > 0 then - profile_name = profile_name .. "-power-meas" - end + if #device_energy_mgmt_eps > 0 then + profile_name = profile_name .. "-energy-mgmt-mode" + end - if #device_energy_mgmt_eps > 0 then - profile_name = profile_name .. "-energy-mgmt-mode" + device.log.info_with({ hub_logs = true }, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({ profile = profile_name }) end - - device.log.info_with({ hub_logs = true }, string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({ profile = profile_name }) end local function info_changed(driver, device) @@ -299,7 +381,7 @@ local function info_changed(driver, device) end end device:subscribe() - create_poll_schedules_for_cumulative_energy_imported(device) + create_poll_schedules_for_cumulative_energy_reports(device) end local function device_removed(driver, device) @@ -425,7 +507,9 @@ local function energy_evse_supported_modes_attr_handler(driver, device, ib, resp local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP) or {} local supportedEvseModes = {} for _, mode in ipairs(ib.data.elements) do - clusters.EnergyEvseMode.types.ModeOptionStruct:augment_type(mode) + if version.api < 11 then + clusters.EnergyEvseMode.types.ModeOptionStruct:augment_type(mode) + end table.insert(supportedEvseModes, mode.elements.label.value) end supportedEvseModesMap[ib.endpoint_id] = supportedEvseModes @@ -454,7 +538,9 @@ local function device_energy_mgmt_supported_modes_attr_handler(driver, device, i local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP) or {} local supportedDeviceEnergyMgmtModes = {} for _, mode in ipairs(ib.data.elements) do - clusters.EnergyEvseMode.types.ModeOptionStruct:augment_type(mode) + if version.api < 12 then + clusters.DeviceEnergyManagementMode.types.ModeOptionStruct:augment_type(mode) + end table.insert(supportedDeviceEnergyMgmtModes, mode.elements.label.value) end supportedDeviceEnergyMgmtModesMap[ib.endpoint_id] = supportedDeviceEnergyMgmtModes @@ -479,61 +565,120 @@ local function device_energy_mgmt_mode_attr_handler(driver, device, ib, response end end -local function cumulative_energy_imported_handler(driver, device, ib, response) - if ib.data then - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct:augment_type(ib.data) - local cumulative_energy_imported = ib.data.elements.energy.value - local endpoint_id = string.format(ib.endpoint_id) - local cumulative_energy_imported_Wh = utils.round(cumulative_energy_imported / 1000) - local total_cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED) or {} +local function report_energy_meter(device, energy_map_id) + --report energyMeter for Solar Power and Battery Storage devices only. + local battery_storage_eps = get_endpoints_for_dt(device, BATTERY_STORAGE_DEVICE_TYPE_ID) or {} + local solar_power_eps = get_endpoints_for_dt(device, SOLAR_POWER_DEVICE_TYPE_ID) or {} + local energy_map = device:get_field(energy_map_id) or {} + local total_energy = get_total(energy_map) or 0 + + if #battery_storage_eps > 0 then + local component = device.profile.components["importedEnergy"] + if energy_map_id == TOTAL_CUMULATIVE_ENERGY_EXPORTED then + component = device.profile.components["exportedEnergy"] + end + device:emit_component_event(component, capabilities.energyMeter.energy({value = total_energy, unit = "Wh"})) + return + end + -- energyMeter in Solar Power devices must report exported energy only. + if #solar_power_eps > 0 and energy_map_id == TOTAL_CUMULATIVE_ENERGY_EXPORTED then + device:emit_event(capabilities.energyMeter.energy({value = total_energy, unit = "Wh"})) + end +end - -- in case there are multiple electrical sensors store them in a table. - total_cumulative_energy_imported[endpoint_id] = cumulative_energy_imported_Wh - device:set_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED, total_cumulative_energy_imported, { persist = true }) +local function cumulative_energy_handler(energy_map_id) + return function(driver, device, ib, response) + if ib.data then + if version.api < 11 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct:augment_type(ib.data) + end + local endpoint_id = string.format(ib.endpoint_id) + local cumulative_energy_Wh = utils.round(ib.data.elements.energy.value / 1000) -- convert mWh to Wh + local total_cumulative_energy = device:get_field(energy_map_id) or {} + + -- in case there are multiple electrical sensors store them in a table. + total_cumulative_energy[endpoint_id] = cumulative_energy_Wh + device:set_field(energy_map_id, total_cumulative_energy, { persist = true }) + report_energy_meter(device, energy_map_id) + end end end -local function periodic_energy_imported_handler(driver, device, ib, response) - local endpoint_id = ib.endpoint_id - local cumul_eps = embedded_cluster_utils.get_endpoints(device, - clusters.ElectricalEnergyMeasurement.ID, - {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY }) - if ib.data then - if version.api < 11 then - clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct:augment_type(ib.data) - end +local function periodic_energy_handler(energy_map_id) + return function(driver, device, ib, response) + local endpoint_id = ib.endpoint_id + local cumul_eps = embedded_cluster_utils.get_endpoints(device, + clusters.ElectricalEnergyMeasurement.ID, + { feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY }) + + if ib.data then + if version.api < 11 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct:augment_type(ib.data) + end - local start_timestamp = ib.data.elements.start_timestamp.value or 0 - local end_timestamp = ib.data.elements.end_timestamp.value or 0 + local start_timestamp = ib.data.elements.start_timestamp.value or 0 + local end_timestamp = ib.data.elements.end_timestamp.value or 0 + + local device_reporting_time_interval = end_timestamp - start_timestamp + if not device:get_field(DEVICE_REPORTED_TIME_INTERVAL_CONSIDERED) and device_reporting_time_interval > REPORT_TIMEOUT then + -- This is a one time setup in order to consider a larger time interval if the interval the device chooses to report is greater than 15 minutes. + device_reporting_time_interval = utils.clamp_value(device_reporting_time_interval, REPORT_TIMEOUT, MAX_REPORT_TIMEOUT) + device:set_field(DEVICE_REPORTED_TIME_INTERVAL_CONSIDERED, true, {persist=true}) + device:set_field(POWER_CONSUMPTION_REPORT_TIME_INTERVAL, device_reporting_time_interval, {persist = true}) + delete_reporting_timer(device) + create_poll_report_schedule(device) + end - local device_reporting_time_interval = end_timestamp - start_timestamp - if not device:get_field(DEVICE_REPORTED_TIME_INTERVAL_CONSIDERED) and device_reporting_time_interval > REPORT_TIMEOUT then - -- This is a one time setup in order to consider a larger time interval if the interval the device chooses to report is greater than 15 minutes. - device:set_field(DEVICE_REPORTED_TIME_INTERVAL_CONSIDERED, true, {persist=true}) - local polling_schedule_timer = device:get_field(RECURRING_REPORT_POLL_TIMER) - if polling_schedule_timer ~= nil then - device.thread:cancel_timer(polling_schedule_timer) + if tbl_contains(cumul_eps, endpoint_id) then + -- Since cluster in this endpoint supports both CUME & PERE features, we will prefer + -- cumulative_energy_handler to handle the energy report for this endpoint over periodic_energy_handler. + return end - device:set_field(POWER_CONSUMPTION_REPORT_TIME_INTERVAL, device_reporting_time_interval, {persist = true}) - create_poll_report_schedule(device) + + endpoint_id = string.format(ib.endpoint_id) + local energy_Wh = utils.round(ib.data.elements.energy.value / 1000) -- convert mWh to Wh + local total_cumulative_energy = device:get_field(energy_map_id) or {} + + -- in case there are multiple electrical sensors store them in a table. + total_cumulative_energy[endpoint_id] = total_cumulative_energy[endpoint_id] or 0 + total_cumulative_energy[endpoint_id] = total_cumulative_energy[endpoint_id] + energy_Wh + device:set_field(energy_map_id, total_cumulative_energy, { persist = true }) + report_energy_meter(device, energy_map_id) end + end +end - if tbl_contains(cumul_eps, endpoint_id) then - -- Since cluster in this endpoint supports both CUME & PERE features, we will prefer - -- cumulative_energy_imported_handler to handle the energy report for this endpoint over PeriodicEnergyImported. - return +local function active_power_handler(driver, device, ib, response) + local battery_storage_eps = get_endpoints_for_dt(device, SOLAR_POWER_DEVICE_TYPE_ID) or {} + local solar_power_eps = get_endpoints_for_dt(device, BATTERY_STORAGE_DEVICE_TYPE_ID) or {} + -- Consider only Solar Power / Battery Storage devices and sum up in case there are multiple endpoints. + if (tbl_contains(solar_power_eps, ib.endpoint_id) or tbl_contains(battery_storage_eps, ib.endpoint_id)) and ib.data.value then + local endpoint_id = string.format(ib.endpoint_id) + local active_power_map = device:get_field(TOTAL_ACTIVE_POWER) or {} + local watt_value = ib.data.value / 1000 + + active_power_map[endpoint_id] = watt_value + local total_active_power = get_total(active_power_map) + device:set_field(TOTAL_ACTIVE_POWER, active_power_map) + if total_active_power ~= nil then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.powerMeter.power({ value = total_active_power, unit = "W" })) end + end +end - local energy_imported = ib.data.elements.energy.value - endpoint_id = string.format(ib.endpoint_id) - local energy_imported_Wh = utils.round(energy_imported / 1000) - local total_cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED) or {} +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))) + end +end - -- in case there are multiple electrical sensors store them in a table. - total_cumulative_energy_imported[endpoint_id] = total_cumulative_energy_imported[endpoint_id] or 0 - total_cumulative_energy_imported[endpoint_id] = total_cumulative_energy_imported[endpoint_id] + energy_imported_Wh - device:set_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED, total_cumulative_energy_imported, { persist = true }) +local function battery_charge_state_handler(driver, device, ib, response) + local charging_state = ib.data.value + if BATTERY_CHARGING_STATE_MAP[charging_state] then + device:emit_event(BATTERY_CHARGING_STATE_MAP[charging_state]()) + else + device:emit_event(capabilities.chargingState.chargingState.error()) end end @@ -608,7 +753,7 @@ local function handle_set_mode_command(driver, device, cmd) end matter_driver_template = { - NAME = "matter-evse", + NAME = "matter-energy", lifecycle_handlers = { init = device_init, added = device_added, @@ -632,6 +777,7 @@ matter_driver_template = { }, [clusters.ElectricalPowerMeasurement.ID] = { [clusters.ElectricalPowerMeasurement.attributes.PowerMode.ID] = power_mode_handler, + [clusters.ElectricalPowerMeasurement.attributes.ActivePower.ID] = active_power_handler, }, [clusters.EnergyEvseMode.ID] = { [clusters.EnergyEvseMode.attributes.SupportedModes.ID] = energy_evse_supported_modes_attr_handler, @@ -642,9 +788,15 @@ matter_driver_template = { [clusters.DeviceEnergyManagementMode.attributes.CurrentMode.ID] = device_energy_mgmt_mode_attr_handler, }, [clusters.ElectricalEnergyMeasurement.ID] = { - [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = cumulative_energy_imported_handler, - [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = periodic_energy_imported_handler + [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = cumulative_energy_handler(TOTAL_CUMULATIVE_ENERGY_IMPORTED), + [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = periodic_energy_handler(TOTAL_CUMULATIVE_ENERGY_IMPORTED), + [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported.ID] = cumulative_energy_handler(TOTAL_CUMULATIVE_ENERGY_EXPORTED), + [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported.ID] = periodic_energy_handler(TOTAL_CUMULATIVE_ENERGY_EXPORTED), }, + [clusters.PowerSource.ID] = { + [clusters.PowerSource.attributes.BatPercentRemaining.ID] = battery_percent_remaining_attr_handler, + [clusters.PowerSource.attributes.BatChargeState.ID] = battery_charge_state_handler + } }, }, subscribed_attributes = { @@ -665,12 +817,25 @@ matter_driver_template = { }, [capabilities.powerConsumptionReport.ID] = { clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported, }, [capabilities.mode.ID] = { clusters.EnergyEvseMode.attributes.SupportedModes, clusters.EnergyEvseMode.attributes.CurrentMode, clusters.DeviceEnergyManagementMode.attributes.CurrentMode, clusters.DeviceEnergyManagementMode.attributes.SupportedModes + }, + [capabilities.powerMeter.ID] = { + clusters.ElectricalPowerMeasurement.attributes.ActivePower + }, + [capabilities.energyMeter.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported + }, + [capabilities.battery.ID] = { + clusters.PowerSource.attributes.BatPercentRemaining + }, + [capabilities.chargingState.ID] = { + clusters.PowerSource.attributes.BatChargeState } }, capability_handlers = { @@ -690,10 +855,14 @@ matter_driver_template = { capabilities.evseChargingSession, capabilities.powerSource, capabilities.powerConsumptionReport, - capabilities.mode + capabilities.mode, + capabilities.powerMeter, + capabilities.energyMeter, + capabilities.battery, + capabilities.chargingState }, } -local matter_driver = MatterDriver("matter-evse", matter_driver_template) +local matter_driver = MatterDriver("matter-energy", matter_driver_template) log.info(string.format("Starting %s driver, with dispatcher: %s", matter_driver.NAME, matter_driver.matter_dispatcher)) matter_driver:run() diff --git a/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua b/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua new file mode 100644 index 0000000000..453858d7c2 --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/test/test_battery_storage.lua @@ -0,0 +1,330 @@ +-- 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 clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local version = require "version" + +local BATTERY_STORAGE_EP = 20 + +local BATTERY_STORAGE_DEVICE_TYPE_ID = 0x0018 +local POWER_SOURCE_DEVICE_TYPE_ID = 0x0011 +local ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510 + +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" +end + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("battery-storage.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 = BATTERY_STORAGE_EP, + clusters = { + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 15 }, -- ALL + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", feature_map = 7 }, -- WIRED, BAT & RECHG + }, + device_types = { + { device_type_id = BATTERY_STORAGE_DEVICE_TYPE_ID, device_type_revision = 1 }, + { device_type_id = ELECTRICAL_SENSOR_DEVICE_TYPE_ID, device_type_revision = 1 }, + { device_type_id = POWER_SOURCE_DEVICE_TYPE_ID, device_type_revision = 1 } + } + } + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.ElectricalPowerMeasurement.attributes.ActivePower, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.PowerSource.attributes.BatPercentRemaining, + clusters.PowerSource.attributes.BatChargeState + } + test.socket.matter:__set_channel_ordering("relaxed") + 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) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, BATTERY_STORAGE_EP) + }) + +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Battery percentage must reported properly", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data( + mock_device, BATTERY_STORAGE_EP, 150 + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5)) + ) + ) + end +) +test.register_coroutine_test( + "Battery charge state must reported properly", + function() + test.socket.matter:__set_channel_ordering("strict") + test.socket.capability:__set_channel_ordering("strict") + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.BatChargeState:build_test_report_data( + mock_device, BATTERY_STORAGE_EP, clusters.PowerSource.types.BatChargeStateEnum.IS_CHARGING + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.chargingState.chargingState.charging()) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.BatChargeState:build_test_report_data( + mock_device, BATTERY_STORAGE_EP, clusters.PowerSource.types.BatChargeStateEnum.IS_AT_FULL_CHARGE + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.chargingState.chargingState.fullyCharged()) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.BatChargeState:build_test_report_data( + mock_device, BATTERY_STORAGE_EP, clusters.PowerSource.types.BatChargeStateEnum.IS_NOT_CHARGING + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.chargingState.chargingState.stopped()) + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + clusters.PowerSource.attributes.BatChargeState:build_test_report_data( + mock_device, BATTERY_STORAGE_EP, 10 -- Error scenario or any other state + ) + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.chargingState.chargingState.error()) + ) + end +) + +test.register_coroutine_test( + "Appropriate powerMeter capability events must be sent in 'W' on receiving ActivePower events", + function() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(mock_device, + BATTERY_STORAGE_EP, + 30000) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 30.0, unit = "W" }))) + end +) + +test.register_coroutine_test( + "Appropriate powerMeter capability events must be sent in 'W' on receiving ActivePower events", + function() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(mock_device, + BATTERY_STORAGE_EP, + 30000) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 30.0, unit = "W" }))) + end +) + +test.register_coroutine_test( + "Ensure the total cumulative energy exported powerConsumption for both endpoints is reported every 15 minutes", + function() + test.socket.matter:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, BATTERY_STORAGE_EP) + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyImported:build_test_report_data(mock_device, + BATTERY_STORAGE_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("importedEnergy", + capabilities.energyMeter.energy({ + value = 100, unit = "Wh" + })) + ) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyExported:build_test_report_data(mock_device, + BATTERY_STORAGE_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 300000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --300Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("exportedEnergy", + capabilities.energyMeter.energy({ + value = 300, unit = "Wh" + })) + ) + + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("exportedEnergy", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 300, + deltaEnergy = 300, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:14:59Z" + })) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("importedEnergy", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 100, + deltaEnergy = 100, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:14:59Z" + })) + ) + + test.wait_for_events() + + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, BATTERY_STORAGE_EP) + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyImported:build_test_report_data(mock_device, + BATTERY_STORAGE_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 200000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --200Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("importedEnergy", + capabilities.energyMeter.energy({ + value = 200, unit = "Wh" + }))) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyExported:build_test_report_data(mock_device, + BATTERY_STORAGE_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 400000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --400Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("exportedEnergy", + capabilities.energyMeter.energy({ + value = 400, unit = "Wh" + })) + ) + + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("exportedEnergy", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 400, + deltaEnergy = 100, + start = "1970-01-01T00:15:00Z", + ["end"] = "1970-01-01T00:29:59Z" + })) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("importedEnergy", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 200, + deltaEnergy = 100, + start = "1970-01-01T00:15:00Z", + ["end"] = "1970-01-01T00:29:59Z" + })) + ) + + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-evse/src/test/test_evse.lua b/drivers/SmartThings/matter-energy/src/test/test_evse.lua similarity index 97% rename from drivers/SmartThings/matter-evse/src/test/test_evse.lua rename to drivers/SmartThings/matter-energy/src/test/test_evse.lua index c4c3640917..ad9db31150 100644 --- a/drivers/SmartThings/matter-evse/src/test/test_evse.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_evse.lua @@ -16,7 +16,6 @@ local test = require "integration_test" local clusters = require "st.matter.clusters" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" -local log = require "log" local EVSE_EP = 1 local ELECTRICAL_SENSOR_EP = 2 @@ -91,7 +90,6 @@ local function test_init() clusters.DeviceEnergyManagementMode.attributes.CurrentMode, clusters.DeviceEnergyManagementMode.attributes.SupportedModes, } - log.info("In test init", os.time()) test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) for i, cluster in ipairs(cluster_subscribe_list) do @@ -336,7 +334,7 @@ test.register_message_test( ) test.register_message_test( - "EnergyEvseMode SupportedModes must be registered.\n2.CurrentMode must trigger approriate mode capability event.\n3.Command to setMode should trigger appropriate changeToMode matter command", + "EnergyEvseMode SupportedModes must be registered. CurrentMode must trigger approriate mode capability event. Command to setMode should trigger appropriate changeToMode matter command", { { channel = "matter", @@ -407,7 +405,7 @@ test.register_message_test( ) test.register_message_test( - "DeviceEnergyManagementMode SupportedModes must be registered.\n2.CurrentMode must trigger approriate mode capability event.\n3.Command to setMode should trigger appropriate changeToMode matter command", + "DeviceEnergyManagementMode SupportedModes must be registered. CurrentMode must trigger approriate mode capability event. Command to setMode should trigger appropriate changeToMode matter command", { { channel = "matter", @@ -506,6 +504,4 @@ test.register_message_test( } ) ---TODO: Include tests for evseChargingSession capability commands. This is anyhow tested with EVSE virtual device app. - test.run_registered_tests() diff --git a/drivers/SmartThings/matter-evse/src/test/test_evse_energy_meas.lua b/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua similarity index 89% rename from drivers/SmartThings/matter-evse/src/test/test_evse_energy_meas.lua rename to drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua index b5b51236bd..91332241b7 100644 --- a/drivers/SmartThings/matter-evse/src/test/test_evse_energy_meas.lua +++ b/drivers/SmartThings/matter-energy/src/test/test_evse_energy_meas.lua @@ -17,9 +17,9 @@ local clusters = require "st.matter.clusters" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" -local EVSE_EP = 1 -local ELECTRICAL_SENSOR_EP_ONE = 2 -local ELECTRICAL_SENSOR_EP_TWO = 3 +local EVSE_EP = 10 +local ELECTRICAL_SENSOR_EP_ONE = 20 +local ELECTRICAL_SENSOR_EP_TWO = 30 clusters.EnergyEvse = require "EnergyEvse" clusters.EnergyEvseMode = require "EnergyEvseMode" @@ -56,7 +56,7 @@ local mock_device = test.mock_device.build_test_matter_device({ { endpoint_id = ELECTRICAL_SENSOR_EP_ONE, clusters = { - { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 5 }, --CUME & IMPE }, device_types = { { device_type_id = 0x0510, device_type_revision = 1 } -- Electrical Sensor @@ -65,7 +65,7 @@ local mock_device = test.mock_device.build_test_matter_device({ { endpoint_id = ELECTRICAL_SENSOR_EP_TWO, clusters = { - { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 5 }, --CUME & IMPE }, device_types = { { device_type_id = 0x0510, device_type_revision = 1 } -- Electrical Sensor @@ -87,6 +87,7 @@ local function test_init() clusters.EnergyEvseMode.attributes.SupportedModes, clusters.EnergyEvseMode.attributes.CurrentMode, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported, } test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) @@ -99,6 +100,11 @@ local function test_init() test.mock_device.add_test_device(mock_device) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.matter:__expect_send({ + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.evseChargingSession.targetEndTime("1970-01-01T00:00:00Z"))) end @@ -145,13 +151,9 @@ test.register_coroutine_test( function() test.mock_time.advance_time(60) test.socket.matter:__set_channel_ordering("relaxed") - local CumulativeEnergyImportedReadReq = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported - :read(mock_device, ELECTRICAL_SENSOR_EP_ONE) - CumulativeEnergyImportedReadReq:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read( - mock_device, ELECTRICAL_SENSOR_EP_TWO)) test.socket.matter:__expect_send({ mock_device.id, - CumulativeEnergyImportedReadReq + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) }) test.wait_for_events() end, @@ -169,14 +171,9 @@ test.register_coroutine_test( test.socket.matter:__set_channel_ordering("relaxed") test.socket.capability:__set_channel_ordering("relaxed") - local CumulativeEnergyImportedReadReq = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported - :read(mock_device, ELECTRICAL_SENSOR_EP_ONE) - CumulativeEnergyImportedReadReq:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read( - mock_device, ELECTRICAL_SENSOR_EP_TWO)) - test.socket.matter:__expect_send({ mock_device.id, - CumulativeEnergyImportedReadReq + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) }) test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes diff --git a/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua b/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua new file mode 100644 index 0000000000..049b364d6f --- /dev/null +++ b/drivers/SmartThings/matter-energy/src/test/test_solar_power.lua @@ -0,0 +1,256 @@ +-- 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 clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local version = require "version" + +local SOLAR_POWER_EP_ONE = 20 +local SOLAR_POWER_EP_TWO = 30 + +local SOLAR_POWER_DEVICE_TYPE_ID = 0x0017 +local ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510 + +if version.api < 11 then +clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" +clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" +end + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("solar-power.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 = SOLAR_POWER_EP_ONE, + clusters = { + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER" , feature_map = 6}, --CUME & PERE + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = SOLAR_POWER_DEVICE_TYPE_ID, device_type_revision = 1 }, -- SOLAR POWER + { device_type_id = ELECTRICAL_SENSOR_DEVICE_TYPE_ID, device_type_revision = 1 } -- ELECTRICAL_SENSOR + } + }, + { + endpoint_id = SOLAR_POWER_EP_TWO, + clusters = { + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER" , feature_map = 6}, --CUME & PERE + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = SOLAR_POWER_DEVICE_TYPE_ID, device_type_revision = 1 }, -- SOLAR POWER + { device_type_id = ELECTRICAL_SENSOR_DEVICE_TYPE_ID, device_type_revision = 1 } -- ELECTRICAL_SENSOR + } + } + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.ElectricalPowerMeasurement.attributes.ActivePower, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported + } + test.socket.matter:__set_channel_ordering("relaxed") + 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) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + local read_req = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, SOLAR_POWER_EP_ONE) + read_req:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, SOLAR_POWER_EP_TWO)) + + test.socket.matter:__expect_send({ + mock_device.id, + read_req + }) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Appropriate powerMeter capability events must be sent in 'W' on receiving ActivePower events", + function() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(mock_device, + SOLAR_POWER_EP_ONE, + 15000) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 15.0, unit = "W" }))) + + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(mock_device, + SOLAR_POWER_EP_TWO, + 16000) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 31.0, unit = "W" }))) + + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(mock_device, + SOLAR_POWER_EP_TWO, + 20000) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 35.0, unit = "W" }))) + end +) + +test.register_coroutine_test( + "Ensure timers are created for the device and terminated on removed", + function() + test.socket.matter:__set_channel_ordering("relaxed") + local poll_timer = mock_device:get_field("__recurring_poll_timer") + assert(poll_timer ~= nil, "poll_timer should not exist") + + local report_poll_timer = mock_device:get_field("__recurring_report_poll_timer") + assert(report_poll_timer ~= nil, "report_poll_timer should exist") + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "removed" }) + test.wait_for_events() + + local poll_timer = mock_device:get_field("__recurring_poll_timer") + assert(poll_timer == nil, "poll_timer should not exist") + + local report_poll_timer = mock_device:get_field("__recurring_report_poll_timer") + assert(report_poll_timer == nil, "report_poll_timer should not exist") + end +) + +test.register_coroutine_test( + "Ensure that every 60 seconds the driver reads the CumulativeEnergyExported attribute for both endpoints", + function() + test.mock_time.advance_time(60) + test.socket.matter:__set_channel_ordering("relaxed") + local read_req = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, SOLAR_POWER_EP_ONE) + read_req:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, SOLAR_POWER_EP_TWO)) + test.socket.matter:__expect_send({ + mock_device.id, + read_req + }) + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.register_coroutine_test( + "Ensure the total cumulative energy exported powerConsumption for both endpoints is reported every 15 minutes", + function() + test.socket.matter:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + + local read_req = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, SOLAR_POWER_EP_ONE) + read_req:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:read(mock_device, SOLAR_POWER_EP_TWO)) + + test.socket.matter:__expect_send({ + mock_device.id, + read_req + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyExported:build_test_report_data(mock_device, + SOLAR_POWER_EP_ONE, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 100, unit = "Wh" + })) + ) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyExported:build_test_report_data(mock_device, + SOLAR_POWER_EP_TWO, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 150000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --150Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 250, unit = "Wh" + })) + ) + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("exportedEnergy", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 250, + deltaEnergy = 250, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:14:59Z" + })) + ) + + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.register_coroutine_test( + "Ensure energyMeter is not reported incase we recieve CumulativeEnergyImported events for Solar Power device", + function() + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyExported:build_test_report_data(mock_device, + SOLAR_POWER_EP_ONE, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 100, unit = "Wh" + })) + ) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyImported:build_test_report_data(mock_device, + SOLAR_POWER_EP_ONE, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-evse/config.yml b/drivers/SmartThings/matter-evse/config.yml deleted file mode 100644 index 611e385a09..0000000000 --- a/drivers/SmartThings/matter-evse/config.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: 'Matter Evse' -packageKey: 'matter-evse' -permissions: - matter: {} -description: "SmartThings driver for Matter EVSE devices" -vendorSupportInformation: "https://support.smartthings.com" diff --git a/drivers/SmartThings/matter-evse/fingerprints.yml b/drivers/SmartThings/matter-evse/fingerprints.yml deleted file mode 100644 index 6da5bcfe7c..0000000000 --- a/drivers/SmartThings/matter-evse/fingerprints.yml +++ /dev/null @@ -1,7 +0,0 @@ -matterGeneric: - - id: "matter/evse" - deviceLabel: Matter EVSE - deviceTypes: - - id: 0x0510 - - id: 0x050C - deviceProfileName: evse diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua deleted file mode 100644 index ad407e23bf..0000000000 --- a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua +++ /dev/null @@ -1,81 +0,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 AcceptedCommandList = { - ID = 0xFFF9, - NAME = "AcceptedCommandList", - base_type = require "st.matter.data_types.Array", - element_type = require "st.matter.data_types.Uint32", -} - -AcceptedCommandList.enum_fields = {} - -function AcceptedCommandList:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print -end - -function AcceptedCommandList.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AcceptedCommandList.enum_fields[value_obj.value]) -end - -function AcceptedCommandList:new_value(...) - local o = self.base_type(table.unpack({...})) - self:augment_type(o) - return o -end - -function AcceptedCommandList:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil --event_id - ) -end - -function AcceptedCommandList:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil --event_id - ) -end - -function AcceptedCommandList:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function AcceptedCommandList: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 AcceptedCommandList:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) -return AcceptedCommandList - diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/MeasurementTypeEnum.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/MeasurementTypeEnum.lua deleted file mode 100644 index 984df9c286..0000000000 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/MeasurementTypeEnum.lua +++ /dev/null @@ -1,51 +0,0 @@ -local data_types = require "st.matter.data_types" -local UintABC = require "st.matter.data_types.base_defs.UintABC" - -local MeasurementTypeEnum = {} -local new_mt = UintABC.new_mt({NAME = "MeasurementTypeEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) -new_mt.__index.pretty_print = function(self) - local name_lookup = { - [self.UNSPECIFIED] = "UNSPECIFIED", - [self.VOLTAGE] = "VOLTAGE", - [self.ACTIVE_CURRENT] = "ACTIVE_CURRENT", - [self.REACTIVE_CURRENT] = "REACTIVE_CURRENT", - [self.APPARENT_CURRENT] = "APPARENT_CURRENT", - [self.ACTIVE_POWER] = "ACTIVE_POWER", - [self.REACTIVE_POWER] = "REACTIVE_POWER", - [self.APPARENT_POWER] = "APPARENT_POWER", - [self.RMS_VOLTAGE] = "RMS_VOLTAGE", - [self.RMS_CURRENT] = "RMS_CURRENT", - [self.RMS_POWER] = "RMS_POWER", - [self.FREQUENCY] = "FREQUENCY", - [self.POWER_FACTOR] = "POWER_FACTOR", - [self.NEUTRAL_CURRENT] = "NEUTRAL_CURRENT", - [self.ELECTRICAL_ENERGY] = "ELECTRICAL_ENERGY", - } - 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.UNSPECIFIED = 0x00 -new_mt.__index.VOLTAGE = 0x01 -new_mt.__index.ACTIVE_CURRENT = 0x02 -new_mt.__index.REACTIVE_CURRENT = 0x03 -new_mt.__index.APPARENT_CURRENT = 0x04 -new_mt.__index.ACTIVE_POWER = 0x05 -new_mt.__index.REACTIVE_POWER = 0x06 -new_mt.__index.APPARENT_POWER = 0x07 -new_mt.__index.RMS_VOLTAGE = 0x08 -new_mt.__index.RMS_CURRENT = 0x09 -new_mt.__index.RMS_POWER = 0x0A -new_mt.__index.FREQUENCY = 0x0B -new_mt.__index.POWER_FACTOR = 0x0C -new_mt.__index.NEUTRAL_CURRENT = 0x0D -new_mt.__index.ELECTRICAL_ENERGY = 0x0E - -MeasurementTypeEnum.augment_type = function(cls, val) - setmetatable(val, new_mt) -end - -setmetatable(MeasurementTypeEnum, new_mt) - -return MeasurementTypeEnum - diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyRangeStruct.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyRangeStruct.lua deleted file mode 100644 index ba03a8b793..0000000000 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyRangeStruct.lua +++ /dev/null @@ -1,115 +0,0 @@ -local data_types = require "st.matter.data_types" -local StructureABC = require "st.matter.data_types.base_defs.StructureABC" - -local MeasurementAccuracyRangeStruct = {} -local new_mt = StructureABC.new_mt({NAME = "MeasurementAccuracyRangeStruct", ID = data_types.name_to_id_map["Structure"]}) - -MeasurementAccuracyRangeStruct.field_defs = { - { - data_type = data_types.Int64, - field_id = 0, - name = "range_min", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Int64, - field_id = 1, - name = "range_max", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Uint16, - field_id = 2, - name = "percent_max", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Uint16, - field_id = 3, - name = "percent_min", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Uint16, - field_id = 4, - name = "percent_typical", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Uint64, - field_id = 5, - name = "fixed_max", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Uint64, - field_id = 6, - name = "fixed_min", - is_nullable = false, - is_optional = false, - }, - { - data_type = data_types.Uint64, - field_id = 7, - name = "fixed_typical", - is_nullable = false, - is_optional = false, - }, -} - -MeasurementAccuracyRangeStruct.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 or not field_def.is_nullable) and not tbl[field_def.name] then - error("Missing non optional or non_nullable field: " .. field_def.name) - else - 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 - -MeasurementAccuracyRangeStruct.serialize = function(self, buf, include_control, tag) - return data_types['Structure'].serialize(self.elements, buf, include_control, tag) -end - -new_mt.__call = MeasurementAccuracyRangeStruct.init -new_mt.__index.serialize = MeasurementAccuracyRangeStruct.serialize - -MeasurementAccuracyRangeStruct.augment_type = function(self, val) - local elems = {} - for _, v in ipairs(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) - 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.array_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.array_type) - end - end - end - end - end - val.elements = elems - setmetatable(val, new_mt) -end - -setmetatable(MeasurementAccuracyRangeStruct, new_mt) - -return MeasurementAccuracyRangeStruct diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementTypeEnum.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementTypeEnum.lua deleted file mode 100644 index 984df9c286..0000000000 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementTypeEnum.lua +++ /dev/null @@ -1,51 +0,0 @@ -local data_types = require "st.matter.data_types" -local UintABC = require "st.matter.data_types.base_defs.UintABC" - -local MeasurementTypeEnum = {} -local new_mt = UintABC.new_mt({NAME = "MeasurementTypeEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) -new_mt.__index.pretty_print = function(self) - local name_lookup = { - [self.UNSPECIFIED] = "UNSPECIFIED", - [self.VOLTAGE] = "VOLTAGE", - [self.ACTIVE_CURRENT] = "ACTIVE_CURRENT", - [self.REACTIVE_CURRENT] = "REACTIVE_CURRENT", - [self.APPARENT_CURRENT] = "APPARENT_CURRENT", - [self.ACTIVE_POWER] = "ACTIVE_POWER", - [self.REACTIVE_POWER] = "REACTIVE_POWER", - [self.APPARENT_POWER] = "APPARENT_POWER", - [self.RMS_VOLTAGE] = "RMS_VOLTAGE", - [self.RMS_CURRENT] = "RMS_CURRENT", - [self.RMS_POWER] = "RMS_POWER", - [self.FREQUENCY] = "FREQUENCY", - [self.POWER_FACTOR] = "POWER_FACTOR", - [self.NEUTRAL_CURRENT] = "NEUTRAL_CURRENT", - [self.ELECTRICAL_ENERGY] = "ELECTRICAL_ENERGY", - } - 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.UNSPECIFIED = 0x00 -new_mt.__index.VOLTAGE = 0x01 -new_mt.__index.ACTIVE_CURRENT = 0x02 -new_mt.__index.REACTIVE_CURRENT = 0x03 -new_mt.__index.APPARENT_CURRENT = 0x04 -new_mt.__index.ACTIVE_POWER = 0x05 -new_mt.__index.REACTIVE_POWER = 0x06 -new_mt.__index.APPARENT_POWER = 0x07 -new_mt.__index.RMS_VOLTAGE = 0x08 -new_mt.__index.RMS_CURRENT = 0x09 -new_mt.__index.RMS_POWER = 0x0A -new_mt.__index.FREQUENCY = 0x0B -new_mt.__index.POWER_FACTOR = 0x0C -new_mt.__index.NEUTRAL_CURRENT = 0x0D -new_mt.__index.ELECTRICAL_ENERGY = 0x0E - -MeasurementTypeEnum.augment_type = function(cls, val) - setmetatable(val, new_mt) -end - -setmetatable(MeasurementTypeEnum, new_mt) - -return MeasurementTypeEnum - diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AttributeList.lua deleted file mode 100644 index c747c15169..0000000000 --- a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AttributeList.lua +++ /dev/null @@ -1,81 +0,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 AttributeList = { - ID = 0xFFFB, - NAME = "AttributeList", - base_type = require "st.matter.data_types.Array", - element_type = require "st.matter.data_types.Uint32", -} - -AttributeList.enum_fields = {} - -function AttributeList:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print -end - -function AttributeList.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AttributeList.enum_fields[value_obj.value]) -end - -function AttributeList:new_value(...) - local o = self.base_type(table.unpack({...})) - self:augment_type(o) - return o -end - -function AttributeList:read(device, endpoint_id) - return cluster_base.read( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil --event_id - ) -end - -function AttributeList:subscribe(device, endpoint_id) - return cluster_base.subscribe( - device, - endpoint_id, - self._cluster.ID, - self.ID, - nil --event_id - ) -end - -function AttributeList:set_parent_cluster(cluster) - self._cluster = cluster - return self -end - -function AttributeList: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 AttributeList:deserialize(tlv_buf) - local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) - return data -end - -setmetatable(AttributeList, {__call = AttributeList.new_value}) -return AttributeList - diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index f7481aebcc..7e654e8c70 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -2616,6 +2616,16 @@ matterGeneric: - id: 0x010A # On Off Plug-in Unit - id: 0x0510 # Electrical Sensor deviceProfileName: plug-power-energy-powerConsumption + - id: "matter/mounted/on-off/control" + deviceLabel: Matter Mounted OnOff Control + deviceTypes: + - id: 0x010F # Mounted On/Off Control + deviceProfileName: switch-binary + - id: "matter/mounted/dimmable/load/control" + deviceLabel: Matter Mounted Dimmable Load Control + deviceTypes: + - id: 0x0110 # Mounted Dimmable Load Control + deviceProfileName: switch-level - id: "matter/water-valve" deviceLabel: Matter Water Valve deviceTypes: diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 5a918d2082..05b5fadb2c 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -75,6 +75,8 @@ local DIMMABLE_PLUG_DEVICE_TYPE_ID = 0x010B local ON_OFF_SWITCH_ID = 0x0103 local ON_OFF_DIMMER_SWITCH_ID = 0x0104 local ON_OFF_COLOR_DIMMER_SWITCH_ID = 0x0105 +local MOUNTED_ON_OFF_CONTROL_ID = 0x010F +local MOUNTED_DIMMABLE_LOAD_CONTROL_ID = 0x0110 local GENERIC_SWITCH_ID = 0x000F local ELECTRICAL_SENSOR_ID = 0x0510 local device_type_profile_map = { @@ -87,6 +89,9 @@ local device_type_profile_map = { [ON_OFF_SWITCH_ID] = "switch-binary", [ON_OFF_DIMMER_SWITCH_ID] = "switch-level", [ON_OFF_COLOR_DIMMER_SWITCH_ID] = "switch-color-level", + [MOUNTED_ON_OFF_CONTROL_ID] = "switch-binary", + [MOUNTED_DIMMABLE_LOAD_CONTROL_ID] = "switch-level", + [GENERIC_SWITCH_ID] = "button" } local device_type_attribute_map = { diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua index d896607284..4d8325d2fa 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua @@ -119,6 +119,66 @@ local mock_device_color_dimmer = test.mock_device.build_test_matter_device({ } }) +local mock_device_mounted_on_off_control = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("switch-binary.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 = 7, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "CLIENT", feature_map = 2}, + + }, + device_types = { + {device_type_id = 0x010F, device_type_revision = 1} -- Mounted On/Off Control + } + } + } +}) + +local mock_device_mounted_dimmable_load_control = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("switch-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 = 7, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "CLIENT", feature_map = 2}, + + }, + device_types = { + {device_type_id = 0x0110, device_type_revision = 1} -- Mounted Dimmable Load Control + } + } + } +}) + local mock_device_water_valve = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("matter-thing.yml"), manufacturer_info = { @@ -343,6 +403,34 @@ local function test_init_color_dimmer() mock_device_color_dimmer:expect_metadata_update({ profile = "switch-color-level" }) end +local function test_init_mounted_on_off_control() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_mounted_on_off_control) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_mounted_on_off_control)) + end + end + test.socket.matter:__expect_send({mock_device_mounted_on_off_control.id, subscribe_request}) + test.mock_device.add_test_device(mock_device_mounted_on_off_control) +end + +local function test_init_mounted_dimmable_load_control() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_mounted_dimmable_load_control) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_mounted_dimmable_load_control)) + end + end + test.socket.matter:__expect_send({mock_device_mounted_dimmable_load_control.id, subscribe_request}) + test.mock_device.add_test_device(mock_device_mounted_dimmable_load_control) +end + local function test_init_water_valve() test.mock_device.add_test_device(mock_device_water_valve) test.socket.device_lifecycle:__queue_receive({ mock_device_water_valve.id, "doConfigure" }) @@ -426,10 +514,24 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Test profile change on init for water valve parent cluster as server", - function() - end, - { test_init = test_init_water_valve } + "Test profile change on init for mounted onoff control parent cluster as server", + function() + end, + { test_init = test_init_mounted_on_off_control } +) + +test.register_coroutine_test( + "Test profile change on init for mounted dimmable load control parent cluster as server", + function() + end, + { test_init = test_init_mounted_dimmable_load_control } +) + +test.register_coroutine_test( + "Test profile change on init for water valve parent cluster as server", + function() + end, + { test_init = test_init_water_valve } ) test.register_coroutine_test( diff --git a/drivers/SmartThings/matter-thermostat/fingerprints.yml b/drivers/SmartThings/matter-thermostat/fingerprints.yml index e93ff919fa..aad950cb65 100644 --- a/drivers/SmartThings/matter-thermostat/fingerprints.yml +++ b/drivers/SmartThings/matter-thermostat/fingerprints.yml @@ -87,3 +87,21 @@ matterGeneric: deviceTypes: - id: 0x002D # Air Purifier deviceProfileName: air-purifier-hepa-ac-wind + - id: "matter/water-heater" + deviceLabel: Matter Water Heater + deviceTypes: + - id: 0x050F # Water Heater + deviceProfileName: water-heater + - id: "matter/heat-pump" + deviceLabel: Matter Heat Pump + deviceTypes: + - id: 0x0309 # Heat Pump + - id: 0x0510 # Electrical Sensor + deviceProfileName: heat-pump + - id: "matter/heat-pump/thermostat" + deviceLabel: Matter Heat Pump + deviceTypes: + - id: 0x0309 # Heat Pump + - id: 0x0510 # Electrical Sensor + - id: 0x0301 # Thermostat + deviceProfileName: heat-pump-thermostat diff --git a/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-aqs.yml b/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-aqs.yml index f17f9695bd..c49c09ee0e 100644 --- a/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-aqs.yml +++ b/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-aqs.yml @@ -23,4 +23,4 @@ components: - id: filterStatus version: 1 categories: - - name: AirPurifier \ No newline at end of file + - name: AirPurifier diff --git a/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-wind.yml b/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-wind.yml index 2cd0e34c4c..43a812dbe9 100644 --- a/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-wind.yml +++ b/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-wind.yml @@ -23,4 +23,4 @@ components: - id: filterStatus version: 1 categories: - - name: AirPurifier \ No newline at end of file + - name: AirPurifier diff --git a/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity-thermostat-humidity.yml b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity-thermostat-humidity.yml new file mode 100644 index 0000000000..5a95bf637d --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity-thermostat-humidity.yml @@ -0,0 +1,40 @@ +name: heat-pump-thermostat-humidity-thermostat-humidity +components: +- id: main + capabilities: + - id: energyMeter + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 +- id: thermostatOne + label: Thermostat 1 + capabilities: + - id: thermostatMode + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 +- id: thermostatTwo + label: Thermostat 2 + capabilities: + - id: thermostatMode + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 diff --git a/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity-thermostat.yml b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity-thermostat.yml new file mode 100644 index 0000000000..1230d8a6b3 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity-thermostat.yml @@ -0,0 +1,38 @@ +name: heat-pump-thermostat-humidity-thermostat +components: +- id: main + capabilities: + - id: energyMeter + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 +- id: thermostatOne + label: Thermostat 1 + capabilities: + - id: thermostatMode + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 +- id: thermostatTwo + label: Thermostat 2 + capabilities: + - id: thermostatMode + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 diff --git a/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity.yml b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity.yml new file mode 100644 index 0000000000..4b638f8ea1 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-humidity.yml @@ -0,0 +1,27 @@ +name: heat-pump-thermostat-humidity +components: +- id: main + capabilities: + - id: energyMeter + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 +- id: thermostatOne + label: Thermostat 1 + capabilities: + - id: thermostatMode + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 diff --git a/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-thermostat-humidity.yml b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-thermostat-humidity.yml new file mode 100644 index 0000000000..94abfb36d7 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-thermostat-humidity.yml @@ -0,0 +1,38 @@ +name: heat-pump-thermostat-thermostat-humidity +components: +- id: main + capabilities: + - id: energyMeter + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 +- id: thermostatOne + label: Thermostat 1 + capabilities: + - id: thermostatMode + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 +- id: thermostatTwo + label: Thermostat 2 + capabilities: + - id: thermostatMode + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 diff --git a/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-thermostat.yml b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-thermostat.yml new file mode 100644 index 0000000000..0c3879577c --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat-thermostat.yml @@ -0,0 +1,36 @@ +name: heat-pump-thermostat-thermostat +components: +- id: main + capabilities: + - id: energyMeter + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 +- id: thermostatOne + label: Thermostat 1 + capabilities: + - id: thermostatMode + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 +- id: thermostatTwo + label: Thermostat 2 + capabilities: + - id: thermostatMode + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 diff --git a/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat.yml b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat.yml new file mode 100644 index 0000000000..f851b16694 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/heat-pump-thermostat.yml @@ -0,0 +1,25 @@ +name: heat-pump-thermostat +components: +- id: main + capabilities: + - id: energyMeter + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 +- id: thermostatOne + label: Thermostat 1 + capabilities: + - id: thermostatMode + version: 1 + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: thermostatCoolingSetpoint + version: 1 diff --git a/drivers/SmartThings/matter-thermostat/profiles/heat-pump.yml b/drivers/SmartThings/matter-thermostat/profiles/heat-pump.yml new file mode 100644 index 0000000000..1255d4af6a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/heat-pump.yml @@ -0,0 +1,14 @@ +name: heat-pump +components: +- id: main + capabilities: + - id: energyMeter + version: 1 + - id: powerMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 diff --git a/drivers/SmartThings/matter-thermostat/profiles/room-air-conditioner-fan-heating-cooling.yml b/drivers/SmartThings/matter-thermostat/profiles/room-air-conditioner-fan-heating-cooling.yml index 98c470c71c..5dbd25c8e0 100644 --- a/drivers/SmartThings/matter-thermostat/profiles/room-air-conditioner-fan-heating-cooling.yml +++ b/drivers/SmartThings/matter-thermostat/profiles/room-air-conditioner-fan-heating-cooling.yml @@ -26,4 +26,4 @@ components: - name: AirConditioner preferences: - preferenceId: tempOffset - explicit: true \ No newline at end of file + explicit: true diff --git a/drivers/SmartThings/matter-thermostat/profiles/water-heater-power-energy-powerConsumption.yml b/drivers/SmartThings/matter-thermostat/profiles/water-heater-power-energy-powerConsumption.yml new file mode 100644 index 0000000000..6329c1e8bc --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/water-heater-power-energy-powerConsumption.yml @@ -0,0 +1,113 @@ +name: water-heater-power-energy-powerConsumption +components: +- id: main + capabilities: + - id: temperatureMeasurement + version: 1 + - id: thermostatMode + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: mode + version: 1 + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterHeater +deviceConfig: + dashboard: + states: + - component: main + capability: temperatureMeasurement + version: 1 + detailView: + - component: main + capability: temperatureMeasurement + version: 1 + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/list/command/supportedValues + value: supportedArguments.value + - component: main + capability: powerMeter + version: 1 + - component: main + capability: energyMeter + version: 1 + - component: main + capability: refresh + version: 1 + automation: + conditions: + - component: main + capability: temperatureMeasurement + version: 1 + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/displayType + value: dynamicList + - op: add + path: /0/dynamicList + value: + value: mode.value + supportedValues: + value: supportedArguments.value + - op: remove + path: /0/list + - component: main + capability: powerMeter + version: 1 + - component: main + capability: energyMeter + version: 1 + actions: + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/displayType + value: dynamicList + - op: add + path: /0/dynamicList + value: + value: mode.value + supportedValues: + value: supportedArguments.value + - op: remove + path: /0/list + - component: main + capability: energyMeter + version: 1 diff --git a/drivers/SmartThings/matter-thermostat/profiles/water-heater.yml b/drivers/SmartThings/matter-thermostat/profiles/water-heater.yml new file mode 100644 index 0000000000..a515f5366a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/profiles/water-heater.yml @@ -0,0 +1,92 @@ +name: water-heater +components: +- id: main + capabilities: + - id: temperatureMeasurement + version: 1 + - id: thermostatMode + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + - id: mode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterHeater +deviceConfig: + dashboard: + states: + - component: main + capability: temperatureMeasurement + version: 1 + detailView: + - component: main + capability: temperatureMeasurement + version: 1 + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/list/command/supportedValues + value: supportedArguments.value + - component: main + capability: refresh + version: 1 + automation: + conditions: + - component: main + capability: temperatureMeasurement + version: 1 + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/displayType + value: dynamicList + - op: add + path: /0/dynamicList + value: + value: mode.value + supportedValues: + value: supportedArguments.value + - op: remove + path: /0/list + actions: + - component: main + capability: thermostatMode + version: 1 + - component: main + capability: thermostatHeatingSetpoint + version: 1 + - component: main + capability: mode + version: 1 + patch: + - op: replace + path: /0/displayType + value: dynamicList + - op: add + path: /0/dynamicList + value: + value: mode.value + supportedValues: + value: supportedArguments.value + - op: remove + path: /0/list diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/init.lua new file mode 100644 index 0000000000..bc5685ae75 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/init.lua @@ -0,0 +1,76 @@ +local cluster_base = require "st.matter.cluster_base" +local ElectricalEnergyMeasurementServerAttributes = require "ElectricalEnergyMeasurement.server.attributes" +local ElectricalEnergyMeasurementEvents = require "ElectricalEnergyMeasurement.server.events" +local ElectricalEnergyMeasurementTypes = require "ElectricalEnergyMeasurement.types" +local ElectricalEnergyMeasurement = {} + +ElectricalEnergyMeasurement.ID = 0x0091 +ElectricalEnergyMeasurement.NAME = "ElectricalEnergyMeasurement" +ElectricalEnergyMeasurement.server = {} +ElectricalEnergyMeasurement.client = {} +ElectricalEnergyMeasurement.server.attributes = ElectricalEnergyMeasurementServerAttributes:set_parent_cluster(ElectricalEnergyMeasurement) +ElectricalEnergyMeasurement.server.events = ElectricalEnergyMeasurementEvents:set_parent_cluster(ElectricalEnergyMeasurement) +ElectricalEnergyMeasurement.types = ElectricalEnergyMeasurementTypes + +function ElectricalEnergyMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "Accuracy", + [0x0001] = "CumulativeEnergyImported", + [0x0002] = "CumulativeEnergyExported", + [0x0003] = "PeriodicEnergyImported", + [0x0004] = "PeriodicEnergyExported", + [0x0005] = "CumulativeEnergyReset", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +ElectricalEnergyMeasurement.attribute_direction_map = { + ["Accuracy"] = "server", + ["CumulativeEnergyImported"] = "server", + ["CumulativeEnergyExported"] = "server", + ["PeriodicEnergyImported"] = "server", + ["PeriodicEnergyExported"] = "server", + ["CumulativeEnergyReset"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + +ElectricalEnergyMeasurement.FeatureMap = ElectricalEnergyMeasurement.types.Feature + +function ElectricalEnergyMeasurement.are_features_supported(feature, feature_map) + if (ElectricalEnergyMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ElectricalEnergyMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ElectricalEnergyMeasurement.NAME)) + end + return ElectricalEnergyMeasurement[direction].attributes[key] +end +ElectricalEnergyMeasurement.attributes = {} +setmetatable(ElectricalEnergyMeasurement.attributes, attribute_helper_mt) + +local event_helper_mt = {} +event_helper_mt.__index = function(self, key) + return ElectricalEnergyMeasurement.server.events[key] +end +ElectricalEnergyMeasurement.events = {} +setmetatable(ElectricalEnergyMeasurement.events, event_helper_mt) + +setmetatable(ElectricalEnergyMeasurement, {__index = cluster_base}) + +return ElectricalEnergyMeasurement + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ApparentPower.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua similarity index 50% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ApparentPower.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua index 40cbcef7eb..22befec642 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ApparentPower.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua @@ -2,55 +2,44 @@ local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" -local ApparentPower = { - ID = 0x000A, - NAME = "ApparentPower", - base_type = data_types.Int64, +local CumulativeEnergyExported = { + ID = 0x0002, + NAME = "CumulativeEnergyExported", + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", } -ApparentPower.enum_fields = {} - -function ApparentPower:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print -end - -function ApparentPower.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, ApparentPower.enum_fields[value_obj.value]) -end - -function ApparentPower:new_value(...) +function CumulativeEnergyExported:new_value(...) local o = self.base_type(table.unpack({...})) self:augment_type(o) return o end -function ApparentPower:read(device, endpoint_id) +function CumulativeEnergyExported:read(device, endpoint_id) return cluster_base.read( device, endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end -function ApparentPower:subscribe(device, endpoint_id) +function CumulativeEnergyExported:subscribe(device, endpoint_id) return cluster_base.subscribe( device, endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end -function ApparentPower:set_parent_cluster(cluster) +function CumulativeEnergyExported:set_parent_cluster(cluster) self._cluster = cluster return self end -function ApparentPower:build_test_report_data( +function CumulativeEnergyExported:build_test_report_data( device, endpoint_id, value, @@ -68,11 +57,12 @@ function ApparentPower:build_test_report_data( ) end -function ApparentPower:deserialize(tlv_buf) +function CumulativeEnergyExported:deserialize(tlv_buf) local data = TLVParser.decode_tlv(tlv_buf) self:augment_type(data) return data end -setmetatable(ApparentPower, {__call = ApparentPower.new_value}) -return ApparentPower +setmetatable(CumulativeEnergyExported, {__call = CumulativeEnergyExported.new_value, __index = CumulativeEnergyExported.base_type}) +return CumulativeEnergyExported + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua similarity index 74% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua index db834c3424..3dc58635e1 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua @@ -5,19 +5,9 @@ local TLVParser = require "st.matter.TLV.TLVParser" local CumulativeEnergyImported = { ID = 0x0001, NAME = "CumulativeEnergyImported", - base_type = data_types.Structure, + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", } -CumulativeEnergyImported.enum_fields = {} - -function CumulativeEnergyImported:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print -end - -function CumulativeEnergyImported.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, CumulativeEnergyImported.enum_fields[value_obj.value]) -end function CumulativeEnergyImported:new_value(...) local o = self.base_type(table.unpack({...})) self:augment_type(o) @@ -30,7 +20,7 @@ function CumulativeEnergyImported:read(device, endpoint_id) endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end @@ -40,7 +30,7 @@ function CumulativeEnergyImported:subscribe(device, endpoint_id) endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end @@ -73,6 +63,6 @@ function CumulativeEnergyImported:deserialize(tlv_buf) return data end -setmetatable(CumulativeEnergyImported, {__call = CumulativeEnergyImported.new_value}) +setmetatable(CumulativeEnergyImported, {__call = CumulativeEnergyImported.new_value, __index = CumulativeEnergyImported.base_type}) return CumulativeEnergyImported diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua new file mode 100644 index 0000000000..4c1ee29274 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local PeriodicEnergyExported = { + ID = 0x0004, + NAME = "PeriodicEnergyExported", + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", +} + +function PeriodicEnergyExported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function PeriodicEnergyExported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PeriodicEnergyExported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PeriodicEnergyExported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function PeriodicEnergyExported: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 PeriodicEnergyExported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(PeriodicEnergyExported, {__call = PeriodicEnergyExported.new_value, __index = PeriodicEnergyExported.base_type}) +return PeriodicEnergyExported + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua similarity index 74% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua index 9a70554a7d..753b91ea2d 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua @@ -5,20 +5,9 @@ local TLVParser = require "st.matter.TLV.TLVParser" local PeriodicEnergyImported = { ID = 0x0003, NAME = "PeriodicEnergyImported", - base_type = data_types.Structure, + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", } -PeriodicEnergyImported.enum_fields = {} - -function PeriodicEnergyImported:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print -end - -function PeriodicEnergyImported.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, PeriodicEnergyImported.enum_fields[value_obj.value]) -end - function PeriodicEnergyImported:new_value(...) local o = self.base_type(table.unpack({...})) self:augment_type(o) @@ -31,7 +20,7 @@ function PeriodicEnergyImported:read(device, endpoint_id) endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end @@ -41,7 +30,7 @@ function PeriodicEnergyImported:subscribe(device, endpoint_id) endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end @@ -74,5 +63,6 @@ function PeriodicEnergyImported:deserialize(tlv_buf) return data end -setmetatable(PeriodicEnergyImported, {__call = PeriodicEnergyImported.new_value}) +setmetatable(PeriodicEnergyImported, {__call = PeriodicEnergyImported.new_value, __index = PeriodicEnergyImported.base_type}) return PeriodicEnergyImported + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/init.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/attributes/init.lua diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/MeasurementPeriodRanges.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/CumulativeEnergyMeasured.lua similarity index 58% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/MeasurementPeriodRanges.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/CumulativeEnergyMeasured.lua index 414f64e3e8..13136bd791 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/MeasurementPeriodRanges.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/CumulativeEnergyMeasured.lua @@ -2,40 +2,49 @@ local data_types = require "st.matter.data_types" local cluster_base = require "st.matter.cluster_base" local TLVParser = require "st.matter.TLV.TLVParser" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" -local MeasurementRangeStruct = require "ElectricalPowerMeasurement.types.MeasurementRangeStruct" -local MeasurementPeriodRanges = { +local CumulativeEnergyMeasured = { ID = 0x0000, - NAME = "MeasurementPeriodRanges", + NAME = "CumulativeEnergyMeasured", base_type = data_types.Structure, } -MeasurementPeriodRanges.field_defs = { +CumulativeEnergyMeasured.field_defs = { { - data_type = MeasurementRangeStruct, + name = "energy_imported", field_id = 0, - is_array = true, - name = "ranges", is_nullable = false, - is_optional = false, + is_optional = true, + data_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", + }, + { + name = "energy_exported", + field_id = 1, + is_nullable = false, + is_optional = true, + data_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", }, } -function MeasurementPeriodRanges:augment_type(base_type_obj) +function CumulativeEnergyMeasured: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 not - ((field_def.is_nullable or field_def.is_optional) and v.value == nil) then + ((field_def.is_nullable or field_def.is_optional) and v.elements == nil) then elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) - elems[field_def.name].field_name = field_def.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 MeasurementPeriodRanges:read(device, endpoint_id) +function CumulativeEnergyMeasured:read(device, endpoint_id) return cluster_base.read( device, endpoint_id, @@ -45,7 +54,7 @@ function MeasurementPeriodRanges:read(device, endpoint_id) ) end -function MeasurementPeriodRanges:subscribe(device, endpoint_id) +function CumulativeEnergyMeasured:subscribe(device, endpoint_id) return cluster_base.subscribe( device, endpoint_id, @@ -55,7 +64,7 @@ function MeasurementPeriodRanges:subscribe(device, endpoint_id) ) end -function MeasurementPeriodRanges:build_test_event_report( +function CumulativeEnergyMeasured:build_test_event_report( device, endpoint_id, fields, @@ -64,9 +73,9 @@ function MeasurementPeriodRanges:build_test_event_report( local data = {} data.elements = {} data.num_elements = 0 - setmetatable(data, StructureABC.new_mt({NAME = "MeasurementPeriodRangesEventData", ID = 0x15})) + setmetatable(data, StructureABC.new_mt({NAME = "CumulativeEnergyMeasuredEventData", ID = 0x15})) for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 - if (not field_def.is_optional or not field_def.is_nullable) and not fields[field_def.name] then + if (not field_def.is_optional and not field_def.is_nullable) and not fields[field_def.name] then error("Missing non optional or non_nullable field: " .. field_def.name) elseif fields[field_def.name] then data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) @@ -84,15 +93,16 @@ function MeasurementPeriodRanges:build_test_event_report( ) end -function MeasurementPeriodRanges:deserialize(tlv_buf) +function CumulativeEnergyMeasured:deserialize(tlv_buf) local data = TLVParser.decode_tlv(tlv_buf) self:augment_type(data) return data end -function MeasurementPeriodRanges:set_parent_cluster(cluster) +function CumulativeEnergyMeasured:set_parent_cluster(cluster) self._cluster = cluster return self end -return MeasurementPeriodRanges +return CumulativeEnergyMeasured + diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/PeriodicEnergyMeasured.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/PeriodicEnergyMeasured.lua new file mode 100644 index 0000000000..e2c4b1b577 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/PeriodicEnergyMeasured.lua @@ -0,0 +1,108 @@ +local data_types = require "st.matter.data_types" +local cluster_base = require "st.matter.cluster_base" +local TLVParser = require "st.matter.TLV.TLVParser" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local PeriodicEnergyMeasured = { + ID = 0x0001, + NAME = "PeriodicEnergyMeasured", + base_type = data_types.Structure, +} + +PeriodicEnergyMeasured.field_defs = { + { + name = "energy_imported", + field_id = 0, + is_nullable = false, + is_optional = true, + data_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", + }, + { + name = "energy_exported", + field_id = 1, + is_nullable = false, + is_optional = true, + data_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", + }, +} + +function PeriodicEnergyMeasured: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 not + ((field_def.is_nullable or field_def.is_optional) and v.elements == 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 PeriodicEnergyMeasured:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function PeriodicEnergyMeasured:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function PeriodicEnergyMeasured:build_test_event_report( + device, + endpoint_id, + fields, + status +) + local data = {} + data.elements = {} + data.num_elements = 0 + setmetatable(data, StructureABC.new_mt({NAME = "PeriodicEnergyMeasuredEventData", ID = 0x15})) + for idx, field_def in ipairs(self.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 fields[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif fields[field_def.name] then + data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) + data.elements[field_def.name].field_id = field_def.field_id + data.num_elements = data.num_elements + 1 + end + end + return cluster_base.build_test_event_report( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function PeriodicEnergyMeasured:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +function PeriodicEnergyMeasured:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +return PeriodicEnergyMeasured + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/init.lua similarity index 56% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/init.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/init.lua index eeec54b6f3..02b085583e 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/server/events/init.lua @@ -2,7 +2,7 @@ local event_mt = {} event_mt.__event_cache = {} event_mt.__index = function(self, key) if event_mt.__event_cache[key] == nil then - local req_loc = string.format("ElectricalPowerMeasurement.server.events.%s", key) + local req_loc = string.format("ElectricalEnergyMeasurement.server.events.%s", key) local raw_def = require(req_loc) local cluster = rawget(self, "_cluster") raw_def:set_parent_cluster(cluster) @@ -11,14 +11,15 @@ event_mt.__index = function(self, key) return event_mt.__event_cache[key] end -local ElectricalPowerMeasurementEvents = {} -function ElectricalPowerMeasurementEvents:set_parent_cluster(cluster) +local ElectricalEnergyMeasurementEvents = {} + +function ElectricalEnergyMeasurementEvents:set_parent_cluster(cluster) self._cluster = cluster return self end -setmetatable(ElectricalPowerMeasurementEvents, event_mt) +setmetatable(ElectricalEnergyMeasurementEvents, event_mt) -return ElectricalPowerMeasurementEvents +return ElectricalEnergyMeasurementEvents diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua similarity index 73% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua index 99dd2dd52c..950b260227 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua @@ -1,42 +1,43 @@ local data_types = require "st.matter.data_types" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" -local energy_mwh = require "st.matter.data_types.Int64" - - -local systime_ms = require "st.matter.data_types.Uint64" - local EnergyMeasurementStruct = {} local new_mt = StructureABC.new_mt({NAME = "EnergyMeasurementStruct", ID = data_types.name_to_id_map["Structure"]}) EnergyMeasurementStruct.field_defs = { { - data_type = energy_mwh, - field_id = 0, name = "energy", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Int64", }, { - data_type = data_types.Uint32, - field_id = 1, name = "start_timestamp", + field_id = 1, is_nullable = false, - is_optional = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint32", }, { - data_type = data_types.Uint32, - field_id = 2, name = "end_timestamp", + field_id = 2, is_nullable = false, - is_optional = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint32", }, { - data_type = systime_ms, - field_id = 3, name = "start_systime", + field_id = 3, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", }, { - data_type = systime_ms, - field_id = 4, name = "end_systime", + field_id = 4, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", }, } @@ -45,8 +46,8 @@ 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 --Note: idx is 1 when field_id is 0 - if (not field_def.is_optional or not field_def.is_nullable) and not tbl[field_def.name] then + 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 o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) @@ -66,24 +67,28 @@ new_mt.__index.serialize = EnergyMeasurementStruct.serialize EnergyMeasurementStruct.augment_type = function(self, val) local elems = {} - for _, v in ipairs(val.elements) do + 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) then elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) - if field_def.array_type ~= nil then + 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.array_type) + 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 diff --git a/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/Feature.lua new file mode 100644 index 0000000000..717ba6a2f3 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/Feature.lua @@ -0,0 +1,116 @@ +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.IMPORTED_ENERGY = 0x0001 +Feature.EXPORTED_ENERGY = 0x0002 +Feature.CUMULATIVE_ENERGY = 0x0004 +Feature.PERIODIC_ENERGY = 0x0008 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + IMPORTED_ENERGY = 0x0001, + EXPORTED_ENERGY = 0x0002, + CUMULATIVE_ENERGY = 0x0004, + PERIODIC_ENERGY = 0x0008, +} + +Feature.is_imported_energy_set = function(self) + return (self.value & self.IMPORTED_ENERGY) ~= 0 +end + +Feature.set_imported_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.IMPORTED_ENERGY + else + self.value = self.IMPORTED_ENERGY + end +end + +Feature.unset_imported_energy = function(self) + self.value = self.value & (~self.IMPORTED_ENERGY & self.BASE_MASK) +end +Feature.is_exported_energy_set = function(self) + return (self.value & self.EXPORTED_ENERGY) ~= 0 +end + +Feature.set_exported_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.EXPORTED_ENERGY + else + self.value = self.EXPORTED_ENERGY + end +end + +Feature.unset_exported_energy = function(self) + self.value = self.value & (~self.EXPORTED_ENERGY & self.BASE_MASK) +end +Feature.is_cumulative_energy_set = function(self) + return (self.value & self.CUMULATIVE_ENERGY) ~= 0 +end + +Feature.set_cumulative_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.CUMULATIVE_ENERGY + else + self.value = self.CUMULATIVE_ENERGY + end +end + +Feature.unset_cumulative_energy = function(self) + self.value = self.value & (~self.CUMULATIVE_ENERGY & self.BASE_MASK) +end +Feature.is_periodic_energy_set = function(self) + return (self.value & self.PERIODIC_ENERGY) ~= 0 +end + +Feature.set_periodic_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.PERIODIC_ENERGY + else + self.value = self.PERIODIC_ENERGY + end +end + +Feature.unset_periodic_energy = function(self) + self.value = self.value & (~self.PERIODIC_ENERGY & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.IMPORTED_ENERGY | + Feature.EXPORTED_ENERGY | + Feature.CUMULATIVE_ENERGY | + Feature.PERIODIC_ENERGY + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_imported_energy_set = Feature.is_imported_energy_set, + set_imported_energy = Feature.set_imported_energy, + unset_imported_energy = Feature.unset_imported_energy, + is_exported_energy_set = Feature.is_exported_energy_set, + set_exported_energy = Feature.set_exported_energy, + unset_exported_energy = Feature.unset_exported_energy, + is_cumulative_energy_set = Feature.is_cumulative_energy_set, + set_cumulative_energy = Feature.set_cumulative_energy, + unset_cumulative_energy = Feature.unset_cumulative_energy, + is_periodic_energy_set = Feature.is_periodic_energy_set, + set_periodic_energy = Feature.set_periodic_energy, + unset_periodic_energy = Feature.unset_periodic_energy, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/init.lua similarity index 65% rename from drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/init.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/init.lua index db0e4230bf..bb0c39fe0e 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalEnergyMeasurement/types/init.lua @@ -2,9 +2,7 @@ local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - local req_loc = string.format("ElectricalEnergyMeasurement.types.%s", key) - local cluster_type = require(req_loc) - types_mt.__types_cache[key] = cluster_type + types_mt.__types_cache[key] = require("ElectricalEnergyMeasurement.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/init.lua similarity index 61% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/init.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/init.lua index cd28c586e2..54785d16c6 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/init.lua @@ -1,6 +1,5 @@ local cluster_base = require "st.matter.cluster_base" local ElectricalPowerMeasurementServerAttributes = require "ElectricalPowerMeasurement.server.attributes" -local ElectricalPowerMeasurementServerCommands = require "ElectricalPowerMeasurement.server.commands" local ElectricalPowerMeasurementTypes = require "ElectricalPowerMeasurement.types" local ElectricalPowerMeasurement = {} @@ -10,23 +9,31 @@ ElectricalPowerMeasurement.NAME = "ElectricalPowerMeasurement" ElectricalPowerMeasurement.server = {} ElectricalPowerMeasurement.client = {} ElectricalPowerMeasurement.server.attributes = ElectricalPowerMeasurementServerAttributes:set_parent_cluster(ElectricalPowerMeasurement) -ElectricalPowerMeasurement.server.commands = ElectricalPowerMeasurementServerCommands:set_parent_cluster(ElectricalPowerMeasurement) ElectricalPowerMeasurement.types = ElectricalPowerMeasurementTypes -ElectricalPowerMeasurement.FeatureMap = ElectricalPowerMeasurement.types.Feature - -function ElectricalPowerMeasurement.are_features_supported(feature, feature_map) - if (ElectricalPowerMeasurement.FeatureMap.bits_are_valid(feature)) then - return (feature & feature_map) == feature - end - return false -end function ElectricalPowerMeasurement:get_attribute_by_id(attr_id) local attr_id_map = { [0x0000] = "PowerMode", + [0x0001] = "NumberOfMeasurementTypes", + [0x0002] = "Accuracy", + [0x0003] = "Ranges", + [0x0004] = "Voltage", + [0x0005] = "ActiveCurrent", + [0x0006] = "ReactiveCurrent", + [0x0007] = "ApparentCurrent", [0x0008] = "ActivePower", + [0x0009] = "ReactivePower", [0x000A] = "ApparentPower", + [0x000B] = "RMSVoltage", + [0x000C] = "RMSCurrent", + [0x000D] = "RMSPower", + [0x000E] = "Frequency", + [0x000F] = "HarmonicCurrents", + [0x0010] = "HarmonicPhases", + [0x0011] = "PowerFactor", + [0x0012] = "NeutralCurrent", [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", [0xFFFB] = "AttributeList", } local attr_name = attr_id_map[attr_id] @@ -36,38 +43,40 @@ function ElectricalPowerMeasurement:get_attribute_by_id(attr_id) return nil end -function ElectricalPowerMeasurement: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 - -function ElectricalPowerMeasurement:get_event_by_id(event_id) - local event_id_map = { - [0x0000] = "MeasurementPeriodRanges", - } - if event_id_map[event_id] ~= nil then - return self.server.events[event_id_map[event_id]] - end - return nil -end --- Attribute Mapping ElectricalPowerMeasurement.attribute_direction_map = { ["PowerMode"] = "server", + ["NumberOfMeasurementTypes"] = "server", + ["Accuracy"] = "server", + ["Ranges"] = "server", + ["Voltage"] = "server", + ["ActiveCurrent"] = "server", + ["ReactiveCurrent"] = "server", + ["ApparentCurrent"] = "server", ["ActivePower"] = "server", + ["ReactivePower"] = "server", ["ApparentPower"] = "server", + ["RMSVoltage"] = "server", + ["RMSCurrent"] = "server", + ["RMSPower"] = "server", + ["Frequency"] = "server", + ["HarmonicCurrents"] = "server", + ["HarmonicPhases"] = "server", + ["PowerFactor"] = "server", + ["NeutralCurrent"] = "server", ["AcceptedCommandList"] = "server", + ["EventList"] = "server", ["AttributeList"] = "server", } --- Command Mapping -ElectricalPowerMeasurement.command_direction_map = { -} +ElectricalPowerMeasurement.FeatureMap = ElectricalPowerMeasurement.types.Feature + +function ElectricalPowerMeasurement.are_features_supported(feature, feature_map) + if (ElectricalPowerMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end --- Cluster Completion local attribute_helper_mt = {} attribute_helper_mt.__index = function(self, key) local direction = ElectricalPowerMeasurement.attribute_direction_map[key] @@ -79,17 +88,7 @@ end ElectricalPowerMeasurement.attributes = {} setmetatable(ElectricalPowerMeasurement.attributes, attribute_helper_mt) -local command_helper_mt = {} -command_helper_mt.__index = function(self, key) - local direction = ElectricalPowerMeasurement.command_direction_map[key] - if direction == nil then - error(string.format("Referenced unknown command %s on cluster %s", key, ElectricalPowerMeasurement.NAME)) - end - return ElectricalPowerMeasurement[direction].commands[key] -end -ElectricalPowerMeasurement.commands = {} -setmetatable(ElectricalPowerMeasurement.commands, command_helper_mt) - setmetatable(ElectricalPowerMeasurement, {__index = cluster_base}) -return ElectricalPowerMeasurement \ No newline at end of file +return ElectricalPowerMeasurement + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua similarity index 68% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua index 16774ae1d6..6c34abd2f4 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua @@ -2,28 +2,15 @@ local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" - local ActivePower = { ID = 0x0008, NAME = "ActivePower", - base_type = data_types.Int64, -} - -ActivePower.enum_fields = { + base_type = require "st.matter.data_types.Int64", } -function ActivePower:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print -end - -function ActivePower.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, ActivePower.enum_fields[value_obj.value]) -end - function ActivePower:new_value(...) local o = self.base_type(table.unpack({...})) - self:augment_type(o) + return o end @@ -33,7 +20,7 @@ function ActivePower:read(device, endpoint_id) endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end @@ -43,7 +30,7 @@ function ActivePower:subscribe(device, endpoint_id) endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end @@ -59,7 +46,7 @@ function ActivePower:build_test_report_data( 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, @@ -72,9 +59,10 @@ end function ActivePower:deserialize(tlv_buf) local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) + return data end -setmetatable(ActivePower, {__call = ActivePower.new_value}) +setmetatable(ActivePower, {__call = ActivePower.new_value, __index = ActivePower.base_type}) return ActivePower + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/init.lua similarity index 100% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/init.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/server/attributes/init.lua diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/Feature.lua similarity index 95% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/Feature.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/Feature.lua index 2229aebe0c..cbda4f3478 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/Feature.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/Feature.lua @@ -98,12 +98,11 @@ end function Feature.bits_are_valid(feature) local max = - Feature.DIRECT_CURRENT | - Feature.ALTERNATING_CURRENT | - Feature.POLYPHASE_POWER | - Feature.HARMONICS | - Feature.POWER_QUALITY - + Feature.DIRECT_CURRENT | + Feature.ALTERNATING_CURRENT | + Feature.POLYPHASE_POWER | + Feature.HARMONICS | + Feature.POWER_QUALITY if (feature <= max) and (feature >= 1) then return true else @@ -135,4 +134,5 @@ end setmetatable(Feature, new_mt) -return Feature \ No newline at end of file +return Feature + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/init.lua b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/init.lua similarity index 65% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/init.lua rename to drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/init.lua index 658256b316..16d13a0688 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/ElectricalPowerMeasurement/types/init.lua @@ -2,9 +2,7 @@ local types_mt = {} types_mt.__types_cache = {} types_mt.__index = function(self, key) if types_mt.__types_cache[key] == nil then - local req_loc = string.format("ElectricalPowerMeasurement.types.%s", key) - local cluster_type = require(req_loc) - types_mt.__types_cache[key] = cluster_type + types_mt.__types_cache[key] = require("ElectricalPowerMeasurement.types." .. key) end return types_mt.__types_cache[key] end diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/init.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/init.lua new file mode 100644 index 0000000000..1155cfd636 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/init.lua @@ -0,0 +1,92 @@ +local cluster_base = require "st.matter.cluster_base" +local WaterHeaterModeServerAttributes = require "WaterHeaterMode.server.attributes" +local WaterHeaterModeServerCommands = require "WaterHeaterMode.server.commands" +local WaterHeaterModeTypes = require "WaterHeaterMode.types" + +local WaterHeaterMode = {} + +WaterHeaterMode.ID = 0x009E +WaterHeaterMode.NAME = "WaterHeaterMode" +WaterHeaterMode.server = {} +WaterHeaterMode.client = {} +WaterHeaterMode.server.attributes = WaterHeaterModeServerAttributes:set_parent_cluster(WaterHeaterMode) +WaterHeaterMode.server.commands = WaterHeaterModeServerCommands:set_parent_cluster(WaterHeaterMode) +WaterHeaterMode.types = WaterHeaterModeTypes + +function WaterHeaterMode:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "SupportedModes", + [0x0001] = "CurrentMode", + [0x0002] = "StartUpMode", + [0x0003] = "OnMode", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function WaterHeaterMode:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "ChangeToMode", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +WaterHeaterMode.attribute_direction_map = { + ["SupportedModes"] = "server", + ["CurrentMode"] = "server", + ["StartUpMode"] = "server", + ["OnMode"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + +WaterHeaterMode.command_direction_map = { + ["ChangeToMode"] = "server", + ["ChangeToModeResponse"] = "client", +} + +WaterHeaterMode.FeatureMap = WaterHeaterMode.types.Feature + +function WaterHeaterMode.are_features_supported(feature, feature_map) + if (WaterHeaterMode.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = WaterHeaterMode.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, WaterHeaterMode.NAME)) + end + return WaterHeaterMode[direction].attributes[key] +end +WaterHeaterMode.attributes = {} +setmetatable(WaterHeaterMode.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = WaterHeaterMode.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, WaterHeaterMode.NAME)) + end + return WaterHeaterMode[direction].commands[key] +end +WaterHeaterMode.commands = {} +setmetatable(WaterHeaterMode.commands, command_helper_mt) + +setmetatable(WaterHeaterMode, {__index = cluster_base}) + +return WaterHeaterMode + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/AcceptedCommandList.lua similarity index 75% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AcceptedCommandList.lua rename to drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/AcceptedCommandList.lua index fd4855a236..6a8d95df1d 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AcceptedCommandList.lua +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/AcceptedCommandList.lua @@ -9,20 +9,15 @@ local AcceptedCommandList = { element_type = require "st.matter.data_types.Uint32", } -AcceptedCommandList.enum_fields = {} - -function AcceptedCommandList:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print -end - -function AcceptedCommandList.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AcceptedCommandList.enum_fields[value_obj.value]) +function AcceptedCommandList: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, AcceptedCommandList.element_type) + end end function AcceptedCommandList:new_value(...) local o = self.base_type(table.unpack({...})) - self:augment_type(o) + return o end @@ -58,7 +53,7 @@ function AcceptedCommandList:build_test_report_data( 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, @@ -71,9 +66,10 @@ end function AcceptedCommandList:deserialize(tlv_buf) local data = TLVParser.decode_tlv(tlv_buf) - self:augment_type(data) + return data end -setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value, __index = AcceptedCommandList.base_type}) return AcceptedCommandList + diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/CurrentMode.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/CurrentMode.lua new file mode 100644 index 0000000000..aa20156f74 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/CurrentMode.lua @@ -0,0 +1,69 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentMode = { + ID = 0x0001, + NAME = "CurrentMode", + base_type = require "st.matter.data_types.Uint8", +} + +function CurrentMode:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function CurrentMode:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + + +function CurrentMode:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentMode: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 CurrentMode:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(CurrentMode, {__call = CurrentMode.new_value, __index = CurrentMode.base_type}) +return CurrentMode + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/SupportedModes.lua similarity index 51% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AttributeList.lua rename to drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/SupportedModes.lua index eea5f89c63..1f393a17d4 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AttributeList.lua +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/SupportedModes.lua @@ -2,57 +2,51 @@ local cluster_base = require "st.matter.cluster_base" local data_types = require "st.matter.data_types" local TLVParser = require "st.matter.TLV.TLVParser" -local AttributeList = { - ID = 0xFFFB, - NAME = "AttributeList", +local SupportedModes = { + ID = 0x0000, + NAME = "SupportedModes", base_type = require "st.matter.data_types.Array", - element_type = require "st.matter.data_types.Uint32", + element_type = require "WaterHeaterMode.types.ModeOptionStruct", } -AttributeList.enum_fields = { -} - -function AttributeList:augment_type(base_type_obj) - base_type_obj.field_name = self.NAME - base_type_obj.pretty_print = self.pretty_print +function SupportedModes: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, SupportedModes.element_type) + end end -function AttributeList.pretty_print(value_obj) - return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AttributeList.enum_fields[value_obj.value]) -end - -function AttributeList:new_value(...) +function SupportedModes:new_value(...) local o = self.base_type(table.unpack({...})) self:augment_type(o) return o end -function AttributeList:read(device, endpoint_id) +function SupportedModes:read(device, endpoint_id) return cluster_base.read( device, endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end -function AttributeList:subscribe(device, endpoint_id) +function SupportedModes:subscribe(device, endpoint_id) return cluster_base.subscribe( device, endpoint_id, self._cluster.ID, self.ID, - nil --event_id + nil ) end -function AttributeList:set_parent_cluster(cluster) +function SupportedModes:set_parent_cluster(cluster) self._cluster = cluster return self end -function AttributeList:build_test_report_data( +function SupportedModes:build_test_report_data( device, endpoint_id, value, @@ -70,11 +64,12 @@ function AttributeList:build_test_report_data( ) end -function AttributeList:deserialize(tlv_buf) +function SupportedModes:deserialize(tlv_buf) local data = TLVParser.decode_tlv(tlv_buf) self:augment_type(data) return data end -setmetatable(AttributeList, {__call = AttributeList.new_value}) -return AttributeList +setmetatable(SupportedModes, {__call = SupportedModes.new_value, __index = SupportedModes.base_type}) +return SupportedModes + diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/init.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/init.lua new file mode 100644 index 0000000000..020a4125ce --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/attributes/init.lua @@ -0,0 +1,24 @@ +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("WaterHeaterMode.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 WaterHeaterModeServerAttributes = {} + +function WaterHeaterModeServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(WaterHeaterModeServerAttributes, attr_mt) + +return WaterHeaterModeServerAttributes + diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/ChangeToMode.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/ChangeToMode.lua new file mode 100644 index 0000000000..73ddbd1029 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/ChangeToMode.lua @@ -0,0 +1,87 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ChangeToMode = {} + +ChangeToMode.NAME = "ChangeToMode" +ChangeToMode.ID = 0x0000 +ChangeToMode.field_defs = { + { + name = "new_mode", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, +} + +function ChangeToMode:init(device, endpoint_id, new_mode) + local out = {} + local args = {new_mode} + 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 = ChangeToMode, + __tostring = ChangeToMode.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function ChangeToMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ChangeToMode: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 ChangeToMode:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(ChangeToMode, {__call = ChangeToMode.init}) + +return ChangeToMode + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/commands/init.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/init.lua similarity index 54% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/commands/init.lua rename to drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/init.lua index 087bb47a9e..9736c4577f 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/commands/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/server/commands/init.lua @@ -2,7 +2,7 @@ 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("ElectricalPowerMeasurement.server.commands.%s", key) + local req_loc = string.format("WaterHeaterMode.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) @@ -10,14 +10,14 @@ command_mt.__index = function(self, key) return command_mt.__command_cache[key] end -local ElectricalPowerMeasurementServerCommands = {} +local WaterHeaterModeServerCommands = {} -function ElectricalPowerMeasurementServerCommands:set_parent_cluster(cluster) +function WaterHeaterModeServerCommands:set_parent_cluster(cluster) self._cluster = cluster return self end -setmetatable(ElectricalPowerMeasurementServerCommands, command_mt) +setmetatable(WaterHeaterModeServerCommands, command_mt) -return ElectricalPowerMeasurementServerCommands +return WaterHeaterModeServerCommands diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/Feature.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/Feature.lua new file mode 100644 index 0000000000..da49bf4115 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/Feature.lua @@ -0,0 +1,54 @@ +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.ON_OFF = 0x0001 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + ON_OFF = 0x0001, +} + +Feature.is_on_off_set = function(self) + return (self.value & self.ON_OFF) ~= 0 +end + +Feature.set_on_off = function(self) + if self.value ~= nil then + self.value = self.value | self.ON_OFF + else + self.value = self.ON_OFF + end +end + +Feature.unset_on_off = function(self) + self.value = self.value & (~self.ON_OFF & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.ON_OFF + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_on_off_set = Feature.is_on_off_set, + set_on_off = Feature.set_on_off, + unset_on_off = Feature.unset_on_off, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyStruct.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeOptionStruct.lua similarity index 54% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyStruct.lua rename to drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeOptionStruct.lua index be596893f6..f770a7916c 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyStruct.lua +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeOptionStruct.lua @@ -1,51 +1,40 @@ local data_types = require "st.matter.data_types" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" -local MeasurementTypeEnum = require "ElectricalPowerMeasurement.types.MeasurementTypeEnum" -local MeasurementAccuracyRangeStruct = require "ElectricalPowerMeasurement.types.MeasurementAccuracyRangeStruct" -local MeasurementAccuracyStruct = {} -local new_mt = StructureABC.new_mt({NAME = "MeasurementAccuracyStruct", ID = data_types.name_to_id_map["Structure"]}) -MeasurementAccuracyStruct.field_defs = { +local ModeOptionStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeOptionStruct", ID = data_types.name_to_id_map["Structure"]}) +ModeOptionStruct.field_defs = { { - data_type = MeasurementTypeEnum, + name = "label", field_id = 0, - name = "measurement_type", - }, - { - data_type = data_types.Boolean, - field_id = 1, - name = "measured", is_nullable = false, is_optional = false, + data_type = require "st.matter.data_types.UTF8String1", }, { - data_type = data_types.Int64, - field_id = 2, - name = "min_measured_value", + name = "mode", + field_id = 1, is_nullable = false, is_optional = false, + data_type = require "st.matter.data_types.Uint8", }, { - data_type = data_types.Int64, - field_id = 3, - name = "max_measured_value", + name = "mode_tags", + field_id = 2, is_nullable = false, is_optional = false, - }, - { - data_type = MeasurementAccuracyRangeStruct, - field_id = 4, - name = "accuracy_ranges", + data_type = require "st.matter.data_types.Array", + element_type = require "WaterHeaterMode.types.ModeTagStruct", }, } -MeasurementAccuracyStruct.init = function(cls, tbl) +ModeOptionStruct.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 or not field_def.is_nullable) and not tbl[field_def.name] then + 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 o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) @@ -56,36 +45,41 @@ MeasurementAccuracyStruct.init = function(cls, tbl) return o end -MeasurementAccuracyStruct.serialize = function(self, buf, include_control, tag) +ModeOptionStruct.serialize = function(self, buf, include_control, tag) return data_types['Structure'].serialize(self.elements, buf, include_control, tag) end -new_mt.__call = MeasurementAccuracyStruct.init -new_mt.__index.serialize = MeasurementAccuracyStruct.serialize +new_mt.__call = ModeOptionStruct.init +new_mt.__index.serialize = ModeOptionStruct.serialize -MeasurementAccuracyStruct.augment_type = function(self, val) +ModeOptionStruct.augment_type = function(self, val) local elems = {} - for _, v in ipairs(val.elements) do + 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) then elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) - if field_def.array_type ~= nil then + 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.array_type) + 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(MeasurementAccuracyStruct, new_mt) +setmetatable(ModeOptionStruct, new_mt) + +return ModeOptionStruct -return MeasurementAccuracyStruct diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTag.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTag.lua new file mode 100644 index 0000000000..009e70a40e --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTag.lua @@ -0,0 +1,31 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ModeTag = {} +local new_mt = UintABC.new_mt({NAME = "ModeTag", ID = data_types.name_to_id_map["Uint16"]}, 2) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.NORMAL] = "NORMAL", + [self.HEAVY] = "HEAVY", + [self.LIGHT] = "LIGHT", + } + 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.NORMAL = 0x4000 +new_mt.__index.HEAVY = 0x4001 +new_mt.__index.LIGHT = 0x4002 + +ModeTag.NORMAL = 0x4000 +ModeTag.HEAVY = 0x4001 +ModeTag.LIGHT = 0x4002 + +ModeTag.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ModeTag, new_mt) + +return ModeTag + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/HarmonicMeasurementStruct.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTagStruct.lua similarity index 59% rename from drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/HarmonicMeasurementStruct.lua rename to drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTagStruct.lua index 1398736a19..1c41eb320e 100644 --- a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/HarmonicMeasurementStruct.lua +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/ModeTagStruct.lua @@ -1,33 +1,33 @@ local data_types = require "st.matter.data_types" local StructureABC = require "st.matter.data_types.base_defs.StructureABC" -local HarmonicMeasurementStruct = {} -local new_mt = StructureABC.new_mt({NAME = "HarmonicMeasurementStruct", ID = data_types.name_to_id_map["Structure"]}) +local ModeTagStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeTagStruct", ID = data_types.name_to_id_map["Structure"]}) -HarmonicMeasurementStruct.field_defs = { +ModeTagStruct.field_defs = { { - data_type = data_types.Uint8, + name = "mfg_code", field_id = 0, - name = "order", is_nullable = false, - is_optional = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", }, { - data_type = data_types.Int64, + name = "value", field_id = 1, - name = "measurement", - is_nullable = true, - is_optional = true, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", }, } -HarmonicMeasurementStruct.init = function(cls, tbl) +ModeTagStruct.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 or not field_def.is_nullable) and not tbl[field_def.name] then + 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 o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) @@ -38,36 +38,41 @@ HarmonicMeasurementStruct.init = function(cls, tbl) return o end -HarmonicMeasurementStruct.serialize = function(self, buf, include_control, tag) +ModeTagStruct.serialize = function(self, buf, include_control, tag) return data_types['Structure'].serialize(self.elements, buf, include_control, tag) end -new_mt.__call = HarmonicMeasurementStruct.init -new_mt.__index.serialize = HarmonicMeasurementStruct.serialize +new_mt.__call = ModeTagStruct.init +new_mt.__index.serialize = ModeTagStruct.serialize -HarmonicMeasurementStruct.augment_type = function(self, val) +ModeTagStruct.augment_type = function(self, val) local elems = {} - for _, v in ipairs(val.elements) do + 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) then elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) - if field_def.array_type ~= nil then + 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.array_type) + 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(HarmonicMeasurementStruct, new_mt) +setmetatable(ModeTagStruct, new_mt) + +return ModeTagStruct -return HarmonicMeasurementStruct diff --git a/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/init.lua b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/init.lua new file mode 100644 index 0000000000..f0198ff8a0 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/WaterHeaterMode/types/init.lua @@ -0,0 +1,15 @@ +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("WaterHeaterMode.types." .. key) + end + return types_mt.__types_cache[key] +end + +local WaterHeaterModeTypes = {} + +setmetatable(WaterHeaterModeTypes, types_mt) + +return WaterHeaterModeTypes + diff --git a/drivers/SmartThings/matter-thermostat/src/embedded-cluster-utils.lua b/drivers/SmartThings/matter-thermostat/src/embedded-cluster-utils.lua index fd4ab43240..3c38ef55d0 100644 --- a/drivers/SmartThings/matter-thermostat/src/embedded-cluster-utils.lua +++ b/drivers/SmartThings/matter-thermostat/src/embedded-cluster-utils.lua @@ -19,9 +19,18 @@ if version.api < 10 then clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" end +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" +end + +if version.api < 13 then + clusters.WaterHeaterMode = require "WaterHeaterMode" +end + local embedded_cluster_utils = {} -local embedded_clusters = { +local embedded_clusters_api_10 = { [clusters.HepaFilterMonitoring.ID] = clusters.HepaFilterMonitoring, [clusters.ActivatedCarbonFilterMonitoring.ID] = clusters.ActivatedCarbonFilterMonitoring, [clusters.AirQuality.ID] = clusters.AirQuality, @@ -37,11 +46,22 @@ local embedded_clusters = { [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID] = clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement, } +local embedded_clusters_api_11 = { + [clusters.ElectricalEnergyMeasurement.ID] = clusters.ElectricalEnergyMeasurement, + [clusters.ElectricalPowerMeasurement.ID] = clusters.ElectricalPowerMeasurement, +} + +local embedded_clusters_api_13 = { + [clusters.WaterHeaterMode.ID] = clusters.WaterHeaterMode +} + 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 < 10 and embedded_clusters[cluster_id] ~= nil then - local embedded_cluster = embedded_clusters[cluster_id] + if version.api < 10 and embedded_clusters_api_10[cluster_id] ~= nil or + version.api < 11 and embedded_clusters_api_11[cluster_id] ~= nil or + version.api < 13 and embedded_clusters_api_13[cluster_id] ~= nil then + local embedded_cluster = embedded_clusters_api_10[cluster_id] or embedded_clusters_api_11[cluster_id] or embedded_clusters_api_13[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") diff --git a/drivers/SmartThings/matter-thermostat/src/init.lua b/drivers/SmartThings/matter-thermostat/src/init.lua index b01371edfe..ff0914d00d 100644 --- a/drivers/SmartThings/matter-thermostat/src/init.lua +++ b/drivers/SmartThings/matter-thermostat/src/init.lua @@ -44,6 +44,15 @@ end local DISALLOWED_THERMOSTAT_MODES = "__DISALLOWED_CONTROL_OPERATIONS" +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" +end + +if version.api < 13 then + clusters.WaterHeaterMode = require "WaterHeaterMode" +end + local THERMOSTAT_MODE_MAP = { [clusters.Thermostat.types.ThermostatSystemMode.OFF] = capabilities.thermostatMode.thermostatMode.off, [clusters.Thermostat.types.ThermostatSystemMode.AUTO] = capabilities.thermostatMode.thermostatMode.auto, @@ -57,18 +66,18 @@ local THERMOSTAT_MODE_MAP = { } local THERMOSTAT_OPERATING_MODE_MAP = { - [0] = capabilities.thermostatOperatingState.thermostatOperatingState.heating, - [1] = capabilities.thermostatOperatingState.thermostatOperatingState.cooling, - [2] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, - [3] = capabilities.thermostatOperatingState.thermostatOperatingState.heating, - [4] = capabilities.thermostatOperatingState.thermostatOperatingState.cooling, - [5] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, - [6] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, + [0] = capabilities.thermostatOperatingState.thermostatOperatingState.heating, + [1] = capabilities.thermostatOperatingState.thermostatOperatingState.cooling, + [2] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, + [3] = capabilities.thermostatOperatingState.thermostatOperatingState.heating, + [4] = capabilities.thermostatOperatingState.thermostatOperatingState.cooling, + [5] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, + [6] = capabilities.thermostatOperatingState.thermostatOperatingState.fan_only, } local WIND_MODE_MAP = { - [0] = capabilities.windMode.windMode.sleepWind, - [1] = capabilities.windMode.windMode.naturalWind + [0] = capabilities.windMode.windMode.sleepWind, + [1] = capabilities.windMode.windMode.naturalWind } local ROCK_MODE_MAP = { @@ -80,20 +89,42 @@ local ROCK_MODE_MAP = { local RAC_DEVICE_TYPE_ID = 0x0072 local AP_DEVICE_TYPE_ID = 0x002D local FAN_DEVICE_TYPE_ID = 0x002B +local WATER_HEATER_DEVICE_TYPE_ID = 0x050F +local HEAT_PUMP_DEVICE_TYPE_ID = 0x0309 +local THERMOSTAT_DEVICE_TYPE_ID = 0x0301 +local ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510 local MIN_ALLOWED_PERCENT_VALUE = 0 local MAX_ALLOWED_PERCENT_VALUE = 100 - +local DEFAULT_REPORT_TIME_INTERVAL = 15 * 60 -- Report cumulative energy every 15 minutes +local MAX_REPORT_TIMEOUT = 30 * 60 +local POLL_INTERVAL = 60 -- To read CumulativeEnergyImported every 60 seconds. + +local RECURRING_POLL_TIMER = "__recurring_poll_timer" +local RECURRING_REPORT_TIMER = "__recurring_report_poll_timer" +local DEVICE_POWER_CONSUMPTION_REPORT_TIME_INTERVAL = "__pcr_time_interval" +local DEVICE_REPORTING_TIME_INTERVAL_CONSIDERED = "__timer_interval_considered" +local TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP = "__total_cumulative_energy_imported_map" +local LAST_REPORTED_TIME = "__last_reported_time" +local SUPPORTED_WATER_HEATER_MODES_WITH_IDX = "__supported_water_heater_modes_with_idx" +local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" local MGM3_PPM_CONVERSION_FACTOR = 24.45 -- This is a work around to handle when units for temperatureSetpoint is changed for the App. -- When units are switched, we will never know the units of the received command value as the arguments don't contain the unit. --- So to handle this we assume the following ranges considering usual laundry temperatures: +-- So to handle this we assume the following ranges considering usual thermostat/water-heater temperatures: +-- Thermostat: -- 1. if the received setpoint command value is in range 5 ~ 40, it is inferred as *C -- 2. if the received setpoint command value is in range 41 ~ 104, it is inferred as *F local THERMOSTAT_MAX_TEMP_IN_C = 40.0 local THERMOSTAT_MIN_TEMP_IN_C = 5.0 +-- Water Heater: +-- 1. if the received setpoint command value is in range 30 ~ 80, it is inferred as *C +-- 2. if the received setpoint command value is in range 86 ~ 176, it is inferred as *F +local WATER_HEATER_MAX_TEMP_IN_C = 80.0 +local WATER_HEATER_MIN_TEMP_IN_C = 30.0 + local setpoint_limit_device_field = { MIN_SETPOINT_DEADBAND_CHECKED = "MIN_SETPOINT_DEADBAND_CHECKED", MIN_HEAT = "MIN_HEAT", @@ -251,9 +282,100 @@ local subscribed_attributes = { }, [capabilities.tvocHealthConcern.ID] = { clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue - } + }, + [capabilities.powerMeter.ID] = { + clusters.ElectricalPowerMeasurement.attributes.ActivePower + }, + [capabilities.mode.ID] = { + clusters.WaterHeaterMode.attributes.CurrentMode, + clusters.WaterHeaterMode.attributes.SupportedModes + }, + [capabilities.powerConsumptionReport.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported + }, + [capabilities.energyMeter.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported + }, } +local function epoch_to_iso8601(time) + return os.date("!%Y-%m-%dT%H:%M:%SZ", time) +end + +local function tbl_contains(array, value) + for _, element in ipairs(array) do + if element == value then + return true + end + end + return false +end + +local get_total_cumulative_energy_imported = function(device) + local total_cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} + local total_energy = 0 + for _, energyWh in pairs(total_cumulative_energy_imported) do + total_energy = total_energy + energyWh + end + return total_energy +end + +local function schedule_energy_report_timer(device) + if not device:supports_capability(capabilities.powerConsumptionReport) then + return + end + + local polling_schedule_timer = device:get_field(RECURRING_REPORT_TIMER) + if polling_schedule_timer ~= nil then + return + end + + -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy + local pcr_interval = device:get_field(DEVICE_POWER_CONSUMPTION_REPORT_TIME_INTERVAL) or DEFAULT_REPORT_TIME_INTERVAL + pcr_interval = utils.clamp_value(pcr_interval, DEFAULT_REPORT_TIME_INTERVAL, MAX_REPORT_TIMEOUT) + local timer = device.thread:call_on_schedule(pcr_interval, function() + local last_time = device:get_field(LAST_REPORTED_TIME) or 0 + local current_time = os.time() + local total_energy = get_total_cumulative_energy_imported(device) + device:set_field(LAST_REPORTED_TIME, current_time, { persist = true }) + + -- Calculate the energy consumed between the start and the end time + local previousTotalConsumptionWh = device:get_latest_state( + "main", capabilities.powerConsumptionReport.ID, capabilities.powerConsumptionReport.powerConsumption.NAME + ) or { energy = 0 } + local deltaEnergyWh = math.max(total_energy - previousTotalConsumptionWh.energy, 0.0) + local startTime = epoch_to_iso8601(last_time) + local endTime = epoch_to_iso8601(current_time - 1) + + -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ + start = startTime, + ["end"] = endTime, + deltaEnergy = deltaEnergyWh, + energy = total_energy + })) + end, "polling_report_schedule_timer") + + device:set_field(RECURRING_REPORT_TIMER, timer) +end + +local function delete_reporting_timer(device) + local reporting_poll_timer = device:get_field(RECURRING_REPORT_TIMER) + if reporting_poll_timer ~= nil then + device.thread:cancel_timer(reporting_poll_timer) + device:set_field(RECURRING_REPORT_TIMER, nil) + end +end + +local function device_removed(driver, device) + delete_reporting_timer(device) + local poll_timer = device:get_field(RECURRING_POLL_TIMER) + if poll_timer ~= nil then + device.thread:cancel_timer(poll_timer) + device:set_field(RECURRING_POLL_TIMER, nil) + end +end + local function get_field_for_endpoint(device, field, endpoint) return device:get_field(string.format("%s_%d", field, endpoint)) end @@ -278,6 +400,10 @@ end local function component_to_endpoint(device, component_name) -- 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) @@ -287,9 +413,56 @@ local function component_to_endpoint(device, component_name) end end +local endpoint_to_component = function (device, endpoint_id) + local component_to_endpoint_map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) + if component_to_endpoint_map ~= nil then + for comp, ep in pairs(component_to_endpoint_map) do + if ep == endpoint_id then + return comp + end + end + end + return "main" +end + +local function create_poll_schedule(device) + local poll_timer = device:get_field(RECURRING_POLL_TIMER) + if poll_timer ~= nil then + return + end + + local cumul_imp_eps = embedded_cluster_utils.get_endpoints( + device, clusters.ElectricalEnergyMeasurement.ID, + { + feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY | + clusters.ElectricalEnergyMeasurement.types.Feature.IMPORTED_ENERGY + } + ) or {} + if #cumul_imp_eps == 0 then + return + end + + device:send(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(device)) + -- Setup a timer to read cumulative energy imported attribute every minute. + local timer = device.thread:call_on_schedule(POLL_INTERVAL, function() + device:send(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(device)) + end, "polling_schedule_timer") + + device:set_field(RECURRING_POLL_TIMER, timer) +end + +local function schedule_polls_for_cumulative_energy_imported(device) + if not device:supports_capability(capabilities.powerConsumptionReport) then + return + end + create_poll_schedule(device) + schedule_energy_report_timer(device) +end + local function device_init(driver, device) device:subscribe() device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) if not device:get_field(setpoint_limit_device_field.MIN_SETPOINT_DEADBAND_CHECKED) then local auto_eps = device:get_endpoints(clusters.Thermostat.ID, {feature_bitmap = clusters.Thermostat.types.ThermostatFeature.AUTOMODE}) --Query min setpoint deadband if needed @@ -299,10 +472,11 @@ local function device_init(driver, device) device:send(deadband_read) end end + schedule_polls_for_cumulative_energy_imported(device) end local function info_changed(driver, device, event, args) - --Note this is needed because device:subscribe() does not recalculate + -- Note this is needed because device:subscribe() does not recalculate -- the subscribed attributes each time it is run, that only happens at init. -- This will change in the 0.48.x release of the lua libs. for cap_id, attributes in pairs(subscribed_attributes) do @@ -313,17 +487,38 @@ local function info_changed(driver, device, event, args) end end device:subscribe() + schedule_polls_for_cumulative_energy_imported(device) end -local function get_device_type(driver, device) +local function get_endpoints_for_dt(device, device_type) + local endpoints = {} for _, ep in ipairs(device.endpoints) do for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == RAC_DEVICE_TYPE_ID then - return RAC_DEVICE_TYPE_ID - elseif dt.device_type_id == AP_DEVICE_TYPE_ID then - return AP_DEVICE_TYPE_ID - elseif dt.device_type_id == FAN_DEVICE_TYPE_ID then - return FAN_DEVICE_TYPE_ID + if dt.device_type_id == device_type then + table.insert(endpoints, ep.endpoint_id) + break + end + end + end + table.sort(endpoints) + return endpoints +end + +local function get_device_type(driver, device) + for _, ep in ipairs(device.endpoints) do + if ep.device_types ~= nil then + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == RAC_DEVICE_TYPE_ID then + return RAC_DEVICE_TYPE_ID + elseif dt.device_type_id == AP_DEVICE_TYPE_ID then + return AP_DEVICE_TYPE_ID + elseif dt.device_type_id == FAN_DEVICE_TYPE_ID then + return FAN_DEVICE_TYPE_ID + elseif dt.device_type_id == WATER_HEATER_DEVICE_TYPE_ID then + return WATER_HEATER_DEVICE_TYPE_ID + elseif dt.device_type_id == HEAT_PUMP_DEVICE_TYPE_ID then + return HEAT_PUMP_DEVICE_TYPE_ID + end end end end @@ -514,7 +709,22 @@ local function match_profile(driver, device, battery_supported) end end profile_name = profile_name .. create_air_quality_sensor_profile(device) - + elseif device_type == WATER_HEATER_DEVICE_TYPE_ID then + -- If a Water Heater is composed of Electrical Sensor device type, it must support both ElectricalEnergyMeasurement and + -- ElectricalPowerMeasurement clusters. + local electrical_sensor_eps = get_endpoints_for_dt(device, ELECTRICAL_SENSOR_DEVICE_TYPE_ID) or {} + if #electrical_sensor_eps > 0 then + profile_name = "water-heater-power-energy-powerConsumption" + end + elseif device_type == HEAT_PUMP_DEVICE_TYPE_ID then + profile_name = "heat-pump" + local MAX_HEAT_PUMP_THERMOSTAT_COMPONENTS = 2 + for i = 1, math.min(MAX_HEAT_PUMP_THERMOSTAT_COMPONENTS, #thermostat_eps) do + profile_name = profile_name .. "-thermostat" + if tbl_contains(humidity_eps, thermostat_eps[i]) then + profile_name = profile_name .. "-humidity" + end + end elseif #thermostat_eps > 0 then profile_name = "thermostat" @@ -574,6 +784,15 @@ local function device_added(driver, device) req:merge(clusters.FanControl.attributes.WindSupport:read(device)) req:merge(clusters.FanControl.attributes.RockSupport:read(device)) device:send(req) + local heat_pump_eps = get_endpoints_for_dt(device, HEAT_PUMP_DEVICE_TYPE_ID) or {} + if #heat_pump_eps > 0 then + local thermostat_eps = get_endpoints_for_dt(device, THERMOSTAT_DEVICE_TYPE_ID) or {} + local component_to_endpoint_map = { + ["thermostatOne"] = thermostat_eps[1], + ["thermostatTwo"] = thermostat_eps[2], + } + device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_to_endpoint_map, {persist = true}) + end end local function store_unit_factory(capability_name) @@ -765,9 +984,17 @@ local function temp_event_handler(attribute) event = capabilities.thermostatCoolingSetpoint.coolingSetpointRange({value = range, unit = unit}) device:emit_event_for_endpoint(ib.endpoint_id, event) elseif attribute == capabilities.thermostatHeatingSetpoint.heatingSetpoint then + 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 + if is_water_heater_device then + MAX_TEMP_IN_C = WATER_HEATER_MAX_TEMP_IN_C + MIN_TEMP_IN_C = WATER_HEATER_MIN_TEMP_IN_C + end + local range = { - minimum = device:get_field(setpoint_limit_device_field.MIN_HEAT) or THERMOSTAT_MIN_TEMP_IN_C, - maximum = device:get_field(setpoint_limit_device_field.MAX_HEAT) or THERMOSTAT_MAX_TEMP_IN_C, + minimum = device:get_field(setpoint_limit_device_field.MIN_HEAT) or MIN_TEMP_IN_C, + maximum = device:get_field(setpoint_limit_device_field.MAX_HEAT) or MAX_TEMP_IN_C, step = 0.1 } event = capabilities.thermostatHeatingSetpoint.heatingSetpointRange({value = range, unit = unit}) @@ -1150,8 +1377,16 @@ end local function set_setpoint(setpoint) return function(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + 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 + if is_water_heater_device then + MAX_TEMP_IN_C = WATER_HEATER_MAX_TEMP_IN_C + MIN_TEMP_IN_C = WATER_HEATER_MIN_TEMP_IN_C + end local value = cmd.args.setpoint - if (value > THERMOSTAT_MAX_TEMP_IN_C) then -- assume this is a fahrenheit value + if (value > MAX_TEMP_IN_C) then -- assume this is a fahrenheit value value = utils.f_to_c(value) end @@ -1161,7 +1396,7 @@ local function set_setpoint(setpoint) local cached_cooling_val, cooling_setpoint = device:get_latest_state( cmd.component, capabilities.thermostatCoolingSetpoint.ID, capabilities.thermostatCoolingSetpoint.coolingSetpoint.NAME, - THERMOSTAT_MAX_TEMP_IN_C, { value = THERMOSTAT_MAX_TEMP_IN_C, unit = "C" } + MAX_TEMP_IN_C, { value = MAX_TEMP_IN_C, unit = "C" } ) if cooling_setpoint and cooling_setpoint.unit == "F" then cached_cooling_val = utils.f_to_c(cached_cooling_val) @@ -1169,7 +1404,7 @@ local function set_setpoint(setpoint) local cached_heating_val, heating_setpoint = device:get_latest_state( cmd.component, capabilities.thermostatHeatingSetpoint.ID, capabilities.thermostatHeatingSetpoint.heatingSetpoint.NAME, - THERMOSTAT_MIN_TEMP_IN_C, { value = THERMOSTAT_MIN_TEMP_IN_C, unit = "C" } + MIN_TEMP_IN_C, { value = MIN_TEMP_IN_C, unit = "C" } ) if heating_setpoint and heating_setpoint.unit == "F" then cached_heating_val = utils.f_to_c(cached_heating_val) @@ -1183,14 +1418,14 @@ local function set_setpoint(setpoint) local setpoint_type = string.match(setpoint.NAME, "Heat") or "Cool" local deadband = device:get_field(setpoint_limit_device_field.MIN_DEADBAND) or 2.5 --spec default if setpoint_type == "Heat" then - local min = device:get_field(setpoint_limit_device_field.MIN_HEAT) or THERMOSTAT_MIN_TEMP_IN_C - local max = device:get_field(setpoint_limit_device_field.MAX_HEAT) or THERMOSTAT_MAX_TEMP_IN_C + local min = device:get_field(setpoint_limit_device_field.MIN_HEAT) or MIN_TEMP_IN_C + local max = device:get_field(setpoint_limit_device_field.MAX_HEAT) or MAX_TEMP_IN_C if value < min or value > max then log.warn(string.format( "Invalid setpoint (%s) outside the min (%s) and the max (%s)", value, min, max )) - device:emit_event(capabilities.thermostatHeatingSetpoint.heatingSetpoint(heating_setpoint, {state_change = true})) + device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatHeatingSetpoint.heatingSetpoint(heating_setpoint, {state_change = true})) return end if is_auto_capable and value > (cached_cooling_val - deadband) then @@ -1198,18 +1433,18 @@ local function set_setpoint(setpoint) "Invalid setpoint (%s) is greater than the cooling setpoint (%s) with the deadband (%s)", value, cooling_setpoint, deadband )) - device:emit_event(capabilities.thermostatHeatingSetpoint.heatingSetpoint(heating_setpoint, {state_change = true})) + device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatHeatingSetpoint.heatingSetpoint(heating_setpoint, {state_change = true})) return end else - local min = device:get_field(setpoint_limit_device_field.MIN_COOL) or THERMOSTAT_MIN_TEMP_IN_C - local max = device:get_field(setpoint_limit_device_field.MAX_COOL) or THERMOSTAT_MAX_TEMP_IN_C + local min = device:get_field(setpoint_limit_device_field.MIN_COOL) or MIN_TEMP_IN_C + local max = device:get_field(setpoint_limit_device_field.MAX_COOL) or MAX_TEMP_IN_C if value < min or value > max then log.warn(string.format( "Invalid setpoint (%s) outside the min (%s) and the max (%s)", value, min, max )) - device:emit_event(capabilities.thermostatCoolingSetpoint.coolingSetpoint(cooling_setpoint, {state_change = true})) + device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatCoolingSetpoint.coolingSetpoint(cooling_setpoint, {state_change = true})) return end if is_auto_capable and value < (cached_heating_val + deadband) then @@ -1217,7 +1452,7 @@ local function set_setpoint(setpoint) "Invalid setpoint (%s) is less than the heating setpoint (%s) with the deadband (%s)", value, heating_setpoint, deadband )) - device:emit_event(capabilities.thermostatCoolingSetpoint.coolingSetpoint(cooling_setpoint, {state_change = true})) + device:emit_event_for_endpoint(endpoint_id, capabilities.thermostatCoolingSetpoint.coolingSetpoint(cooling_setpoint, {state_change = true})) return end end @@ -1230,8 +1465,15 @@ local heating_setpoint_limit_handler_factory = function(minOrMax) if ib.data.value == nil then return end + 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) + if is_water_heater_device then + MAX_TEMP_IN_C = WATER_HEATER_MAX_TEMP_IN_C + MIN_TEMP_IN_C = WATER_HEATER_MIN_TEMP_IN_C + end local val = ib.data.value / 100.0 - val = utils.clamp_value(val, THERMOSTAT_MIN_TEMP_IN_C, THERMOSTAT_MAX_TEMP_IN_C) + val = utils.clamp_value(val, MIN_TEMP_IN_C, MAX_TEMP_IN_C) device:set_field(minOrMax, val) local min = device:get_field(setpoint_limit_device_field.MIN_HEAT) local max = device:get_field(setpoint_limit_device_field.MAX_HEAT) @@ -1362,12 +1604,115 @@ local function set_rock_mode(driver, device, cmd) device:send(clusters.FanControl.attributes.RockSetting:write(device, device:component_to_endpoint(cmd.component), 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 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 + device:send(clusters.WaterHeaterMode.commands.ChangeToMode(device, endpoint_id, mode[1])) + return + end + 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))) end end +local function active_power_handler(driver, device, ib, response) + if ib.data.value then + local watt_value = ib.data.value / 1000 + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.powerMeter.power({ value = watt_value, unit = "W" })) + end +end + +local function periodic_energy_imported_handler(driver, device, ib, response) + local endpoint_id = ib.endpoint_id + local cumul_eps = embedded_cluster_utils.get_endpoints(device, + clusters.ElectricalEnergyMeasurement.ID, + { feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY }) + + if ib.data then + if version.api < 11 then + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:augment_type(ib.data) + end + + local start_timestamp = ib.data.elements.start_timestamp.value or 0 + local end_timestamp = ib.data.elements.end_timestamp.value or 0 + + local device_reporting_time_interval = end_timestamp - start_timestamp + if not device:get_field(DEVICE_REPORTING_TIME_INTERVAL_CONSIDERED) and device_reporting_time_interval > DEFAULT_REPORT_TIME_INTERVAL then + -- This is a one time setup in order to consider a larger time interval if the interval the device chooses to report is greater than 15 minutes. + device:set_field(DEVICE_REPORTING_TIME_INTERVAL_CONSIDERED, true, { persist = true }) + device:set_field(DEVICE_POWER_CONSUMPTION_REPORT_TIME_INTERVAL, device_reporting_time_interval, { persist = true }) + delete_reporting_timer(device) + schedule_energy_report_timer(device) + end + + if tbl_contains(cumul_eps, endpoint_id) then + -- Since cluster at this endpoint supports both CUME & PERE features, we will prefer + -- cumulative_energy_imported_handler to handle the energy report for this endpoint. + return + end + + endpoint_id = string.format(ib.endpoint_id) + local energy_imported_Wh = utils.round(ib.data.elements.energy.value / 1000) --convert mWh to Wh + local cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} + cumulative_energy_imported[endpoint_id] = cumulative_energy_imported[endpoint_id] or 0 + cumulative_energy_imported[endpoint_id] = cumulative_energy_imported[endpoint_id] + energy_imported_Wh + device:set_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP, cumulative_energy_imported, { persist = true }) + local total_cumulative_energy_imported = get_total_cumulative_energy_imported(device) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.energyMeter.energy({value = total_cumulative_energy_imported, unit = "Wh"})) + end +end + +local function cumulative_energy_imported_handler(driver, device, ib, response) + if ib.data then + if version.api < 11 then + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:augment_type(ib.data) + end + local endpoint_id = string.format(ib.endpoint_id) + local cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP) or {} + local cumulative_energy_imported_Wh = utils.round( ib.data.elements.energy.value / 1000) -- convert mWh to Wh + cumulative_energy_imported[endpoint_id] = cumulative_energy_imported_Wh + device:set_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED_MAP, cumulative_energy_imported, { persist = true }) + local total_cumulative_energy_imported = get_total_cumulative_energy_imported(device) + device:emit_event(capabilities.energyMeter.energy({ value = total_cumulative_energy_imported, unit = "Wh" })) + end +end + +local function water_heater_supported_modes_attr_handler(driver, device, ib, response) + local supportWaterHeaterModes = {} + local supportWaterHeaterModesWithIdx = {} + for _, mode in ipairs(ib.data.elements) do + if version.api < 13 then + clusters.WaterHeaterMode.types.ModeOptionStruct:augment_type(mode) + end + table.insert(supportWaterHeaterModes, mode.elements.label.value) + table.insert(supportWaterHeaterModesWithIdx, {mode.elements.mode.value, mode.elements.label.value}) + end + device:set_field(SUPPORTED_WATER_HEATER_MODES_WITH_IDX, supportWaterHeaterModesWithIdx, { persist = true }) + local event = capabilities.mode.supportedModes(supportWaterHeaterModes, { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) + event = capabilities.mode.supportedArguments(supportWaterHeaterModes, { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +local function water_heater_mode_handler(driver, device, ib, response) + device.log.info(string.format("water_heater_mode_handler mode: %s", ib.data.value)) + local supportWaterHeaterModesWithIdx = device:get_field(SUPPORTED_WATER_HEATER_MODES_WITH_IDX) or {} + local currentMode = ib.data.value + for i, mode in ipairs(supportWaterHeaterModesWithIdx) do + if mode[1] == currentMode then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.mode.mode(mode[2])) + break + end + end +end + local function battery_charge_level_attr_handler(driver, device, ib, response) if ib.data.value == clusters.PowerSource.types.BatChargeLevelEnum.OK then device:emit_event(capabilities.batteryLevel.battery.normal()) @@ -1398,6 +1743,7 @@ local matter_driver_template = { added = device_added, doConfigure = do_configure, infoChanged = info_changed, + removed = device_removed }, matter_handlers = { attr = { @@ -1499,8 +1845,19 @@ local matter_driver_template = { [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue.ID] = measurementHandlerFactory(capabilities.tvocMeasurement.NAME, capabilities.tvocMeasurement.tvocLevel, units.PPB), [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit.ID] = store_unit_factory(capabilities.tvocMeasurement.NAME), [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue.ID] = levelHandlerFactory(capabilities.tvocHealthConcern.tvocHealthConcern) - } - } + }, + [clusters.ElectricalPowerMeasurement.ID] = { + [clusters.ElectricalPowerMeasurement.attributes.ActivePower.ID] = active_power_handler + }, + [clusters.ElectricalEnergyMeasurement.ID] = { + [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = cumulative_energy_imported_handler, + [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = periodic_energy_imported_handler + }, + [clusters.WaterHeaterMode.ID] = { + [clusters.WaterHeaterMode.attributes.CurrentMode.ID] = water_heater_mode_handler, + [clusters.WaterHeaterMode.attributes.SupportedModes.ID] = water_heater_supported_modes_attr_handler + }, + }, }, subscribed_attributes = subscribed_attributes, capability_handlers = { @@ -1541,6 +1898,9 @@ local matter_driver_template = { }, [capabilities.fanOscillationMode.ID] = { [capabilities.fanOscillationMode.commands.setFanOscillationMode.NAME] = set_rock_mode, + }, + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = set_water_heater_mode, } }, supported_capabilities = { @@ -1578,10 +1938,14 @@ local matter_driver_template = { capabilities.radonHealthConcern, capabilities.radonMeasurement, capabilities.tvocHealthConcern, - capabilities.tvocMeasurement + capabilities.tvocMeasurement, + capabilities.powerMeter, + capabilities.energyMeter, + capabilities.powerConsumptionReport, + capabilities.mode }, } local matter_driver = MatterDriver("matter-thermostat", matter_driver_template) log.info_with({hub_logs=true}, string.format("Starting %s driver, with dispatcher: %s", matter_driver.NAME, matter_driver.matter_dispatcher)) -matter_driver:run() \ No newline at end of file +matter_driver:run() diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_heat_pump.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_heat_pump.lua new file mode 100644 index 0000000000..bd5ac646d4 --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_heat_pump.lua @@ -0,0 +1,849 @@ +-- Copyright 2024 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 clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local version = require "version" +local im = require "st.matter.interaction_model" + +local HEAT_PUMP_EP = 10 +local THERMOSTAT_ONE_EP = 20 +local THERMOSTAT_TWO_EP = 30 + +local HEAT_PUMP_DEVICE_TYPE_ID = 0x0309 +local THERMOSTAT_DEVICE_TYPE_ID = 0x0301 + +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" +end + +local device_desc = { + profile = t_utils.get_profile_definition("heat-pump-thermostat-humidity-thermostat-humidity.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 = HEAT_PUMP_EP, + clusters = { + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 13 }, -- IMPE, CUME, PERE + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = HEAT_PUMP_DEVICE_TYPE_ID, device_type_revision = 1 } -- Heat Pump + } + }, + { + endpoint_id = THERMOSTAT_ONE_EP, + clusters = { + { cluster_id = clusters.Thermostat.ID, cluster_type = "SERVER", feature_map = 3 }, -- HEAT & COOL + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = THERMOSTAT_DEVICE_TYPE_ID, device_type_revision = 1 } -- Thermostat + } + }, + { + endpoint_id = THERMOSTAT_TWO_EP, + clusters = { + { cluster_id = clusters.Thermostat.ID, cluster_type = "SERVER", feature_map = 3 }, -- HEAT & COOL + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = THERMOSTAT_DEVICE_TYPE_ID, device_type_revision = 1 } -- Thermostat + } + }, + } +} + +local test_init_common = function(device) + local cluster_subscribe_list = { + clusters.Thermostat.attributes.SystemMode, + clusters.Thermostat.attributes.ControlSequenceOfOperation, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint, + clusters.Thermostat.attributes.AbsMinHeatSetpointLimit, + clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit, + clusters.Thermostat.attributes.OccupiedCoolingSetpoint, + clusters.Thermostat.attributes.AbsMinCoolSetpointLimit, + clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit, + clusters.Thermostat.attributes.LocalTemperature, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue, + clusters.ElectricalPowerMeasurement.attributes.ActivePower, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + } + test.socket.matter:__set_channel_ordering("relaxed") + local subscribe_request = cluster_subscribe_list[1]:subscribe(device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(device)) + end + end + test.socket.matter:__expect_send({ device.id, subscribe_request }) + test.socket.device_lifecycle:__queue_receive({ device.id, "added" }) + local read_request_on_added = { + clusters.Thermostat.attributes.ControlSequenceOfOperation, + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.WindSupport, + clusters.FanControl.attributes.RockSupport, + } + local read_request = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {}) + for _, clus in ipairs(read_request_on_added) do + read_request:merge(clus:read(device)) + end + test.socket.matter:__expect_send({ + device.id, read_request + }) + + test.mock_device.add_test_device(device) +end + +local mock_device = test.mock_device.build_test_matter_device(device_desc) +local function test_init() + test_init_common(mock_device) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) +end + +-- Create device with Thermostat clusters having features AUTO, HEAT & COOL +device_desc.endpoints[3].clusters[1].feature_map = 35 +device_desc.endpoints[4].clusters[1].feature_map = 35 +local mock_device_with_auto = test.mock_device.build_test_matter_device(device_desc) +local test_init_auto = function() + test_init_common(mock_device_with_auto) + test.socket.matter:__expect_send({ + mock_device_with_auto.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device_with_auto) + }) + test.socket.matter:__expect_send({ + mock_device_with_auto.id, clusters.Thermostat.attributes.MinSetpointDeadBand:read(mock_device_with_auto) + }) +end + +-- Set feature map of ElectricalEnergyMeasurement Cluster to only PERE and IMPE +device_desc.endpoints[2].clusters[1].feature_map = 9 +local mock_device_with_pere_impe = test.mock_device.build_test_matter_device(device_desc) +local test_init_pere_impe = function() + test_init_common(mock_device_with_pere_impe) + test.socket.matter:__expect_send({ + mock_device_with_pere_impe.id, clusters.Thermostat.attributes.MinSetpointDeadBand:read(mock_device_with_pere_impe) + }) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Assert component to endpoint map", + function() + local component_to_endpoint_map = mock_device:get_field("__component_to_endpoint_map") + assert(component_to_endpoint_map["thermostatOne"] == THERMOSTAT_ONE_EP, string.format("Thermostat One Endpoint must be %d", THERMOSTAT_ONE_EP)) + assert(component_to_endpoint_map["thermostatTwo"] == THERMOSTAT_TWO_EP, string.format("Thermostat Two Endpoint must be %d", THERMOSTAT_TWO_EP)) + end +) + +test.register_message_test( + "Heating setpoint reports from component thermostat devices should emit correct events to the correct endpoint", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.OccupiedHeatingSetpoint:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, 40*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 40.0, unit = "C" })) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.OccupiedHeatingSetpoint:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, 23*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 23.0, unit = "C" })) + } + } +) + +test.register_message_test( + "Cooling setpoint reports reports from component thermostat devices should emit correct events to the correct endpoint", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.OccupiedCoolingSetpoint:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, 39*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 39.0, unit = "C" })) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.OccupiedCoolingSetpoint:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, 19*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 19.0, unit = "C" })) + } + } +) + +test.register_message_test( + "Heating setpoint commands recieved from a particular component should send the appropriate commands to the correct corresponding thermostat endpoint", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "thermostatHeatingSetpoint", component = "thermostatOne", command = "setHeatingSetpoint", args = { 20 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, THERMOSTAT_ONE_EP, 20*100) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "thermostatHeatingSetpoint", component = "thermostatTwo", command = "setHeatingSetpoint", args = { 25 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, THERMOSTAT_TWO_EP, 25*100) + } + } + } +) + +test.register_message_test( + "Setting the cooling setpoint should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "thermostatOne", command = "setCoolingSetpoint", args = { 13 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, THERMOSTAT_ONE_EP , 13*100) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "thermostatTwo", command = "setCoolingSetpoint", args = { 13 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.Thermostat.attributes.OccupiedCoolingSetpoint:write(mock_device, THERMOSTAT_TWO_EP , 13*100) + } + } + } +) + + +test.register_message_test( + "Thermostat mode reports from the component endpoints should generate correct messages to the right component", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "emergency heat"}, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.thermostatMode.emergency_heat()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, 4) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.thermostatMode.heat()) + }, + } +) + +local ControlSequenceOfOperation = clusters.Thermostat.attributes.ControlSequenceOfOperation +test.register_message_test( + "Thermostat control sequence reports form component thermostats should generate correct messages to the right component", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, ControlSequenceOfOperation.COOLING_AND_HEATING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, ControlSequenceOfOperation.HEATING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, ControlSequenceOfOperation.COOLING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, ControlSequenceOfOperation.COOLING_AND_HEATING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, ControlSequenceOfOperation.HEATING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, ControlSequenceOfOperation.COOLING_WITH_REHEAT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool"}, {visibility={displayed=false}})) + }, + } +) + +test.register_message_test( + "Additional mode reports from component thermostat endpoints should extend the supported modes for the correct component", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.ControlSequenceOfOperation:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device, THERMOSTAT_ONE_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "emergency heat"}, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatOne", capabilities.thermostatMode.thermostatMode.emergency_heat()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device, THERMOSTAT_TWO_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "emergency heat"}, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("thermostatTwo", capabilities.thermostatMode.thermostatMode.emergency_heat()) + }, + } +) + +test.register_message_test( + "Additional mode reports from component thermostat endpoints should extend the supported modes for their corresponding components when auto is supported", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device_with_auto.id, + clusters.Thermostat.server.attributes.ControlSequenceOfOperation:build_test_report_data(mock_device_with_auto, THERMOSTAT_ONE_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "auto"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_with_auto.id, + clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device_with_auto, THERMOSTAT_ONE_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "auto", "emergency heat"}, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device_with_auto:generate_test_message("thermostatOne", capabilities.thermostatMode.thermostatMode.emergency_heat()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_with_auto.id, + clusters.Thermostat.server.attributes.ControlSequenceOfOperation:build_test_report_data(mock_device_with_auto, THERMOSTAT_TWO_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_with_auto:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "auto"}, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_with_auto.id, + clusters.Thermostat.server.attributes.SystemMode:build_test_report_data(mock_device_with_auto, THERMOSTAT_TWO_EP, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_with_auto:generate_test_message("thermostatTwo", capabilities.thermostatMode.supportedThermostatModes({"off", "cool", "heat", "auto", "emergency heat"}, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device_with_auto:generate_test_message("thermostatTwo", capabilities.thermostatMode.thermostatMode.emergency_heat()) + }, + }, + { test_init = test_init_auto } +) + +test.register_message_test( + "Appropriate powerMeter capability events must be sent in 'W' on receiving ActivePower events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(mock_device, + HEAT_PUMP_EP, + 15000) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 15.0, unit = "W" })) + } + } +) + +test.register_message_test( + "energyMeter capability events must be sent in 'Wh' on receiving CumulativeEnergyMeasured events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyImported:build_test_report_data(mock_device, + HEAT_PUMP_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 15000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 15, unit = "Wh" })) + } + } +) + +test.register_coroutine_test( + "The total energy consumption of the device must be reported every 15 minutes", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.matter:__expect_send({ + mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, + HEAT_PUMP_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 20Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 20, unit = "Wh" + })) + ) + + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 20, + deltaEnergy = 20, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:14:59Z" + })) + ) + + test.wait_for_events() + + test.socket.matter:__expect_send({ + mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, + HEAT_PUMP_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 30000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 30Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 30, unit = "Wh" + })) + ) + + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 30, + deltaEnergy = 10, + start = "1970-01-01T00:15:00Z", + ["end"] = "1970-01-01T00:29:59Z" + })) + ) + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "polling_report_schedule_timer") + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.register_coroutine_test( + "Ensure the driver does not send read request to devices without CUME & IMPE features", + function() + local timer = mock_device_with_pere_impe:get_field("__recurring_poll_timer") + assert(timer == nil, "Polling timer must not be created if the device does not support CUME & IMPE features") + end, + { + test_init = function() + test_init_pere_impe() + end + } +) + +test.register_coroutine_test( + "PeriodicEnergyImported should report the energyMeter values", + function() + test.socket.matter:__queue_receive({ mock_device_with_pere_impe.id, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported:build_test_report_data(mock_device_with_pere_impe, + HEAT_PUMP_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 30000, start_timestamp = 0, end_timestamp = 100, start_systime = 0, end_systime = 0 })) }) -- 30Wh + + test.socket.capability:__expect_send( + mock_device_with_pere_impe:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 30, unit = "Wh" + })) + ) + end, + { + test_init = function() + test_init_pere_impe() + end + } +) + +test.register_coroutine_test( + "Ensure only the cumulative energy reports are considered if the device supports both PERE and CUME features.", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.matter:__expect_send({ + mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, + HEAT_PUMP_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 20Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 20, unit = "Wh" + })) + ) + + test.wait_for_events() + + -- do not expect energyMeter event for this report. + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported:build_test_report_data(mock_device, + HEAT_PUMP_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 800, start_systime = 0, end_systime = 0 })) }) -- 20Wh + + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 20, + deltaEnergy = 20, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:14:59Z" + })) + ) + + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "polling_report_schedule_timer") + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.register_coroutine_test( + "Consider the device reported time interval in case it is greater than 15 minutes for powerConsumptionReport capability reports", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.matter:__expect_send({ + mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, + HEAT_PUMP_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 20Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 20, unit = "Wh" + })) + ) + + test.wait_for_events() + + -- do not expect energyMeter event for this report. Only consider the time interval as it is greater than 15 minutes. + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported:build_test_report_data(mock_device, + HEAT_PUMP_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 1080, start_systime = 0, end_systime = 0 })) }) -- 20Wh 18 minutes + + + test.wait_for_events() + test.mock_time.advance_time(60 * 18) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 20, + deltaEnergy = 20, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:17:59Z" + })) + ) + + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "polling_report_schedule_timer") + test.timer.__create_and_queue_test_time_advance_timer(60 * 18, "interval", "polling_report_schedule_timer") + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-thermostat/src/test/test_matter_water_heater.lua b/drivers/SmartThings/matter-thermostat/src/test/test_matter_water_heater.lua new file mode 100644 index 0000000000..4f50ee621a --- /dev/null +++ b/drivers/SmartThings/matter-thermostat/src/test/test_matter_water_heater.lua @@ -0,0 +1,468 @@ +-- 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. + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local utils = require "st.utils" +local version = require "version" +local clusters = require "st.matter.clusters" + +if version.api < 13 then + clusters.WaterHeaterMode = require "WaterHeaterMode" +end + +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" +end + +local WATER_HEATER_EP = 10 +local ELECTRICAL_SENSOR_EP = 11 + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("water-heater-power-energy-powerConsumption.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 = WATER_HEATER_EP, + clusters = { + { + cluster_id = clusters.Thermostat.ID, + cluster_revision = 5, + cluster_type = "SERVER", + feature_map = 9, -- Heat and SCH features + }, + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.WaterHeaterMode.ID, cluster_type = "SERVER" }, + }, + device_types = { + {device_type_id = 0x050F, device_type_revision = 1}, -- Water Heater + } + }, + { + endpoint_id = ELECTRICAL_SENSOR_EP, + clusters = { + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 13 }, -- IMPE, CUME, PERE + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + {device_type_id = 0x0510, device_type_revision = 1}, -- Electrical Sensor + } + } + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.Thermostat.attributes.SystemMode, + clusters.Thermostat.attributes.ControlSequenceOfOperation, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint, + clusters.Thermostat.attributes.AbsMinHeatSetpointLimit, + clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit, + clusters.Thermostat.attributes.LocalTemperature, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + clusters.WaterHeaterMode.attributes.CurrentMode, + clusters.WaterHeaterMode.attributes.SupportedModes, + clusters.ElectricalPowerMeasurement.attributes.ActivePower, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported + } + test.socket.matter:__set_channel_ordering("relaxed") + 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) + test.socket.matter:__expect_send({ + mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) +end +test.set_test_init_function(test_init) + +test.register_message_test( + "Setting the heating setpoint to a Fahrenheit value should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "thermostatHeatingSetpoint", component = "main", command = "setHeatingSetpoint", args = { 90 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, WATER_HEATER_EP, + utils.round((90 - 32) * (5 / 9.0) * 100)) + } + } + } +) + +test.register_message_test( + "Heating setpoint reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Thermostat.server.attributes.OccupiedHeatingSetpoint:build_test_report_data(mock_device, 1, 70*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({ value = 70.0, unit = "C" })) + } + } +) + +test.register_message_test( + "Setting the heating setpoint should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "thermostatHeatingSetpoint", component = "main", command = "setHeatingSetpoint", args = { 80 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, WATER_HEATER_EP, 80*100) + } + } + } +) + +test.register_message_test( + "Setting the heating setpoint to a Fahrenheit value should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "thermostatHeatingSetpoint", component = "main", command = "setHeatingSetpoint", args = { 100 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, WATER_HEATER_EP, utils.round((100 - 32) * (5 / 9.0) * 100)) + } + } + } +) + +test.register_message_test( + "Ensure WaterHeaderMode supportedModes are registered and setting Oven mode should send appropriate commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.WaterHeaterMode.attributes.SupportedModes:build_test_report_data(mock_device, WATER_HEATER_EP, + { + clusters.WaterHeaterMode.types.ModeOptionStruct({ + ["label"] = "Mode 1", + ["mode"] = 0, + ["mode_tags"] = { + clusters.WaterHeaterMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 0 }) + } + }), + clusters.WaterHeaterMode.types.ModeOptionStruct({ + ["label"] = "Mode 2", + ["mode"] = 1, + ["mode_tags"] = { + clusters.WaterHeaterMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 1 }) + } + }) + } + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.mode.supportedModes({ "Mode 1", "Mode 2" }, { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.mode.supportedArguments({ "Mode 1", "Mode 2" }, { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "main", command = "setMode", args = { "Mode 1" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.WaterHeaterMode.commands.ChangeToMode(mock_device, WATER_HEATER_EP, 0) -- Index where Mode 1 is stored) + } + } + } +) + +test.register_message_test( + "Appropriate powerMeter capability events must be sent in 'W' on receiving ActivePower events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(mock_device, + ELECTRICAL_SENSOR_EP, + 15000) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 15.0, unit = "W" })) + } + } +) + +test.register_message_test( + "energyMeter capability events must be sent in 'Wh' on receiving CumulativeEnergyMeasured events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyImported:build_test_report_data(mock_device, + ELECTRICAL_SENSOR_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 15000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 15, unit = "Wh" })) + } + } +) + +test.register_coroutine_test( + "The total energy consumption of the device must be reported every 15 minutes", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.matter:__expect_send({ + mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, + ELECTRICAL_SENSOR_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 20000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 20Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 20, unit = "Wh" + })) + ) + + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 20, + deltaEnergy = 20, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:14:59Z" + })) + ) + + test.wait_for_events() + + test.socket.matter:__expect_send({ + mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(mock_device) + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(mock_device, + ELECTRICAL_SENSOR_EP, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 30000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) -- 30Wh + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ + value = 30, unit = "Wh" + })) + ) + + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 30, + deltaEnergy = 10, + start = "1970-01-01T00:15:00Z", + ["end"] = "1970-01-01T00:29:59Z" + })) + ) + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "polling_report_schedule_timer") + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.register_message_test( + "WaterHeaterMode SupportedModes must be registered. CurrentMode reports should report appropriate mode capability event. Command to setMode should send appropriate changeToMode matter command", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.WaterHeaterMode.attributes.SupportedModes:build_test_report_data(mock_device, + WATER_HEATER_EP, { + clusters.WaterHeaterMode.types.ModeOptionStruct({ + ["label"] = "Water Heater Mode 1", + ["mode"] = 0, + ["mode_tags"] = { + clusters.WaterHeaterMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 0 }) + } + }), + clusters.WaterHeaterMode.types.ModeOptionStruct({ + ["label"] = "Water Heater Mode 2", + ["mode"] = 1, + ["mode_tags"] = { + clusters.WaterHeaterMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 1 }) + } + }), + clusters.WaterHeaterMode.types.ModeOptionStruct({ + ["label"] = "Water Heater Mode 3", + ["mode"] = 2, + ["mode_tags"] = { + clusters.WaterHeaterMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 2 }) + } + }) + }) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.mode.supportedModes( + { "Water Heater Mode 1", "Water Heater Mode 2", "Water Heater Mode 3" }, + { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.mode.supportedArguments( + { "Water Heater Mode 1", "Water Heater Mode 2", "Water Heater Mode 3" }, + { visibility = { displayed = false } })) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.WaterHeaterMode.attributes.CurrentMode:build_test_report_data(mock_device, WATER_HEATER_EP, 1) -- 1 is the index for Water Heater Mode 2 mode + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.mode.mode("Water Heater Mode 2")) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "main", command = "setMode", args = { "Water Heater Mode 3" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.WaterHeaterMode.commands.ChangeToMode(mock_device, WATER_HEATER_EP, 2) -- Index is Water Heater Mode 3 + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "main", command = "setMode", args = { "Water Heater Mode 1" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.WaterHeaterMode.commands.ChangeToMode(mock_device, WATER_HEATER_EP, 0) -- Index is Water Heater Mode 1 + } + } + } +) + +test.run_registered_tests() diff --git a/tools/config.luacov b/tools/config.luacov index 137c9c91a1..b3edf6673b 100644 --- a/tools/config.luacov +++ b/tools/config.luacov @@ -42,6 +42,9 @@ configuration = { "EnergyEvseMode", "BooleanStateConfiguration", "ValveConfigurationAndControl", + "ThreadBorderRouterManagement", + "WaterHeaterMode", + "WiFiNetworkManagement", }, cobertura = { filenameparser = function(filename) @@ -51,4 +54,4 @@ configuration = { } } -return configuration \ No newline at end of file +return configuration