| /* |
| * 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 "energy-evse-server.h" |
| |
| #include <app/AttributeAccessInterface.h> |
| #include <app/AttributeAccessInterfaceRegistry.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::DataModel; |
| using namespace chip::app::Clusters; |
| using namespace chip::app::Clusters::EnergyEvse; |
| using namespace chip::app::Clusters::EnergyEvse::Attributes; |
| using chip::Protocols::InteractionModel::Status; |
| |
| namespace chip { |
| namespace app { |
| namespace Clusters { |
| namespace EnergyEvse { |
| |
| CHIP_ERROR Instance::Init() |
| { |
| ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->RegisterCommandHandler(this)); |
| VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INCORRECT_STATE); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void Instance::Shutdown() |
| { |
| InteractionModelEngine::GetInstance()->UnregisterCommandHandler(this); |
| unregisterAttributeAccessOverride(this); |
| } |
| |
| bool Instance::HasFeature(Feature aFeature) const |
| { |
| return mFeature.Has(aFeature); |
| } |
| |
| bool Instance::SupportsOptAttr(OptionalAttributes aOptionalAttrs) const |
| { |
| return mOptionalAttrs.Has(aOptionalAttrs); |
| } |
| |
| bool Instance::SupportsOptCmd(OptionalCommands aOptionalCmds) const |
| { |
| return mOptionalCmds.Has(aOptionalCmds); |
| } |
| |
| // AttributeAccessInterface |
| CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) |
| { |
| switch (aPath.mAttributeId) |
| { |
| case State::Id: |
| return aEncoder.Encode(mDelegate.GetState()); |
| case SupplyState::Id: |
| return aEncoder.Encode(mDelegate.GetSupplyState()); |
| case FaultState::Id: |
| return aEncoder.Encode(mDelegate.GetFaultState()); |
| case ChargingEnabledUntil::Id: |
| return aEncoder.Encode(mDelegate.GetChargingEnabledUntil()); |
| case DischargingEnabledUntil::Id: |
| /* V2X */ |
| return aEncoder.Encode(mDelegate.GetDischargingEnabledUntil()); |
| case CircuitCapacity::Id: |
| return aEncoder.Encode(mDelegate.GetCircuitCapacity()); |
| case MinimumChargeCurrent::Id: |
| return aEncoder.Encode(mDelegate.GetMinimumChargeCurrent()); |
| case MaximumChargeCurrent::Id: |
| return aEncoder.Encode(mDelegate.GetMaximumChargeCurrent()); |
| case MaximumDischargeCurrent::Id: |
| /* V2X */ |
| return aEncoder.Encode(mDelegate.GetMaximumDischargeCurrent()); |
| |
| case UserMaximumChargeCurrent::Id: |
| return aEncoder.Encode(mDelegate.GetUserMaximumChargeCurrent()); |
| case RandomizationDelayWindow::Id: |
| /* Optional */ |
| return aEncoder.Encode(mDelegate.GetRandomizationDelayWindow()); |
| /* PREF - ChargingPreferences attributes */ |
| case NextChargeStartTime::Id: |
| return aEncoder.Encode(mDelegate.GetNextChargeStartTime()); |
| case NextChargeTargetTime::Id: |
| return aEncoder.Encode(mDelegate.GetNextChargeTargetTime()); |
| case NextChargeRequiredEnergy::Id: |
| return aEncoder.Encode(mDelegate.GetNextChargeRequiredEnergy()); |
| case NextChargeTargetSoC::Id: |
| return aEncoder.Encode(mDelegate.GetNextChargeTargetSoC()); |
| case ApproximateEVEfficiency::Id: |
| return aEncoder.Encode(mDelegate.GetApproximateEVEfficiency()); |
| /* SOC attributes */ |
| case StateOfCharge::Id: |
| return aEncoder.Encode(mDelegate.GetStateOfCharge()); |
| case BatteryCapacity::Id: |
| return aEncoder.Encode(mDelegate.GetBatteryCapacity()); |
| /* PNC attributes*/ |
| case VehicleID::Id: |
| return aEncoder.Encode(mDelegate.GetVehicleID()); |
| /* Session SESS attributes */ |
| case SessionID::Id: |
| return aEncoder.Encode(mDelegate.GetSessionID()); |
| case SessionDuration::Id: |
| return aEncoder.Encode(mDelegate.GetSessionDuration()); |
| case SessionEnergyCharged::Id: |
| return aEncoder.Encode(mDelegate.GetSessionEnergyCharged()); |
| case SessionEnergyDischarged::Id: |
| return aEncoder.Encode(mDelegate.GetSessionEnergyDischarged()); |
| |
| /* 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; |
| } |
| |
| CHIP_ERROR Instance::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) |
| { |
| switch (aPath.mAttributeId) |
| { |
| case UserMaximumChargeCurrent::Id: { |
| // Optional Attribute |
| if (!SupportsOptAttr(OptionalAttributes::kSupportsUserMaximumChargingCurrent)) |
| { |
| return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); |
| } |
| |
| int64_t newValue; |
| ReturnErrorOnFailure(aDecoder.Decode(newValue)); |
| ReturnErrorOnFailure(mDelegate.SetUserMaximumChargeCurrent(newValue)); |
| return CHIP_NO_ERROR; |
| } |
| case RandomizationDelayWindow::Id: { |
| // Optional Attribute |
| if (!SupportsOptAttr(OptionalAttributes::kSupportsRandomizationWindow)) |
| { |
| return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); |
| } |
| uint32_t newValue; |
| ReturnErrorOnFailure(aDecoder.Decode(newValue)); |
| ReturnErrorOnFailure(mDelegate.SetRandomizationDelayWindow(newValue)); |
| return CHIP_NO_ERROR; |
| } |
| case ApproximateEVEfficiency::Id: { |
| // Optional Attribute if ChargingPreferences is supported |
| if ((!HasFeature(Feature::kChargingPreferences)) || |
| (!SupportsOptAttr(OptionalAttributes::kSupportsApproximateEvEfficiency))) |
| { |
| return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); |
| } |
| uint16_t newValue; |
| ReturnErrorOnFailure(aDecoder.Decode(newValue)); |
| ReturnErrorOnFailure(mDelegate.SetApproximateEVEfficiency(MakeNullable(newValue))); |
| return CHIP_NO_ERROR; |
| } |
| |
| default: |
| // Unknown attribute; return error. None of the other attributes for |
| // this cluster are writable, so should not be ending up in this code to |
| // start with. |
| return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); |
| } |
| } |
| |
| // CommandHandlerInterface |
| CHIP_ERROR Instance::EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) |
| { |
| using namespace Commands; |
| |
| for (auto && cmd : { |
| Disable::Id, |
| EnableCharging::Id, |
| }) |
| { |
| VerifyOrExit(callback(cmd, context) == Loop::Continue, /**/); |
| } |
| |
| if (HasFeature(Feature::kV2x)) |
| { |
| VerifyOrExit(callback(EnableDischarging::Id, context) == Loop::Continue, /**/); |
| } |
| |
| if (HasFeature(Feature::kChargingPreferences)) |
| { |
| for (auto && cmd : { |
| SetTargets::Id, |
| GetTargets::Id, |
| ClearTargets::Id, |
| }) |
| { |
| VerifyOrExit(callback(cmd, context) == Loop::Continue, /**/); |
| } |
| } |
| |
| if (SupportsOptCmd(OptionalCommands::kSupportsStartDiagnostics)) |
| { |
| callback(StartDiagnostics::Id, context); |
| } |
| |
| exit: |
| return CHIP_NO_ERROR; |
| } |
| |
| void Instance::InvokeCommand(HandlerContext & handlerContext) |
| { |
| using namespace Commands; |
| |
| switch (handlerContext.mRequestPath.mCommandId) |
| { |
| case Disable::Id: |
| HandleCommand<Disable::DecodableType>( |
| handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleDisable(ctx, commandData); }); |
| return; |
| case EnableCharging::Id: |
| HandleCommand<EnableCharging::DecodableType>( |
| handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleEnableCharging(ctx, commandData); }); |
| return; |
| case EnableDischarging::Id: |
| if (!HasFeature(Feature::kV2x)) |
| { |
| handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); |
| } |
| else |
| { |
| HandleCommand<EnableDischarging::DecodableType>(handlerContext, [this](HandlerContext & ctx, const auto & commandData) { |
| HandleEnableDischarging(ctx, commandData); |
| }); |
| } |
| return; |
| case StartDiagnostics::Id: |
| if (!SupportsOptCmd(OptionalCommands::kSupportsStartDiagnostics)) |
| { |
| handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); |
| } |
| else |
| { |
| HandleCommand<StartDiagnostics::DecodableType>(handlerContext, [this](HandlerContext & ctx, const auto & commandData) { |
| HandleStartDiagnostics(ctx, commandData); |
| }); |
| } |
| return; |
| case SetTargets::Id: |
| if (!HasFeature(Feature::kChargingPreferences)) |
| { |
| handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); |
| } |
| else |
| { |
| HandleCommand<SetTargets::DecodableType>( |
| handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleSetTargets(ctx, commandData); }); |
| } |
| return; |
| case GetTargets::Id: |
| if (!HasFeature(Feature::kChargingPreferences)) |
| { |
| handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); |
| } |
| else |
| { |
| HandleCommand<GetTargets::DecodableType>( |
| handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleGetTargets(ctx, commandData); }); |
| } |
| return; |
| case ClearTargets::Id: |
| if (!HasFeature(Feature::kChargingPreferences)) |
| { |
| handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); |
| } |
| else |
| { |
| HandleCommand<ClearTargets::DecodableType>( |
| handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleClearTargets(ctx, commandData); }); |
| } |
| return; |
| } |
| } |
| |
| void Instance::HandleDisable(HandlerContext & ctx, const Commands::Disable::DecodableType & commandData) |
| { |
| // No parameters for this command |
| // Call the delegate |
| Status status = mDelegate.Disable(); |
| |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| } |
| |
| void Instance::HandleEnableCharging(HandlerContext & ctx, const Commands::EnableCharging::DecodableType & commandData) |
| { |
| auto & chargingEnabledUntil = commandData.chargingEnabledUntil; |
| auto & minimumChargeCurrent = commandData.minimumChargeCurrent; |
| auto & maximumChargeCurrent = commandData.maximumChargeCurrent; |
| |
| if ((minimumChargeCurrent < kMinimumChargeCurrent) || (minimumChargeCurrent > kMaximumChargeCurrent)) |
| { |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); |
| return; |
| } |
| |
| if ((maximumChargeCurrent < kMinimumChargeCurrent) || (maximumChargeCurrent > kMaximumChargeCurrent)) |
| { |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); |
| return; |
| } |
| |
| if (minimumChargeCurrent > maximumChargeCurrent) |
| { |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); |
| return; |
| } |
| |
| // Call the delegate |
| Status status = mDelegate.EnableCharging(chargingEnabledUntil, minimumChargeCurrent, maximumChargeCurrent); |
| |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| } |
| |
| void Instance::HandleEnableDischarging(HandlerContext & ctx, const Commands::EnableDischarging::DecodableType & commandData) |
| { |
| |
| auto & dischargingEnabledUntil = commandData.dischargingEnabledUntil; |
| auto & maximumDischargeCurrent = commandData.maximumDischargeCurrent; |
| |
| if ((maximumDischargeCurrent < kMinimumChargeCurrent) || (maximumDischargeCurrent > kMaximumChargeCurrent)) |
| { |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); |
| return; |
| } |
| |
| // Call the delegate |
| Status status = mDelegate.EnableDischarging(dischargingEnabledUntil, maximumDischargeCurrent); |
| |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| } |
| void Instance::HandleStartDiagnostics(HandlerContext & ctx, const Commands::StartDiagnostics::DecodableType & commandData) |
| { |
| // No parameters for this command |
| // Call the delegate |
| Status status = mDelegate.StartDiagnostics(); |
| |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| } |
| |
| void Instance::HandleSetTargets(HandlerContext & ctx, const Commands::SetTargets::DecodableType & commandData) |
| { |
| // Call the delegate |
| // TODO |
| // Status status = mDelegate.SetTargets(); |
| Status status = Status::UnsupportedCommand; |
| |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| } |
| void Instance::HandleGetTargets(HandlerContext & ctx, const Commands::GetTargets::DecodableType & commandData) |
| { |
| // Call the delegate |
| // TODO |
| // Status status = mDelegate.GetTargets(); |
| Status status = Status::UnsupportedCommand; |
| |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| } |
| void Instance::HandleClearTargets(HandlerContext & ctx, const Commands::ClearTargets::DecodableType & commandData) |
| { |
| // Call the delegate |
| // TODO |
| // Status status = mDelegate.ClearTargets(); |
| Status status = Status::UnsupportedCommand; |
| |
| ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| } |
| |
| } // namespace EnergyEvse |
| } // namespace Clusters |
| } // namespace app |
| } // namespace chip |
| |
| // ----------------------------------------------------------------------------- |
| // Plugin initialization |
| |
| void MatterEnergyEvsePluginServerInitCallback() {} |