blob: 157b85d4773f5f41d5c3bd70c9c72cdd942b9258 [file] [log] [blame]
/*
*
* Copyright (c) 2023-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 "DeviceEnergyManagementDelegateImpl.h"
#include "DEMManufacturerDelegate.h"
#include "EnergyTimeUtils.h"
#include <app/EventLogging.h>
#include <protocols/interaction_model/StatusCode.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::DeviceEnergyManagement;
using namespace chip::app::Clusters::DeviceEnergyManagement::Attributes;
using chip::Protocols::InteractionModel::Status;
using chip::Optional;
using CostsList = DataModel::List<const Structs::CostStruct::Type>;
DeviceEnergyManagementDelegate::DeviceEnergyManagementDelegate() :
mpDEMManufacturerDelegate(nullptr), mEsaType(ESATypeEnum::kEvse), mEsaCanGenerate(false), mEsaState(ESAStateEnum::kOffline),
mAbsMinPowerMw(0), mAbsMaxPowerMw(0), mOptOutState(OptOutStateEnum::kNoOptOut), mPowerAdjustmentInProgress(false),
mPowerAdjustmentStartTimeUtc(0), mPauseRequestInProgress(false)
{}
void DeviceEnergyManagementDelegate::SetDeviceEnergyManagementInstance(DeviceEnergyManagement::Instance & instance)
{
mpDEMInstance = &instance;
}
uint32_t DeviceEnergyManagementDelegate::HasFeature(Feature feature) const
{
bool hasFeature = false;
if (mpDEMInstance != nullptr)
{
hasFeature = mpDEMInstance->HasFeature(feature);
}
return hasFeature;
}
void DeviceEnergyManagementDelegate::SetDEMManufacturerDelegate(
DEMManufacturerDelegate & deviceEnergyManagementManufacturerDelegate)
{
mpDEMManufacturerDelegate = &deviceEnergyManagementManufacturerDelegate;
}
/**
* @brief Delegate handler for PowerAdjustRequest
*
* Note: checking of the validity of the PowerAdjustRequest has been done by the lower layer
*
* This function needs to notify the appliance that it should apply a new power setting.
* It should:
* 1) notify the appliance - if the appliance hardware cannot be adjusted, then return Failure
* 2) start a timer (or restart the existing PowerAdjust timer) for duration seconds
* 3) generate a PowerAdjustStart event (if there is not an existing PowerAdjustRequest running)
* 4) if appropriate, update the forecast with the new expected end time
*
* and when the timer expires:
* 5) notify the appliance's that it can resume its intended power setting (or go idle)
* 6) generate a PowerAdjustEnd event with cause NormalCompletion
* 7) if necessary, update the forecast with new expected end time
*/
Status DeviceEnergyManagementDelegate::PowerAdjustRequest(const int64_t powerMw, const uint32_t durationS,
AdjustmentCauseEnum cause)
{
bool generateEvent = false;
// If a timer is running, cancel it so we can start it with the new duration
if (mPowerAdjustmentInProgress)
{
DeviceLayer::SystemLayer().CancelTimer(PowerAdjustTimerExpiry, this);
}
else
{
// Going to start a new power adjustment so will need to generate an event
generateEvent = true;
// Record when this PowerAdjustment starts. Note if we do not set this value if a PowerAdjustment is in progress
CHIP_ERROR err = GetEpochTS(mPowerAdjustmentStartTimeUtc);
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Unable to get time: %" CHIP_ERROR_FORMAT, err.Format());
return Status::Failure;
}
}
// Update the forecast with the new expected end time
if (mpDEMManufacturerDelegate != nullptr)
{
CHIP_ERROR err = mpDEMManufacturerDelegate->HandleDeviceEnergyManagementPowerAdjustRequest(powerMw, durationS, cause);
if (err != CHIP_NO_ERROR)
{
return Status::Failure;
}
}
SetESAState(ESAStateEnum::kPowerAdjustActive);
// mPowerAdjustCapabilityStruct is guaranteed to have a value as validated in Instance::HandlePowerAdjustRequest.
// If it did not have a value, this method would not have been called.
switch (cause)
{
case AdjustmentCauseEnum::kLocalOptimization:
SetPowerAdjustmentCapabilityPowerAdjustReason(PowerAdjustReasonEnum::kLocalOptimizationAdjustment);
break;
case AdjustmentCauseEnum::kGridOptimization:
SetPowerAdjustmentCapabilityPowerAdjustReason(PowerAdjustReasonEnum::kGridOptimizationAdjustment);
break;
default:
HandlePowerAdjustRequestFailure();
return Status::Failure;
}
// Remember we have a timer running so we don't generate a PowerAdjustStart event should another request come
// in before this timer expires
mPowerAdjustmentInProgress = true;
CHIP_ERROR err = DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(durationS), PowerAdjustTimerExpiry, this);
if (err != CHIP_NO_ERROR)
{
// TODO: Note: should the PowerAdjust just initiated be cancelled because an Event could not be logged?
ChipLogError(AppServer, "Unable to start a PowerAdjustStart timer: %" CHIP_ERROR_FORMAT, err.Format());
HandlePowerAdjustRequestFailure();
return Status::Failure;
}
if (generateEvent)
{
Events::PowerAdjustStart::Type event;
EventNumber eventNumber;
err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
// TODO: Note: should the PowerAdjust just initiated be cancelled because an Event could not be logged?
ChipLogError(AppServer, "Unable to generate PowerAdjustStart event: %" CHIP_ERROR_FORMAT, err.Format());
HandlePowerAdjustRequestFailure();
return Status::Failure;
}
}
return Status::Success;
}
/**
* @brief Handle a PowerAdjustRequest failing
*
* Cleans up the PowerAdjust state should the request fail
*/
void DeviceEnergyManagementDelegate::HandlePowerAdjustRequestFailure()
{
DeviceLayer::SystemLayer().CancelTimer(PowerAdjustTimerExpiry, this);
SetESAState(ESAStateEnum::kOnline);
mPowerAdjustmentInProgress = false;
SetPowerAdjustmentCapabilityPowerAdjustReason(PowerAdjustReasonEnum::kNoAdjustment);
// TODO
// Should we inform the mpDEMManufacturerDelegate that PowerAdjustRequest has failed?
}
/**
* @brief Timer for handling the PowerAdjustRequest
*
* This static function calls the non-static HandlePowerAdjustTimerExpiry method.
*/
void DeviceEnergyManagementDelegate::PowerAdjustTimerExpiry(System::Layer * systemLayer, void * delegate)
{
DeviceEnergyManagementDelegate * dg = reinterpret_cast<DeviceEnergyManagementDelegate *>(delegate);
dg->HandlePowerAdjustTimerExpiry();
}
/**
* @brief Timer for handling the completion of a PowerAdjustRequest
*
* When the timer expires:
* 1) notify the appliance's that it can resume its intended power setting (or go idle)
* 2) generate a PowerAdjustEnd event with cause NormalCompletion
* 3) if necessary, update the forecast with new expected end time
*/
void DeviceEnergyManagementDelegate::HandlePowerAdjustTimerExpiry()
{
ChipLogError(AppServer, "DeviceEnergyManagementDelegate::HandlePowerAdjustTimerExpiry");
// The PowerAdjustment is no longer in progress
mPowerAdjustmentInProgress = false;
SetESAState(ESAStateEnum::kOnline);
SetPowerAdjustmentCapabilityPowerAdjustReason(PowerAdjustReasonEnum::kNoAdjustment);
// Generate a PowerAdjustEnd event
GeneratePowerAdjustEndEvent(CauseEnum::kNormalCompletion);
// Update the forecast with new expected end time
if (mpDEMManufacturerDelegate != nullptr)
{
mpDEMManufacturerDelegate->HandleDeviceEnergyManagementPowerAdjustCompletion();
}
}
/**
* @brief Delegate handler for CancelPowerAdjustRequest
*
* Note: checking of the validity of the CancelPowerAdjustRequest has been done by the lower layer
*
* This function needs to notify the appliance that it should resume its intended power setting (or go idle).
*
* It should:
* 1) notify the appliance's that it can resume its intended power setting (or go idle)
* 2) generate a PowerAdjustEnd event with cause code Cancelled
* 3) if necessary, update the forecast with new expected end time
*/
Status DeviceEnergyManagementDelegate::CancelPowerAdjustRequest()
{
Status status = Status::Success;
CHIP_ERROR err = CancelPowerAdjustRequestAndGenerateEvent(DeviceEnergyManagement::CauseEnum::kCancelled);
if (CHIP_NO_ERROR != err)
{
status = Status::Failure;
}
return status;
}
/**
* @brief Handles the cancelation of a PowerAdjust operation
*
* This function needs to notify the appliance that it should resume its intended power setting (or go idle).
*
* It should:
* 1) notify the appliance's that it can resume its intended power setting (or go idle)
* 2) generate a PowerAdjustEnd event with cause code Cancelled
* 3) if necessary, update the forecast with new expected end time
*/
CHIP_ERROR DeviceEnergyManagementDelegate::CancelPowerAdjustRequestAndGenerateEvent(CauseEnum cause)
{
DeviceLayer::SystemLayer().CancelTimer(PowerAdjustTimerExpiry, this);
SetESAState(ESAStateEnum::kOnline);
mPowerAdjustmentInProgress = false;
SetPowerAdjustmentCapabilityPowerAdjustReason(PowerAdjustReasonEnum::kNoAdjustment);
CHIP_ERROR err = GeneratePowerAdjustEndEvent(cause);
// Notify the appliance's that it can resume its intended power setting (or go idle)
if (mpDEMManufacturerDelegate != nullptr)
{
// It is expected the mpDEMManufacturerDelegate will update the forecast with new expected end time
// as a consequence of the cancel request.
err = mpDEMManufacturerDelegate->HandleDeviceEnergyManagementCancelPowerAdjustRequest(cause);
}
return err;
}
/**
* @brief Generate a PowerAdjustEvent
*
*/
CHIP_ERROR DeviceEnergyManagementDelegate::GeneratePowerAdjustEndEvent(CauseEnum cause)
{
Events::PowerAdjustEnd::Type event;
EventNumber eventNumber;
event.cause = cause;
uint32_t timeNowUtc;
CHIP_ERROR err = GetEpochTS(timeNowUtc);
if (err == CHIP_NO_ERROR)
{
event.duration = timeNowUtc - mPowerAdjustmentStartTimeUtc;
}
else
{
ChipLogError(AppServer, "Unable to get time: %" CHIP_ERROR_FORMAT, err.Format());
return err;
}
if (mpDEMManufacturerDelegate != nullptr)
{
event.energyUse = mpDEMManufacturerDelegate->GetApproxEnergyDuringSession();
}
else
{
event.energyUse = 0;
}
err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
ChipLogError(AppServer, "Unable to generate PowerAdjustEnd event: %" CHIP_ERROR_FORMAT, err.Format());
return err;
}
return err;
}
/**
* @brief Delegate handler for StartTimeAdjustRequest
*
* Note: checking of the validity of the StartTimeAdjustRequest has been done by the lower layer
*
* This function needs to notify the appliance that the forecast has been updated by a client.
*
* It should:
* 1) update the forecast attribute with the revised start time
* 2) send a callback notification to the appliance so it can refresh its internal schedule
*/
Status DeviceEnergyManagementDelegate::StartTimeAdjustRequest(const uint32_t requestedStartTimeUtc, AdjustmentCauseEnum cause)
{
if (mForecast.IsNull())
{
return Status::Failure;
}
switch (cause)
{
case AdjustmentCauseEnum::kLocalOptimization:
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kLocalOptimization;
break;
case AdjustmentCauseEnum::kGridOptimization:
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kGridOptimization;
break;
default:
ChipLogDetail(AppServer, "Bad cause %d", to_underlying(cause));
return Status::Failure;
break;
}
mForecast.Value().forecastID++;
uint32_t durationS = mForecast.Value().endTime - mForecast.Value().startTime; // the current entire forecast duration
// Save the start and end time in case there is an issue with the mpDEMManufacturerDelegate handling this
// startTimeAdjustment request
uint32_t savedStartTime = mForecast.Value().startTime;
uint32_t savedEndTime = mForecast.Value().endTime;
/* Modify start time and end time */
mForecast.Value().startTime = requestedStartTimeUtc;
mForecast.Value().endTime = requestedStartTimeUtc + durationS;
if (mpDEMManufacturerDelegate != nullptr)
{
CHIP_ERROR err =
mpDEMManufacturerDelegate->HandleDeviceEnergyManagementStartTimeAdjustRequest(requestedStartTimeUtc, cause);
if (err != CHIP_NO_ERROR)
{
// Reset state
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kInternalOptimization;
mForecast.Value().startTime = savedStartTime;
mForecast.Value().endTime = savedEndTime;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
return Status::Failure;
}
}
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
return Status::Success;
}
/**
* @brief Delegate handler for Pause Request
*
* Note: checking of the validity of the Pause Request has been done by the lower layer
*
* This function needs to notify the appliance that it should now pause.
* It should:
* 1) pause the appliance - if the appliance hardware cannot be paused, then return Failure
* 2) start a timer for duration seconds
* 3) generate a Paused event
* 4) update the forecast with the new expected end time
*
* and when the timer expires:
* 5) restore the appliance's operational state
* 6) generate a Resumed event
* 7) if necessary, update the forecast with new expected end time
*/
Status DeviceEnergyManagementDelegate::PauseRequest(const uint32_t durationS, AdjustmentCauseEnum cause)
{
bool generateEvent = false;
// If a timer is running, cancel it so we can start it with the new duration
if (mPauseRequestInProgress)
{
DeviceLayer::SystemLayer().CancelTimer(PauseRequestTimerExpiry, this);
}
else
{
generateEvent = true;
// Remember we have a timer running so we don't generate a Paused event should another request come
// in before this timer expires
mPauseRequestInProgress = true;
}
CHIP_ERROR err = DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(durationS), PauseRequestTimerExpiry, this);
if (err != CHIP_NO_ERROR)
{
HandlePauseRequestFailure();
return Status::Failure;
}
// Pause the appliance
if (mpDEMManufacturerDelegate != nullptr)
{
// It is expected that the mpDEMManufacturerDelegate will update the forecast with the new expected end time
err = mpDEMManufacturerDelegate->HandleDeviceEnergyManagementPauseRequest(durationS, cause);
if (err != CHIP_NO_ERROR)
{
HandlePauseRequestFailure();
return Status::Failure;
}
}
if (generateEvent)
{
Events::Paused::Type event;
EventNumber eventNumber;
err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
ChipLogError(AppServer, "Unable to generate Paused event: %" CHIP_ERROR_FORMAT, err.Format());
HandlePauseRequestFailure();
return Status::Failure;
}
}
SetESAState(ESAStateEnum::kPaused);
// Update the forecaseUpdateReason based on the AdjustmentCause
if (cause == AdjustmentCauseEnum::kLocalOptimization)
{
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kLocalOptimization;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
}
else if (cause == AdjustmentCauseEnum::kGridOptimization)
{
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kGridOptimization;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
}
return Status::Success;
}
/**
* @brief Handle a PauseRequest failing
*
* Cleans up the state should the PauseRequest fail
*/
void DeviceEnergyManagementDelegate::HandlePauseRequestFailure()
{
DeviceLayer::SystemLayer().CancelTimer(PowerAdjustTimerExpiry, this);
SetESAState(ESAStateEnum::kOnline);
mPauseRequestInProgress = false;
// TODO
// Should we inform the mpDEMManufacturerDelegate that PauseRequest has failed?
}
/**
* @brief Timer for handling the PauseRequest
*
* This static function calls the non-static HandlePauseRequestTimerExpiry method.
*/
void DeviceEnergyManagementDelegate::PauseRequestTimerExpiry(System::Layer * systemLayer, void * delegate)
{
DeviceEnergyManagementDelegate * dg = reinterpret_cast<DeviceEnergyManagementDelegate *>(delegate);
dg->HandlePauseRequestTimerExpiry();
}
/**
* @brief Timer for handling the completion of a PauseRequest
*
* When the timer expires:
* 1) restore the appliance's operational state
* 2) generate a Resumed event
* 3) if necessary, update the forecast with new expected end time
*/
void DeviceEnergyManagementDelegate::HandlePauseRequestTimerExpiry()
{
// The PauseRequestment is no longer in progress
mPauseRequestInProgress = false;
SetESAState(ESAStateEnum::kOnline);
// Generate a Resumed event
GenerateResumedEvent(CauseEnum::kNormalCompletion);
// It is expected the mpDEMManufacturerDelegate will update the forecast with new expected end time
if (mpDEMManufacturerDelegate != nullptr)
{
mpDEMManufacturerDelegate->HandleDeviceEnergyManagementPauseCompletion();
}
}
/**
* @brief Handles the cancelation of a pause operation
*
* This function needs to notify the appliance that it should resume its intended power setting (or go idle).
*
* It should:
* 1) notify the appliance's that it can resume its intended power setting (or go idle)
* 2) generate a PowerAdjustEnd event with cause code Cancelled
* 3) if necessary, update the forecast with new expected end time
*/
CHIP_ERROR DeviceEnergyManagementDelegate::CancelPauseRequestAndGenerateEvent(CauseEnum cause)
{
mPauseRequestInProgress = false;
SetESAState(ESAStateEnum::kOnline);
DeviceLayer::SystemLayer().CancelTimer(PauseRequestTimerExpiry, this);
CHIP_ERROR err = GenerateResumedEvent(cause);
CHIP_ERROR err2 = CHIP_NO_ERROR;
// Notify the appliance's that it can resume its intended power setting (or go idle)
if (mpDEMManufacturerDelegate != nullptr)
{
// It is expected that the mpDEMManufacturerDelegate will update the forecast with new expected end time
err2 = mpDEMManufacturerDelegate->HandleDeviceEnergyManagementCancelPauseRequest(cause);
}
// Need to pick one of the error codes two return...
if (err == CHIP_NO_ERROR && err2 == CHIP_NO_ERROR)
{
return CHIP_NO_ERROR;
}
if (err2 != CHIP_NO_ERROR)
{
return err2;
}
return err;
}
/**
* @brief Generate a Resumed event
*
*/
CHIP_ERROR DeviceEnergyManagementDelegate::GenerateResumedEvent(CauseEnum cause)
{
Events::Resumed::Type event;
EventNumber eventNumber;
event.cause = cause;
CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
ChipLogError(AppServer, "Unable to generate Resumed event: %" CHIP_ERROR_FORMAT, err.Format());
}
return err;
}
/**
* @brief Delegate handler for ResumeRequest
*
* Note: checking of the validity of the ResumeRequest has been done by the lower layer
*
* This function needs to notify the appliance that it should now resume operation
*
* It should:
* 1) restore the appliance's operational state
* 2) generate a Resumed event
* 3) update the forecast with new expected end time (given that the pause duration was shorter than originally requested)
*
*/
Status DeviceEnergyManagementDelegate::ResumeRequest()
{
Status status = Status::Failure;
if (mPauseRequestInProgress)
{
// Guard against mForecast being null
if (!mForecast.IsNull())
{
// The PauseRequest has effectively been cancelled so as a result the device should
// go back to InternalOptimisation
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kInternalOptimization;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
}
CHIP_ERROR err = CancelPauseRequestAndGenerateEvent(CauseEnum::kCancelled);
if (err == CHIP_NO_ERROR)
{
status = Status::Success;
}
}
return status;
}
/**
* @brief Delegate handler for ModifyForecastRequest
*
* Note: Only basic checking of the validity of the ModifyForecastRequest has been
* done by the lower layer. This is a more complex use-case and requires higher-level
* work by the delegate.
*
* It should:
* 1) determine if the new forecast adjustments are acceptable to the appliance
* - if not return Failure. For example, if it may cause the home to be too hot
* or too cold, or a battery to be insufficiently charged
* 2) if the slot adjustments are acceptable, then update the forecast
* 3) notify the appliance to follow the revised schedule
*/
Status DeviceEnergyManagementDelegate::ModifyForecastRequest(
const uint32_t forecastID, const DataModel::DecodableList<Structs::SlotAdjustmentStruct::DecodableType> & slotAdjustments,
AdjustmentCauseEnum cause)
{
Status status = Status::Success;
if (mForecast.IsNull())
{
status = Status::Failure;
}
else if (mForecast.Value().forecastID != forecastID)
{
status = Status::Failure;
}
else if (mpDEMManufacturerDelegate != nullptr)
{
// Determine if the new forecast adjustments are acceptable to the appliance
CHIP_ERROR err = mpDEMManufacturerDelegate->HandleModifyForecastRequest(forecastID, slotAdjustments, cause);
if (err != CHIP_NO_ERROR)
{
status = Status::Failure;
}
}
if (status == Status::Success)
{
switch (cause)
{
case AdjustmentCauseEnum::kLocalOptimization:
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kLocalOptimization;
break;
case AdjustmentCauseEnum::kGridOptimization:
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kGridOptimization;
break;
default:
// Already checked in chip::app::Clusters::DeviceEnergyManagement::Instance::HandleModifyForecastRequest
break;
}
mForecast.Value().forecastID++;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
}
return status;
}
/**
* @brief Delegate handler for RequestConstraintBasedForecast
*
* Note: Only basic checking of the validity of the RequestConstraintBasedForecast has been
* done by the lower layer. This is a more complex use-case and requires higher-level
* work by the delegate.
*
* It should:
* 1) perform a higher level optimization (e.g. using tariff information, and user preferences)
* 2) if a solution can be found, then update the forecast, else return Failure
* 3) notify the appliance to follow the revised schedule
*/
Status DeviceEnergyManagementDelegate::RequestConstraintBasedForecast(
const DataModel::DecodableList<Structs::ConstraintsStruct::DecodableType> & constraints, AdjustmentCauseEnum cause)
{
Status status = Status::Success;
if (mForecast.IsNull())
{
status = Status::Failure;
}
else if (mpDEMManufacturerDelegate != nullptr)
{
// Determine if the new forecast adjustments are acceptable to the appliance
CHIP_ERROR err = mpDEMManufacturerDelegate->RequestConstraintBasedForecast(constraints, cause);
if (err != CHIP_NO_ERROR)
{
status = Status::Failure;
}
}
if (status == Status::Success)
{
switch (cause)
{
case AdjustmentCauseEnum::kLocalOptimization:
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kLocalOptimization;
break;
case AdjustmentCauseEnum::kGridOptimization:
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kGridOptimization;
break;
default:
// Already checked in chip::app::Clusters::DeviceEnergyManagement::Instance::HandleModifyForecastRequest
break;
}
mForecast.Value().forecastID++;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
status = Status::Success;
}
return status;
}
/**
* @brief Delegate handler for CancelRequest
*
* Note: This is a more complex use-case and requires higher-level work by the delegate.
*
* It SHALL:
* 1) Check if the forecastUpdateReason was already InternalOptimization (and reject the command)
* 2) Update its forecast (based on its optimization strategy) ignoring previous requests
* 3) Update its Forecast attribute to match its new intended operation, and update the
* ForecastStruct.ForecastUpdateReason to `Internal Optimization`.
*/
Status DeviceEnergyManagementDelegate::CancelRequest()
{
Status status = Status::Success;
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kInternalOptimization;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
/* It is expected the mpDEMManufacturerDelegate will cancel the effects of any previous adjustment
* request commands, and re-evaluate its forecast for intended operation ignoring those previous
* requests.
*/
if (mpDEMManufacturerDelegate != nullptr)
{
CHIP_ERROR error = mpDEMManufacturerDelegate->HandleDeviceEnergyManagementCancelRequest();
if (error != CHIP_NO_ERROR)
{
status = Status::Failure;
}
}
return status;
}
// ------------------------------------------------------------------
// Get attribute methods
ESATypeEnum DeviceEnergyManagementDelegate::GetESAType()
{
return mEsaType;
}
bool DeviceEnergyManagementDelegate::GetESACanGenerate()
{
return mEsaCanGenerate;
}
ESAStateEnum DeviceEnergyManagementDelegate::GetESAState()
{
return mEsaState;
}
int64_t DeviceEnergyManagementDelegate::GetAbsMinPower()
{
return mAbsMinPowerMw;
}
int64_t DeviceEnergyManagementDelegate::GetAbsMaxPower()
{
return mAbsMaxPowerMw;
}
const DataModel::Nullable<Structs::PowerAdjustCapabilityStruct::Type> &
DeviceEnergyManagementDelegate::GetPowerAdjustmentCapability()
{
return mPowerAdjustCapabilityStruct;
}
const DataModel::Nullable<Structs::ForecastStruct::Type> & DeviceEnergyManagementDelegate::GetForecast()
{
ChipLogDetail(Zcl, "DeviceEnergyManagementDelegate::GetForecast");
return mForecast;
}
OptOutStateEnum DeviceEnergyManagementDelegate::GetOptOutState()
{
ChipLogDetail(AppServer, "mOptOutState %d", to_underlying(mOptOutState));
return mOptOutState;
}
// ------------------------------------------------------------------
// Set attribute methods
CHIP_ERROR DeviceEnergyManagementDelegate::SetESAType(ESATypeEnum newValue)
{
ESATypeEnum oldValue = mEsaType;
if (newValue >= ESATypeEnum::kUnknownEnumValue)
{
return CHIP_IM_GLOBAL_STATUS(ConstraintError);
}
mEsaType = newValue;
if (oldValue != newValue)
{
ChipLogDetail(AppServer, "mEsaType updated to %d", static_cast<int>(mEsaType));
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, ESAType::Id);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceEnergyManagementDelegate::SetESACanGenerate(bool newValue)
{
bool oldValue = mEsaCanGenerate;
mEsaCanGenerate = newValue;
if (oldValue != newValue)
{
ChipLogDetail(AppServer, "mEsaCanGenerate updated to %d", static_cast<int>(mEsaCanGenerate));
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, ESACanGenerate::Id);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceEnergyManagementDelegate::SetESAState(ESAStateEnum newValue)
{
ESAStateEnum oldValue = mEsaState;
if (newValue >= ESAStateEnum::kUnknownEnumValue)
{
return CHIP_IM_GLOBAL_STATUS(ConstraintError);
}
mEsaState = newValue;
if (oldValue != newValue)
{
ChipLogDetail(AppServer, "mEsaState updated to %d", static_cast<int>(mEsaState));
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, ESAState::Id);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceEnergyManagementDelegate::SetAbsMinPower(int64_t newValueMw)
{
int64_t oldValueMw = mAbsMinPowerMw;
mAbsMinPowerMw = newValueMw;
if (oldValueMw != newValueMw)
{
ChipLogDetail(AppServer, "mAbsMinPower updated to " ChipLogFormatX64, ChipLogValueX64(mAbsMinPowerMw));
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, AbsMinPower::Id);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceEnergyManagementDelegate::SetAbsMaxPower(int64_t newValueMw)
{
int64_t oldValueMw = mAbsMaxPowerMw;
mAbsMaxPowerMw = newValueMw;
if (oldValueMw != newValueMw)
{
ChipLogDetail(AppServer, "mAbsMaxPower updated to " ChipLogFormatX64, ChipLogValueX64(mAbsMaxPowerMw));
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, AbsMaxPower::Id);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR
DeviceEnergyManagementDelegate::SetPowerAdjustmentCapability(
const DataModel::Nullable<Structs::PowerAdjustCapabilityStruct::Type> & powerAdjustCapabilityStruct)
{
assertChipStackLockedByCurrentThread();
mPowerAdjustCapabilityStruct = powerAdjustCapabilityStruct;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, PowerAdjustmentCapability::Id);
return CHIP_NO_ERROR;
}
CHIP_ERROR
DeviceEnergyManagementDelegate::SetPowerAdjustmentCapabilityPowerAdjustReason(PowerAdjustReasonEnum powerAdjustReason)
{
assertChipStackLockedByCurrentThread();
mPowerAdjustCapabilityStruct.Value().cause = powerAdjustReason;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, PowerAdjustmentCapability::Id);
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceEnergyManagementDelegate::SetForecast(const DataModel::Nullable<Structs::ForecastStruct::Type> & forecast)
{
assertChipStackLockedByCurrentThread();
// TODO see Issue #31147
mForecast = forecast;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
return CHIP_NO_ERROR;
}
CHIP_ERROR DeviceEnergyManagementDelegate::SetOptOutState(OptOutStateEnum newValue)
{
CHIP_ERROR err = CHIP_NO_ERROR;
OptOutStateEnum oldValue = mOptOutState;
// The OptOutState is cumulative
if ((oldValue == OptOutStateEnum::kGridOptOut && newValue == OptOutStateEnum::kLocalOptOut) ||
(oldValue == OptOutStateEnum::kLocalOptOut && newValue == OptOutStateEnum::kGridOptOut))
{
mOptOutState = OptOutStateEnum::kOptOut;
}
else
{
mOptOutState = newValue;
}
if (oldValue != newValue)
{
ChipLogDetail(AppServer, "mOptOutState updated to %d mPowerAdjustmentInProgress %d", to_underlying(mOptOutState),
mPowerAdjustmentInProgress);
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, OptOutState::Id);
}
// Cancel any outstanding PowerAdjustment if necessary
if (mPowerAdjustmentInProgress)
{
if ((newValue == OptOutStateEnum::kLocalOptOut &&
GetPowerAdjustmentCapability().Value().cause == PowerAdjustReasonEnum::kLocalOptimizationAdjustment) ||
(newValue == OptOutStateEnum::kGridOptOut &&
GetPowerAdjustmentCapability().Value().cause == PowerAdjustReasonEnum::kGridOptimizationAdjustment) ||
newValue == OptOutStateEnum::kOptOut)
{
err = CancelPowerAdjustRequestAndGenerateEvent(DeviceEnergyManagement::CauseEnum::kUserOptOut);
}
}
// Cancel any outstanding PauseRequest if necessary
if (mPauseRequestInProgress)
{
// Cancel any outstanding PauseRequest
if ((newValue == OptOutStateEnum::kLocalOptOut &&
mForecast.Value().forecastUpdateReason == ForecastUpdateReasonEnum::kLocalOptimization) ||
(newValue == OptOutStateEnum::kGridOptOut &&
mForecast.Value().forecastUpdateReason == ForecastUpdateReasonEnum::kGridOptimization) ||
newValue == OptOutStateEnum::kOptOut)
{
err = CancelPauseRequestAndGenerateEvent(DeviceEnergyManagement::CauseEnum::kUserOptOut);
}
}
if (!mForecast.IsNull())
{
switch (mForecast.Value().forecastUpdateReason)
{
case ForecastUpdateReasonEnum::kInternalOptimization:
// We don't need to redo a forecast since its internal already
break;
case ForecastUpdateReasonEnum::kLocalOptimization:
if ((mOptOutState == OptOutStateEnum::kOptOut) || (mOptOutState == OptOutStateEnum::kLocalOptOut))
{
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kInternalOptimization;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
// Generate a new forecast with Internal Optimization
// TODO
}
break;
case ForecastUpdateReasonEnum::kGridOptimization:
if ((mOptOutState == OptOutStateEnum::kOptOut) || (mOptOutState == OptOutStateEnum::kGridOptOut))
{
mForecast.Value().forecastUpdateReason = ForecastUpdateReasonEnum::kInternalOptimization;
MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, Forecast::Id);
// Generate a new forecast with Internal Optimization
// TODO
}
break;
default:
ChipLogDetail(AppServer, "Bad ForecastUpdateReasonEnum value of %d",
to_underlying(mForecast.Value().forecastUpdateReason));
return CHIP_ERROR_BAD_REQUEST;
break;
}
}
return err;
}