blob: afb3463c3c8b0bbc79d71a51aeedf6a6da65b8c2 [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 <EnergyEvseDelegateImpl.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app/EventLogging.h>
#include <app/SafeAttributePersistenceProvider.h>
#include <app/clusters/energy-evse-server/CodegenIntegration.h>
#include <app/reporting/reporting.h>
#include <platform/CHIPDeviceLayer.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::app::LogEvent;
using chip::Protocols::InteractionModel::Status;
/**
* @brief Called when EVSE cluster receives Disable command
*/
Status EnergyEvseDelegate::Disable()
{
ChipLogProgress(AppServer, "EnergyEvseDelegate::Disable()");
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
DataModel::Nullable<uint32_t> disableTime(0);
/* update ChargingEnabledUntil & DischargingEnabledUntil to show 0 */
TEMPORARY_RETURN_IGNORED mInstance->SetChargingEnabledUntil(disableTime);
TEMPORARY_RETURN_IGNORED mInstance->SetDischargingEnabledUntil(disableTime);
/* update MinimumChargeCurrent & MaximumChargeCurrent to 0 */
TEMPORARY_RETURN_IGNORED mInstance->SetMinimumChargeCurrent(0);
mMaximumChargingCurrentLimitFromCommand = 0;
ComputeMaxChargeCurrentLimit();
/* update MaximumDischargeCurrent to 0 */
mMaximumDischargingCurrentLimitFromCommand = 0;
ComputeMaxDischargeCurrentLimit();
return HandleStateMachineEvent(EVSEStateMachineEvent::DisabledEvent);
}
/**
* @brief Called when EVSE cluster receives EnableCharging command
*
* @param chargingEnabledUntil (can be null to indefinite charging)
* @param minimumChargeCurrent (in mA)
* @param maximumChargeCurrent (in mA)
*/
Status EnergyEvseDelegate::EnableCharging(const DataModel::Nullable<uint32_t> & chargingEnabledUntil,
const int64_t & minimumChargeCurrent, const int64_t & maximumChargeCurrent)
{
ChipLogProgress(AppServer, "EnergyEvseDelegate::EnableCharging()");
if (maximumChargeCurrent < kMinimumChargeCurrentLimit)
{
ChipLogError(AppServer, "Maximum Current outside limits");
return Status::ConstraintError;
}
if (minimumChargeCurrent < kMinimumChargeCurrentLimit)
{
ChipLogError(AppServer, "Minimum Current outside limits");
return Status::ConstraintError;
}
if (minimumChargeCurrent > maximumChargeCurrent)
{
ChipLogError(AppServer, "Minium Current > Maximum Current!");
return Status::ConstraintError;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
if (chargingEnabledUntil.IsNull())
{
/* Charging enabled indefinitely */
ChipLogProgress(AppServer, "Charging enabled indefinitely");
}
else
{
/* check chargingEnabledUntil is in the future */
ChipLogProgress(AppServer, "Charging enabled until: %lu", static_cast<long unsigned int>(chargingEnabledUntil.Value()));
}
TEMPORARY_RETURN_IGNORED mInstance->SetChargingEnabledUntil(chargingEnabledUntil);
/* If it looks ok, store the min & max charging current */
mMaximumChargingCurrentLimitFromCommand = maximumChargeCurrent;
TEMPORARY_RETURN_IGNORED mInstance->SetMinimumChargeCurrent(minimumChargeCurrent);
// TODO persist these to KVS
ComputeMaxChargeCurrentLimit();
return HandleStateMachineEvent(EVSEStateMachineEvent::ChargingEnabledEvent);
}
/**
* @brief Called when EVSE cluster receives EnableDischarging command
*
* @param dischargingEnabledUntil (can be null to indefinite discharging)
* @param maximumDischargeCurrent (in mA)
*/
Status EnergyEvseDelegate::EnableDischarging(const DataModel::Nullable<uint32_t> & dischargingEnabledUntil,
const int64_t & maximumDischargeCurrent)
{
ChipLogProgress(AppServer, "EnergyEvseDelegate::EnableDischarging() called.");
if (maximumDischargeCurrent < kMinimumChargeCurrentLimit)
{
ChipLogError(AppServer, "Maximum Discharging Current outside limits - cannot be negative");
return Status::ConstraintError;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
if (dischargingEnabledUntil.IsNull())
{
/* Discharging enabled indefinitely */
ChipLogProgress(AppServer, "Discharging enabled indefinitely");
}
else
{
ChipLogProgress(AppServer, "Discharging enabled until: %lu",
static_cast<long unsigned int>(dischargingEnabledUntil.Value()));
}
TEMPORARY_RETURN_IGNORED mInstance->SetDischargingEnabledUntil(dischargingEnabledUntil);
/* If it looks ok, store the max discharging current */
mMaximumDischargingCurrentLimitFromCommand = maximumDischargeCurrent;
ComputeMaxDischargeCurrentLimit();
// TODO persist these to KVS
return HandleStateMachineEvent(EVSEStateMachineEvent::DischargingEnabledEvent);
}
/**
* @brief Helper function to get the earliest time from two Nullable<uint32_t> values
*
* If either time is null, it returns the other time. If both are non-null, it returns the earlier one.
*/
static DataModel::Nullable<uint32_t> GetEarliestTime(const DataModel::Nullable<uint32_t> & time1,
const DataModel::Nullable<uint32_t> & time2)
{
if (time1.IsNull())
return time2;
if (time2.IsNull())
return time1;
return (time1.Value() < time2.Value()) ? time1 : time2;
}
/**
* @brief Helper function to check if a time value has expired
*
* @param timeValue The Nullable<uint32_t> time value to check
* @param currentTime The current time in seconds since epoch
* @return true if the time has expired, false otherwise
*/
static bool IsTimeExpired(const DataModel::Nullable<uint32_t> & timeValue, uint32_t currentTime)
{
return !timeValue.IsNull() && (timeValue.Value() <= currentTime);
}
/**
* @brief Helper function to handle timer expiration when in enabled state
*
* @param matterEpochSeconds Current time in Matter epoch seconds
* This function is called when the EVSE is in an enabled state
* (either charging or discharging) and the timer expires.
* It checks if the charging or discharging enabled times have expired
* and updates the EVSE state accordingly.
* If both charging and discharging have expired or are Zero, it disables the EVSE.
* If only one has expired, it updates the state to the other enabled state.
* If both are still valid, it does nothing.
*/
void EnergyEvseDelegate::HandleEnabledStateExpiration(uint32_t matterEpochSeconds)
{
if (mInstance == nullptr)
{
return;
}
DataModel::Nullable<uint32_t> chargingEnabledUntil = GetChargingEnabledUntil();
DataModel::Nullable<uint32_t> dischargingEnabledUntil = GetDischargingEnabledUntil();
bool chargingExpired = IsTimeExpired(chargingEnabledUntil, matterEpochSeconds);
bool dischargingExpired = IsTimeExpired(dischargingEnabledUntil, matterEpochSeconds);
if (chargingExpired)
{
// set to zero to indicate disabled
TEMPORARY_RETURN_IGNORED mInstance->SetChargingEnabledUntil(DataModel::Nullable<uint32_t>(0));
// update MinimumChargeCurrent & MaximumChargeCurrent to 0
TEMPORARY_RETURN_IGNORED mInstance->SetMinimumChargeCurrent(0);
mMaximumChargingCurrentLimitFromCommand = 0;
ComputeMaxChargeCurrentLimit();
// Change to discharging-only if discharging is still enabled
if (!dischargingExpired)
{
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kDischargingEnabled);
}
else
{
// If both charging and discharging have expired, disable the EVSE
Disable();
}
}
if (dischargingExpired)
{
// set to zero to indicate disabled
TEMPORARY_RETURN_IGNORED mInstance->SetDischargingEnabledUntil(DataModel::Nullable<uint32_t>(0));
// update MaximumDischargeCurrent to 0
mMaximumDischargingCurrentLimitFromCommand = 0;
ComputeMaxDischargeCurrentLimit();
// Change to charging-only if charging is still enabled
if (!chargingExpired)
{
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kChargingEnabled);
}
else
{
// If both charging and discharging have expired, disable the EVSE
Disable();
}
}
}
/**
* @brief Routine to help schedule a timer callback to check if the EVSE should go disabled
*
* If the clock is sync'd we can work out when to call back to check when to disable the EVSE
* automatically. If the clock isn't sync'd the we just set a timer to check once every 30s.
*
* We first check the SupplyState to check if it is EnabledCharging or EnabledDischarging
* Then if the EnabledCharging/DischargingUntil is not Null, then we compute a delay to come
* back and check.
*/
Status EnergyEvseDelegate::ScheduleCheckOnEnabledTimeout()
{
// Determine the relevant timeout based on current supply state
DataModel::Nullable<uint32_t> enabledUntilTime;
SupplyStateEnum currentSupplyState = GetSupplyState();
switch (currentSupplyState)
{
case SupplyStateEnum::kChargingEnabled:
enabledUntilTime = GetChargingEnabledUntil();
break;
case SupplyStateEnum::kDischargingEnabled:
enabledUntilTime = GetDischargingEnabledUntil();
break;
case SupplyStateEnum::kEnabled:
// For enabled state, use the earliest of charging or discharging timeout
enabledUntilTime = GetEarliestTime(GetChargingEnabledUntil(), GetDischargingEnabledUntil());
break;
default:
// In all other states the EVSE is disabled, no timer needed
return Status::Success;
}
if (enabledUntilTime.IsNull())
{
ChipLogDetail(AppServer, "EVSE is enabled indefinitely, no timer needed");
return Status::Success;
}
uint32_t matterEpochSeconds = 0;
CHIP_ERROR err = System::Clock::GetClock_MatterEpochS(matterEpochSeconds);
if (err == CHIP_ERROR_REAL_TIME_NOT_SYNCED)
{
// Real time isn't sync'd - check again in 30 seconds
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().StartTimer(
System::Clock::Seconds32(kPeriodicCheckIntervalRealTimeClockNotSynced_sec), EvseCheckTimerExpiry, this);
return Status::Success;
}
if (err != CHIP_NO_ERROR)
{
return Status::Failure; // Can't get time, skip scheduling
}
if (enabledUntilTime.Value() > matterEpochSeconds)
{
// Timer hasn't expired yet - schedule future check
uint32_t delta = enabledUntilTime.Value() - matterEpochSeconds;
ChipLogDetail(AppServer, "Setting EVSE Enable check timer for %lu seconds", static_cast<unsigned long>(delta));
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(delta), EvseCheckTimerExpiry, this);
return Status::Success;
}
// Time has expired - handle expiration based on current state
ChipLogDetail(AppServer, "EVSE enable time expired, processing expiration");
// Re-check supply state as it may have changed
SupplyStateEnum currentState = GetSupplyState();
if (currentState == SupplyStateEnum::kChargingEnabled || currentState == SupplyStateEnum::kDischargingEnabled)
{
Disable();
}
else if (currentState == SupplyStateEnum::kEnabled)
{
HandleEnabledStateExpiration(matterEpochSeconds);
// Call ourselves again now that one of our 2 timers has expired
// The other timer expiry may need to be scheduled now
ScheduleCheckOnEnabledTimeout();
}
return Status::Success;
}
void EnergyEvseDelegate::CancelActiveTimers()
{
// Cancel the EVSE check timer if it is active
DeviceLayer::SystemLayer().CancelTimer(EvseCheckTimerExpiry, this);
}
void EnergyEvseDelegate::EvseCheckTimerExpiry(System::Layer * systemLayer, void * delegate)
{
EnergyEvseDelegate * dg = reinterpret_cast<EnergyEvseDelegate *>(delegate);
dg->ScheduleCheckOnEnabledTimeout();
}
/**
* @brief Called when EVSE cluster receives StartDiagnostics command
*
* NOTE: Application code needs to call HwDiagnosticsComplete
* once diagnostics have been completed.
*/
Status EnergyEvseDelegate::StartDiagnostics()
{
/* For EVSE manufacturers to customize */
ChipLogProgress(AppServer, "EnergyEvseDelegate::StartDiagnostics()");
if (GetSupplyState() != SupplyStateEnum::kDisabled)
{
ChipLogError(AppServer, "EVSE: cannot be put into diagnostics mode if it is not Disabled!");
return Status::Failure;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
// Update the SupplyState - this will automatically callback the Application StateChanged callback
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kDisabledDiagnostics);
return Status::Success;
}
/**
* @brief Called when EVSE cluster receives SetTargets command
*/
Status EnergyEvseDelegate::SetTargets(
const DataModel::DecodableList<Structs::ChargingTargetScheduleStruct::DecodableType> & chargingTargetSchedules)
{
ChipLogProgress(AppServer, "EnergyEvseDelegate::SetTargets()");
EvseTargetsDelegate * targets = GetEvseTargetsDelegate();
VerifyOrReturnError(targets != nullptr, Status::Failure);
CHIP_ERROR err = targets->SetTargets(chargingTargetSchedules);
if (err == CHIP_ERROR_NO_MEMORY)
{
return Status::ResourceExhausted;
}
VerifyOrReturnError(err == CHIP_NO_ERROR, StatusIB(err).mStatus);
/* The Application needs to be told that the Targets have been updated
* so it can potentially re-optimize the charging start time etc
*/
NotifyApplicationChargingPreferencesChange();
return Status::Success;
}
Status EnergyEvseDelegate::LoadTargets()
{
ChipLogProgress(AppServer, "EnergyEvseDelegate::LoadTargets()");
EvseTargetsDelegate * targets = GetEvseTargetsDelegate();
VerifyOrReturnError(targets != nullptr, StatusIB(CHIP_ERROR_UNINITIALIZED).mStatus);
CHIP_ERROR err = targets->LoadTargets();
VerifyOrReturnError(err == CHIP_NO_ERROR, StatusIB(err).mStatus);
return Status::Success;
}
Status EnergyEvseDelegate::GetTargets(DataModel::List<const Structs::ChargingTargetScheduleStruct::Type> & chargingTargetSchedules)
{
ChipLogProgress(AppServer, "EnergyEvseDelegate::GetTargets()");
EvseTargetsDelegate * targets = GetEvseTargetsDelegate();
VerifyOrReturnError(targets != nullptr, StatusIB(CHIP_ERROR_UNINITIALIZED).mStatus);
chargingTargetSchedules = targets->GetTargets();
return Status::Success;
}
/**
* @brief Called when EVSE cluster receives ClearTargets command
*/
Status EnergyEvseDelegate::ClearTargets()
{
ChipLogProgress(AppServer, "EnergyEvseDelegate::ClearTargets()");
CHIP_ERROR err;
EvseTargetsDelegate * targets = GetEvseTargetsDelegate();
if (targets == nullptr)
{
return StatusIB(CHIP_ERROR_UNINITIALIZED).mStatus;
}
err = targets->ClearTargets();
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to clear Evse targets: %" CHIP_ERROR_FORMAT, err.Format());
return Status::Failure;
}
/* The Application needs to be told that the Targets have been deleted
* so it can potentially re-optimize the charging start time etc
*/
NotifyApplicationChargingPreferencesChange();
return Status::Success;
}
/* ---------------------------------------------------------------------------
* EVSE Hardware interface below
*/
/**
* @brief Called by EVSE Hardware to register a callback handler mechanism
*
* This is normally called at start-up.
*
* @param EVSECallbackFunct - function pointer to call
* @param intptr_t - optional context to provide back to callback handler
*/
Status EnergyEvseDelegate::HwRegisterEvseCallbackHandler(EVSECallbackFunc handler, intptr_t arg)
{
if (mCallbacks.handler != nullptr)
{
ChipLogError(AppServer, "Callback handler already initialized");
return Status::Failure;
}
mCallbacks.handler = handler;
mCallbacks.arg = arg;
return Status::Success;
}
/**
* @brief Called by EVSE Hardware to notify the delegate of the maximum
* current limit supported by the hardware (for charging).
*
* This is normally called at start-up.
*
* @param currentmA - Maximum current limit supported by the hardware
*/
Status EnergyEvseDelegate::HwSetMaxHardwareChargeCurrentLimit(int64_t currentmA)
{
if (currentmA < kMinimumChargeCurrentLimit)
{
return Status::ConstraintError;
}
/* there is no attribute to store this so store in private variable */
mMaxHardwareChargeCurrentLimit = currentmA;
return ComputeMaxChargeCurrentLimit();
}
/**
* @brief Called by EVSE Hardware to notify the delegate of the maximum
* current limit supported by the hardware (for discharging).
*
* This is normally called at start-up.
*
* @param currentmA - Maximum current limit supported by the hardware for discharging
*/
Status EnergyEvseDelegate::HwSetMaxHardwareDischargeCurrentLimit(int64_t currentmA)
{
if (currentmA < kMinimumChargeCurrentLimit)
{
return Status::ConstraintError;
}
/* there is no attribute to store this so store in private variable */
mMaxHardwareDischargeCurrentLimit = currentmA;
return ComputeMaxDischargeCurrentLimit();
}
/**
* @brief Called by EVSE Hardware to notify the delegate of the nominal
* mains voltage (in mV)
*
* This is normally called at start-up.
*
* @param voltage_mV - nominal mains voltage
*/
Status EnergyEvseDelegate::HwSetNominalMainsVoltage(int64_t voltage_mV)
{
if (voltage_mV < kMinimumMainsVoltage_mV)
{
ChipLogError(AppServer, "Mains voltage looks too low - check value is in mV");
return Status::ConstraintError;
}
mNominalMainsVoltage = voltage_mV;
return Status::Success;
}
/**
* @brief Called by EVSE Hardware to notify the delegate of maximum electrician
* set current limit.
*
* This is normally called at start-up when reading from DIP-switch
* settings.
*
* @param currentmA - Maximum current limit specified by electrician
*/
Status EnergyEvseDelegate::HwSetCircuitCapacity(int64_t currentmA)
{
if (currentmA < kMinimumChargeCurrentLimit)
{
return Status::ConstraintError;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
TEMPORARY_RETURN_IGNORED mInstance->SetCircuitCapacity(currentmA);
return ComputeMaxChargeCurrentLimit();
}
/**
* @brief Called by EVSE Hardware to notify the delegate of the cable assembly
* current limit.
*
* This is normally called when the EV is plugged into the EVSE and the
* PP voltage is measured by the EVSE. A pull-up resistor in the cable
* causes a voltage drop. Different current limits can be indicated
* using different resistors, which results in different voltages
* measured by the EVSE.
*
* @param currentmA - Maximum current limit detected from Cable assembly
*/
Status EnergyEvseDelegate::HwSetCableAssemblyLimit(int64_t currentmA)
{
if (currentmA < kMinimumChargeCurrentLimit)
{
return Status::ConstraintError;
}
/* there is no attribute to store this so store in private variable */
mCableAssemblyCurrentLimit = currentmA;
return ComputeMaxChargeCurrentLimit();
}
/**
* @brief Called by EVSE Hardware to indicate if EV is detected
*
* The only allowed states that the EVSE hardware can tell us about are:
* kNotPluggedIn
* kPluggedInNoDemand
* kPluggedInDemand
*
* The actual overall state is more complex and includes faults,
* enable & disable charging or discharging etc.
*
* @param StateEnum - the state of the EV being plugged in and asking for demand etc
*/
Status EnergyEvseDelegate::HwSetState(StateEnum newState)
{
switch (newState)
{
case StateEnum::kNotPluggedIn:
switch (mHwState)
{
case StateEnum::kNotPluggedIn:
// No change
break;
case StateEnum::kPluggedInNoDemand:
case StateEnum::kPluggedInDemand:
/* EVSE has been unplugged now */
mHwState = newState;
HandleStateMachineEvent(EVSEStateMachineEvent::EVNotDetectedEvent);
break;
default:
// invalid value for mHwState
ChipLogError(AppServer, "HwSetState newstate(kNotPluggedIn) - Invalid value for mHwState");
mHwState = newState; // set it to the new state anyway
break;
}
break;
case StateEnum::kPluggedInNoDemand:
switch (mHwState)
{
case StateEnum::kNotPluggedIn:
/* EV was unplugged, now is plugged in */
mHwState = newState;
HandleStateMachineEvent(EVSEStateMachineEvent::EVPluggedInEvent);
break;
case StateEnum::kPluggedInNoDemand:
// No change
break;
case StateEnum::kPluggedInDemand:
/* EV was plugged in and wanted demand, now doesn't want demand */
mHwState = newState;
HandleStateMachineEvent(EVSEStateMachineEvent::EVNoDemandEvent);
break;
default:
// invalid value for mHwState
ChipLogError(AppServer, "HwSetState newstate(kPluggedInNoDemand) - Invalid value for mHwState");
mHwState = newState; // set it to the new state anyway
break;
}
break;
case StateEnum::kPluggedInDemand:
switch (mHwState)
{
case StateEnum::kNotPluggedIn:
/* EV was unplugged, now is plugged in and wants demand */
mHwState = newState;
HandleStateMachineEvent(EVSEStateMachineEvent::EVPluggedInEvent);
HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent);
break;
case StateEnum::kPluggedInNoDemand:
/* EV was plugged in and didn't want demand, now does want demand */
mHwState = newState;
HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent);
break;
case StateEnum::kPluggedInDemand:
// No change
break;
default:
// invalid value for mHwState
ChipLogError(AppServer, "HwSetState newstate(kPluggedInDemand) - Invalid value for mHwState");
mHwState = newState; // set it to the new state anyway
break;
}
break;
default:
/* All other states should be managed by the Delegate */
ChipLogError(AppServer, "HwSetState received invalid enum from caller");
return Status::Failure;
}
return Status::Success;
}
/**
* @brief Called by EVSE Hardware to indicate a fault
*
* @param FaultStateEnum - the fault condition detected
*/
Status EnergyEvseDelegate::HwSetFault(FaultStateEnum newFaultState)
{
ChipLogProgress(AppServer, "EnergyEvseDelegate::Fault()");
if (GetFaultState() == newFaultState)
{
ChipLogError(AppServer, "No change in fault state, ignoring call");
return Status::Failure;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
/** Before we do anything we log the fault
* any change in FaultState reports previous fault and new fault
* and the state prior to the fault being raised */
SendFaultEvent(newFaultState);
/* Updated FaultState before we call into the handlers */
TEMPORARY_RETURN_IGNORED mInstance->SetFaultState(newFaultState);
if (newFaultState == FaultStateEnum::kNoError)
{
/* Fault has been cleared */
HandleStateMachineEvent(EVSEStateMachineEvent::FaultCleared);
}
else
{
/* a new Fault has been raised */
HandleStateMachineEvent(EVSEStateMachineEvent::FaultRaised);
}
return Status::Success;
}
/**
* @brief Called by EVSE Hardware to Send a RFID event
*
* @param ByteSpan RFID tag value (max 10 octets)
*/
Status EnergyEvseDelegate::HwSetRFID(ByteSpan uid)
{
Events::Rfid::Type event{ .uid = uid };
EventNumber eventNumber;
CHIP_ERROR error = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != error)
{
ChipLogError(Zcl, "[Notify] Unable to send notify event: %s [endpointId=%d]", error.AsString(), mEndpointId);
return Status::Failure;
}
return Status::Success;
}
/**
* @brief Called by EVSE Hardware to share the VehicleID
*
* This routine will make a copy of the string so the callee doesn't
* have to hold onto it forever.
*
* @param CharSpan containing up to 32 chars.
*/
Status EnergyEvseDelegate::HwSetVehicleID(const CharSpan & newValue)
{
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
DataModel::Nullable<CharSpan> currentVehicleID = mInstance->GetVehicleID();
if ((!currentVehicleID.IsNull() && newValue.data_equal(currentVehicleID.Value())) ||
(currentVehicleID.IsNull() && newValue.empty()))
{
// No change in VehicleID, nothing to do
return Status::Success;
}
if (newValue.size() > kMaxVehicleIDBufSize)
{
ChipLogError(AppServer, "HwSetVehicleID - input too long. Max size = %d", kMaxVehicleIDBufSize);
return Status::Failure;
}
// If the input is empty, treat it as a request to clear the vehicle ID
if (newValue.empty())
{
TEMPORARY_RETURN_IGNORED mInstance->SetVehicleID(DataModel::NullNullable);
ChipLogDetail(AppServer, "VehicleID cleared");
return Status::Success;
}
memcpy(mVehicleIDBuf, newValue.data(), newValue.size());
DataModel::Nullable<CharSpan> vehicleID = MakeNullable(CharSpan(mVehicleIDBuf, newValue.size()));
TEMPORARY_RETURN_IGNORED mInstance->SetVehicleID(vehicleID);
ChipLogDetail(AppServer, "VehicleID updated %s", NullTerminated(vehicleID.Value()).c_str());
return Status::Success;
}
/**
* @brief Allows the caller to get a copy of the VehicleID into its own
* MutableCharSpan avoiding potential use-after-free if vehicleID
* was to change in the background
*/
CHIP_ERROR EnergyEvseDelegate::HwGetVehicleID(DataModel::Nullable<MutableCharSpan> & outValue)
{
VerifyOrReturnError(mInstance != nullptr, CHIP_ERROR_INCORRECT_STATE);
DataModel::Nullable<CharSpan> vehicleID = mInstance->GetVehicleID();
if (vehicleID.IsNull())
{
outValue.SetNull();
return CHIP_NO_ERROR;
}
if (outValue.IsNull())
{
// Defensive: outValue must be non-null to copy into it
return CHIP_ERROR_INVALID_ARGUMENT;
}
return CopyCharSpanToMutableCharSpan(vehicleID.Value(), outValue.Value());
}
/**
* @brief Called by EVSE Hardware to indicate that it has finished its diagnostics test
*/
Status EnergyEvseDelegate::HwDiagnosticsComplete()
{
if (GetSupplyState() != SupplyStateEnum::kDisabledDiagnostics)
{
ChipLogError(AppServer, "Incorrect state to be completing diagnostics");
return Status::Failure;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
/* Restore the SupplyState to Disabled (per spec) - client will need to
* re-enable charging or discharging to get out of this state */
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kDisabled);
return Status::Success;
}
/* ---------------------------------------------------------------------------
* Functions below are private helper functions internal to the delegate
*/
/**
* @brief Main EVSE state machine
*
* This routine handles state transition events to determine behaviour
*
*
*/
Status EnergyEvseDelegate::HandleStateMachineEvent(EVSEStateMachineEvent event)
{
switch (event)
{
case EVSEStateMachineEvent::EVPluggedInEvent:
ChipLogDetail(AppServer, "EVSE: EV PluggedIn event");
return HandleEVPluggedInEvent();
break;
case EVSEStateMachineEvent::EVNotDetectedEvent:
ChipLogDetail(AppServer, "EVSE: EV NotDetected event");
return HandleEVNotDetectedEvent();
break;
case EVSEStateMachineEvent::EVNoDemandEvent:
ChipLogDetail(AppServer, "EVSE: EV NoDemand event");
return HandleEVNoDemandEvent();
break;
case EVSEStateMachineEvent::EVDemandEvent:
ChipLogDetail(AppServer, "EVSE: EV Demand event");
return HandleEVDemandEvent();
break;
case EVSEStateMachineEvent::ChargingEnabledEvent:
ChipLogDetail(AppServer, "EVSE: ChargingEnabled event");
return HandleChargingEnabledEvent();
break;
case EVSEStateMachineEvent::DischargingEnabledEvent:
ChipLogDetail(AppServer, "EVSE: DischargingEnabled event");
return HandleDischargingEnabledEvent();
break;
case EVSEStateMachineEvent::DisabledEvent:
ChipLogDetail(AppServer, "EVSE: Disabled event");
return HandleDisabledEvent();
break;
case EVSEStateMachineEvent::FaultRaised:
ChipLogDetail(AppServer, "EVSE: FaultRaised event");
return HandleFaultRaised();
break;
case EVSEStateMachineEvent::FaultCleared:
ChipLogDetail(AppServer, "EVSE: FaultCleared event");
return HandleFaultCleared();
break;
default:
return Status::Failure;
}
return Status::Success;
}
Status EnergyEvseDelegate::HandleEVPluggedInEvent()
{
StateEnum currentState = GetState();
if (currentState == StateEnum::kNotPluggedIn)
{
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
/* EV was not plugged in - start a new session */
// TODO get energy meter readings - #35370
mSession.StartSession(mInstance, 0, 0);
SendEVConnectedEvent();
/* Set the state to either PluggedInNoDemand or PluggedInDemand as indicated by mHwState */
TEMPORARY_RETURN_IGNORED mInstance->SetState(mHwState);
}
// else we are already plugged in - ignore
return Status::Success;
}
Status EnergyEvseDelegate::HandleEVNotDetectedEvent()
{
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
StateEnum currentState = GetState();
if (currentState == StateEnum::kPluggedInCharging || currentState == StateEnum::kPluggedInDischarging)
{
/*
* EV was transferring current - unusual to get to this case without
* first having the state set to kPluggedInNoDemand or kPluggedInDemand
*/
SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kOther);
}
// TODO get energy meter readings - #35370
mSession.StopSession(mInstance, 0, 0);
SendEVNotDetectedEvent();
TEMPORARY_RETURN_IGNORED mInstance->SetState(StateEnum::kNotPluggedIn);
return Status::Success;
}
Status EnergyEvseDelegate::HandleEVNoDemandEvent()
{
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
StateEnum currentState = GetState();
if (currentState == StateEnum::kPluggedInCharging || currentState == StateEnum::kPluggedInDischarging)
{
/*
* EV was transferring current - EV decided to stop
*/
mSession.RecalculateSessionDuration(mInstance);
SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVStopped);
}
/* We must still be plugged in to get here - so no need to check if we are plugged in! */
TEMPORARY_RETURN_IGNORED mInstance->SetState(StateEnum::kPluggedInNoDemand);
return Status::Success;
}
Status EnergyEvseDelegate::HandleEVDemandEvent()
{
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
/* Check to see if the supply is enabled for charging / discharging*/
SupplyStateEnum currentSupplyState = GetSupplyState();
switch (currentSupplyState)
{
case SupplyStateEnum::kChargingEnabled:
ComputeMaxChargeCurrentLimit();
TEMPORARY_RETURN_IGNORED mInstance->SetState(StateEnum::kPluggedInCharging);
SendEnergyTransferStartedEvent();
break;
case SupplyStateEnum::kDischargingEnabled:
ComputeMaxDischargeCurrentLimit();
TEMPORARY_RETURN_IGNORED mInstance->SetState(StateEnum::kPluggedInDischarging);
SendEnergyTransferStartedEvent();
break;
case SupplyStateEnum::kEnabled:
/* We are enabled for both charging and discharging
since the vehicle is asking for demand, we should start charging
NOTE: for discharging the PowerAdjustment feature of DEM is used.
This assumes we are not in TimeOfUse mode and charging should begin
as soon as the vehicle asks for demand.
*/
ComputeMaxChargeCurrentLimit();
ComputeMaxDischargeCurrentLimit();
TEMPORARY_RETURN_IGNORED mInstance->SetState(StateEnum::kPluggedInCharging);
SendEnergyTransferStartedEvent();
break;
case SupplyStateEnum::kDisabled:
case SupplyStateEnum::kDisabledError:
case SupplyStateEnum::kDisabledDiagnostics:
/* We must be plugged in, and the event is asking for demand
* but we can't charge or discharge now - leave it as kPluggedInDemand */
TEMPORARY_RETURN_IGNORED mInstance->SetState(StateEnum::kPluggedInDemand);
break;
case SupplyStateEnum::kUnknownEnumValue:
ChipLogError(AppServer, "EVSE: HandleEVDemandEvent called in unexpected SupplyState");
return Status::Failure;
default:
break;
}
return Status::Success;
}
Status EnergyEvseDelegate::CheckFaultOrDiagnostic()
{
if (GetFaultState() != FaultStateEnum::kNoError)
{
ChipLogError(AppServer, "EVSE: Trying to handle command when fault is present");
return Status::Failure;
}
if (GetSupplyState() == SupplyStateEnum::kDisabledDiagnostics)
{
ChipLogError(AppServer, "EVSE: Trying to handle command when in diagnostics mode");
return Status::Failure;
}
return Status::Success;
}
Status EnergyEvseDelegate::HandleChargingEnabledEvent()
{
/* Check there is no Fault or Diagnostics condition */
Status status = CheckFaultOrDiagnostic();
if (status != Status::Success)
{
return status;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
SupplyStateEnum currentSupplyState = GetSupplyState();
switch (currentSupplyState)
{
case SupplyStateEnum::kDisabled:
// it was kDisabled, then the state becomes kChargingEnabled
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kChargingEnabled);
break;
case SupplyStateEnum::kChargingEnabled:
// No change
break;
case SupplyStateEnum::kDischargingEnabled:
// If the SupplyState was already kDischargingEnabled the state becomes kEnabled
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kEnabled);
break;
case SupplyStateEnum::kDisabledError:
case SupplyStateEnum::kDisabledDiagnostics:
break;
case SupplyStateEnum::kEnabled:
// No change
break;
case SupplyStateEnum::kUnknownEnumValue:
ChipLogError(AppServer, "EVSE: ChargingEnabledEvent called in unexpected SupplyState");
return Status::Failure;
}
StateEnum currentState = GetState();
switch (currentState)
{
case StateEnum::kNotPluggedIn:
case StateEnum::kPluggedInNoDemand:
break;
case StateEnum::kPluggedInDemand:
ComputeMaxChargeCurrentLimit();
TEMPORARY_RETURN_IGNORED mInstance->SetState(StateEnum::kPluggedInCharging);
SendEnergyTransferStartedEvent();
break;
case StateEnum::kPluggedInCharging:
case StateEnum::kPluggedInDischarging:
/* Switching from Discharging to Charging is controlled via PowerAdjust
command from DEM - we don't do anything specific here
and we do not send EnergyTransferStopped and StartedEvents when switching
from Discharging to Charging and vice-versa. */
break;
default:
break;
}
ScheduleCheckOnEnabledTimeout();
return Status::Success;
}
Status EnergyEvseDelegate::HandleDischargingEnabledEvent()
{
/* Check there is no Fault or Diagnostics condition */
Status status = CheckFaultOrDiagnostic();
if (status != Status::Success)
{
return status;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
SupplyStateEnum currentSupplyState = GetSupplyState();
switch (currentSupplyState)
{
case SupplyStateEnum::kDisabled:
// it was kDisabled, then the state becomes kDischargingEnabled
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kDischargingEnabled);
break;
case SupplyStateEnum::kChargingEnabled:
// If the SupplyState was already kChargingEnabled the state becomes kEnabled
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kEnabled);
break;
case SupplyStateEnum::kDischargingEnabled:
// No change
break;
case SupplyStateEnum::kDisabledError:
case SupplyStateEnum::kDisabledDiagnostics:
break;
case SupplyStateEnum::kEnabled:
// No change
break;
case SupplyStateEnum::kUnknownEnumValue:
ChipLogError(AppServer, "EVSE: DischargingEnabledEvent called in unexpected SupplyState");
return Status::Failure;
}
StateEnum currentState = GetState();
switch (currentState)
{
case StateEnum::kNotPluggedIn:
case StateEnum::kPluggedInNoDemand:
break;
case StateEnum::kPluggedInDemand:
case StateEnum::kPluggedInCharging:
/* Discharging is controlled from DEM PowerAdjust command - we don't change state here or send events here */
ComputeMaxDischargeCurrentLimit();
ComputeMaxDischargeCurrentLimit();
break;
case StateEnum::kPluggedInDischarging:
default:
break;
}
ScheduleCheckOnEnabledTimeout();
return Status::Success;
}
Status EnergyEvseDelegate::HandleDisabledEvent()
{
/* Check there is no Fault or Diagnostics condition */
Status status = CheckFaultOrDiagnostic();
if (status != Status::Success)
{
return status;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
/* update SupplyState to disabled */
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kDisabled);
StateEnum currentState = GetState();
switch (currentState)
{
case StateEnum::kNotPluggedIn:
case StateEnum::kPluggedInNoDemand:
case StateEnum::kPluggedInDemand:
break;
case StateEnum::kPluggedInCharging:
case StateEnum::kPluggedInDischarging:
SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped);
TEMPORARY_RETURN_IGNORED mInstance->SetState(mHwState);
break;
default:
break;
}
return Status::Success;
}
/**
* @brief This handles the new fault
*
* Note that if multiple faults happen and this is called repeatedly
* We only save the previous State and SupplyState if its the first raising
* of the fault, so we can restore the state back once all faults have cleared
)*/
Status EnergyEvseDelegate::HandleFaultRaised()
{
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
/* Save the current State and SupplyState so we can restore them if the fault clears */
if (mStateBeforeFault == StateEnum::kUnknownEnumValue)
{
/* No existing fault - save this value to restore it later if it clears */
mStateBeforeFault = GetState();
}
if (mSupplyStateBeforeFault == SupplyStateEnum::kUnknownEnumValue)
{
/* No existing fault */
mSupplyStateBeforeFault = GetSupplyState();
}
/* Update State & SupplyState */
TEMPORARY_RETURN_IGNORED mInstance->SetState(StateEnum::kFault);
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(SupplyStateEnum::kDisabledError);
return Status::Success;
}
Status EnergyEvseDelegate::HandleFaultCleared()
{
/* Check that something strange hasn't happened */
if ((mStateBeforeFault == StateEnum::kUnknownEnumValue) || (mSupplyStateBeforeFault == SupplyStateEnum::kUnknownEnumValue))
{
ChipLogError(AppServer, "EVSE: Something wrong trying to clear fault");
return Status::Failure;
}
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
/* Restore the State and SupplyState back to old values once all the faults have cleared
* Changing the State should notify the application, so it can continue charging etc
*/
TEMPORARY_RETURN_IGNORED mInstance->SetState(mStateBeforeFault);
TEMPORARY_RETURN_IGNORED mInstance->SetSupplyState(mSupplyStateBeforeFault);
/* put back the sentinel to catch new faults if more are raised */
mStateBeforeFault = StateEnum::kUnknownEnumValue;
mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue;
return Status::Success;
}
/**
* @brief Called to compute the safe charging current limit
*
* mActualChargingCurrentLimit is the minimum of:
* - MaxHardwareChargeCurrentLimit (of the hardware)
* - CircuitCapacity (set by the electrician - less than the hardware)
* - CableAssemblyLimit (detected when the cable is inserted)
* - MaximumChargeCurrent (from charging command)
* - UserMaximumChargeCurrent (could dynamically change)
*
*/
Status EnergyEvseDelegate::ComputeMaxChargeCurrentLimit()
{
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
int64_t oldValue = mActualChargingCurrentLimit;
mActualChargingCurrentLimit = mMaxHardwareChargeCurrentLimit;
mActualChargingCurrentLimit = std::min(mActualChargingCurrentLimit, mInstance->GetCircuitCapacity());
mActualChargingCurrentLimit = std::min(mActualChargingCurrentLimit, mCableAssemblyCurrentLimit);
mActualChargingCurrentLimit = std::min(mActualChargingCurrentLimit, mMaximumChargingCurrentLimitFromCommand);
mActualChargingCurrentLimit = std::min(mActualChargingCurrentLimit, mInstance->GetUserMaximumChargeCurrent());
int64_t newMaximumChargeCurrent = mActualChargingCurrentLimit;
if (oldValue != newMaximumChargeCurrent)
{
ChipLogDetail(AppServer, "MaximumChargeCurrent updated to %ld", static_cast<long>(newMaximumChargeCurrent));
TEMPORARY_RETURN_IGNORED mInstance->SetMaximumChargeCurrent(newMaximumChargeCurrent);
NotifyApplicationChargeCurrentLimitChange(newMaximumChargeCurrent);
}
return Status::Success;
}
/**
* @brief Called to compute the safe discharging current limit
*
* mActualDischargingCurrentLimit is the minimum of:
* - MaxHardwareDischargeCurrentLimit (of the hardware)
* - CircuitCapacity (set by the electrician - less than the hardware)
* - CableAssemblyLimit (detected when the cable is inserted)
* - MaximumDischargeCurrent (from Enable Discharging command)
*/
Status EnergyEvseDelegate::ComputeMaxDischargeCurrentLimit()
{
VerifyOrReturnValue(mInstance != nullptr, Status::Failure);
int64_t oldValue = mActualDischargingCurrentLimit;
mActualDischargingCurrentLimit = mMaxHardwareDischargeCurrentLimit;
mActualDischargingCurrentLimit = std::min(mActualDischargingCurrentLimit, mInstance->GetCircuitCapacity());
mActualDischargingCurrentLimit = std::min(mActualDischargingCurrentLimit, mCableAssemblyCurrentLimit);
mActualDischargingCurrentLimit = std::min(mActualDischargingCurrentLimit, mMaximumDischargingCurrentLimitFromCommand);
int64_t newMaximumDischargeCurrent = mActualDischargingCurrentLimit;
if (oldValue != newMaximumDischargeCurrent)
{
ChipLogDetail(AppServer, "MaximumDischargeCurrent updated to %ld", static_cast<long>(newMaximumDischargeCurrent));
TEMPORARY_RETURN_IGNORED mInstance->SetMaximumDischargeCurrent(newMaximumDischargeCurrent);
NotifyApplicationDischargeCurrentLimitChange(newMaximumDischargeCurrent);
}
return Status::Success;
}
Status EnergyEvseDelegate::NotifyApplicationChargeCurrentLimitChange(int64_t maximumChargeCurrent)
{
EVSECbInfo cbInfo;
cbInfo.type = EVSECallbackType::ChargeCurrentChanged;
cbInfo.ChargingCurrent.maximumChargeCurrent = maximumChargeCurrent;
if (mCallbacks.handler != nullptr)
{
mCallbacks.handler(&cbInfo, mCallbacks.arg);
}
return Status::Success;
}
Status EnergyEvseDelegate::NotifyApplicationDischargeCurrentLimitChange(int64_t maximumDischargeCurrent)
{
EVSECbInfo cbInfo;
cbInfo.type = EVSECallbackType::DischargeCurrentChanged;
cbInfo.DischargingCurrent.maximumDischargeCurrent = maximumDischargeCurrent;
if (mCallbacks.handler != nullptr)
{
mCallbacks.handler(&cbInfo, mCallbacks.arg);
}
return Status::Success;
}
Status EnergyEvseDelegate::NotifyApplicationStateChange()
{
EVSECbInfo cbInfo;
cbInfo.type = EVSECallbackType::StateChanged;
cbInfo.StateChange.state = GetState();
cbInfo.StateChange.supplyState = GetSupplyState();
if (mCallbacks.handler != nullptr)
{
mCallbacks.handler(&cbInfo, mCallbacks.arg);
}
return Status::Success;
}
Status EnergyEvseDelegate::NotifyApplicationChargingPreferencesChange()
{
EVSECbInfo cbInfo;
cbInfo.type = EVSECallbackType::ChargingPreferencesChanged;
if (mCallbacks.handler != nullptr)
{
mCallbacks.handler(&cbInfo, mCallbacks.arg);
}
return Status::Success;
}
Status EnergyEvseDelegate::GetEVSEEnergyMeterValue(ChargingDischargingType meterType, int64_t & aMeterValue)
{
EVSECbInfo cbInfo;
cbInfo.type = EVSECallbackType::EnergyMeterReadingRequested;
cbInfo.EnergyMeterReadingRequest.meterType = meterType;
cbInfo.EnergyMeterReadingRequest.energyMeterValuePtr = &aMeterValue;
if (mCallbacks.handler != nullptr)
{
mCallbacks.handler(&cbInfo, mCallbacks.arg);
}
return Status::Success;
}
Status EnergyEvseDelegate::SendEVConnectedEvent()
{
Events::EVConnected::Type event;
EventNumber eventNumber;
VerifyOrReturnError(mInstance != nullptr, Status::Failure, ChipLogError(AppServer, "Instance is Null"));
DataModel::Nullable<uint32_t> sessionID = mInstance->GetSessionID();
VerifyOrReturnError(!sessionID.IsNull(), Status::Failure, ChipLogError(AppServer, "SessionID is Null"));
event.sessionID = sessionID.Value();
CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format());
return Status::Failure;
}
return Status::Success;
}
Status EnergyEvseDelegate::SendEVNotDetectedEvent()
{
Events::EVNotDetected::Type event;
EventNumber eventNumber;
VerifyOrReturnError(mInstance != nullptr, Status::Failure, ChipLogError(AppServer, "Instance is Null"));
DataModel::Nullable<uint32_t> sessionID = mInstance->GetSessionID();
VerifyOrReturnError(!sessionID.IsNull(), Status::Failure, ChipLogError(AppServer, "SessionID is Null"));
event.sessionID = sessionID.Value();
event.state = GetState();
event.sessionDuration = mInstance->GetSessionDuration().Value();
event.sessionEnergyCharged = mInstance->GetSessionEnergyCharged().Value();
event.sessionEnergyDischarged = MakeOptional(mInstance->GetSessionEnergyDischarged().Value());
CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format());
return Status::Failure;
}
return Status::Success;
}
Status EnergyEvseDelegate::SendEnergyTransferStartedEvent()
{
Events::EnergyTransferStarted::Type event;
EventNumber eventNumber;
VerifyOrReturnError(mInstance != nullptr, Status::Failure, ChipLogError(AppServer, "Instance is Null"));
DataModel::Nullable<uint32_t> sessionID = mInstance->GetSessionID();
VerifyOrReturnError(!sessionID.IsNull(), Status::Failure, ChipLogError(AppServer, "SessionID is Null"));
event.sessionID = sessionID.Value();
event.state = GetState();
/* Sample the energy meter for charging */
GetEVSEEnergyMeterValue(ChargingDischargingType::kCharging, mImportedMeterValueAtEnergyTransferStart);
event.maximumCurrent = GetMaximumChargeCurrent();
/* For V2X we may switch between charging and discharging, but we don't
* keep sending EnergyTransferStarted events */
if (mInstance->HasFeature(Feature::kV2x))
{
/* Sample the energy meter for discharging */
GetEVSEEnergyMeterValue(ChargingDischargingType::kDischarging, mExportedMeterValueAtEnergyTransferStart);
event.maximumDischargeCurrent.SetValue(GetMaximumDischargeCurrent());
}
else
{
mExportedMeterValueAtEnergyTransferStart = 0;
event.maximumDischargeCurrent.ClearValue();
}
CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format());
return Status::Failure;
}
return Status::Success;
}
Status EnergyEvseDelegate::SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum reason)
{
Events::EnergyTransferStopped::Type event;
EventNumber eventNumber;
VerifyOrReturnError(mInstance != nullptr, Status::Failure, ChipLogError(AppServer, "Instance is Null"));
DataModel::Nullable<uint32_t> sessionID = mInstance->GetSessionID();
VerifyOrReturnError(!sessionID.IsNull(), Status::Failure, ChipLogError(AppServer, "SessionID is Null"));
event.sessionID = sessionID.Value();
event.state = GetState();
event.reason = reason;
int64_t meterValueNow = 0;
GetEVSEEnergyMeterValue(ChargingDischargingType::kCharging, meterValueNow);
event.energyTransferred = meterValueNow - mImportedMeterValueAtEnergyTransferStart;
if (mInstance->HasFeature(Feature::kV2x))
{
GetEVSEEnergyMeterValue(ChargingDischargingType::kDischarging, meterValueNow);
event.energyDischarged.SetValue(mExportedMeterValueAtEnergyTransferStart - meterValueNow);
}
else
{
event.energyDischarged.ClearValue();
}
CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format());
return Status::Failure;
}
return Status::Success;
}
Status EnergyEvseDelegate::SendFaultEvent(FaultStateEnum newFaultState)
{
Events::Fault::Type event;
EventNumber eventNumber;
VerifyOrReturnError(mInstance != nullptr, Status::Failure, ChipLogError(AppServer, "Instance is Null"));
event.sessionID = mInstance->GetSessionID(); // Note here the event sessionID can be Null!
event.state = GetState(); // This is the state prior to the fault being raised
event.faultStatePreviousState = GetFaultState();
event.faultStateCurrentState = newFaultState;
CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber);
if (CHIP_NO_ERROR != err)
{
ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format());
return Status::Failure;
}
return Status::Success;
}
/**
* Attribute change callbacks - called by cluster after attribute is updated
*/
void EnergyEvseDelegate::OnStateChanged(StateEnum newValue)
{
ChipLogDetail(AppServer, "State updated to %d", static_cast<int>(newValue));
NotifyApplicationStateChange();
}
void EnergyEvseDelegate::OnSupplyStateChanged(SupplyStateEnum newValue)
{
ChipLogDetail(AppServer, "SupplyState updated to %d", static_cast<int>(newValue));
NotifyApplicationStateChange();
}
void EnergyEvseDelegate::OnFaultStateChanged(FaultStateEnum newValue)
{
ChipLogDetail(AppServer, "FaultState updated to %d", static_cast<int>(newValue));
}
void EnergyEvseDelegate::OnChargingEnabledUntilChanged(DataModel::Nullable<uint32_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "ChargingEnabledUntil updated to Null");
}
else
{
ChipLogDetail(AppServer, "ChargingEnabledUntil updated to %lu", static_cast<unsigned long int>(newValue.Value()));
}
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, ChargingEnabledUntil::Id);
TEMPORARY_RETURN_IGNORED GetSafeAttributePersistenceProvider()->WriteScalarValue(path, newValue);
}
void EnergyEvseDelegate::OnDischargingEnabledUntilChanged(DataModel::Nullable<uint32_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "DischargingEnabledUntil updated to Null");
}
else
{
ChipLogDetail(AppServer, "DischargingEnabledUntil updated to %lu", static_cast<unsigned long int>(newValue.Value()));
}
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, DischargingEnabledUntil::Id);
TEMPORARY_RETURN_IGNORED GetSafeAttributePersistenceProvider()->WriteScalarValue(path, newValue);
}
void EnergyEvseDelegate::OnCircuitCapacityChanged(int64_t newValue)
{
ChipLogDetail(AppServer, "CircuitCapacity updated to %ld", static_cast<long>(newValue));
}
void EnergyEvseDelegate::OnMinimumChargeCurrentChanged(int64_t newValue)
{
ChipLogDetail(AppServer, "MinimumChargeCurrent updated to %ld", static_cast<long>(newValue));
}
void EnergyEvseDelegate::OnMaximumChargeCurrentChanged(int64_t newValue)
{
ChipLogDetail(AppServer, "MaximumChargeCurrent updated to %ld", static_cast<long>(newValue));
}
void EnergyEvseDelegate::OnMaximumDischargeCurrentChanged(int64_t newValue)
{
ChipLogDetail(AppServer, "MaximumDischargeCurrent updated to %ld", static_cast<long>(newValue));
}
void EnergyEvseDelegate::OnUserMaximumChargeCurrentChanged(int64_t newValue)
{
ChipLogDetail(AppServer, "UserMaximumChargeCurrent updated to %ld", static_cast<long>(newValue));
ComputeMaxChargeCurrentLimit();
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, UserMaximumChargeCurrent::Id);
TEMPORARY_RETURN_IGNORED GetSafeAttributePersistenceProvider()->WriteScalarValue(path, newValue);
}
void EnergyEvseDelegate::OnRandomizationDelayWindowChanged(uint32_t newValue)
{
ChipLogDetail(AppServer, "RandomizationDelayWindow updated to %lu", static_cast<unsigned long int>(newValue));
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, RandomizationDelayWindow::Id);
TEMPORARY_RETURN_IGNORED GetSafeAttributePersistenceProvider()->WriteScalarValue(path, newValue);
}
void EnergyEvseDelegate::OnNextChargeStartTimeChanged(DataModel::Nullable<uint32_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "NextChargeStartTime updated to Null");
}
else
{
ChipLogDetail(AppServer, "NextChargeStartTime updated to %lu", static_cast<unsigned long int>(newValue.Value()));
}
}
void EnergyEvseDelegate::OnNextChargeTargetTimeChanged(DataModel::Nullable<uint32_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "NextChargeTargetTime updated to Null");
}
else
{
ChipLogDetail(AppServer, "NextChargeTargetTime updated to %lu", static_cast<unsigned long int>(newValue.Value()));
}
}
void EnergyEvseDelegate::OnNextChargeRequiredEnergyChanged(DataModel::Nullable<int64_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "NextChargeRequiredEnergy updated to Null");
}
else
{
ChipLogDetail(AppServer, "NextChargeRequiredEnergy updated to %ld", static_cast<long>(newValue.Value()));
}
}
void EnergyEvseDelegate::OnNextChargeTargetSoCChanged(DataModel::Nullable<Percent> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "NextChargeTargetSoC updated to Null");
}
else
{
ChipLogDetail(AppServer, "NextChargeTargetSoC updated to %d %%", newValue.Value());
}
}
void EnergyEvseDelegate::OnApproximateEVEfficiencyChanged(DataModel::Nullable<uint16_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "ApproximateEVEfficiency updated to Null");
}
else
{
ChipLogDetail(AppServer, "ApproximateEVEfficiency updated to %d", newValue.Value());
}
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, ApproximateEVEfficiency::Id);
TEMPORARY_RETURN_IGNORED GetSafeAttributePersistenceProvider()->WriteScalarValue(path, newValue);
}
void EnergyEvseDelegate::OnStateOfChargeChanged(DataModel::Nullable<Percent> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "StateOfCharge updated to Null");
}
else
{
ChipLogDetail(AppServer, "StateOfCharge updated to %d", newValue.Value());
}
}
void EnergyEvseDelegate::OnBatteryCapacityChanged(DataModel::Nullable<int64_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "BatteryCapacity updated to Null");
}
else
{
ChipLogDetail(AppServer, "BatteryCapacity updated to %ld", static_cast<long>(newValue.Value()));
}
}
void EnergyEvseDelegate::OnVehicleIDChanged(DataModel::Nullable<CharSpan> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "VehicleID updated to Null");
}
else
{
ChipLogDetail(AppServer, "VehicleID updated to %.*s", static_cast<int>(newValue.Value().size()), newValue.Value().data());
}
}
void EnergyEvseDelegate::OnSessionIDChanged(DataModel::Nullable<uint32_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "SessionID updated to Null");
}
else
{
ChipLogDetail(AppServer, "SessionID updated to %lu", static_cast<unsigned long int>(newValue.Value()));
}
// Write value to persistent storage.
ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, SessionID::Id);
TEMPORARY_RETURN_IGNORED GetSafeAttributePersistenceProvider()->WriteScalarValue(path, newValue);
}
void EnergyEvseDelegate::OnSessionDurationChanged(DataModel::Nullable<uint32_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "SessionDuration updated to Null");
}
else
{
ChipLogDetail(AppServer, "SessionDuration updated to %lu", static_cast<unsigned long int>(newValue.Value()));
}
}
void EnergyEvseDelegate::OnSessionEnergyChargedChanged(DataModel::Nullable<int64_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "SessionEnergyCharged updated to Null");
}
else
{
ChipLogDetail(AppServer, "SessionEnergyCharged updated to %ld", static_cast<long>(newValue.Value()));
}
}
void EnergyEvseDelegate::OnSessionEnergyDischargedChanged(DataModel::Nullable<int64_t> newValue)
{
if (newValue.IsNull())
{
ChipLogDetail(AppServer, "SessionEnergyDischarged updated to Null");
}
else
{
ChipLogDetail(AppServer, "SessionEnergyDischarged updated to %ld", static_cast<long>(newValue.Value()));
}
}
/**
* @brief Helper function to get know if the EV is plugged in based on state
* (regardless of if it is actually transferring energy)
*/
bool EnergyEvseDelegate::IsEvsePluggedIn()
{
StateEnum currentState = GetState();
return (currentState == StateEnum::kPluggedInCharging || currentState == StateEnum::kPluggedInDemand ||
currentState == StateEnum::kPluggedInDischarging || currentState == StateEnum::kPluggedInNoDemand);
}
/**
* @brief This function samples the start-time, and energy meter to hold the session info
*
* @param endpointId - The endpoint to report the update on
* @param chargingMeterValue - The current value of the energy meter (charging) in mWh
* @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh
*/
void EvseSession::StartSession(Instance * instance, int64_t chargingMeterValue, int64_t dischargingMeterValue)
{
VerifyOrReturn(instance != nullptr);
/* Get Timestamp */
uint32_t matterEpochSeconds = 0;
CHIP_ERROR err = System::Clock::GetClock_MatterEpochS(matterEpochSeconds);
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "EVSE: Unable to get current time when starting session - err:%" CHIP_ERROR_FORMAT, err.Format());
return;
}
mStartTime = matterEpochSeconds;
mSessionEnergyChargedAtStart = chargingMeterValue;
mSessionEnergyDischargedAtStart = dischargingMeterValue;
// Compute next session ID
DataModel::Nullable<uint32_t> currentSessionID = instance->GetSessionID();
if (currentSessionID.IsNull())
{
TEMPORARY_RETURN_IGNORED instance->SetSessionID(MakeNullable(static_cast<uint32_t>(0)));
}
else
{
TEMPORARY_RETURN_IGNORED instance->SetSessionID(MakeNullable(currentSessionID.Value() + 1));
}
// Reset session counters
TEMPORARY_RETURN_IGNORED instance->SetSessionDuration(MakeNullable(static_cast<uint32_t>(0)));
TEMPORARY_RETURN_IGNORED instance->SetSessionEnergyCharged(MakeNullable(static_cast<int64_t>(0)));
TEMPORARY_RETURN_IGNORED instance->SetSessionEnergyDischarged(MakeNullable(static_cast<int64_t>(0)));
// TODO persist mStartTime
// TODO persist mSessionEnergyChargedAtStart
// TODO persist mSessionEnergyDischargedAtStart
}
/**
* @brief This function updates the session information at the unplugged event
*
* @param endpointId - The endpoint to report the update on
* @param chargingMeterValue - The current value of the energy meter (charging) in mWh
* @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh
*/
void EvseSession::StopSession(Instance * instance, int64_t chargingMeterValue, int64_t dischargingMeterValue)
{
RecalculateSessionDuration(instance);
UpdateEnergyCharged(instance, chargingMeterValue);
UpdateEnergyDischarged(instance, dischargingMeterValue);
}
/*---------------------- EvseSession functions --------------------------*/
/**
* @brief This function updates the session attrs to allow read attributes to return latest values
*
* @param endpointId - The endpoint to report the update on
*/
void EvseSession::RecalculateSessionDuration(Instance * instance)
{
VerifyOrReturn(instance != nullptr);
uint32_t matterEpochSeconds = 0;
CHIP_ERROR err = System::Clock::GetClock_MatterEpochS(matterEpochSeconds);
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "EVSE: Unable to get current time when updating session duration - err:%" CHIP_ERROR_FORMAT,
err.Format());
return;
}
uint32_t duration = matterEpochSeconds - mStartTime;
TEMPORARY_RETURN_IGNORED instance->SetSessionDuration(MakeNullable(duration));
}
/**
* @brief This function updates the EnergyCharged meter value
*
* @param endpointId - The endpoint to report the update on
* @param chargingMeterValue - The value of the energy meter (charging) in mWh
*/
void EvseSession::UpdateEnergyCharged(Instance * instance, int64_t chargingMeterValue)
{
VerifyOrReturn(instance != nullptr);
TEMPORARY_RETURN_IGNORED instance->SetSessionEnergyCharged(MakeNullable(chargingMeterValue - mSessionEnergyChargedAtStart));
}
/**
* @brief This function updates the EnergyDischarged meter value
*
* @param endpointId - The endpoint to report the update on
* @param dischargingMeterValue - The value of the energy meter (discharging) in mWh
*/
void EvseSession::UpdateEnergyDischarged(Instance * instance, int64_t dischargingMeterValue)
{
VerifyOrReturn(instance != nullptr);
TEMPORARY_RETURN_IGNORED instance->SetSessionEnergyDischarged(
MakeNullable(dischargingMeterValue - mSessionEnergyDischargedAtStart));
}
// ------------------------------------------------------------------
// Local getters for internal delegate use - delegates to cluster instance
// ------------------------------------------------------------------
StateEnum EnergyEvseDelegate::GetState() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetState();
}
SupplyStateEnum EnergyEvseDelegate::GetSupplyState() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetSupplyState();
}
FaultStateEnum EnergyEvseDelegate::GetFaultState() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetFaultState();
}
DataModel::Nullable<uint32_t> EnergyEvseDelegate::GetChargingEnabledUntil() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetChargingEnabledUntil();
}
DataModel::Nullable<uint32_t> EnergyEvseDelegate::GetDischargingEnabledUntil() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetDischargingEnabledUntil();
}
int64_t EnergyEvseDelegate::GetCircuitCapacity() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetCircuitCapacity();
}
int64_t EnergyEvseDelegate::GetMinimumChargeCurrent() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetMinimumChargeCurrent();
}
int64_t EnergyEvseDelegate::GetMaximumChargeCurrent() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetMaximumChargeCurrent();
}
int64_t EnergyEvseDelegate::GetMaximumDischargeCurrent() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetMaximumDischargeCurrent();
}
int64_t EnergyEvseDelegate::GetUserMaximumChargeCurrent() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetUserMaximumChargeCurrent();
}
uint32_t EnergyEvseDelegate::GetRandomizationDelayWindow() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetRandomizationDelayWindow();
}
DataModel::Nullable<uint32_t> EnergyEvseDelegate::GetNextChargeStartTime() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetNextChargeStartTime();
}
DataModel::Nullable<uint32_t> EnergyEvseDelegate::GetNextChargeTargetTime() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetNextChargeTargetTime();
}
DataModel::Nullable<int64_t> EnergyEvseDelegate::GetNextChargeRequiredEnergy() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetNextChargeRequiredEnergy();
}
DataModel::Nullable<Percent> EnergyEvseDelegate::GetNextChargeTargetSoC() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetNextChargeTargetSoC();
}
DataModel::Nullable<uint16_t> EnergyEvseDelegate::GetApproximateEVEfficiency() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetApproximateEVEfficiency();
}
DataModel::Nullable<Percent> EnergyEvseDelegate::GetStateOfCharge() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetStateOfCharge();
}
DataModel::Nullable<int64_t> EnergyEvseDelegate::GetBatteryCapacity() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetBatteryCapacity();
}
DataModel::Nullable<CharSpan> EnergyEvseDelegate::GetVehicleID() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetVehicleID();
}
DataModel::Nullable<uint32_t> EnergyEvseDelegate::GetSessionID() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetSessionID();
}
DataModel::Nullable<uint32_t> EnergyEvseDelegate::GetSessionDuration() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetSessionDuration();
}
DataModel::Nullable<int64_t> EnergyEvseDelegate::GetSessionEnergyCharged() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetSessionEnergyCharged();
}
DataModel::Nullable<int64_t> EnergyEvseDelegate::GetSessionEnergyDischarged() const
{
VerifyOrDie(mInstance != nullptr);
return mInstance->GetSessionEnergyDischarged();
}