blob: 0b7205dd83a33a3361bab095c1bed6fc1c71fc9f [file] [log] [blame]
/*
* Copyright (c) 2023 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 "device-energy-management-server.h"
#include <app/AttributeAccessInterface.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/CommandHandlerInterfaceRegistry.h>
#include <app/ConcreteAttributePath.h>
#include <app/InteractionModelEngine.h>
#include <app/util/attribute-storage.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;
namespace chip {
namespace app {
namespace Clusters {
namespace DeviceEnergyManagement {
CHIP_ERROR Instance::Init()
{
ReturnErrorOnFailure(CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this));
VerifyOrReturnError(AttributeAccessInterfaceRegistry::Instance().Register(this), CHIP_ERROR_INCORRECT_STATE);
return CHIP_NO_ERROR;
}
void Instance::Shutdown()
{
CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this);
AttributeAccessInterfaceRegistry::Instance().Unregister(this);
}
bool Instance::HasFeature(Feature aFeature) const
{
return mFeature.Has(aFeature);
}
// AttributeAccessInterface
CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
switch (aPath.mAttributeId)
{
case ESAType::Id:
return aEncoder.Encode(mDelegate.GetESAType());
case ESACanGenerate::Id:
return aEncoder.Encode(mDelegate.GetESACanGenerate());
case ESAState::Id:
return aEncoder.Encode(mDelegate.GetESAState());
case AbsMinPower::Id:
return aEncoder.Encode(mDelegate.GetAbsMinPower());
case AbsMaxPower::Id:
return aEncoder.Encode(mDelegate.GetAbsMaxPower());
case PowerAdjustmentCapability::Id:
/* PA - PowerAdjustment */
if (!HasFeature(Feature::kPowerAdjustment))
{
return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
}
return aEncoder.Encode(mDelegate.GetPowerAdjustmentCapability());
case Forecast::Id:
/* PFR | SFR - Power Forecast Reporting or State Forecast Reporting */
if (!HasFeature(Feature::kPowerForecastReporting) && !HasFeature(Feature::kStateForecastReporting))
{
return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
}
return aEncoder.Encode(mDelegate.GetForecast());
case OptOutState::Id:
/* PA | STA | PAU | FA | CON */
if (!HasFeature(Feature::kPowerAdjustment) && !HasFeature(Feature::kStartTimeAdjustment) &&
!HasFeature(Feature::kPausable) && !HasFeature(Feature::kForecastAdjustment) &&
!HasFeature(Feature::kConstraintBasedAdjustment))
{
return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
}
return aEncoder.Encode(mDelegate.GetOptOutState());
/* FeatureMap - is held locally */
case FeatureMap::Id:
return aEncoder.Encode(mFeature);
}
/* Allow all other unhandled attributes to fall through to Ember */
return CHIP_NO_ERROR;
}
// CommandHandlerInterface
CHIP_ERROR Instance::EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context)
{
using namespace Commands;
if (HasFeature(Feature::kPowerAdjustment))
{
for (auto && cmd : {
PowerAdjustRequest::Id,
CancelPowerAdjustRequest::Id,
})
{
VerifyOrExit(callback(cmd, context) == Loop::Continue, /**/);
}
}
if (HasFeature(Feature::kStartTimeAdjustment))
{
VerifyOrExit(callback(StartTimeAdjustRequest::Id, context) == Loop::Continue, /**/);
}
if (HasFeature(Feature::kPausable))
{
VerifyOrExit(callback(PauseRequest::Id, context) == Loop::Continue, /**/);
VerifyOrExit(callback(ResumeRequest::Id, context) == Loop::Continue, /**/);
}
if (HasFeature(Feature::kForecastAdjustment))
{
VerifyOrExit(callback(ModifyForecastRequest::Id, context) == Loop::Continue, /**/);
}
if (HasFeature(Feature::kConstraintBasedAdjustment))
{
VerifyOrExit(callback(RequestConstraintBasedForecast::Id, context) == Loop::Continue, /**/);
}
if (HasFeature(Feature::kStartTimeAdjustment) || HasFeature(Feature::kForecastAdjustment) ||
HasFeature(Feature::kConstraintBasedAdjustment))
{
VerifyOrExit(callback(CancelRequest::Id, context) == Loop::Continue, /**/);
}
exit:
return CHIP_NO_ERROR;
}
void Instance::InvokeCommand(HandlerContext & handlerContext)
{
using namespace Commands;
switch (handlerContext.mRequestPath.mCommandId)
{
case PowerAdjustRequest::Id:
if (!HasFeature(Feature::kPowerAdjustment))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
else
{
HandleCommand<PowerAdjustRequest::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandlePowerAdjustRequest(ctx, commandData); });
}
return;
case CancelPowerAdjustRequest::Id:
if (!HasFeature(Feature::kPowerAdjustment))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
else
{
HandleCommand<CancelPowerAdjustRequest::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandleCancelPowerAdjustRequest(ctx, commandData); });
}
return;
case StartTimeAdjustRequest::Id:
if (!HasFeature(Feature::kStartTimeAdjustment))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
else
{
HandleCommand<StartTimeAdjustRequest::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandleStartTimeAdjustRequest(ctx, commandData); });
}
return;
case PauseRequest::Id:
if (!HasFeature(Feature::kPausable))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
else
{
HandleCommand<PauseRequest::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandlePauseRequest(ctx, commandData); });
}
return;
case ResumeRequest::Id:
if (!HasFeature(Feature::kPausable))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
else
{
HandleCommand<ResumeRequest::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleResumeRequest(ctx, commandData); });
}
return;
case ModifyForecastRequest::Id:
if (!HasFeature(Feature::kForecastAdjustment))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
else
{
HandleCommand<ModifyForecastRequest::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandleModifyForecastRequest(ctx, commandData); });
}
return;
case RequestConstraintBasedForecast::Id:
if (!HasFeature(Feature::kConstraintBasedAdjustment))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
else
{
HandleCommand<RequestConstraintBasedForecast::DecodableType>(
handlerContext,
[this](HandlerContext & ctx, const auto & commandData) { HandleRequestConstraintBasedForecast(ctx, commandData); });
}
return;
case CancelRequest::Id:
if (!HasFeature(Feature::kStartTimeAdjustment) && !HasFeature(Feature::kForecastAdjustment) &&
!HasFeature(Feature::kConstraintBasedAdjustment))
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand);
}
else
{
HandleCommand<CancelRequest::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleCancelRequest(ctx, commandData); });
}
return;
}
}
Status Instance::CheckOptOutAllowsRequest(AdjustmentCauseEnum adjustmentCause)
{
OptOutStateEnum optOutState = mDelegate.GetOptOutState();
if (adjustmentCause == AdjustmentCauseEnum::kUnknownEnumValue)
{
ChipLogError(Zcl, "DEM: adjustment cause is invalid (%d)", static_cast<int>(adjustmentCause));
return Status::InvalidValue;
}
switch (optOutState)
{
case OptOutStateEnum::kNoOptOut: /* User has NOT opted out so allow it */
ChipLogProgress(Zcl, "DEM: OptOutState = kNoOptOut");
return Status::Success;
case OptOutStateEnum::kLocalOptOut: /* User has opted out from Local only*/
ChipLogProgress(Zcl, "DEM: OptOutState = kLocalOptOut");
switch (adjustmentCause)
{
case AdjustmentCauseEnum::kGridOptimization:
return Status::Success;
case AdjustmentCauseEnum::kLocalOptimization:
default:
return Status::ConstraintError;
}
case OptOutStateEnum::kGridOptOut: /* User has opted out from Grid only */
ChipLogProgress(Zcl, "DEM: OptOutState = kGridOptOut");
switch (adjustmentCause)
{
case AdjustmentCauseEnum::kLocalOptimization:
return Status::Success;
case AdjustmentCauseEnum::kGridOptimization:
default:
return Status::ConstraintError;
}
case OptOutStateEnum::kOptOut: /* User has opted out from both local and grid */
ChipLogProgress(Zcl, "DEM: OptOutState = kOptOut");
return Status::ConstraintError;
default:
ChipLogError(Zcl, "DEM: invalid optOutState %d", static_cast<int>(optOutState));
return Status::InvalidValue;
}
}
void Instance::HandlePowerAdjustRequest(HandlerContext & ctx, const Commands::PowerAdjustRequest::DecodableType & commandData)
{
bool validArgs = false;
int64_t power = commandData.power;
uint32_t durationSec = commandData.duration;
AdjustmentCauseEnum adjustmentCause = commandData.cause;
// Notify the appliance if the appliance hardware cannot be adjusted, then return Failure
if (!HasFeature(DeviceEnergyManagement::Feature::kPowerAdjustment))
{
ChipLogError(Zcl, "PowerAdjust not supported");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
Status status = CheckOptOutAllowsRequest(adjustmentCause);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: PowerAdjustRequest command rejected");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
return;
}
DataModel::Nullable<Structs::PowerAdjustCapabilityStruct::Type> powerAdjustmentCapabilityStruct =
mDelegate.GetPowerAdjustmentCapability();
if (powerAdjustmentCapabilityStruct.IsNull())
{
ChipLogError(Zcl, "DEM: powerAdjustmentCapabilityStruct IsNull");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
if (powerAdjustmentCapabilityStruct.Value().powerAdjustCapability.IsNull())
{
ChipLogError(Zcl, "DEM: powerAdjustmentCapabilityStruct.powerAdjustCapability IsNull");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
/* PowerAdjustmentCapability is a list - so iterate through checking if the command is within one of the offers */
for (auto pas : powerAdjustmentCapabilityStruct.Value().powerAdjustCapability.Value())
{
if ((power >= pas.minPower) && (durationSec >= pas.minDuration) && (power <= pas.maxPower) &&
(durationSec <= pas.maxDuration))
{
ChipLogProgress(Zcl, "DEM: Good PowerAdjustment args");
validArgs = true;
break;
}
}
if (!validArgs)
{
ChipLogError(Zcl, "DEM: invalid request range");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
ChipLogProgress(Zcl, "DEM: Good PowerAdjustRequest() args.");
status = mDelegate.PowerAdjustRequest(power, durationSec, adjustmentCause);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: Failed to PowerAdjustRequest() args.");
}
}
void Instance::HandleCancelPowerAdjustRequest(HandlerContext & ctx,
const Commands::CancelPowerAdjustRequest::DecodableType & commandData)
{
if (!HasFeature(DeviceEnergyManagement::Feature::kPowerAdjustment))
{
ChipLogError(Zcl, "PowerAdjust not supported");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
/* Check that the ESA state is PowerAdjustActive */
ESAStateEnum esaStatus = mDelegate.GetESAState();
if (ESAStateEnum::kPowerAdjustActive != esaStatus)
{
ChipLogError(Zcl, "DEM: kPowerAdjustActive != esaStatus");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidInState);
return;
}
Status status = mDelegate.CancelPowerAdjustRequest();
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: Failed to CancelPowerAdjustRequest()");
return;
}
}
void Instance::HandleStartTimeAdjustRequest(HandlerContext & ctx,
const Commands::StartTimeAdjustRequest::DecodableType & commandData)
{
Status status;
uint32_t earliestStartTimeEpoch = 0;
uint32_t latestEndTimeEpoch = 0;
uint32_t duration;
uint32_t requestedStartTimeEpoch = commandData.requestedStartTime;
AdjustmentCauseEnum adjustmentCause = commandData.cause;
status = CheckOptOutAllowsRequest(adjustmentCause);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: StartTimeAdjustRequest command rejected");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
return;
}
DataModel::Nullable<Structs::ForecastStruct::Type> forecastNullable = mDelegate.GetForecast();
if (forecastNullable.IsNull())
{
ChipLogError(Zcl, "DEM: Forecast is Null");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
/* Temporary variable to save calling .Value() on forecastNullable */
auto & forecast = forecastNullable.Value();
/**
* If the RequestedStartTime value resulted in a time shift which is
* outside the time constraints of EarliestStartTime and
* LatestEndTime, then the command SHALL be rejected with CONSTRAINT_ERROR;
* in other failure scenarios the command SHALL be rejected with FAILURE
*/
/* earliestStartTime is optional based on the StartTimeAdjust (STA) feature AND is nullable */
if (!(forecast.earliestStartTime.HasValue()) || !(forecast.latestEndTime.HasValue()))
{
/* These should have values, since this command requires STA feature and these are mandatory for that */
ChipLogError(Zcl, "DEM: EarliestStartTime / LatestEndTime do not have values");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
/* Temporary variable to save keep calling .Value() on the Optional element */
DataModel::Nullable<uint32_t> & earliestStartTimeNullable = forecast.earliestStartTime.Value();
/* Latest End Time is optional & cannot be null - unlike earliestStartTime! */
latestEndTimeEpoch = forecast.latestEndTime.Value();
if (earliestStartTimeNullable.IsNull())
{
System::Clock::Milliseconds64 cTMs;
CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(cTMs);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "DEM: Unable to get current time - err:%" CHIP_ERROR_FORMAT, err.Format());
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
auto unixEpoch = std::chrono::duration_cast<System::Clock::Seconds32>(cTMs).count();
uint32_t chipEpoch = 0;
if (!UnixEpochToChipEpochTime(unixEpoch, chipEpoch))
{
ChipLogError(Zcl, "DEM: unable to convert Unix Epoch time to Matter Epoch Time");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
/* Null means - We can start immediately */
earliestStartTimeEpoch = chipEpoch; /* NOW */
}
else
{
earliestStartTimeEpoch = earliestStartTimeNullable.Value();
}
duration = forecast.endTime - forecast.startTime; // the current entire forecast duration
if (requestedStartTimeEpoch < earliestStartTimeEpoch)
{
ChipLogError(Zcl, "DEM: Bad requestedStartTime %ld, earlier than earliestStartTime %ld.",
static_cast<long unsigned int>(requestedStartTimeEpoch),
static_cast<long unsigned int>(earliestStartTimeEpoch));
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
if ((requestedStartTimeEpoch + duration) > latestEndTimeEpoch)
{
ChipLogError(Zcl, "DEM: Bad requestedStartTimeEpoch + duration %ld, later than latestEndTime %ld.",
static_cast<long unsigned int>(requestedStartTimeEpoch + duration),
static_cast<long unsigned int>(latestEndTimeEpoch));
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
ChipLogProgress(Zcl, "DEM: Good requestedStartTimeEpoch %ld.", static_cast<long unsigned int>(requestedStartTimeEpoch));
status = mDelegate.StartTimeAdjustRequest(requestedStartTimeEpoch, adjustmentCause);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: StartTimeAdjustRequest(%ld) FAILURE", static_cast<long unsigned int>(requestedStartTimeEpoch));
return;
}
}
void Instance::HandlePauseRequest(HandlerContext & ctx, const Commands::PauseRequest::DecodableType & commandData)
{
Status status = Status::Success;
CHIP_ERROR err = CHIP_NO_ERROR;
DataModel::Nullable<Structs::ForecastStruct::Type> forecast = mDelegate.GetForecast();
if (!HasFeature(DeviceEnergyManagement::Feature::kPausable))
{
ChipLogError(AppServer, "Pause not supported");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
uint32_t duration = commandData.duration;
AdjustmentCauseEnum adjustmentCause = commandData.cause;
status = CheckOptOutAllowsRequest(adjustmentCause);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: PauseRequest command rejected");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
return;
}
if (forecast.IsNull())
{
ChipLogError(Zcl, "DEM: Forecast is Null");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
/* value SHALL be between the MinPauseDuration and MaxPauseDuration indicated in the
ActiveSlotNumber index in the Slots list in the Forecast.
*/
uint16_t activeSlotNumber;
if (forecast.Value().activeSlotNumber.IsNull())
{
ChipLogError(Zcl, "DEM: activeSlotNumber Is Null");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
activeSlotNumber = forecast.Value().activeSlotNumber.Value();
if (activeSlotNumber >= forecast.Value().slots.size())
{
ChipLogError(Zcl, "DEM: Bad activeSlotNumber %d , size()=%d.", activeSlotNumber,
static_cast<int>(forecast.Value().slots.size()));
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
/* We expect that there should be a slotIsPausable entry (but it is optional) */
if (!forecast.Value().slots[activeSlotNumber].slotIsPausable.HasValue())
{
ChipLogError(Zcl, "DEM: activeSlotNumber %d does not include slotIsPausable.", activeSlotNumber);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
if (!forecast.Value().slots[activeSlotNumber].minPauseDuration.HasValue())
{
ChipLogError(Zcl, "DEM: activeSlotNumber %d does not include minPauseDuration.", activeSlotNumber);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
if (!forecast.Value().slots[activeSlotNumber].maxPauseDuration.HasValue())
{
ChipLogError(Zcl, "DEM: activeSlotNumber %d does not include minPauseDuration.", activeSlotNumber);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
if (!forecast.Value().slots[activeSlotNumber].slotIsPausable.Value())
{
ChipLogError(Zcl, "DEM: activeSlotNumber %d is NOT pausable.", activeSlotNumber);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
if ((duration < forecast.Value().slots[activeSlotNumber].minPauseDuration.Value()) ||
(duration > forecast.Value().slots[activeSlotNumber].maxPauseDuration.Value()))
{
ChipLogError(Zcl, "DEM: out of range pause duration %ld", static_cast<long unsigned int>(duration));
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
err = mDelegate.SetESAState(ESAStateEnum::kPaused);
if (CHIP_NO_ERROR != err)
{
ChipLogError(Zcl, "DEM: SetESAState(paused) FAILURE");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
status = mDelegate.PauseRequest(duration, adjustmentCause);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: PauseRequest(%ld) FAILURE", static_cast<long unsigned int>(duration));
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
return;
}
}
void Instance::HandleResumeRequest(HandlerContext & ctx, const Commands::ResumeRequest::DecodableType & commandData)
{
if (!HasFeature(DeviceEnergyManagement::Feature::kPausable))
{
ChipLogError(AppServer, "Pause not supported");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
if (ESAStateEnum::kPaused != mDelegate.GetESAState())
{
ChipLogError(Zcl, "DEM: ESAState not Paused.");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidInState);
return;
}
Status status = mDelegate.ResumeRequest();
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: ResumeRequest FAILURE");
return;
}
}
void Instance::HandleModifyForecastRequest(HandlerContext & ctx, const Commands::ModifyForecastRequest::DecodableType & commandData)
{
if (!HasFeature(DeviceEnergyManagement::Feature::kForecastAdjustment))
{
ChipLogError(Zcl, "ModifyForecast not supported");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
Status status;
DataModel::Nullable<Structs::ForecastStruct::Type> forecast;
uint32_t forecastID = commandData.forecastID;
DataModel::DecodableList<Structs::SlotAdjustmentStruct::Type> slotAdjustments = commandData.slotAdjustments;
AdjustmentCauseEnum adjustmentCause = commandData.cause;
status = CheckOptOutAllowsRequest(adjustmentCause);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: ModifyForecastRequest command rejected");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
return;
}
forecast = mDelegate.GetForecast();
if (forecast.IsNull())
{
ChipLogError(Zcl, "DEM: Forecast is Null");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
// Check the various values in the slot structures
auto iterator = slotAdjustments.begin();
while (iterator.Next())
{
const Structs::SlotAdjustmentStruct::Type & slotAdjustment = iterator.GetValue();
// Check for an invalid slotIndex
if (slotAdjustment.slotIndex >= forecast.Value().slots.size())
{
ChipLogError(Zcl, "DEM: Bad slot index %d", slotAdjustment.slotIndex);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
// Check to see if trying to modify a slot which has already been run
if (!forecast.Value().activeSlotNumber.IsNull() && slotAdjustment.slotIndex < forecast.Value().activeSlotNumber.Value())
{
ChipLogError(Zcl, "DEM: Modifying already run slot index %d", slotAdjustment.slotIndex);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
const Structs::SlotStruct::Type & slot = forecast.Value().slots[slotAdjustment.slotIndex];
// NominalPower is only relevant if PFR is supported
if (HasFeature(Feature::kPowerForecastReporting))
{
if (!slotAdjustment.nominalPower.HasValue() || !slot.minPowerAdjustment.HasValue() ||
!slot.maxPowerAdjustment.HasValue() || slotAdjustment.nominalPower.Value() < slot.minPowerAdjustment.Value() ||
slotAdjustment.nominalPower.Value() > slot.maxPowerAdjustment.Value())
{
ChipLogError(Zcl, "DEM: Bad nominalPower");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
}
if (!slot.minDurationAdjustment.HasValue() || !slot.maxDurationAdjustment.HasValue() ||
slotAdjustment.duration < slot.minDurationAdjustment.Value() ||
slotAdjustment.duration > slot.maxDurationAdjustment.Value())
{
ChipLogError(Zcl, "DEM: Bad min/max duration");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
}
if (iterator.GetStatus() != CHIP_NO_ERROR)
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
status = mDelegate.ModifyForecastRequest(forecastID, slotAdjustments, adjustmentCause);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: ModifyForecastRequest FAILURE");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
}
void Instance::HandleRequestConstraintBasedForecast(HandlerContext & ctx,
const Commands::RequestConstraintBasedForecast::DecodableType & commandData)
{
if (!HasFeature(DeviceEnergyManagement::Feature::kConstraintBasedAdjustment))
{
ChipLogError(AppServer, "RequestConstraintBasedForecast CON not supported");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure);
return;
}
Status status = Status::Success;
DataModel::DecodableList<Structs::ConstraintsStruct::DecodableType> constraints = commandData.constraints;
AdjustmentCauseEnum adjustmentCause = commandData.cause;
status = CheckOptOutAllowsRequest(adjustmentCause);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: RequestConstraintBasedForecast command rejected");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
return;
}
uint32_t currentUtcTime = 0;
status = GetMatterEpochTimeFromUnixTime(currentUtcTime);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: Failed to get UTC time");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
return;
}
// Check for invalid power levels and whether the constraint time/duration is in the past
{
auto iterator = constraints.begin();
if (iterator.Next())
{
const Structs::ConstraintsStruct::DecodableType & constraint = iterator.GetValue();
// Check to see if this constraint is in the past
if (constraint.startTime < currentUtcTime)
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
if (HasFeature(Feature::kPowerForecastReporting))
{
if (!constraint.nominalPower.HasValue())
{
ChipLogError(Zcl, "DEM: RequestConstraintBasedForecast no nominalPower");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
if (constraint.nominalPower.Value() < mDelegate.GetAbsMinPower() ||
constraint.nominalPower.Value() > mDelegate.GetAbsMaxPower())
{
ChipLogError(Zcl,
"DEM: RequestConstraintBasedForecast nominalPower " ChipLogFormatX64
" out of range [" ChipLogFormatX64 ", " ChipLogFormatX64 "]",
ChipLogValueX64(constraint.nominalPower.Value()), ChipLogValueX64(mDelegate.GetAbsMinPower()),
ChipLogValueX64(mDelegate.GetAbsMaxPower()));
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
if (!constraint.maximumEnergy.HasValue())
{
ChipLogError(Zcl, "DEM: RequestConstraintBasedForecast no value for maximumEnergy");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
}
if (HasFeature(Feature::kStateForecastReporting))
{
if (!constraint.loadControl.HasValue())
{
ChipLogError(Zcl, "DEM: RequestConstraintBasedForecast no loadControl");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
if (constraint.loadControl.Value() < -100 || constraint.loadControl.Value() > 100)
{
ChipLogError(Zcl, "DEM: RequestConstraintBasedForecast bad loadControl %d", constraint.loadControl.Value());
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
}
}
if (iterator.GetStatus() != CHIP_NO_ERROR)
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
}
// Check for overlappping elements
{
auto iterator = constraints.begin();
if (iterator.Next())
{
// Get the first constraint
Structs::ConstraintsStruct::DecodableType prevConstraint = iterator.GetValue();
// Start comparing next vs prev constraints
while (iterator.Next())
{
const Structs::ConstraintsStruct::DecodableType & constraint = iterator.GetValue();
if (constraint.startTime < prevConstraint.startTime ||
prevConstraint.startTime + prevConstraint.duration >= constraint.startTime)
{
ChipLogError(Zcl, "DEM: RequestConstraintBasedForecast overlapping constraint times");
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError);
return;
}
prevConstraint = constraint;
}
}
if (iterator.GetStatus() != CHIP_NO_ERROR)
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
}
status = mDelegate.RequestConstraintBasedForecast(constraints, adjustmentCause);
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
if (status != Status::Success)
{
ChipLogError(Zcl, "DEM: RequestConstraintBasedForecast FAILURE");
return;
}
}
void Instance::HandleCancelRequest(HandlerContext & ctx, const Commands::CancelRequest::DecodableType & commandData)
{
Status status = Status::Failure;
DataModel::Nullable<Structs::ForecastStruct::Type> forecast = mDelegate.GetForecast();
if (forecast.IsNull())
{
ChipLogDetail(AppServer, "Cancelling on a Null forecast!");
status = Status::Failure;
}
else if (forecast.Value().forecastUpdateReason == ForecastUpdateReasonEnum::kInternalOptimization)
{
ChipLogDetail(AppServer, "Bad Cancel when ESA ForecastUpdateReason was already Internal Optimization!");
status = Status::InvalidInState;
}
else
{
status = mDelegate.CancelRequest();
}
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
Status Instance::GetMatterEpochTimeFromUnixTime(uint32_t & currentUtcTime) const
{
currentUtcTime = 0;
System::Clock::Milliseconds64 cTMs;
CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(cTMs);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "DEM: Unable to get current time - err:%" CHIP_ERROR_FORMAT, err.Format());
return Status::Failure;
}
auto unixEpoch = std::chrono::duration_cast<System::Clock::Seconds32>(cTMs).count();
if (!UnixEpochToChipEpochTime(unixEpoch, currentUtcTime))
{
ChipLogError(Zcl, "DEM: unable to convert Unix Epoch time to Matter Epoch Time");
return Status::Failure;
}
return Status::Success;
}
} // namespace DeviceEnergyManagement
} // namespace Clusters
} // namespace app
} // namespace chip
void MatterDeviceEnergyManagementPluginServerInitCallback() {}