/* * * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * * 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 #include #include #include #include #include #include #include #include #include "AppConfig.h" #include "AppTask.h" #include "ElectricalEnergyMeasurementInstance.h" #include "EnergyTimeUtils.h" #define mWms_TO_mWh(power) ((power) / 3600'000) using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::DeviceEnergyManagement; using namespace chip::app::Clusters::ElectricalEnergyMeasurement; using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Attributes; using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Structs; using namespace chip::app::DataModel; // Example of setting CumulativeEnergyReset structure - for now set these to 0 // but the manufacturer may want to store these in non volatile storage for timestamp (based on epoch_s) // Create an accuracy entry which is between +/-0.5 and +/- 5% across the range of all possible energy readings static const MeasurementAccuracyRangeStruct::Type kEnergyAccuracyRanges[] = { { .rangeMin = 0, .rangeMax = 1'000'000'000'000'000, // 1 million Mwh .percentMax = MakeOptional(static_cast(500)), .percentMin = MakeOptional(static_cast(50)) } }; static const MeasurementAccuracyStruct::Type kAccuracy = { .measurementType = MeasurementTypeEnum::kElectricalEnergy, .measured = false, // this should be made true in an implementation where a real metering device is used. .minMeasuredValue = 0, .maxMeasuredValue = 1'000'000'000'000'000, // 1 million Mwh .accuracyRanges = List(kEnergyAccuracyRanges) }; static const CumulativeEnergyResetStruct::Type kResetStruct = { .importedResetTimestamp = MakeOptional(MakeNullable(static_cast(0))), .exportedResetTimestamp = MakeOptional(MakeNullable(static_cast(0))), .importedResetSystime = MakeOptional(MakeNullable(static_cast(0))), .exportedResetSystime = MakeOptional(MakeNullable(static_cast(0))), }; static EnergyMeasurementStruct::Type sCumulativeImported = { .energy = static_cast(0), }; static const EnergyMeasurementStruct::Type sCumulativeExported = { .energy = static_cast(0), }; namespace { ElectricalPowerMeasurement::ElectricalPowerMeasurementDelegate * gEPMDelegate; EndpointId gEndpointId; int64_t sCumulativeActivePower = 0; uint8_t sSecondsSinceUpdate = 0; } // namespace CHIP_ERROR ElectricalEnergyMeasurementInstance::Init() { // Initialize attributes SetMeasurementAccuracy(mEndpointId, kAccuracy); SetCumulativeReset(mEndpointId, MakeOptional(kResetStruct)); // Assign class attributes to instantiated global variables // for the access to TimerEventHandler gEPMDelegate = GetEPMDelegate(); gEndpointId = mEndpointId; CHIP_ERROR err; uint32_t currentTimestamp; ReturnErrorOnFailure(GetEpochTS(currentTimestamp)); sCumulativeImported.startTimestamp.SetValue(currentTimestamp); sCumulativeImported.startSystime.SetValue(System::SystemClock().GetMonotonicTimestamp().count()); // Initialize and start timer to calculate cumulative energy err = InitTimer(); VerifyOrReturnError(CHIP_NO_ERROR == err, err, ChipLogError(AppServer, "Init failed on EEM Timer")); StartTimer(kTimerPeriodms); return ElectricalEnergyMeasurementAttrAccess::Init(); } void ElectricalEnergyMeasurementInstance::Shutdown() { CancelTimer(); ElectricalEnergyMeasurementAttrAccess::Shutdown(); } CHIP_ERROR ElectricalEnergyMeasurementInstance::InitTimer() { // Create cmsis os sw timer for EEM Cumulative timer mTimer = osTimerNew(TimerEventHandler, // Timer callback handler osTimerPeriodic, // Timer reload (void *) this, // Pass the app task obj context NULL // No osTimerAttr_t to provide. ); VerifyOrReturnError(mTimer != NULL, APP_ERROR_CREATE_TIMER_FAILED, SILABS_LOG("Timer create failed")); return CHIP_NO_ERROR; } void ElectricalEnergyMeasurementInstance::StartTimer(uint32_t aTimeoutMs) { // Start or restart the function timer if (osTimerStart(mTimer, pdMS_TO_TICKS(aTimeoutMs)) != osOK) { SILABS_LOG("Timer start failed"); appError(APP_ERROR_START_TIMER_FAILED); } } void ElectricalEnergyMeasurementInstance::CancelTimer() { if (osTimerStop(mTimer) == osError) { SILABS_LOG("Timer stop failed"); appError(APP_ERROR_STOP_TIMER_FAILED); } } void ElectricalEnergyMeasurementInstance::TimerEventHandler(void * timerCbArg) { // Get different EPM Active Power values according to operational mode change Nullable EPMActivePower = gEPMDelegate ? gEPMDelegate->GetActivePower() : Nullable(0); int64_t activePower = (EPMActivePower.IsNull()) ? 0 : EPMActivePower.Value(); // Calculate cumulative imported active power sCumulativeActivePower += activePower; sSecondsSinceUpdate += 1; if (sSecondsSinceUpdate >= ElectricalEnergyMeasurementInstance::kAttributeFrequency) { sSecondsSinceUpdate = 0; AppEvent event; event.Type = AppEvent::kEventType_Timer; event.Handler = UpdateEnergyAttributesAndNotify; AppTask::GetAppTask().PostEvent(&event); } } void ElectricalEnergyMeasurementInstance::UpdateEnergyAttributesAndNotify(AppEvent * aEvent) { // cumulativeImported update code - To update energy (mWs to mWh), startSystime and endSystime // Convert the unit : mW * ms -> mWh sCumulativeImported.energy = mWms_TO_mWh(sCumulativeActivePower * kTimerPeriodms); uint32_t currentTimestamp; if (GetEpochTS(currentTimestamp) == CHIP_NO_ERROR) { // Use EpochTS sCumulativeImported.endTimestamp.SetValue(currentTimestamp); } sCumulativeImported.endSystime.SetValue(System::SystemClock().GetMonotonicTimestamp().count()); // Call the SDK to update attributes and generate an event chip::DeviceLayer::PlatformMgr().LockChipStack(); NotifyCumulativeEnergyMeasured(gEndpointId, MakeOptional(sCumulativeImported), MakeOptional(sCumulativeExported)); MatterReportingAttributeChangeCallback(gEndpointId, ElectricalEnergyMeasurement::Id, CumulativeEnergyImported::Id); chip::DeviceLayer::PlatformMgr().UnlockChipStack(); }