-
Notifications
You must be signed in to change notification settings - Fork 464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change use of the EnergyExported attribute to EnergyImported in matter switch #1830
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 CumulativeEnergyImported = { | ||
ID = 0x0001, | ||
NAME = "CumulativeEnergyImported", | ||
base_type = require "st.matter.generated.zap_clusters.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 --event_id | ||
) | ||
end | ||
|
||
function CumulativeEnergyImported:subscribe(device, endpoint_id) | ||
return cluster_base.subscribe( | ||
device, | ||
endpoint_id, | ||
self._cluster.ID, | ||
self.ID, | ||
nil --event_id | ||
) | ||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 PeriodicEnergyImported = { | ||
ID = 0x0003, | ||
NAME = "PeriodicEnergyImported", | ||
base_type = require "st.matter.generated.zap_clusters.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 --event_id | ||
) | ||
end | ||
|
||
function PeriodicEnergyImported:subscribe(device, endpoint_id) | ||
return cluster_base.subscribe( | ||
device, | ||
endpoint_id, | ||
self._cluster.ID, | ||
self.ID, | ||
nil --event_id | ||
) | ||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -158,12 +158,12 @@ local child_device_profile_overrides = { | |
local detect_matter_thing | ||
|
||
local CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported" | ||
local FIRST_EXPORT_REPORT_TIMESTAMP = "__first_export_report_timestamp" | ||
local EXPORT_POLL_TIMER_SETTING_ATTEMPTED = "__export_poll_timer_setting_attempted" | ||
local EXPORT_REPORT_TIMEOUT = "__export_report_timeout" | ||
local TOTAL_EXPORTED_ENERGY = "__total_exported_energy" | ||
local LAST_EXPORTED_REPORT_TIMESTAMP = "__last_exported_report_timestamp" | ||
local RECURRING_EXPORT_REPORT_POLL_TIMER = "__recurring_export_report_poll_timer" | ||
local FIRST_IMPORT_REPORT_TIMESTAMP = "__first_import_report_timestamp" | ||
local IMPORT_POLL_TIMER_SETTING_ATTEMPTED = "__import_poll_timer_setting_attempted" | ||
local IMPORT_REPORT_TIMEOUT = "__import_report_timeout" | ||
local TOTAL_IMPORTED_ENERGY = "__total_imported_energy" | ||
local LAST_IMPORTED_REPORT_TIMESTAMP = "__last_imported_report_timestamp" | ||
local RECURRING_IMPORT_REPORT_POLL_TIMER = "__recurring_import_report_poll_timer" | ||
local MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in seconds | ||
local SUBSCRIPTION_REPORT_OCCURRED = "__subscription_report_occurred" | ||
local CONVERSION_CONST_MILLIWATT_TO_WATT = 1000 -- A milliwatt is 1/1000th of a watt | ||
|
@@ -183,44 +183,44 @@ local function iso8061Timestamp(time) | |
return os.date("!%Y-%m-%dT%H:%M:%SZ", time) | ||
end | ||
|
||
local function delete_export_poll_schedule(device) | ||
local export_poll_timer = device:get_field(RECURRING_EXPORT_REPORT_POLL_TIMER) | ||
if export_poll_timer then | ||
device.thread:cancel_timer(export_poll_timer) | ||
device:set_field(RECURRING_EXPORT_REPORT_POLL_TIMER, nil) | ||
device:set_field(EXPORT_POLL_TIMER_SETTING_ATTEMPTED, nil) | ||
local function delete_import_poll_schedule(device) | ||
local import_poll_timer = device:get_field(RECURRING_IMPORT_REPORT_POLL_TIMER) | ||
if import_poll_timer then | ||
device.thread:cancel_timer(import_poll_timer) | ||
device:set_field(RECURRING_IMPORT_REPORT_POLL_TIMER, nil) | ||
device:set_field(IMPORT_POLL_TIMER_SETTING_ATTEMPTED, nil) | ||
end | ||
end | ||
|
||
local function send_export_poll_report(device, latest_total_exported_energy_wh) | ||
local function send_import_poll_report(device, latest_total_imported_energy_wh) | ||
local current_time = os.time() | ||
local last_time = device:get_field(LAST_EXPORTED_REPORT_TIMESTAMP) or 0 | ||
device:set_field(LAST_EXPORTED_REPORT_TIMESTAMP, current_time, { persist = true }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flag will have been persisted in the case on devices that have already joined to the platform and run this code. However, it wouldn't be needed anymore so we should set it to nil and remove it. However, I don't think there are a lot/any devices like this yet, so in this case we are okay to just remove this, but just something to be aware of for the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To note: I have not removed anything- only renamed the word export to import. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, nevermind I see what you mean. Thanks for pointing that out! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. update on this: from the superset query counting all matter profiles in use, I'm seeing: @ctowns Do you think we should add extra handling to transition these devices? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you see what device these are? If it's the eve energy plug, then those devices would use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ctowns These wouldn't be for the eve-energy driver, since those devices still fingerprints to this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for clarifying! So is it reasonable to assume that the 510 devices just aren't working in terms of power reports right now? If we are reading the wrong attribute, then I would assume the data we are getting is wrong so I'm not sure what "transition" we'd need to do clean up wise since the data would've just been wrong. I assume we can't change it once it's made it to ST energy. So in that case, I guess the only clean up we could do would be setting the export fields to nil. FWIW, ultimately this would save just a few bytes of memory per device. I am surprised there are this many devices using this driver already, I would suggest we figure out what these devices are and try to procure one so we can test this functionality with a real device! That could give us some more confidence moving forward. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that should be the case. In fact, if these devices are correctly using the 'Imported' attributes, we would not have read anything at all, so nothing would have even been reported to ST Energy in the first place, since the report reads Further, if anyone were incorrectly using the 'exported' attribute like us, there's this logic gate in the report: Therefore, by this logic we shouldn't have reported anything incorrectly so far to ST Energy except sending a lot of nil values from the fact that we're never updating And as for saving a few bytes like you mentioned, I'm not sure this is worth it either. |
||
local last_time = device:get_field(LAST_IMPORTED_REPORT_TIMESTAMP) or 0 | ||
device:set_field(LAST_IMPORTED_REPORT_TIMESTAMP, current_time, { persist = true }) | ||
|
||
-- Calculate the energy delta between reports | ||
local energy_delta_wh = 0.0 | ||
local previous_exported_report = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, | ||
local previous_imported_report = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, | ||
capabilities.powerConsumptionReport.powerConsumption.NAME) | ||
if previous_exported_report and previous_exported_report.energy then | ||
energy_delta_wh = math.max(latest_total_exported_energy_wh - previous_exported_report.energy, 0.0) | ||
if previous_imported_report and previous_imported_report.energy then | ||
energy_delta_wh = math.max(latest_total_imported_energy_wh - previous_imported_report.energy, 0.0) | ||
end | ||
|
||
-- Report the energy consumed during the time interval. The unit of these values should be 'Wh' | ||
device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ | ||
start = iso8061Timestamp(last_time), | ||
["end"] = iso8061Timestamp(current_time - 1), | ||
deltaEnergy = energy_delta_wh, | ||
energy = latest_total_exported_energy_wh | ||
energy = latest_total_imported_energy_wh | ||
})) | ||
end | ||
|
||
local function create_poll_report_schedule(device) | ||
local export_timer = device.thread:call_on_schedule( | ||
device:get_field(EXPORT_REPORT_TIMEOUT), | ||
send_export_poll_report(device, device:get_field(TOTAL_EXPORTED_ENERGY)), | ||
"polling_export_report_schedule_timer" | ||
local import_timer = device.thread:call_on_schedule( | ||
device:get_field(IMPORT_REPORT_TIMEOUT), | ||
send_import_poll_report(device, device:get_field(TOTAL_IMPORTED_ENERGY)), | ||
"polling_import_report_schedule_timer" | ||
) | ||
device:set_field(RECURRING_EXPORT_REPORT_POLL_TIMER, export_timer) | ||
device:set_field(RECURRING_IMPORT_REPORT_POLL_TIMER, import_timer) | ||
end | ||
|
||
local function set_poll_report_timer_and_schedule(device, is_cumulative_report) | ||
|
@@ -234,18 +234,18 @@ local function set_poll_report_timer_and_schedule(device, is_cumulative_report) | |
return | ||
elseif not device:get_field(SUBSCRIPTION_REPORT_OCCURRED) then | ||
device:set_field(SUBSCRIPTION_REPORT_OCCURRED, true) | ||
elseif not device:get_field(FIRST_EXPORT_REPORT_TIMESTAMP) then | ||
device:set_field(FIRST_EXPORT_REPORT_TIMESTAMP, os.time()) | ||
elseif not device:get_field(FIRST_IMPORT_REPORT_TIMESTAMP) then | ||
device:set_field(FIRST_IMPORT_REPORT_TIMESTAMP, os.time()) | ||
else | ||
local first_timestamp = device:get_field(FIRST_EXPORT_REPORT_TIMESTAMP) | ||
local first_timestamp = device:get_field(FIRST_IMPORT_REPORT_TIMESTAMP) | ||
local second_timestamp = os.time() | ||
local report_interval_secs = second_timestamp - first_timestamp | ||
device:set_field(EXPORT_REPORT_TIMEOUT, math.max(report_interval_secs, MINIMUM_ST_ENERGY_REPORT_INTERVAL)) | ||
device:set_field(IMPORT_REPORT_TIMEOUT, math.max(report_interval_secs, MINIMUM_ST_ENERGY_REPORT_INTERVAL)) | ||
-- the poll schedule is only needed for devices that support powerConsumption | ||
if device:supports_capability(capabilities.powerConsumptionReport) then | ||
create_poll_report_schedule(device) | ||
end | ||
device:set_field(EXPORT_POLL_TIMER_SETTING_ATTEMPTED, true) | ||
device:set_field(IMPORT_POLL_TIMER_SETTING_ATTEMPTED, true) | ||
end | ||
end | ||
|
||
|
@@ -700,7 +700,7 @@ end | |
|
||
local function device_removed(driver, device) | ||
log.info("device removed") | ||
delete_export_poll_schedule(device) | ||
delete_import_poll_schedule(device) | ||
end | ||
|
||
local function handle_switch_on(driver, device, cmd) | ||
|
@@ -979,33 +979,33 @@ local function occupancy_attr_handler(driver, device, ib, response) | |
device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) | ||
end | ||
|
||
local function cumul_energy_exported_handler(driver, device, ib, response) | ||
local function cumul_energy_imported_handler(driver, device, ib, response) | ||
if ib.data.elements.energy then | ||
local watt_hour_value = ib.data.elements.energy.value / CONVERSION_CONST_MILLIWATT_TO_WATT | ||
device:set_field(TOTAL_EXPORTED_ENERGY, watt_hour_value) | ||
device:set_field(TOTAL_IMPORTED_ENERGY, watt_hour_value) | ||
device:emit_event(capabilities.energyMeter.energy({ value = watt_hour_value, unit = "Wh" })) | ||
end | ||
end | ||
|
||
local function per_energy_exported_handler(driver, device, ib, response) | ||
local function per_energy_imported_handler(driver, device, ib, response) | ||
if ib.data.elements.energy then | ||
local watt_hour_value = ib.data.elements.energy.value / CONVERSION_CONST_MILLIWATT_TO_WATT | ||
local latest_energy_report = device:get_field(TOTAL_EXPORTED_ENERGY) or 0 | ||
local latest_energy_report = device:get_field(TOTAL_IMPORTED_ENERGY) or 0 | ||
local summed_energy_report = latest_energy_report + watt_hour_value | ||
device:set_field(TOTAL_EXPORTED_ENERGY, summed_energy_report) | ||
device:set_field(TOTAL_IMPORTED_ENERGY, summed_energy_report) | ||
device:emit_event(capabilities.energyMeter.energy({ value = summed_energy_report, unit = "Wh" })) | ||
end | ||
end | ||
|
||
local function energy_report_handler_factory(is_cumulative_report) | ||
return function(driver, device, ib, response) | ||
if not device:get_field(EXPORT_POLL_TIMER_SETTING_ATTEMPTED) then | ||
if not device:get_field(IMPORT_POLL_TIMER_SETTING_ATTEMPTED) then | ||
set_poll_report_timer_and_schedule(device, is_cumulative_report) | ||
end | ||
if is_cumulative_report then | ||
cumul_energy_exported_handler(driver, device, ib, response) | ||
cumul_energy_imported_handler(driver, device, ib, response) | ||
elseif device:get_field(CUMULATIVE_REPORTS_NOT_SUPPORTED) then | ||
per_energy_exported_handler(driver, device, ib, response) | ||
per_energy_imported_handler(driver, device, ib, response) | ||
end | ||
end | ||
end | ||
|
@@ -1174,8 +1174,8 @@ local matter_driver_template = { | |
[clusters.ElectricalPowerMeasurement.attributes.ActivePower.ID] = active_power_handler, | ||
}, | ||
[clusters.ElectricalEnergyMeasurement.ID] = { | ||
[clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported.ID] = energy_report_handler_factory(true), | ||
[clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported.ID] = energy_report_handler_factory(false), | ||
[clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = energy_report_handler_factory(true), | ||
[clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = energy_report_handler_factory(false), | ||
}, | ||
[clusters.ValveConfigurationAndControl.ID] = { | ||
[clusters.ValveConfigurationAndControl.attributes.CurrentState.ID] = valve_state_attr_handler, | ||
|
@@ -1234,8 +1234,8 @@ local matter_driver_template = { | |
clusters.PowerSource.attributes.BatPercentRemaining, | ||
}, | ||
[capabilities.energyMeter.ID] = { | ||
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported, | ||
clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported | ||
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported, | ||
clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported | ||
}, | ||
[capabilities.powerMeter.ID] = { | ||
clusters.ElectricalPowerMeasurement.attributes.ActivePower | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would we be able to remove the
CumulativeEnergyExported
andPeriodicEnergyExported
embedded cluster defs now that we are just using the imported variation?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could. I figured why not leave them in, but it's just as well to take them out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed in 438b6252f08fba1d