From 69f8bd5fec04bec6aa30d20244e715b396efcb8c Mon Sep 17 00:00:00 2001 From: jamesharrow <93921463+jamesharrow@users.noreply.github.com> Date: Sat, 27 Jan 2024 00:08:38 +0000 Subject: [PATCH] =?UTF-8?q?Enabled=20Electrical=20Energy=20Measurement=20i?= =?UTF-8?q?n=20example-energy-management-ap=E2=80=A6=20(#31622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enabled Electrical Energy Measurement in example-energy-management-app ZAP and regen all. * Added ElectricalEnergyMeasurement to chip-repl __init__.py * Restyled by isort * Changed feature map to indicate CUME and IMP support only * Adding into __all__ some missing clusters which are causing flake8 issues. * Removed out of date comment * Added a test event trigger to fake energy reporting readings into the EEM cluster (hooks ready for EPM cluster) * Added new Energy Reporting TE Trigger delegate * Updated random calculation and scaled into mWh (divide by 3600) * Restyled by whitespace * Restyled by gn * Removed accidental launch.json inclusion * Fix for ARM linux etc builds due to gn dependency check error * Added code review suggestions. * More code review suggestions fixed. * Added electrical-energy-measurement-server to CMakelist to fix linker issue. --------- Co-authored-by: Restyled.io --- .../energy-management-app.matter | 91 +++++++ .../energy-management-app.zap | 148 +++++++++++ .../include/EVSEManufacturerImpl.h | 50 ++++ .../include/EnergyEvseDelegateImpl.h | 8 + .../src/EVSEManufacturerImpl.cpp | 234 ++++++++++++++++++ .../esp32/main/CMakeLists.txt | 1 + examples/energy-management-app/linux/args.gni | 1 + examples/energy-management-app/linux/main.cpp | 1 - examples/platform/linux/AppMain.cpp | 9 + examples/platform/linux/BUILD.gn | 7 + src/app/chip_data_model.gni | 7 + ...nergyReportingTestEventTriggerDelegate.cpp | 40 +++ .../EnergyReportingTestEventTriggerDelegate.h | 83 +++++++ .../electrical-energy-measurement-server.cpp | 45 ++-- .../electrical-energy-measurement-server.h | 11 + .../python/chip/clusters/__init__.py | 6 +- 16 files changed, 711 insertions(+), 31 deletions(-) create mode 100644 src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.cpp create mode 100644 src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.matter b/examples/energy-management-app/energy-management-common/energy-management-app.matter index 2c580b5d463276..dae189309d4dbb 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.matter +++ b/examples/energy-management-app/energy-management-common/energy-management-app.matter @@ -900,6 +900,85 @@ cluster GroupKeyManagement = 63 { fabric command access(invoke: administer) KeySetReadAllIndices(): KeySetReadAllIndicesResponse = 4; } +/** This cluster provides a mechanism for querying data about the electrical energy imported or provided by the server. */ +provisional cluster ElectricalEnergyMeasurement = 145 { + revision 1; + + enum MeasurementTypeEnum : enum16 { + kUnspecified = 0; + kVoltage = 1; + kActiveCurrent = 2; + kReactiveCurrent = 3; + kApparentCurrent = 4; + kActivePower = 5; + kReactivePower = 6; + kApparentPower = 7; + kRMSVoltage = 8; + kRMSCurrent = 9; + kRMSPower = 10; + kFrequency = 11; + kPowerFactor = 12; + kNeutralCurrent = 13; + kElectricalEnergy = 14; + } + + bitmap Feature : bitmap32 { + kImportedEnergy = 0x1; + kExportedEnergy = 0x2; + kCumulativeEnergy = 0x4; + kPeriodicEnergy = 0x8; + } + + struct MeasurementAccuracyRangeStruct { + int64s rangeMin = 0; + int64s rangeMax = 1; + optional percent100ths percentMax = 2; + optional percent100ths percentMin = 3; + optional percent100ths percentTypical = 4; + optional int64u fixedMax = 5; + optional int64u fixedMin = 6; + optional int64u fixedTypical = 7; + } + + struct MeasurementAccuracyStruct { + MeasurementTypeEnum measurementType = 0; + boolean measured = 1; + int64s minMeasuredValue = 2; + int64s maxMeasuredValue = 3; + MeasurementAccuracyRangeStruct accuracyRanges[] = 4; + } + + struct EnergyMeasurementStruct { + int64s energy = 0; + optional epoch_s startTimestamp = 1; + optional epoch_s endTimestamp = 2; + optional systime_ms startSystime = 3; + optional systime_ms endSystime = 4; + } + + info event CumulativeEnergyMeasured = 0 { + optional EnergyMeasurementStruct energyImported = 0; + optional EnergyMeasurementStruct energyExported = 1; + } + + info event PeriodicEnergyMeasured = 1 { + optional EnergyMeasurementStruct energyImported = 0; + optional EnergyMeasurementStruct energyExported = 1; + } + + readonly attribute MeasurementAccuracyStruct accuracy = 0; + readonly attribute optional nullable EnergyMeasurementStruct cumulativeEnergyImported = 1; + readonly attribute optional nullable EnergyMeasurementStruct cumulativeEnergyExported = 2; + readonly attribute optional nullable EnergyMeasurementStruct periodicEnergyImported = 3; + readonly attribute optional nullable EnergyMeasurementStruct periodicEnergyExported = 4; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + /** This cluster allows a client to manage the power draw of a device. An example of such a client could be an Energy Management System (EMS) which controls an Energy Smart Appliance (ESA). */ provisional cluster DeviceEnergyManagement = 152 { revision 3; @@ -1598,6 +1677,18 @@ endpoint 1 { callback attribute clusterRevision; } + server cluster ElectricalEnergyMeasurement { + emits event CumulativeEnergyMeasured; + callback attribute accuracy; + callback attribute cumulativeEnergyImported; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 5; + ram attribute clusterRevision default = 1; + } + server cluster DeviceEnergyManagement { emits event PowerAdjustStart; emits event PowerAdjustEnd; diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.zap b/examples/energy-management-app/energy-management-common/energy-management-app.zap index 78485dfabbd55d..0e1f15b808317d 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.zap +++ b/examples/energy-management-app/energy-management-common/energy-management-app.zap @@ -2517,6 +2517,154 @@ } ] }, + { + "name": "Electrical Energy Measurement", + "code": 145, + "mfgCode": null, + "define": "ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER", + "side": "server", + "enabled": 1, + "apiMaturity": "provisional", + "attributes": [ + { + "name": "Accuracy", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "MeasurementAccuracyStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CumulativeEnergyImported", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "EnergyMeasurementStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "CumulativeEnergyMeasured", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, { "name": "Device Energy Management", "code": 152, diff --git a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h index df757f7374e672..f1c3eac6f60dc9 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h @@ -60,6 +60,56 @@ class EVSEManufacturer */ static void ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg); + /** + * @brief Allows a client application to send in power readings into the system + * + * @param[in] aEndpointId - Endpoint to send to EPM Cluster + * @param[in] aActivePower_mW - Power measured in milli-watts + * @param[in] aVoltage_mV - Voltage measured in milli-volts + * @param[in] aCurrent_mA - Current measured in milli-amps + */ + CHIP_ERROR SendPowerReading(EndpointId aEndpointId, int64_t aActivePower_mW, int64_t aVoltage_mV, int64_t aCurrent_mA); + + /** + * @brief Allows a client application to send in energy readings into the system + * + * This is a helper function to add timestamps to the readings + * + * @param[in] aCumulativeEnergyImported -total energy imported in milli-watthours + * @param[in] aCumulativeEnergyExported -total energy exported in milli-watthours + */ + CHIP_ERROR SendEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, int64_t aCumulativeEnergyExported); + + /** Fake Meter data generation - used for testing EPM/EEM clusters */ + /** + * @brief Starts a fake load/generator to periodically callback the power and energy + * clusters. + * @param[in] aEndpointId - which endpoint is the meter to be updated on + * @param[in] aPower_mW - the mean power of the load + * Positive power indicates Imported energy (e.g. a load) + * Negative power indicated Exported energy (e.g. a generator) + * @param[in] aPowerRandomness_mW This is used to scale random power fluctuations around the mean power of the load + * + * @param[in] aInterval_s - the callback interval in seconds + * @param[in] bReset - boolean: true will reset the energy values to 0 + */ + void StartFakeReadings(EndpointId aEndpointId, int64_t aPower_mW, uint32_t aPowerRandomness_mW, uint8_t aInterval_s, + bool bReset); + + /** + * @brief Stops any active updates to the fake load data callbacks + */ + void StopFakeReadings(); + + /** + * @brief Sends fake meter data into the cluster and restarts the timer + */ + void FakeReadingsUpdate(); + /** + * @brief Timer expiry callback to handle fake load + */ + static void FakeReadingsTimerExpiry(System::Layer * systemLayer, void * manufacturer); + private: EnergyEvseManager * mInstance; diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 558f7c13df2766..e6f5c8c324d91e 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -26,6 +26,14 @@ #include using chip::Protocols::InteractionModel::Status; + +/** + * @brief Helper function to get current timestamp in Epoch format + * + * @param chipEpoch reference to hold return timestamp + */ +CHIP_ERROR GetEpochTS(uint32_t & chipEpoch); + namespace chip { namespace app { namespace Clusters { diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index fe51c9da664b6d..70cd63e7926c8d 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -18,12 +18,16 @@ #include #include +#include +#include #include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; +using namespace chip::app::Clusters::ElectricalEnergyMeasurement; +using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Structs; CHIP_ERROR EVSEManufacturer::Init() { @@ -89,6 +93,179 @@ CHIP_ERROR EVSEManufacturer::Shutdown() return CHIP_NO_ERROR; } +/** + * @brief Allows a client application to send in power readings into the system + * + * @param[in] aEndpointId - Endpoint to send to EPM Cluster + * @param[in] aActivePower_mW - Power measured in milli-watts + * @param[in] aVoltage_mV - Voltage measured in milli-volts + * @param[in] aCurrent_mA - Current measured in milli-amps + */ +CHIP_ERROR EVSEManufacturer::SendPowerReading(EndpointId aEndpointId, int64_t aActivePower_mW, int64_t aVoltage_mV, + int64_t aCurrent_mA) +{ + // TODO add Power Readings when EPM cluster is merged + + return CHIP_NO_ERROR; +} + +/** + * @brief Allows a client application to send in energy readings into the system + * + * This is a helper function to add timestamps to the readings + * + * @param[in] aCumulativeEnergyImported -total energy imported in milli-watthours + * @param[in] aCumulativeEnergyExported -total energy exported in milli-watthours + */ +CHIP_ERROR EVSEManufacturer::SendEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, + int64_t aCumulativeEnergyExported) +{ + MeasurementData * data = MeasurementDataForEndpoint(aEndpointId); + + EnergyMeasurementStruct::Type energyImported; + EnergyMeasurementStruct::Type energyExported; + + // Get current timestamp + uint32_t currentTimestamp; + CHIP_ERROR err = GetEpochTS(currentTimestamp); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "GetEpochTS returned error getting timestamp"); + return err; + } + + /** IMPORT */ + // Copy last endTimestamp into new startTimestamp if it exists + energyImported.startTimestamp.ClearValue(); + if (data->cumulativeImported.HasValue()) + { + energyImported.startTimestamp = data->cumulativeImported.Value().endTimestamp; + } + + energyImported.endTimestamp.SetValue(currentTimestamp); + energyImported.energy = aCumulativeEnergyImported; + + /** EXPORT */ + // Copy last endTimestamp into new startTimestamp if it exists + energyExported.startTimestamp.ClearValue(); + if (data->cumulativeExported.HasValue()) + { + energyExported.startTimestamp = data->cumulativeExported.Value().endTimestamp; + } + energyExported.endTimestamp.SetValue(currentTimestamp); + energyExported.energy = aCumulativeEnergyExported; + + // call the SDK to update attributes and generate an event + if (!NotifyCumulativeEnergyMeasured(aEndpointId, MakeOptional(energyImported), MakeOptional(energyExported))) + { + ChipLogError(AppServer, "Failed to notify Cumulative Energy reading."); + return CHIP_ERROR_INTERNAL; + } + + return CHIP_NO_ERROR; +} + +struct FakeReadingsData +{ + bool bEnabled; /* If enabled then the timer callback will re-trigger */ + EndpointId mEndpointId; /* Which endpoint the meter is on */ + uint8_t mInterval_s; /* Interval in seconds to callback */ + int64_t mPower_mW; /* Power on the load in mW (signed value) +ve = imported */ + uint32_t mPowerRandomness_mW; /* The amount to randomize the Power on the load in mW */ + /* These energy values can only be positive values. + * however the underlying energy type (power_mWh) is signed, so keeping with that convention */ + int64_t mTotalEnergyImported = 0; /* Energy Imported which is updated if mPower > 0 */ + int64_t mTotalEnergyExported = 0; /* Energy Imported which is updated if mPower < 0 */ +}; + +static FakeReadingsData gFakeReadingsData; + +/* This helper routine starts and handles a callback */ +/** + * @brief Starts a fake load/generator to periodically callback the power and energy + * clusters. + * @param[in] aEndpointId - which endpoint is the meter to be updated on + * @param[in] aPower_mW - the mean power of the load + * Positive power indicates Imported energy (e.g. a load) + * Negative power indicated Exported energy (e.g. a generator) + * @param[in] aPowerRandomness_mW This is used to define the std.dev of the + * random power values around the mean power of the load + * + * @param[in] aInterval_s - the callback interval in seconds + * @param[in] bReset - boolean: true will reset the energy values to 0 + */ +void EVSEManufacturer::StartFakeReadings(EndpointId aEndpointId, int64_t aPower_mW, uint32_t aPowerRandomness_mW, + uint8_t aInterval_s, bool bReset) +{ + gFakeReadingsData.bEnabled = true; + gFakeReadingsData.mEndpointId = aEndpointId; + gFakeReadingsData.mPower_mW = aPower_mW; + gFakeReadingsData.mPowerRandomness_mW = aPowerRandomness_mW; + gFakeReadingsData.mInterval_s = aInterval_s; + + if (bReset) + { + gFakeReadingsData.mTotalEnergyImported = 0; + gFakeReadingsData.mTotalEnergyExported = 0; + } + + // Call update function to kick off regular readings + FakeReadingsUpdate(); +} +/** + * @brief Stops any active updates to the fake load data callbacks + */ +void EVSEManufacturer::StopFakeReadings() +{ + gFakeReadingsData.bEnabled = false; +} +/** + * @brief Sends fake meter data into the cluster and restarts the timer + */ +void EVSEManufacturer::FakeReadingsUpdate() +{ + /* Check to see if the fake Load is still running - don't send updates if the timer was already cancelled */ + if (!gFakeReadingsData.bEnabled) + { + return; + } + + // Update meter values + // Avoid using floats - so we will do a basic rand() call which will generate a integer value between 0 and RAND_MAX + // first compute power as a mean + some random value in range 0 to mPowerRandomness_mW + int64_t power = (rand() % gFakeReadingsData.mPowerRandomness_mW); + power += gFakeReadingsData.mPower_mW; // add in the base power + + // TODO call the EPM cluster to send a power reading + + // update the energy meter - we'll assume that the power has been constant during the previous interval + if (gFakeReadingsData.mPower_mW > 0) + { + // Positive power - means power is imported + gFakeReadingsData.mTotalEnergyImported += ((power * gFakeReadingsData.mInterval_s) / 3600); + } + else + { + // Negative power - means power is exported, but the cumulative energy is positive + gFakeReadingsData.mTotalEnergyExported += ((-power * gFakeReadingsData.mInterval_s) / 3600); + } + + SendEnergyReading(gFakeReadingsData.mEndpointId, gFakeReadingsData.mTotalEnergyImported, + gFakeReadingsData.mTotalEnergyExported); + + // start/restart the timer + DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(gFakeReadingsData.mInterval_s), FakeReadingsTimerExpiry, this); +} +/** + * @brief Timer expiry callback to handle fake load + */ +void EVSEManufacturer::FakeReadingsTimerExpiry(System::Layer * systemLayer, void * manufacturer) +{ + EVSEManufacturer * mn = reinterpret_cast(manufacturer); + + mn->FakeReadingsUpdate(); +} + /** * @brief Main Callback handler - to be implemented by Manufacturer * @@ -231,6 +408,37 @@ void SetTestEventTrigger_EVSEDiagnosticsComplete() dg->HwDiagnosticsComplete(); } +void SetTestEventTrigger_FakeReadingsLoadStart() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); + + int64_t aPower_mW = 1'000'000; // Fake load 1000 W + uint32_t aPowerRandomness_mW = 20'000; // randomness 20W + uint8_t aInterval_s = 2; // 2s updates + bool bReset = true; + mn->StartFakeReadings(EndpointId(1), aPower_mW, aPowerRandomness_mW, aInterval_s, bReset); +} + +void SetTestEventTrigger_FakeReadingsGeneratorStart() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); + + int64_t aPower_mW = -3'000'000; // Fake Generator -3000 W + uint32_t aPowerRandomness_mW = 20'000; // randomness 20W + uint8_t aInterval_s = 5; // 5s updates + bool bReset = true; + mn->StartFakeReadings(EndpointId(1), aPower_mW, aPowerRandomness_mW, aInterval_s, bReset); +} + +void SetTestEventTrigger_FakeReadingsStop() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); + mn->StopFakeReadings(); +} + bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) { EnergyEvseTrigger trigger = static_cast(eventTrigger); @@ -284,3 +492,29 @@ bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) return true; } + +bool HandleEnergyReportingTestEventTrigger(uint64_t eventTrigger) +{ + EnergyReportingTrigger trigger = static_cast(eventTrigger); + + switch (trigger) + { + case EnergyReportingTrigger::kFakeReadingsStop: + ChipLogProgress(Support, "[EnergyReporting-Test-Event] => Stop Fake load"); + SetTestEventTrigger_FakeReadingsStop(); + break; + case EnergyReportingTrigger::kFakeReadingsLoadStart_1kW_2s: + ChipLogProgress(Support, "[EnergyReporting-Test-Event] => Start Fake load 1kW @2s Import"); + SetTestEventTrigger_FakeReadingsLoadStart(); + break; + case EnergyReportingTrigger::kFakeReadingsGenStart_3kW_5s: + ChipLogProgress(Support, "[EnergyReporting-Test-Event] => Start Fake generator 3kW @5s Export"); + SetTestEventTrigger_FakeReadingsGeneratorStart(); + break; + + default: + return false; + } + + return true; +} diff --git a/examples/energy-management-app/esp32/main/CMakeLists.txt b/examples/energy-management-app/esp32/main/CMakeLists.txt index 46f97d766dbfeb..edba8f9671d1bc 100644 --- a/examples/energy-management-app/esp32/main/CMakeLists.txt +++ b/examples/energy-management-app/esp32/main/CMakeLists.txt @@ -65,6 +65,7 @@ set(SRC_DIRS_LIST "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/groups-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/group-key-mgmt-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/mode-base-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/electrical-energy-measurement-server" ) set(PRIV_REQUIRES_LIST chip QRCode bt led_strip app_update openthread driver nvs_flash spi_flash) diff --git a/examples/energy-management-app/linux/args.gni b/examples/energy-management-app/linux/args.gni index b7c71c3097a680..e1000e6cb3fa27 100644 --- a/examples/energy-management-app/linux/args.gni +++ b/examples/energy-management-app/linux/args.gni @@ -30,3 +30,4 @@ matter_enable_tracing_support = true chip_enable_read_client = false chip_enable_energy_evse_trigger = true +chip_enable_energy_reporting_trigger = true diff --git a/examples/energy-management-app/linux/main.cpp b/examples/energy-management-app/linux/main.cpp index 824fa17316cc70..a150b82c4bb171 100644 --- a/examples/energy-management-app/linux/main.cpp +++ b/examples/energy-management-app/linux/main.cpp @@ -208,7 +208,6 @@ CHIP_ERROR EVSEManufacturerInit() } /* Now create EVSEManufacturer */ - // TODO this takes just the EVSE Instance for now, but will need the DEM adding gEvseManufacturer = std::make_unique(gEvseInstance.get()); if (!gEvseManufacturer) { diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 933502dc050c69..04274ab2f33c24 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -86,6 +86,9 @@ #if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER #include #endif +#if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_REPORTING_TRIGGER +#include +#endif #include #include @@ -571,6 +574,12 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) }; otherDelegate = &energyEvseTestEventTriggerDelegate; #endif +#if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_REPORTING_TRIGGER + static EnergyReportingTestEventTriggerDelegate energyReportingTestEventTriggerDelegate{ + ByteSpan(LinuxDeviceOptions::GetInstance().testEventTriggerEnableKey), otherDelegate + }; + otherDelegate = &energyReportingTestEventTriggerDelegate; +#endif // For general testing of TestEventTrigger, we have a common "core" event trigger delegate. static SampleTestEventTriggerDelegate testEventTriggerDelegate; diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index fbbf7e1833894e..f82702be227709 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -23,6 +23,7 @@ declare_args() { chip_enable_smoke_co_trigger = false chip_enable_boolean_state_configuration_trigger = false chip_enable_energy_evse_trigger = false + chip_enable_energy_reporting_trigger = false } config("app-main-config") { @@ -47,6 +48,10 @@ source_set("energy-evse-test-event-trigger") { sources = [ "${chip_root}/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h" ] } +source_set("energy-reporting-test-event-trigger") { + sources = [ "${chip_root}/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h" ] +} + source_set("app-main") { defines = [ "ENABLE_TRACING=${matter_enable_tracing_support}" ] sources = [ @@ -71,6 +76,7 @@ source_set("app-main") { public_deps = [ ":boolean-state-configuration-test-event-trigger", ":energy-evse-test-event-trigger", + ":energy-reporting-test-event-trigger", ":smco-test-event-trigger", "${chip_root}/src/lib", "${chip_root}/src/platform/logging:force_stdio", @@ -110,6 +116,7 @@ source_set("app-main") { "CHIP_DEVICE_CONFIG_ENABLE_SMOKE_CO_TRIGGER=${chip_enable_smoke_co_trigger}", "CHIP_DEVICE_CONFIG_ENABLE_BOOLEAN_STATE_CONFIGURATION_TRIGGER=${chip_enable_boolean_state_configuration_trigger}", "CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER=${chip_enable_energy_evse_trigger}", + "CHIP_DEVICE_CONFIG_ENABLE_ENERGY_REPORTING_TRIGGER=${chip_enable_energy_reporting_trigger}", ] public_configs = [ ":app-main-config" ] diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 7f8f477c19a2cd..6404eb306d2c45 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -352,6 +352,13 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/BDXDiagnosticLogsProvider.h", "${_app_root}/clusters/${cluster}/DiagnosticLogsProviderDelegate.h", ] + } else if (cluster == "electrical-energy-measurement-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/${cluster}.h", + "${_app_root}/clusters/${cluster}/EnergyReportingTestEventTriggerDelegate.cpp", + "${_app_root}/clusters/${cluster}/EnergyReportingTestEventTriggerDelegate.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } diff --git a/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.cpp b/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.cpp new file mode 100644 index 00000000000000..210ab401bc2221 --- /dev/null +++ b/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.cpp @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#include "EnergyReportingTestEventTriggerDelegate.h" + +namespace chip { + +bool EnergyReportingTestEventTriggerDelegate::DoesEnableKeyMatch(const ByteSpan & enableKey) const +{ + return !mEnableKey.empty() && mEnableKey.data_equal(enableKey); +} + +CHIP_ERROR EnergyReportingTestEventTriggerDelegate::HandleEventTrigger(uint64_t eventTrigger) +{ + if (HandleEnergyReportingTestEventTrigger(eventTrigger)) + { + return CHIP_NO_ERROR; + } + if (mOtherDelegate != nullptr) + { + return mOtherDelegate->HandleEventTrigger(eventTrigger); + } + return CHIP_ERROR_INVALID_ARGUMENT; +} + +} // namespace chip diff --git a/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h b/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h new file mode 100644 index 00000000000000..a7beef6c2838ce --- /dev/null +++ b/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h @@ -0,0 +1,83 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#pragma once + +#include +#include + +namespace chip { + +/* + * These Test EventTrigger values can be used to fake meter reading data + * + * They are sent along with the enableKey (manufacturer defined secret) + * in the General Diagnostic cluster TestEventTrigger command + */ +enum class EnergyReportingTrigger : uint64_t +{ + // We use the Electrical Energy Measurement cluster ID as our TE trigger space + // Stop Fake readings + kFakeReadingsStop = 0x0091'0000'0000'0000, + // Fake a load (importing) readings at 1kW 230V 4.34A with 2s updates + kFakeReadingsLoadStart_1kW_2s = 0x0091'0000'0000'0001, + // Fake a generator (exporting) readings at 3kW 230V 3.33A with 5s updates + kFakeReadingsGenStart_3kW_5s = 0x0091'0000'0000'0002, + +}; + +class EnergyReportingTestEventTriggerDelegate : public TestEventTriggerDelegate +{ +public: + /** + * This class expects the enableKey ByteSpan to be valid forever. + * Typically this feature is only enabled in certification testing + * and uses a static secret key in the device for testing (e.g. in factory data) + */ + explicit EnergyReportingTestEventTriggerDelegate(const ByteSpan & enableKey, TestEventTriggerDelegate * otherDelegate) : + mEnableKey(enableKey), mOtherDelegate(otherDelegate) + {} + + /* This function returns True if the enableKey received in the TestEventTrigger command + * matches the value passed into the constructor. + */ + bool DoesEnableKeyMatch(const ByteSpan & enableKey) const override; + + /** This function must return True if the eventTrigger is recognised and handled + * It must return False to allow a higher level TestEvent handler to check other + * clusters that may handle it. + */ + CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override; + +private: + ByteSpan mEnableKey; + TestEventTriggerDelegate * mOtherDelegate; +}; + +} // namespace chip + +/** + * @brief User handler for handling the test event trigger + * + * @note If TestEventTrigger is enabled, it needs to be implemented in the app + * + * @param eventTrigger Event trigger to handle + * + * @retval true on success + * @retval false if error happened + */ +bool HandleEnergyReportingTestEventTrigger(uint64_t eventTrigger); diff --git a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp index fcc75ef319d60c..728b41dee02f74 100644 --- a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp +++ b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp @@ -33,36 +33,9 @@ using namespace chip::app::Clusters::ElectricalEnergyMeasurement; using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Attributes; using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Structs; -struct MeasurementData -{ - MeasurementAccuracyStruct::Type measurementAccuracy; - Optional cumulativeImported; - Optional cumulativeExported; - Optional periodicImported; - Optional periodicExported; -}; - MeasurementData gMeasurements[EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT]; -MeasurementData * MeasurementDataForEndpoint(EndpointId endpointId) -{ - auto index = emberAfGetClusterServerEndpointIndex(endpointId, app::Clusters::ElectricalEnergyMeasurement::Id, - EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT); - - if (index == kEmberInvalidEndpointIndex) - { - return nullptr; - } - - if (index >= EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT) - { - ChipLogError(NotSpecified, "Internal error: invalid/unexpected energy measurement index."); - return nullptr; - } - return &gMeasurements[index]; -} - class ElectricalEnergyMeasurementAttrAccess : public app::AttributeAccessInterface { public: @@ -126,6 +99,24 @@ namespace app { namespace Clusters { namespace ElectricalEnergyMeasurement { +MeasurementData * MeasurementDataForEndpoint(EndpointId endpointId) +{ + auto index = emberAfGetClusterServerEndpointIndex(endpointId, app::Clusters::ElectricalEnergyMeasurement::Id, + EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT); + + if (index == kEmberInvalidEndpointIndex) + { + return nullptr; + } + + if (index >= EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT) + { + ChipLogError(NotSpecified, "Internal error: invalid/unexpected energy measurement index."); + return nullptr; + } + return &gMeasurements[index]; +} + CHIP_ERROR SetMeasurementAccuracy(EndpointId endpointId, const MeasurementAccuracyStruct::Type & accuracy) { diff --git a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.h b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.h index 8647b9bde0b750..9c3d87683d680c 100644 --- a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.h +++ b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.h @@ -26,6 +26,15 @@ namespace app { namespace Clusters { namespace ElectricalEnergyMeasurement { +struct MeasurementData +{ + Structs::MeasurementAccuracyStruct::Type measurementAccuracy; + Optional cumulativeImported; + Optional cumulativeExported; + Optional periodicImported; + Optional periodicExported; +}; + bool NotifyCumulativeEnergyMeasured(EndpointId endpointId, const Optional & energyImported, const Optional & energyExported); @@ -34,6 +43,8 @@ bool NotifyPeriodicEnergyMeasured(EndpointId endpointId, const Optional