blob: 2d48dd821d5ea8ce9acf25653aab31115f22f2e0 [file] [log] [blame]
/*
*
* 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 <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/AttributeAccessInterface.h>
#include <app/EventLogging.h>
#include <app/reporting/reporting.h>
#include <app/server/Server.h>
#include <app/util/attribute-storage.h>
#include <lib/core/ClusterEnums.h>
#include <system/SystemClock.h>
#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<chip::Percent100ths>(500)),
.percentMin = MakeOptional(static_cast<chip::Percent100ths>(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<const ElectricalEnergyMeasurement::Structs::MeasurementAccuracyRangeStruct::Type>(kEnergyAccuracyRanges)
};
static const CumulativeEnergyResetStruct::Type kResetStruct = {
.importedResetTimestamp = MakeOptional(MakeNullable(static_cast<uint32_t>(0))),
.exportedResetTimestamp = MakeOptional(MakeNullable(static_cast<uint32_t>(0))),
.importedResetSystime = MakeOptional(MakeNullable(static_cast<uint64_t>(0))),
.exportedResetSystime = MakeOptional(MakeNullable(static_cast<uint64_t>(0))),
};
static EnergyMeasurementStruct::Type sCumulativeImported = {
.energy = static_cast<int64_t>(0),
};
static const EnergyMeasurementStruct::Type sCumulativeExported = {
.energy = static_cast<int64_t>(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<int64_t> EPMActivePower = gEPMDelegate ? gEPMDelegate->GetActivePower() : Nullable<int64_t>(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();
}