From 3f0cdedbc478f219df4cbfcb3fa1d1f32fadea9a Mon Sep 17 00:00:00 2001 From: James Harrow Date: Wed, 27 Dec 2023 18:22:36 +0000 Subject: [PATCH 01/52] Beginnings of Session handling --- .../include/EnergyEvseDelegateImpl.h | 52 +++++++- .../src/EnergyEvseDelegateImpl.cpp | 118 +++++++++++++++++- 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index f3c003d081fc6e..6f80064d2dd9b6 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -31,6 +31,51 @@ namespace app { namespace Clusters { namespace EnergyEvse { +/** + * Helper class to handle all of the session related info + */ +class EvseSession +{ +public: + /** + * @brief This function samples the start-time, and energy meter to hold the session info + * + * @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 StartSession(int64_t chargingMeterValue, int64_t dischargingMeterValue); + + /** + * @brief This function updates the session Duration to allow read attributes to return latest values + */ + void RecalculateSessionDuration(); + + /** + * @brief This function updates the EnergyCharged meter value + * + * @param chargingMeterValue - The value of the energy meter (charging) in mWh + */ + void UpdateEnergyCharged(int64_t chargingMeterValue); + + /** + * @brief This function updates the EnergyDischarged meter value + * + * @param dischargingMeterValue - The value of the energy meter (discharging) in mWh + */ + void UpdateEnergyDischarged(int64_t dischargingMeterValue); + + /* Public members - represent attributes in the cluster */ + DataModel::Nullable mSessionID; + DataModel::Nullable mSessionDuration; + DataModel::Nullable mSessionEnergyCharged; + DataModel::Nullable mSessionEnergyDischarged; + +private: + uint32_t mStartTime = 0; // Epoch_s - 0 means it hasn't started yet + int64_t mSessionEnergyChargedAtStart = 0; // in mWh - 0 means it hasn't been set yet + int64_t mSessionEnergyDischargedAtStart = 0; // in mWh - 0 means it hasn't been set yet +}; + /** * The application delegate. */ @@ -193,11 +238,8 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate /* PNC attributes*/ DataModel::Nullable mVehicleID; - /* Session SESS attributes */ - DataModel::Nullable mSessionID; - DataModel::Nullable mSessionDuration; - DataModel::Nullable mSessionEnergyCharged; - DataModel::Nullable mSessionEnergyDischarged; + /* Session Object */ + EvseSession mSession; }; } // namespace EnergyEvse diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 4cc83eaaf8a835..3ea92aa78d6298 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -831,17 +831,127 @@ DataModel::Nullable EnergyEvseDelegate::GetVehicleID() /* Session SESS attributes */ DataModel::Nullable EnergyEvseDelegate::GetSessionID() { - return mSessionID; + return mSession.mSessionID; } DataModel::Nullable EnergyEvseDelegate::GetSessionDuration() { - return mSessionDuration; + mSession.RecalculateSessionDuration(); + return mSession.mSessionDuration; } DataModel::Nullable EnergyEvseDelegate::GetSessionEnergyCharged() { - return mSessionEnergyCharged; + return mSession.mSessionEnergyCharged; } DataModel::Nullable EnergyEvseDelegate::GetSessionEnergyDischarged() { - return mSessionEnergyDischarged; + return mSession.mSessionEnergyDischarged; +} + +/** + * @brief Helper function to get current timestamp in Epoch format + * + * @param chipEpoch reference to hold return timestamp + */ +CHIP_ERROR GetEpochTS(uint32_t & chipEpoch) +{ + chipEpoch = 0; + + System::Clock::Milliseconds64 cTMs; + CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(cTMs); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "EVSE: Unable to get current time - err:%" CHIP_ERROR_FORMAT, err.Format()); + return err; + } + + auto unixEpoch = std::chrono::duration_cast(cTMs).count(); + if (!UnixEpochToChipEpochTime(unixEpoch, chipEpoch)) + { + ChipLogError(Zcl, "EVSE: unable to convert Unix Epoch time to Matter Epoch Time"); + return err; + } + + return CHIP_NO_ERROR; +} + +/** + * @brief This function samples the start-time, and energy meter to hold the session info + * + * @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(int64_t chargingMeterValue, int64_t dischargingMeterValue) +{ + /* Get Timestamp */ + uint32_t chipEpoch = 0; + if (GetEpochTS(chipEpoch) != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "EVSE: Failed to get timestamp when starting session"); + return; + } + mStartTime = chipEpoch; + + mSessionEnergyChargedAtStart = chargingMeterValue; + mSessionEnergyDischargedAtStart = dischargingMeterValue; + + if (mSessionID.IsNull()) + { + mSessionID = MakeNullable(static_cast(0)); + } + else + { + mSessionID = MakeNullable(mSessionID.Value()++); + } + + /* Reset other session values */ + mSessionDuration = MakeNullable(static_cast(0)); + mSessionEnergyCharged = MakeNullable(static_cast(0)); + mSessionEnergyDischarged = MakeNullable(static_cast(0)); + + // TODO persist mSessionID + // TODO persist mStartTime + // TODO persist mSessionEnergyChargedAtStart + // TODO persist mSessionEnergyDischargedAtStart + + // TODO call MatterReportingAttributeChangeCallback +} + +/** + * @brief This function updates the session attrs to allow read attributes to return latest values + */ +void EvseSession::RecalculateSessionDuration() +{ + /* Get Timestamp */ + uint32_t chipEpoch = 0; + if (GetEpochTS(chipEpoch) != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "EVSE: Failed to get timestamp when updating session duration"); + return; + } + + uint32_t duration = chipEpoch - mStartTime; + mSessionDuration = MakeNullable(duration); + // TODO call MatterReportingAttributeChangeCallback +} + +/** + * @brief This function updates the EnergyCharged meter value + * + * @param chargingMeterValue - The value of the energy meter (charging) in mWh + */ +void EvseSession::UpdateEnergyCharged(int64_t chargingMeterValue) +{ + mSessionEnergyCharged = MakeNullable(chargingMeterValue - mSessionEnergyChargedAtStart); + // TODO call MatterReportingAttributeChangeCallback +} + +/** + * @brief This function updates the EnergyDischarged meter value + * + * @param dischargingMeterValue - The value of the energy meter (discharging) in mWh + */ +void EvseSession::UpdateEnergyDischarged(int64_t dischargingMeterValue) +{ + mSessionEnergyDischarged = MakeNullable(dischargingMeterValue - mSessionEnergyDischargedAtStart); + // TODO call MatterReportingAttributeChangeCallback } From 2990233368235d1e1b8abbe2bd779ff8dd026efb Mon Sep 17 00:00:00 2001 From: James Harrow Date: Thu, 28 Dec 2023 19:50:33 +0000 Subject: [PATCH 02/52] Added beginnings of EVConnected,EVNotDetected,EnergyTransferStarted,EnergyTransferStopped handling. State machine is not finished. Callback to read Energy Meter added --- .../include/EVSECallbacks.h | 17 ++ .../include/EVSEManufacturerImpl.h | 2 + .../include/EnergyEvseDelegateImpl.h | 14 +- .../src/EVSEManufacturerImpl.cpp | 18 +- .../src/EnergyEvseDelegateImpl.cpp | 243 ++++++++++++++++-- 5 files changed, 274 insertions(+), 20 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h index 5bdac2f8e853d6..e72ee80ef9fcca 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h +++ b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h @@ -44,12 +44,22 @@ enum EVSECallbackType * Charging Preferences have changed */ ChargingPreferencesChanged, + /* + * Energy Meter Reading requested + */ + EnergyMeterReadingRequested, /* * DeviceEnergyManagement has changed */ DeviceEnergyManagementChanged, }; +enum ChargingDischargingType +{ + kCharging, + kDischarging +}; + struct EVSECbInfo { EVSECallbackType type; @@ -68,6 +78,13 @@ struct EVSECbInfo { int64_t maximumChargeCurrent; } ChargingCurrent; + + /* for type = EnergyMeterReadingRequested */ + struct + { + ChargingDischargingType meterType; + int64_t * energyMeterValuePtr; + } EnergyMeterReadingRequest; }; }; diff --git a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h index 4ff45e925674aa..4bb7cfb1dcafbf 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h @@ -49,6 +49,8 @@ class EVSEManufacturer static void ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg); private: + int64_t mLastChargingEnergyMeter = 0; + int64_t mLastDischargingEnergyMeter = 0; }; } // namespace EnergyEvse diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 6f80064d2dd9b6..c7be153da04fd0 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -37,6 +37,7 @@ namespace EnergyEvse { class EvseSession { public: + EvseSession(EndpointId aEndpoint) { mEndpointId = aEndpoint; } /** * @brief This function samples the start-time, and energy meter to hold the session info * @@ -71,6 +72,8 @@ class EvseSession DataModel::Nullable mSessionEnergyDischarged; private: + EndpointId mEndpointId = 0; + uint32_t mStartTime = 0; // Epoch_s - 0 means it hasn't started yet int64_t mSessionEnergyChargedAtStart = 0; // in mWh - 0 means it hasn't been set yet int64_t mSessionEnergyDischargedAtStart = 0; // in mWh - 0 means it hasn't been set yet @@ -129,6 +132,11 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate Status HwSetRFID(ByteSpan uid); Status HwSetVehicleID(const CharSpan & vehID); + Status SendEVConnectedEvent(); + Status SendEVNotDetectedEvent(); + Status SendEnergyTransferStartedEvent(); + Status SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum reason); + // ------------------------------------------------------------------ // Get attribute methods StateEnum GetState() override; @@ -204,6 +212,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate EVSECallbackWrapper mCallbacks = { .handler = nullptr, .arg = 0 }; /* Wrapper to allow callbacks to be registered */ Status NotifyApplicationCurrentLimitChange(int64_t maximumChargeCurrent); Status NotifyApplicationStateChange(); + Status GetEVSEEnergyMeterValue(ChargingDischargingType meterType, int64_t & aMeterValue); /** * @brief Helper function to work out the charge limit based on conditions and settings @@ -239,7 +248,10 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate DataModel::Nullable mVehicleID; /* Session Object */ - EvseSession mSession; + EvseSession mSession = EvseSession(mEndpointId); + + /* Helper variable to hold meter val since last EnergyTransferStarted event */ + int64_t mMeterValueAtEnergyTransferStart; }; } // namespace EnergyEvse diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index 8daf1781103831..a49ae50d96a4c0 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -35,7 +35,7 @@ CHIP_ERROR EVSEManufacturer::Init(EnergyEvseManager * aInstance) return CHIP_ERROR_UNINITIALIZED; } - dg->HwRegisterEvseCallbackHandler(ApplicationCallbackHandler, reinterpret_cast(nullptr)); + dg->HwRegisterEvseCallbackHandler(ApplicationCallbackHandler, reinterpret_cast(this)); /* Set the EVSE Hardware Maximum current limit */ // For Manufacturer to specify the hardware capability in mA @@ -78,6 +78,8 @@ CHIP_ERROR EVSEManufacturer::Shutdown(EnergyEvseManager * aInstance) */ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg) { + EVSEManufacturer * pClass = reinterpret_cast(arg); + switch (cb->type) { case EVSECallbackType::StateChanged: @@ -87,7 +89,19 @@ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_ ChipLogProgress(AppServer, "EVSE callback - maxChargeCurrent changed to %ld", static_cast(cb->ChargingCurrent.maximumChargeCurrent)); break; + case EVSECallbackType::EnergyMeterReadingRequested: + ChipLogProgress(AppServer, "EVSE callback - EnergyMeterReadingRequested"); + if (cb->EnergyMeterReadingRequest.meterType == ChargingDischargingType::kCharging) + { + *(cb->EnergyMeterReadingRequest.energyMeterValuePtr) = pClass->mLastChargingEnergyMeter; + } + else + { + *(cb->EnergyMeterReadingRequest.energyMeterValuePtr) = pClass->mLastDischargingEnergyMeter; + } + break; + default: - ChipLogError(AppServer, "Unhandler EVSE Callback type %d", static_cast(cb->type)); + ChipLogError(AppServer, "Unhandled EVSE Callback type %d", static_cast(cb->type)); } } diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 3ea92aa78d6298..d54e936581e264 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -309,36 +309,95 @@ Status EnergyEvseDelegate::HwSetCableAssemblyLimit(int64_t currentmA) return this->ComputeMaxChargeCurrentLimit(); } +bool IsEvPluggedIn(StateEnum state) +{ + if ((state == StateEnum::kNotPluggedIn) || (state == StateEnum::kFault) || (state == StateEnum::kSessionEnding)) + { + return false; + } + else + { + return true; + } +} + +bool IsEvTransferringEnergy(StateEnum state) +{ + if ((state == StateEnum::kPluggedInCharging) || (state == StateEnum::kPluggedInDischarging)) + { + return true; + } + else + { + return false; + } +} /** * @brief Called by EVSE Hardware to indicate if EV is detected * - * The only allowed states that the EVSE hardware can set are: + * 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 state) +Status EnergyEvseDelegate::HwSetState(StateEnum newState) { - switch (state) + switch (newState) { case StateEnum::kNotPluggedIn: - // TODO - work out logic here - mHwState = state; - break; - case StateEnum::kPluggedInNoDemand: - // TODO - work out logic here - mHwState = state; + if (IsEvPluggedIn(mState)) + { + /* EV was plugged in, but no longer is */ + mSession.RecalculateSessionDuration(); + if (IsEvTransferringEnergy(mState)) + { + /* + * EV was transferring current - unusual to get to this case without + * first having the state set to kPluggedInNoDemand or kPluggedInDemand + */ + mSession.RecalculateSessionDuration(); + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kOther); + } + + SendEVNotDetectedEvent(); + SetState(newState); + } break; + + case StateEnum::kPluggedInNoDemand: /* deliberate fall-thru */ case StateEnum::kPluggedInDemand: - // TODO - work out logic here - mHwState = state; + if (IsEvPluggedIn(mState)) + { + /* EV was already plugged in before */ + if (IsEvTransferringEnergy(mState)) + { + mSession.RecalculateSessionDuration(); + SendEnergyTransferStoppedEvent(newState == StateEnum::kPluggedInNoDemand + ? EnergyTransferStoppedReasonEnum::kEVStopped + : EnergyTransferStoppedReasonEnum::kEVSEStopped); + } + SetState(newState); + } + else + { + /* EV was not plugged in - start a new session */ + // TODO get energy meter readings + mSession.StartSession(0, 0); + SendEVConnectedEvent(); + + /* If*/ + + SetState(newState); + } break; default: /* All other states should be managed by the Delegate */ - // TODO (assert?) break; } @@ -507,6 +566,153 @@ Status EnergyEvseDelegate::NotifyApplicationStateChange() 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; + + if (mSession.mSessionID.IsNull()) + { + ChipLogError(AppServer, "SessionID is Null"); + return Status::Failure; + } + + event.sessionID = mSession.mSessionID.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; + + if (mSession.mSessionID.IsNull()) + { + ChipLogError(AppServer, "SessionID is Null"); + return Status::Failure; + } + + event.sessionID = mSession.mSessionID.Value(); + event.state = mState; + event.sessionDuration = mSession.mSessionDuration.Value(); + event.sessionEnergyCharged = mSession.mSessionEnergyCharged.Value(); + event.sessionEnergyDischarged = MakeOptional(mSession.mSessionEnergyDischarged.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; + + if (mSession.mSessionID.IsNull()) + { + ChipLogError(AppServer, "SessionID is Null"); + return Status::Failure; + } + + event.sessionID = mSession.mSessionID.Value(); + event.state = mState; + /** + * A positive value indicates the EV has been enabled for charging and the value is + * taken directly from the MaximumChargeCurrent attribute. + * A negative value indicates that the EV has been enabled for discharging and the value can be taken + * from the MaximumDischargeCurrent attribute with its sign inverted. + */ + + if (mState == StateEnum::kPluggedInCharging) + { + /* Sample the energy meter for charging */ + GetEVSEEnergyMeterValue(ChargingDischargingType::kCharging, mMeterValueAtEnergyTransferStart); + event.maximumCurrent = mMaximumChargeCurrent; + } + else if (mState == StateEnum::kPluggedInDischarging) + { + /* Sample the energy meter for discharging */ + GetEVSEEnergyMeterValue(ChargingDischargingType::kDischarging, mMeterValueAtEnergyTransferStart); + + /* discharging should have a negative current */ + event.maximumCurrent = -mMaximumDischargeCurrent; + } + + 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; + + if (mSession.mSessionID.IsNull()) + { + ChipLogError(AppServer, "SessionID is Null"); + return Status::Failure; + } + + event.sessionID = mSession.mSessionID.Value(); + event.state = mState; + event.reason = reason; + int64_t meterValueNow = 0; + + if (mState == StateEnum::kPluggedInCharging) + { + GetEVSEEnergyMeterValue(ChargingDischargingType::kCharging, meterValueNow); + event.energyTransferred = meterValueNow - mMeterValueAtEnergyTransferStart; + } + else if (mState == StateEnum::kPluggedInDischarging) + { + GetEVSEEnergyMeterValue(ChargingDischargingType::kDischarging, meterValueNow); + + /* discharging should have a negative value */ + event.energyTransferred = mMeterValueAtEnergyTransferStart - meterValueNow; + } + + 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 methods */ @@ -908,12 +1114,15 @@ void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMe mSessionEnergyCharged = MakeNullable(static_cast(0)); mSessionEnergyDischarged = MakeNullable(static_cast(0)); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionID::Id); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionDuration::Id); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyCharged::Id); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); + // TODO persist mSessionID // TODO persist mStartTime // TODO persist mSessionEnergyChargedAtStart // TODO persist mSessionEnergyDischargedAtStart - - // TODO call MatterReportingAttributeChangeCallback } /** @@ -931,7 +1140,7 @@ void EvseSession::RecalculateSessionDuration() uint32_t duration = chipEpoch - mStartTime; mSessionDuration = MakeNullable(duration); - // TODO call MatterReportingAttributeChangeCallback + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionDuration::Id); } /** @@ -942,7 +1151,7 @@ void EvseSession::RecalculateSessionDuration() void EvseSession::UpdateEnergyCharged(int64_t chargingMeterValue) { mSessionEnergyCharged = MakeNullable(chargingMeterValue - mSessionEnergyChargedAtStart); - // TODO call MatterReportingAttributeChangeCallback + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyCharged::Id); } /** @@ -953,5 +1162,5 @@ void EvseSession::UpdateEnergyCharged(int64_t chargingMeterValue) void EvseSession::UpdateEnergyDischarged(int64_t dischargingMeterValue) { mSessionEnergyDischarged = MakeNullable(dischargingMeterValue - mSessionEnergyDischargedAtStart); - // TODO call MatterReportingAttributeChangeCallback + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); } From c7034536d48f8f5db93affe7e5c8b475f9d12ede Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 29 Dec 2023 00:33:20 +0000 Subject: [PATCH 03/52] Added framework for EVSE Test Event triggers --- .../src/EVSEManufacturerImpl.cpp | 35 +++++++++++++++++++ examples/platform/linux/AppMain.cpp | 10 ++++++ examples/platform/linux/BUILD.gn | 9 ++++- src/app/chip_data_model.gni | 7 ++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index a49ae50d96a4c0..a8bdb85c35c84d 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -18,7 +18,9 @@ #include #include +#include +using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; @@ -105,3 +107,36 @@ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_ ChipLogError(AppServer, "Unhandled EVSE Callback type %d", static_cast(cb->type)); } } + +bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) +{ + EnergyEvseTrigger trigger = static_cast(eventTrigger); + + switch (trigger) + { + case EnergyEvseTrigger::kBasicFunctionality: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => Basic Functionality install"); + break; + case EnergyEvseTrigger::kBasicFunctionalityClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => Basic Functionality clear"); + break; + case EnergyEvseTrigger::kEVPluggedIn: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV plugged in"); + break; + case EnergyEvseTrigger::kEVPluggedInClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV unplugged"); + break; + case EnergyEvseTrigger::kEVChargeDemand: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV Charge Demand"); + break; + case EnergyEvseTrigger::kEVChargeDemandClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV Charge NoDemand"); + break; + + default: + + return false; + } + + return true; +} \ No newline at end of file diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 79e3e08edce718..1d2127aad36dba 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -80,6 +80,9 @@ #if CHIP_DEVICE_CONFIG_ENABLE_SMOKE_CO_TRIGGER #include #endif +#if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER +#include +#endif #include #include @@ -552,6 +555,13 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) }; otherDelegate = &smokeCOTestEventTriggerDelegate; #endif +#if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER + static EnergyEvseTestEventTriggerDelegate energyEvseTestEventTriggerDelegate{ + ByteSpan(LinuxDeviceOptions::GetInstance().testEventTriggerEnableKey), otherDelegate + }; + otherDelegate = &energyEvseTestEventTriggerDelegate; +#endif + // For general testing of TestEventTrigger, we have a common "core" event trigger delegate. static SampleTestEventTriggerDelegate testEventTriggerDelegate; VerifyOrDie(testEventTriggerDelegate.Init(ByteSpan(LinuxDeviceOptions::GetInstance().testEventTriggerEnableKey), diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 46229c5f7ed42c..7c2ea169437b21 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -21,6 +21,7 @@ import("${chip_root}/src/tracing/tracing_args.gni") declare_args() { chip_enable_smoke_co_trigger = false + chip_enable_energy_evse_trigger = false } config("app-main-config") { @@ -37,6 +38,10 @@ source_set("smco-test-event-trigger") { sources = [ "${chip_root}/src/app/clusters/smoke-co-alarm-server/SmokeCOTestEventTriggerDelegate.h" ] } +source_set("energy-evse-test-event-trigger") { + sources = [ "${chip_root}/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h" ] +} + source_set("app-main") { defines = [ "ENABLE_TRACING=${matter_enable_tracing_support}" ] sources = [ @@ -94,7 +99,9 @@ source_set("app-main") { ] } - defines += [ "CHIP_DEVICE_CONFIG_ENABLE_SMOKE_CO_TRIGGER=${chip_enable_smoke_co_trigger}" ] + defines += [ "CHIP_DEVICE_CONFIG_ENABLE_SMOKE_CO_TRIGGER=${chip_enable_smoke_co_trigger}", + "CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER=${chip_enable_energy_evse_trigger}" ] + public_configs = [ ":app-main-config" ] } diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index b882c875dc3ac2..835e1467b57e15 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -329,6 +329,13 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/${cluster}.cpp", "${_app_root}/clusters/${cluster}/${cluster}.h", ] + } else if (cluster == "energy-evse-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/${cluster}.h", + "${_app_root}/clusters/${cluster}/EnergyEvseTestEventTriggerDelegate.cpp", + "${_app_root}/clusters/${cluster}/EnergyEvseTestEventTriggerDelegate.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } From bfc9bfb5f02309547422b78bb2c436963f40fdc4 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 29 Dec 2023 00:34:18 +0000 Subject: [PATCH 04/52] Added EnergyEvseTestEventTrigger delegates --- .../EnergyEvseTestEventTriggerDelegate.cpp | 42 ++++++++++++ .../EnergyEvseTestEventTriggerDelegate.h | 66 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.cpp create mode 100644 src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.cpp b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.cpp new file mode 100644 index 00000000000000..78fb87085f3b10 --- /dev/null +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.cpp @@ -0,0 +1,42 @@ +/* + * + * 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 "EnergyEvseTestEventTriggerDelegate.h" + +using namespace chip::app::Clusters::EnergyEvse; + +namespace chip { + +bool EnergyEvseTestEventTriggerDelegate::DoesEnableKeyMatch(const ByteSpan & enableKey) const +{ + return !mEnableKey.empty() && mEnableKey.data_equal(enableKey); +} + +CHIP_ERROR EnergyEvseTestEventTriggerDelegate::HandleEventTrigger(uint64_t eventTrigger) +{ + if (HandleEnergyEvseTestEventTrigger(eventTrigger)) + { + return CHIP_NO_ERROR; + } + if (mOtherDelegate != nullptr) + { + return mOtherDelegate->HandleEventTrigger(eventTrigger); + } + return CHIP_ERROR_INVALID_ARGUMENT; +} + +} // namespace chip diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h new file mode 100644 index 00000000000000..489ca7974eefcb --- /dev/null +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h @@ -0,0 +1,66 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include + +namespace chip { + +enum class EnergyEvseTrigger : uint64_t +{ + // Scenarios + kBasicFunctionality = 0x0099000000000000, // Basic Functionality Test Event | Simulate installation with + // _{A_CIRCUIT_CAPACITY}_=32A and _{A_USER_MAXIMUM_CHARGE_CURRENT}_=32A + kBasicFunctionalityClear = 0x0099000000000001, // Basic Functionality Test Event Clear | End simulation of installation + kEVPluggedIn = 0x0099000000000002, // EV Plugged-in Test Event | Simulate plugging + // the EV into the EVSE using a cable of 63A capacity + kEVPluggedInClear = 0x0099000000000003, // EV Plugged-in Test Event Clear | Simulate unplugging the EV + kEVChargeDemand = 0x0099000000000004, // EV Charge Demand Test Event | Simulate the EV presenting charge demand to the EVSE + kEVChargeDemandClear = 0x0099000000000005, // EV Charge Demand Test Event Clear | Simulate the EV becoming fully charged + +}; + +class EnergyEvseTestEventTriggerDelegate : public TestEventTriggerDelegate +{ +public: + explicit EnergyEvseTestEventTriggerDelegate(const ByteSpan & enableKey, TestEventTriggerDelegate * otherDelegate) : + mEnableKey(enableKey), mOtherDelegate(otherDelegate) + {} + + bool DoesEnableKeyMatch(const ByteSpan & enableKey) const override; + CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override; + +private: + ByteSpan mEnableKey; + TestEventTriggerDelegate * mOtherDelegate; +}; + +} // namespace chip + +/** + * @brief User handler for handling the test event trigger + * + * @note If TestEventTrigger is enabled, it needs to be implemented in the app + * + * @param eventTrigger Event trigger to handle + * + * @retval true on success + * @retval false if error happened + */ +bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger); From 590e63f1ed9890d14c61cd106fb332f6c3247f75 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Fri, 29 Dec 2023 01:06:11 +0000 Subject: [PATCH 05/52] Restyled by whitespace --- .../energy-management-common/src/EVSEManufacturerImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index a8bdb85c35c84d..7b671e2d73e3bf 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -139,4 +139,4 @@ bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) } return true; -} \ No newline at end of file +} From 451428e8ef1605f6da2cf2283cdc1fed69987db3 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Fri, 29 Dec 2023 01:06:14 +0000 Subject: [PATCH 06/52] Restyled by gn --- examples/platform/linux/BUILD.gn | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 7c2ea169437b21..576192ca6cc2f0 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -99,9 +99,10 @@ source_set("app-main") { ] } - defines += [ "CHIP_DEVICE_CONFIG_ENABLE_SMOKE_CO_TRIGGER=${chip_enable_smoke_co_trigger}", - "CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER=${chip_enable_energy_evse_trigger}" ] - + defines += [ + "CHIP_DEVICE_CONFIG_ENABLE_SMOKE_CO_TRIGGER=${chip_enable_smoke_co_trigger}", + "CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER=${chip_enable_energy_evse_trigger}", + ] public_configs = [ ":app-main-config" ] } From 22d25045a50d97aa1c58c99121427f5eb4def81f Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 29 Dec 2023 08:09:40 +0000 Subject: [PATCH 07/52] Added :energy-evse-test-event-trigger to public_deps to see if it resolves build errors --- examples/platform/linux/BUILD.gn | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 576192ca6cc2f0..70605c3a7dee44 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -65,6 +65,7 @@ source_set("app-main") { public_deps = [ ":smco-test-event-trigger", + ":energy-evse-test-event-trigger", "${chip_root}/src/lib", "${chip_root}/src/platform/logging:force_stdio", ] From 85c68edf4b298e89db4075b84de078a77893ce5c Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Fri, 29 Dec 2023 08:10:28 +0000 Subject: [PATCH 08/52] Restyled by gn --- examples/platform/linux/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 70605c3a7dee44..c926a9998e8207 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -64,8 +64,8 @@ source_set("app-main") { ] public_deps = [ - ":smco-test-event-trigger", ":energy-evse-test-event-trigger", + ":smco-test-event-trigger", "${chip_root}/src/lib", "${chip_root}/src/platform/logging:force_stdio", ] From a897decdbf4a8e55ee4d12991cabe7c30b7c34ff Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sat, 30 Dec 2023 13:13:09 +0000 Subject: [PATCH 09/52] Fixed Darwin compile error - do not use else after return --- .../src/EnergyEvseDelegateImpl.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index d54e936581e264..431886b83a64f2 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -315,10 +315,7 @@ bool IsEvPluggedIn(StateEnum state) { return false; } - else - { - return true; - } + return true; } bool IsEvTransferringEnergy(StateEnum state) @@ -327,10 +324,7 @@ bool IsEvTransferringEnergy(StateEnum state) { return true; } - else - { - return false; - } + return false; } /** * @brief Called by EVSE Hardware to indicate if EV is detected From 9ccf8e557e08e85eec0fd7aaac86c535c50f7947 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sun, 31 Dec 2023 23:35:46 +0000 Subject: [PATCH 10/52] Refactored code so that the EvseManufacturer instance could be retrieved for Test Event triggers --- .../include/EVSEManufacturerImpl.h | 17 ++++- .../src/EVSEManufacturerImpl.cpp | 70 ++++++++++++++----- examples/energy-management-app/linux/main.cpp | 11 ++- 3 files changed, 74 insertions(+), 24 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h index 4bb7cfb1dcafbf..01f46a9d717e19 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h @@ -33,15 +33,26 @@ namespace EnergyEvse { class EVSEManufacturer { public: + EVSEManufacturer(EnergyEvseManager * aInstance) { mInstance = aInstance; } + EnergyEvseManager * GetInstance() { return mInstance; } + EnergyEvseDelegate * GetDelegate() + { + if (mInstance) + { + return mInstance->GetDelegate(); + } + return nullptr; + } + /** * @brief Called at start up to apply hardware settings */ - CHIP_ERROR Init(EnergyEvseManager * aInstance); + CHIP_ERROR Init(); /** * @brief Called at shutdown */ - CHIP_ERROR Shutdown(EnergyEvseManager * aInstance); + CHIP_ERROR Shutdown(); /** * @brief Main Callback handler from delegate to user code @@ -49,6 +60,8 @@ class EVSEManufacturer static void ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg); private: + EnergyEvseManager * mInstance; + int64_t mLastChargingEnergyMeter = 0; int64_t mLastDischargingEnergyMeter = 0; }; diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index 7b671e2d73e3bf..682d1b07d77f2c 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -25,12 +25,15 @@ using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; -CHIP_ERROR EVSEManufacturer::Init(EnergyEvseManager * aInstance) +/* Function prototype - this should be implemented somewhere in main.cpp or similar */ +EVSEManufacturer * GetEvseManufacturer(); + +CHIP_ERROR EVSEManufacturer::Init() { /* Manufacturers should modify this to do any custom initialisation */ /* Register callbacks */ - EnergyEvseDelegate * dg = aInstance->GetDelegate(); + EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); if (dg == nullptr) { ChipLogError(AppServer, "Delegate is not initialized"); @@ -39,34 +42,28 @@ CHIP_ERROR EVSEManufacturer::Init(EnergyEvseManager * aInstance) dg->HwRegisterEvseCallbackHandler(ApplicationCallbackHandler, reinterpret_cast(this)); - /* Set the EVSE Hardware Maximum current limit */ // For Manufacturer to specify the hardware capability in mA - dg->HwSetMaxHardwareCurrentLimit(32000); + // dg->HwSetMaxHardwareCurrentLimit(32000); // For Manufacturer to specify the CircuitCapacity (e.g. from DIP switches) - dg->HwSetCircuitCapacity(20000); - - /* For now let's pretend the EV is plugged in, and asking for demand */ - dg->HwSetState(StateEnum::kPluggedInDemand); - dg->HwSetCableAssemblyLimit(63000); + // dg->HwSetCircuitCapacity(20000); - /* For now let's pretend the vehicle ID is set */ - dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_123456789")); - dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_9876543210")); + // For now let's pretend the EV is plugged in, and asking for demand + // dg->HwSetState(StateEnum::kPluggedInDemand); + // dg->HwSetCableAssemblyLimit(63000); - /* This next one will fail because it is too long */ - dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_9876543210TOOOOOOOOOOOOOOOOOOO")); + // For now let's pretend the vehicle ID is set + // dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_123456789")); - /* For now let's pretend the RFID sensor was triggered - send an event */ - uint8_t uid[10] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; - dg->HwSetRFID(ByteSpan(uid)); + // For now let's pretend the RFID sensor was triggered - send an event + // uint8_t uid[10] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; + // dg->HwSetRFID(ByteSpan(uid)); return CHIP_NO_ERROR; } -CHIP_ERROR EVSEManufacturer::Shutdown(EnergyEvseManager * aInstance) +CHIP_ERROR EVSEManufacturer::Shutdown() { - return CHIP_NO_ERROR; } @@ -108,6 +105,35 @@ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_ } } +void SetTestEventTrigger_BasicFunctionality() +{ + EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + + // TODO save the values so we can restore them in the clear event + dg->HwSetCircuitCapacity(32000); + dg->SetUserMaximumChargeCurrent(32000); + dg->HwSetState(StateEnum::kNotPluggedIn); +} +void SetTestEventTrigger_BasicFunctionalityClear() {} + +void SetTestEventTrigger_EVPluggedIn() +{ + EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + + // TODO save the values so we can restore them in the clear event + dg->HwSetState(StateEnum::kPluggedInNoDemand); +} +void SetTestEventTrigger_EVPluggedInClear() {} + +void SetTestEventTrigger_EVChargeDemand() +{ + EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + + // TODO save the values so we can restore them in the clear event + dg->HwSetState(StateEnum::kPluggedInDemand); +} +void SetTestEventTrigger_EVChargeDemandClear() {} + bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) { EnergyEvseTrigger trigger = static_cast(eventTrigger); @@ -116,21 +142,27 @@ bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) { case EnergyEvseTrigger::kBasicFunctionality: ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => Basic Functionality install"); + SetTestEventTrigger_BasicFunctionality(); break; case EnergyEvseTrigger::kBasicFunctionalityClear: ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => Basic Functionality clear"); + SetTestEventTrigger_BasicFunctionalityClear(); break; case EnergyEvseTrigger::kEVPluggedIn: ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV plugged in"); + SetTestEventTrigger_EVPluggedIn(); break; case EnergyEvseTrigger::kEVPluggedInClear: ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV unplugged"); + SetTestEventTrigger_EVPluggedInClear(); break; case EnergyEvseTrigger::kEVChargeDemand: ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV Charge Demand"); + SetTestEventTrigger_EVChargeDemand(); break; case EnergyEvseTrigger::kEVChargeDemandClear: ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV Charge NoDemand"); + SetTestEventTrigger_EVChargeDemandClear(); break; default: diff --git a/examples/energy-management-app/linux/main.cpp b/examples/energy-management-app/linux/main.cpp index dddc6f53d76b20..83cdecbe83db15 100644 --- a/examples/energy-management-app/linux/main.cpp +++ b/examples/energy-management-app/linux/main.cpp @@ -39,6 +39,11 @@ static EnergyEvseDelegate * gDelegate = nullptr; static EnergyEvseManager * gInstance = nullptr; static EVSEManufacturer * gEvseManufacturer = nullptr; +EVSEManufacturer * GetEvseManufacturer() +{ + return gEvseManufacturer; +} + void ApplicationInit() { CHIP_ERROR err; @@ -87,7 +92,7 @@ void ApplicationInit() } /* Now create EVSEManufacturer*/ - gEvseManufacturer = new EVSEManufacturer(); + gEvseManufacturer = new EVSEManufacturer(gInstance); if (gEvseManufacturer == nullptr) { ChipLogError(AppServer, "Failed to allocate memory for EvseManufacturer"); @@ -99,7 +104,7 @@ void ApplicationInit() } /* Call Manufacturer specific init */ - err = gEvseManufacturer->Init(gInstance); + err = gEvseManufacturer->Init(); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "Init failed on gEvseManufacturer"); @@ -118,7 +123,7 @@ void ApplicationShutdown() ChipLogDetail(AppServer, "Energy Management App: ApplicationShutdown()"); /* Shutdown the EVSEManufacturer*/ - gEvseManufacturer->Shutdown(gInstance); + gEvseManufacturer->Shutdown(); /* Shutdown the Instance - deregister attribute & command handler */ gInstance->Shutdown(); From 57cde7a5ef0ef9d28407cba40959e28d5ba8081b Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sun, 31 Dec 2023 23:36:30 +0000 Subject: [PATCH 11/52] Started adding TC_EEVSE_2_2.py --- src/python_testing/TC_EEVSE_2_2.py | 253 +++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 src/python_testing/TC_EEVSE_2_2.py diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py new file mode 100644 index 00000000000000..bca70f873cc7bf --- /dev/null +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -0,0 +1,253 @@ +# +# Copyright (c) 2023 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. + +import datetime +import logging +import time + +import chip.clusters as Clusters +import pytz +from chip.interaction_model import InteractionModelError, Status +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main, TestStep +from mobly import asserts + +logger = logging.getLogger(__name__) + + +class TC_EEVSE_2_2(MatterBaseTest): + async def read_evse_attribute_expect_success(self, endpoint, attribute): + full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) + cluster = Clusters.Objects.EnergyEvse + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + async def write_user_max_charge(self, endpoint, user_max_charge): + result = await self.default_controller.WriteAttribute(self.dut_node_id, + [(endpoint, + Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent(user_max_charge))]) + asserts.assert_equal(result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") + + async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 60000, + min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( + chargingEnabledUntil=charge_until, + minimumChargeCurrent=6000, + maximumChargeCurrent=60000), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + async def send_disable_command(self, endpoint: int = 0, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), endpoint=1) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + def desc_TC_EEVSE_2_2(self) -> str: + """Returns a description of this test""" + return "5.1.3. [TC-EEVSE-2.2] Primary functionality with DUT as Server" + + def pics_TC_EEVSE_2_2(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + # In this case - there is no feature flags needed to run this test case + return None + + def steps_TC_EEVSE_2_2(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event"), + TestStep("3a", "After a few seconds TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + ] + + return steps + + async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000): + try: + await self.send_single_cmd(endpoint=0, + cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( + enableKey, + eventTrigger) + ) + + except InteractionModelError as e: + asserts.fail(f"Unexpected error returned - {e.status}") + + # TC_EEVSE_2_2 tests steps + async def steps_2_check_test_event_triggers_enabled(self): + full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled + cluster = Clusters.Objects.GeneralDiagnostics + test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) + asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") + + async def steps_3_send_test_event_triggers(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000000) + + @async_test_body + async def test_TC_EEVSE_2_2(self): + print_steps = True + + # Part 1 + self.step("1") + + # Part 2 + self.step("2") + await self.steps_2_check_test_event_triggers_enabled() + + # Part 3 + self.step("3") + await self.steps_3_send_test_event_triggers() + self.step("3a") + state = await self.read_evse_attribute_expect_success(endpoint=1, attribute="State") + asserts.assert_equal(state, Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn, + f"Unexpected State value - expected kNotPluggedIn (0), was {state}") + self.step("3b") + self.step("3c") + self.step("3d") + self.print_step("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)") + self.print_step("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)") + self.print_step("3d", "TH reads from the DUT the SessionID attribute") + + # Part 4 + self.print_step("4", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event") + self.print_step("4", "Verify Event EEVSE.S.E00(EVConnected) sent") + + self.print_step("4a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)") + + # Part 5 + self.print_step( + "5", "TH sends command EnableCharging with ChargingEnabledUntil=2 minutes in the future, minimumChargeCurrent=6000, maximumChargeCurrent=60000") + # get epoch time for ChargeUntil variable (2 minutes from now) + utc_time_2_mins = datetime.datetime.now(pytz.utc) + datetime.timedelta(minutes=2) + epoch_time = (utc_time_2_mins - datetime.datetime(2000, 1, 1, tzinfo=pytz.utc)).total_seconds() + await self.send_enable_charge_command(endpoint=1, charge_until=epoch_time, min_charge=6000, max_charge=60000) + + # Part 6 + self.print_step("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event") + self.print_step("6", "Verify Event EEVSE.S.E02(EnergyTransferStarted) sent") + + self.print_step('6a', 'TH reads from the DUT the State attribute. Verify it is 3 (PluggedinCharging)') + current_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='State') + asserts.assert_equal(current_state, 3, f'State should be 3, but is actually {current_state}') + + self.print_step('6b', 'TH reads from the DUT the SupplyState attribute. Verify it is 1 (ChargingEnabled)') + current_supply_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='SupplyState') + asserts.assert_equal(current_supply_state, 1, f'SupplyState should be 1, but is actually {current_supply_state}') + + self.print_step('6c', f'TH reads from the DUT the ChargingEnabledUntil attribute. Verify it is the {epoch_time}') + charge_until_time = await self.read_evse_attribute_expect_success(endpoint=1, attribute='ChargingEnabledUntil') + asserts.assert_equal(charge_until_time, epoch_time, + f'ChargingEnabledUntil should be {epoch_time}, but is actually {charge_until_time}') + + self.print_step('6d', 'TH reads from the DUT the MinimumChargeCurrent attribute. Verify it is the commanded value (6000)') + minimum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MinimumChargeCurrent') + asserts.assert_equal(minimum_charge_value, 6000, + f'MinimumChargeValue should be 6000, but is actually {minimum_charge_value}') + + self.print_step( + '6e', 'TH reads from the DUT the MaximumChargeCurrent attribute. Verify it is the MIN(command value (60000), CircuitCapacity)') + maximum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MinimumChargeCurrent') + circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute='CircuitCapacity') + expected_max_charge = min(6000, circuit_capacity) + asserts.assert_equal(maximum_charge_value, expected_max_charge, + f'MaximumChargeValue should be {expected_max_charge}, but is actually {maximum_charge_value}') + + # Part 7 + self.print_step(7, 'Wait for 2 minutes') + time.sleep(120) + + self.print_step('7a', 'TH reads from the DUT the State attribute. Verify it is 2 (PluggedinDemand)') + current_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='State') + asserts.assert_equal(current_state, 2, f'State should be 2, but is actually {current_state}') + + self.print_step('7b', 'TH reads from the DUT the SupplyState attribute. Verify it is 0 (Disabled)') + current_supply_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='SupplyState') + asserts.assert_equal(current_supply_state, 0, f'SupplyState should be 0, but is actually {current_supply_state}') + + # Part 8 + self.print_step( + 8, 'TH sends command EnableCharging with ChargingEnabledUntil=NULL, minimumChargeCurrent = 6000, maximumChargeCurrent=12000') + await self.send_enable_charge_command(endpoint=1, charge_until=epoch_time, min_charge=6000, max_charge=12000) + + self.print_step('8a', 'TH reads from the DUT the State attribute. Verify it is 3 (PluggedinCharging)') + current_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='State') + asserts.assert_equal(current_state, 3, f'State should be 3, but is actually {current_state}') + + self.print_step('8b', 'TH reads from the DUT the SupplyState attribute. Verify it is 1 (ChargingEnabled)') + current_supply_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='SupplyState') + asserts.assert_equal(current_supply_state, 1, f'SupplyState should be 1, but is actually {current_supply_state}') + + self.print_step('8c', f'TH reads from the DUT the ChargingEnabledUntil attribute. Verify it is the {epoch_time}') + charge_until_time = await self.read_evse_attribute_expect_success(endpoint=1, attribute='ChargingEnabledUntil') + asserts.assert_equal(charge_until_time, epoch_time, + f'ChargingEnabledUntil should be {epoch_time}, but is actually {charge_until_time}') + + self.print_step('8d', 'TH reads from the DUT the MinimumChargeCurrent attribute. Verify it is the commanded value (6000)') + minimum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MinimumChargeCurrent') + asserts.assert_equal(minimum_charge_value, 6000, + f'MinimumChargeValue should be 6000, but is actually {minimum_charge_value}') + + self.print_step( + '8e', 'TH reads from the DUT the MaximumChargeCurrent attribute. Verify it is the MIN(command value (60000), CircuitCapacity)') + maximum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MaximumChargeCurrent') + circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute='CircuitCapacity') + expected_max_charge = min(12000, circuit_capacity) + asserts.assert_equal(maximum_charge_value, expected_max_charge, + f'MaximumChargeValue should be {expected_max_charge}, but is actually {maximum_charge_value}') + + # Part 9 + # This may not work as the optional attribute may not be currently supported. + self.print_step(9, 'If the optional attribute is supported TH writes to the DUT UserMaximumChargeCurrent=6000') + self.write_user_max_charge(1, user_max_charge=6000) + + self.print_step('9a', 'After a few seconds TH reads from the DUT the MaximumChargeCurrent') + time.sleep(3) + maximum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MaximumChargeCurrent') + circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute='CircuitCapacity') + expected_max_charge = min(maximum_charge_value, circuit_capacity) + asserts.assert_equal(maximum_charge_value, expected_max_charge, + f'MaximumChargeValue should be {expected_max_charge}, but is actually {maximum_charge_value}') + + # Part 10 - TODO Requires Test Event Triggers + + # Part 11 - TODO Requires Test Event Triggers + + # Part 12 - TODO Requires Test Event Triggers + + # Part 13 + self.print_step(13, 'TH sends a Disable command') + await self.send_disable_command(endpoint=1) + + self.print_step('13a', 'TH reads from the DUT the State attribute. Verify it is 2 (PluggedinDemand)') + current_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='State') + asserts.assert_equal(current_state, 2, f'State should be 2, but is actually {current_state}') + + self.print_step('13b', 'TH reads from the DUT the SupplyState attribute. Verify it is 0 (Disabled)') + current_supply_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='SupplyState') + asserts.assert_equal(current_supply_state, 0, f'SupplyState should be 0, but is actually {current_supply_state}') + + # Part 14 - TODO Requires Test Event Triggers + + # Part 15 - TODO Requires Test Event Triggers + + # Part 16 - TODO Requires Test Event Triggers + + +if __name__ == "__main__": + default_matter_test_main() From b775fedc73381e25586c99729b5f68b8e0741f80 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Mon, 1 Jan 2024 22:09:43 +0000 Subject: [PATCH 12/52] Updated TC_EEVSE_2_2.py to support test events. Still needs to handle reading of Logged Events and verifying they are correct. --- src/python_testing/TC_EEVSE_2_2.py | 346 ++++++++++++++++++----------- 1 file changed, 220 insertions(+), 126 deletions(-) diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index bca70f873cc7bf..d7968f8e6989e5 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -33,6 +33,14 @@ async def read_evse_attribute_expect_success(self, endpoint, attribute): cluster = Clusters.Objects.EnergyEvse return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + async def check_evse_attribute(self, attribute, expected_value): + value = await self.read_evse_attribute_expect_success(endpoint=1, attribute=attribute) + asserts.assert_equal(value, expected_value, + f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") + + async def get_supported_energy_evse_attributes(self, endpoint): + return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") + async def write_user_max_charge(self, endpoint, user_max_charge): result = await self.default_controller.WriteAttribute(self.dut_node_id, [(endpoint, @@ -74,6 +82,48 @@ def steps_TC_EEVSE_2_2(self) -> list[TestStep]: TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event"), TestStep("3a", "After a few seconds TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)"), + TestStep("3d", "TH reads from the DUT the SessionID attribute"), + TestStep("4", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event. Verify Event EEVSE.S.E00(EVConnected) sent"), + TestStep("4a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), + TestStep("5", "TH sends command EnableCharging with ChargingEnabledUntil=2 minutes in the future, minimumChargeCurrent=6000, maximumChargeCurrent=60000"), + TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), + TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x3 (PluggedInCharging)"), + TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x1 (ChargingEnabled)"), + TestStep("6c", "TH reads from the DUT the ChargingEnabledUntil attribute. Verify value is the commanded value"), + TestStep("6d", "TH reads from the DUT the MinimumChargeCurrent attribute. Verify value is the commanded value (6000)"), + TestStep("6e", "TH reads from the DUT the MaximumChargeCurrent attribute. Verify value is the min(command value (60000), CircuitCapacity)"), + TestStep("7", "Wait 2 minutes. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvseStopped"), + TestStep("7a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), + TestStep("7b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("8", "TH sends command EnableCharging with ChargingEnabledUntil=NULL, minimumChargeCurrent = 6000, maximumChargeCurrent=12000"), + TestStep("8a", "TH reads from the DUT the State attribute. Verify value is 0x03 (PluggedInCharging)"), + TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 1 (ChargingEnabled)"), + TestStep("8c", "TH reads from the DUT the ChargingEnabledUntil attribute. Verify value is the commanded value (NULL)"), + TestStep("8d", "TH reads from the DUT the MinimumChargeCurrent attribute. Verify value is the commanded value (6000)"), + TestStep("8d", "TH reads from the DUT the MaximumChargeCurrent attribute. Verify value is the MIN(command value (60000), CircuitCapacity)"), + TestStep("9", "If the optional attribute is supported TH writes to the DUT UserMaximumChargeCurrent=6000"), + TestStep("9a", "After a few seconds TH reads from the DUT the MaximumChargeCurrent. Verify value is UserMaximumChargeCurrent value (6000)"), + TestStep("10", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped"), + TestStep("10a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), + TestStep("11", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), + TestStep("11a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), + TestStep("12", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped"), + TestStep("12a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), + TestStep("13", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event Clear. Verify Event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("13a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("13b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x01 (ChargingEnabled)"), + TestStep("13c", "TH reads from the DUT the SessionID attribute. Verify value is the same value noted in 5c"), + TestStep("13d", "TH reads from the DUT the SessionDuration attribute. Verify value is greater than 120 (and match the time taken for the tests from step 4 to step 13)"), + TestStep("14", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event. Verify Event EEVSE.S.E00(EVConnected) sent"), + TestStep("14a", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), + TestStep("14b", "TH reads from the DUT the SessionID attribute. Verify value is 1 more than the value noted in 5c"), + TestStep("15", "TH sends command Disable. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvseStopped"), + TestStep("15a", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("16", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear."), + TestStep("17", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event Clear. Verify Event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("18", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event Clear."), ] return steps @@ -90,163 +140,207 @@ async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]) asserts.fail(f"Unexpected error returned - {e.status}") # TC_EEVSE_2_2 tests steps - async def steps_2_check_test_event_triggers_enabled(self): + async def check_test_event_triggers_enabled(self): full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled cluster = Clusters.Objects.GeneralDiagnostics test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") - async def steps_3_send_test_event_triggers(self): + async def send_test_event_trigger_basic(self): await self.send_test_event_triggers(eventTrigger=0x0099000000000000) + async def send_test_event_trigger_basic_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000001) + + async def send_test_event_trigger_pluggedin(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000002) + + async def send_test_event_trigger_pluggedin_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000003) + + async def send_test_event_trigger_charge_demand(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000004) + + async def send_test_event_trigger_charge_demand_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000005) + @async_test_body async def test_TC_EEVSE_2_2(self): - print_steps = True - - # Part 1 self.step("1") + # Commission DUT - already done - # Part 2 self.step("2") - await self.steps_2_check_test_event_triggers_enabled() + await self.check_test_event_triggers_enabled() - # Part 3 self.step("3") - await self.steps_3_send_test_event_triggers() + await self.send_test_event_trigger_basic() + + # After a few seconds... + time.sleep(3) + self.step("3a") - state = await self.read_evse_attribute_expect_success(endpoint=1, attribute="State") - asserts.assert_equal(state, Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn, - f"Unexpected State value - expected kNotPluggedIn (0), was {state}") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + self.step("3b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + self.step("3c") + await self.check_evse_attribute("FaultState", Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError) + self.step("3d") - self.print_step("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)") - self.print_step("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)") - self.print_step("3d", "TH reads from the DUT the SessionID attribute") + # Save Session ID - it may be NULL at this point + session_id = await self.read_evse_attribute_expect_success(endpoint=1, attribute="SessionID") - # Part 4 - self.print_step("4", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event") - self.print_step("4", "Verify Event EEVSE.S.E00(EVConnected) sent") + self.step("4") + await self.send_test_event_trigger_pluggedin() + # TODO check PluggedIn Event - self.print_step("4a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)") + self.step("4a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) - # Part 5 - self.print_step( - "5", "TH sends command EnableCharging with ChargingEnabledUntil=2 minutes in the future, minimumChargeCurrent=6000, maximumChargeCurrent=60000") + self.step("5") + charging_duration = 5 # TODO test plan spec says 120s - reduced for now + min_charge_current = 6000 + max_charge_current = 60000 # get epoch time for ChargeUntil variable (2 minutes from now) - utc_time_2_mins = datetime.datetime.now(pytz.utc) + datetime.timedelta(minutes=2) - epoch_time = (utc_time_2_mins - datetime.datetime(2000, 1, 1, tzinfo=pytz.utc)).total_seconds() - await self.send_enable_charge_command(endpoint=1, charge_until=epoch_time, min_charge=6000, max_charge=60000) - - # Part 6 - self.print_step("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event") - self.print_step("6", "Verify Event EEVSE.S.E02(EnergyTransferStarted) sent") - - self.print_step('6a', 'TH reads from the DUT the State attribute. Verify it is 3 (PluggedinCharging)') - current_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='State') - asserts.assert_equal(current_state, 3, f'State should be 3, but is actually {current_state}') - - self.print_step('6b', 'TH reads from the DUT the SupplyState attribute. Verify it is 1 (ChargingEnabled)') - current_supply_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='SupplyState') - asserts.assert_equal(current_supply_state, 1, f'SupplyState should be 1, but is actually {current_supply_state}') - - self.print_step('6c', f'TH reads from the DUT the ChargingEnabledUntil attribute. Verify it is the {epoch_time}') - charge_until_time = await self.read_evse_attribute_expect_success(endpoint=1, attribute='ChargingEnabledUntil') - asserts.assert_equal(charge_until_time, epoch_time, - f'ChargingEnabledUntil should be {epoch_time}, but is actually {charge_until_time}') - - self.print_step('6d', 'TH reads from the DUT the MinimumChargeCurrent attribute. Verify it is the commanded value (6000)') - minimum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MinimumChargeCurrent') - asserts.assert_equal(minimum_charge_value, 6000, - f'MinimumChargeValue should be 6000, but is actually {minimum_charge_value}') - - self.print_step( - '6e', 'TH reads from the DUT the MaximumChargeCurrent attribute. Verify it is the MIN(command value (60000), CircuitCapacity)') - maximum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MinimumChargeCurrent') - circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute='CircuitCapacity') - expected_max_charge = min(6000, circuit_capacity) - asserts.assert_equal(maximum_charge_value, expected_max_charge, - f'MaximumChargeValue should be {expected_max_charge}, but is actually {maximum_charge_value}') - - # Part 7 - self.print_step(7, 'Wait for 2 minutes') - time.sleep(120) - - self.print_step('7a', 'TH reads from the DUT the State attribute. Verify it is 2 (PluggedinDemand)') - current_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='State') - asserts.assert_equal(current_state, 2, f'State should be 2, but is actually {current_state}') - - self.print_step('7b', 'TH reads from the DUT the SupplyState attribute. Verify it is 0 (Disabled)') - current_supply_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='SupplyState') - asserts.assert_equal(current_supply_state, 0, f'SupplyState should be 0, but is actually {current_supply_state}') - - # Part 8 - self.print_step( - 8, 'TH sends command EnableCharging with ChargingEnabledUntil=NULL, minimumChargeCurrent = 6000, maximumChargeCurrent=12000') - await self.send_enable_charge_command(endpoint=1, charge_until=epoch_time, min_charge=6000, max_charge=12000) - - self.print_step('8a', 'TH reads from the DUT the State attribute. Verify it is 3 (PluggedinCharging)') - current_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='State') - asserts.assert_equal(current_state, 3, f'State should be 3, but is actually {current_state}') - - self.print_step('8b', 'TH reads from the DUT the SupplyState attribute. Verify it is 1 (ChargingEnabled)') - current_supply_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='SupplyState') - asserts.assert_equal(current_supply_state, 1, f'SupplyState should be 1, but is actually {current_supply_state}') - - self.print_step('8c', f'TH reads from the DUT the ChargingEnabledUntil attribute. Verify it is the {epoch_time}') - charge_until_time = await self.read_evse_attribute_expect_success(endpoint=1, attribute='ChargingEnabledUntil') - asserts.assert_equal(charge_until_time, epoch_time, - f'ChargingEnabledUntil should be {epoch_time}, but is actually {charge_until_time}') - - self.print_step('8d', 'TH reads from the DUT the MinimumChargeCurrent attribute. Verify it is the commanded value (6000)') - minimum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MinimumChargeCurrent') - asserts.assert_equal(minimum_charge_value, 6000, - f'MinimumChargeValue should be 6000, but is actually {minimum_charge_value}') - - self.print_step( - '8e', 'TH reads from the DUT the MaximumChargeCurrent attribute. Verify it is the MIN(command value (60000), CircuitCapacity)') - maximum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MaximumChargeCurrent') - circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute='CircuitCapacity') - expected_max_charge = min(12000, circuit_capacity) - asserts.assert_equal(maximum_charge_value, expected_max_charge, - f'MaximumChargeValue should be {expected_max_charge}, but is actually {maximum_charge_value}') - - # Part 9 - # This may not work as the optional attribute may not be currently supported. - self.print_step(9, 'If the optional attribute is supported TH writes to the DUT UserMaximumChargeCurrent=6000') - self.write_user_max_charge(1, user_max_charge=6000) - - self.print_step('9a', 'After a few seconds TH reads from the DUT the MaximumChargeCurrent') - time.sleep(3) - maximum_charge_value = await self.read_evse_attribute_expect_success(endpoint=1, attribute='MaximumChargeCurrent') - circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute='CircuitCapacity') - expected_max_charge = min(maximum_charge_value, circuit_capacity) - asserts.assert_equal(maximum_charge_value, expected_max_charge, - f'MaximumChargeValue should be {expected_max_charge}, but is actually {maximum_charge_value}') + utc_time_charging_end = datetime.datetime.now(pytz.utc) + datetime.timedelta(seconds=charging_duration) + epoch_time = (utc_time_charging_end - datetime.datetime(2000, 1, 1, tzinfo=pytz.utc)).total_seconds() + await self.send_enable_charge_command(endpoint=1, charge_until=epoch_time, min_charge=min_charge_current, max_charge=max_charge_current) + + self.step("6") + await self.send_test_event_trigger_charge_demand() + # TODO check EnergyTransferStarted Event + + self.step("6a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("6b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("6c") + await self.check_evse_attribute("ChargingEnabledUntil", epoch_time) + + self.step("6d") + await self.check_evse_attribute("MinimumChargeCurrent", min_charge_current) + + self.step("6e") + circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute="CircuitCapacity") + expected_max_charge = min(max_charge_current, circuit_capacity) + await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) + + self.step("7") + # Sleep for the charging duration plus a couple of seconds to check it has stopped + time.sleep(charging_duration + 2) + # TODO check EnergyTransferredStoped (EvseStopped) + + self.step("7a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand) + + self.step("7b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("8") + charge_until = None + min_charge_current = 6000 + max_charge_current = 12000 + + await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + + self.step("8a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("8b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("8c") + await self.check_evse_attribute("ChargingEnabledUntil", charge_until) + + self.step("8d") + await self.check_evse_attribute("MinimumChargeCurrent", min_charge_current) + + self.step("8e") + circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute="CircuitCapacity") + expected_max_charge = min(max_charge_current, circuit_capacity) + await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) + + self.step("9") + # This will only work if the optional UserMaximumChargeCurrent attribute is supported + if Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent.attribute_id in self.get_supported_energy_evse_attributes(): + logging.info("UserMaximumChargeCurrent is supported...") + user_max_charge_current = 6000 + self.write_user_max_charge(1, user_max_charge_current) + + self.step("9a") + time.sleep(3) + + expected_max_charge = min(user_max_charge_current, circuit_capacity) + await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) + else: + logging.info("UserMaximumChargeCurrent is NOT supported... skipping.") + + self.step("10") + await self.send_test_event_trigger_charge_demand_clear() + # TODO Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped + + self.step("10a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) + + self.step("11") + await self.send_test_event_trigger_charge_demand() + # TODO Verify Event EEVSE.S.E03(EnergyTransferStarted) sent + + self.step("12") + await self.send_test_event_trigger_charge_demand_clear() + # TODO Verify Event EEVSE.S.E03(EnergyTransferStopped with reason EvStopped) sent + + self.step("12a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) + + self.step("13") + await self.send_test_event_trigger_pluggedin_clear() + # TODO Verify EVNotDetected sent + + self.step("13a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("13b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("13c") + await self.check_evse_attribute("SessionID", session_id) - # Part 10 - TODO Requires Test Event Triggers + self.step("13d") + session_duration = await self.read_evse_attribute_expect_success(endpoint=1, attribute="SessionDuration") + asserts.assert_greater_equal(session_duration, charging_duration, + f"Unexpected 'SessionDuration' value - expected >= {charging_duration}, was {session_duration}") - # Part 11 - TODO Requires Test Event Triggers + self.step("14") + await self.send_test_event_trigger_pluggedin() + # TODO check PluggedIn Event - # Part 12 - TODO Requires Test Event Triggers + self.step("14a") + await self.send_test_event_trigger_charge_demand() + # TODO check EnergyTransferStarted Event - # Part 13 - self.print_step(13, 'TH sends a Disable command') - await self.send_disable_command(endpoint=1) + self.step("14b") + await self.check_evse_attribute("SessionID", session_id + 1) - self.print_step('13a', 'TH reads from the DUT the State attribute. Verify it is 2 (PluggedinDemand)') - current_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='State') - asserts.assert_equal(current_state, 2, f'State should be 2, but is actually {current_state}') + self.step("15") + await self.send_disable_command() + # TODO Verify Event EEVSE.S.E03(EnergyTransferStopped with reason EvseStopped) sent - self.print_step('13b', 'TH reads from the DUT the SupplyState attribute. Verify it is 0 (Disabled)') - current_supply_state = await self.read_evse_attribute_expect_success(endpoint=1, attribute='SupplyState') - asserts.assert_equal(current_supply_state, 0, f'SupplyState should be 0, but is actually {current_supply_state}') + self.step("15a") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) - # Part 14 - TODO Requires Test Event Triggers + self.step("16") + await self.send_test_event_trigger_charge_demand_clear() - # Part 15 - TODO Requires Test Event Triggers + self.step("17") + await self.send_test_event_trigger_pluggedin_clear() + # TODO Verify EVNotDetected sent - # Part 16 - TODO Requires Test Event Triggers + self.step("18") + await self.send_test_event_trigger_basic_clear() if __name__ == "__main__": From 80756576cabb930f63c8ca6ecde97a4eb521046e Mon Sep 17 00:00:00 2001 From: James Harrow Date: Mon, 1 Jan 2024 22:10:57 +0000 Subject: [PATCH 13/52] Refactored Handling of TestEvents to allow clear, and better error handling. --- .../include/EnergyEvseDelegateImpl.h | 1 + .../src/EVSEManufacturerImpl.cpp | 56 ++++++++++++++++--- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index c7be153da04fd0..2be194e23640f7 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -128,6 +128,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate Status HwSetCircuitCapacity(int64_t currentmA); Status HwSetCableAssemblyLimit(int64_t currentmA); Status HwSetState(StateEnum state); + StateEnum HwGetState() { return mHwState; }; Status HwSetFault(FaultStateEnum fault); Status HwSetRFID(ByteSpan uid); Status HwSetVehicleID(const CharSpan & vehID); diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index 682d1b07d77f2c..1bde91ac89dd83 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -105,34 +105,72 @@ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_ } } +struct EVSETestEventSaveData +{ + int64_t mOldCircuitCapacity; + int64_t mOldUserMaximumChargeCurrent; + StateEnum mOldHwStateBasic; /* For storing hwState before Basic Func event */ + StateEnum mOldHwStatePluggedIn; /* For storing hwState before PluggedIn event */ + StateEnum mOldHwStatePluggedInDemand; /* For storing hwState before PluggedInDemand event */ +}; + +static EVSETestEventSaveData sEVSETestEventSaveData; + +EnergyEvseDelegate * GetEvseDelegate() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); + EnergyEvseDelegate * dg = mn->GetDelegate(); + VerifyOrDieWithMsg(dg != nullptr, AppServer, "EVSE Delegate is null"); + + return dg; +} + void SetTestEventTrigger_BasicFunctionality() { - EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + EnergyEvseDelegate * dg = GetEvseDelegate(); - // TODO save the values so we can restore them in the clear event + sEVSETestEventSaveData.mOldCircuitCapacity = dg->GetCircuitCapacity(); + sEVSETestEventSaveData.mOldUserMaximumChargeCurrent = dg->GetUserMaximumChargeCurrent(); + sEVSETestEventSaveData.mOldHwStateBasic = dg->HwGetState(); dg->HwSetCircuitCapacity(32000); dg->SetUserMaximumChargeCurrent(32000); dg->HwSetState(StateEnum::kNotPluggedIn); } -void SetTestEventTrigger_BasicFunctionalityClear() {} +void SetTestEventTrigger_BasicFunctionalityClear() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + dg->HwSetCircuitCapacity(sEVSETestEventSaveData.mOldCircuitCapacity); + dg->SetUserMaximumChargeCurrent(sEVSETestEventSaveData.mOldUserMaximumChargeCurrent); + dg->HwSetState(sEVSETestEventSaveData.mOldHwStateBasic); +} void SetTestEventTrigger_EVPluggedIn() { - EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + EnergyEvseDelegate * dg = GetEvseDelegate(); - // TODO save the values so we can restore them in the clear event + sEVSETestEventSaveData.mOldHwStatePluggedIn = dg->HwGetState(); dg->HwSetState(StateEnum::kPluggedInNoDemand); } -void SetTestEventTrigger_EVPluggedInClear() {} +void SetTestEventTrigger_EVPluggedInClear() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + dg->HwSetState(sEVSETestEventSaveData.mOldHwStatePluggedIn); +} void SetTestEventTrigger_EVChargeDemand() { - EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + EnergyEvseDelegate * dg = GetEvseDelegate(); - // TODO save the values so we can restore them in the clear event + sEVSETestEventSaveData.mOldHwStatePluggedInDemand = dg->HwGetState(); dg->HwSetState(StateEnum::kPluggedInDemand); } -void SetTestEventTrigger_EVChargeDemandClear() {} +void SetTestEventTrigger_EVChargeDemandClear() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetState(sEVSETestEventSaveData.mOldHwStatePluggedInDemand); +} bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) { From 8631c077ea8612a8b83a27d1d95095276eb98cf6 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Tue, 2 Jan 2024 01:07:17 +0000 Subject: [PATCH 14/52] Refactored state handling by decomposing into state machine events where similar functions are performed based on state transition. Fixed TC chargingEnabledUntil cast to int. Note gets to step 6e --- .../include/EnergyEvseDelegateImpl.h | 26 ++ .../src/EnergyEvseDelegateImpl.cpp | 370 ++++++++++++------ src/python_testing/TC_EEVSE_2_2.py | 2 +- 3 files changed, 287 insertions(+), 111 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 2be194e23640f7..2c3fcf8c58d5ac 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -31,6 +31,20 @@ namespace app { namespace Clusters { namespace EnergyEvse { +/* Local state machine Events to allow simpler handling of state transitions */ +enum EVSEStateMachineEvent +{ + EVPluggedInEvent, /* EV has been plugged in */ + EVNotDetectedEvent, /* EV has been unplugged or detected as not connected */ + EVNoDemandEvent, /* EV has stopped asking for demand */ + EVDemandEvent, /* EV has asked for demand*/ + ChargingEnabledEvent, /* Charging has been enabled */ + DischargingEnabledEvent, /* Discharging has been enabled */ + DisabledEvent, /* EVSE has been disabled */ + FaultRaised, /* Fault has been raised */ + FaultCleared, /* Fault has been cleared */ +}; + /** * Helper class to handle all of the session related info */ @@ -215,6 +229,18 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate Status NotifyApplicationStateChange(); Status GetEVSEEnergyMeterValue(ChargingDischargingType meterType, int64_t & aMeterValue); + /* Local State machine handling */ + Status HandleStateMachineEvent(EVSEStateMachineEvent event); + Status HandleEVPluggedInEvent(); + Status HandleEVNotDetectedEvent(); + Status HandleEVNoDemandEvent(); + Status HandleEVDemandEvent(); + Status HandleChargingEnabledEvent(); + Status HandleDischargingEnabledEvent(); + Status HandleDisabledEvent(); + Status HandleFaultRaised(); + Status HandleFaultCleared(); + /** * @brief Helper function to work out the charge limit based on conditions and settings */ diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 431886b83a64f2..640336e8fdb373 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -48,30 +48,6 @@ Status EnergyEvseDelegate::Disable() { ChipLogProgress(AppServer, "EnergyEvseDelegate::Disable()"); - /* Update State */ - switch (mHwState) - { - case StateEnum::kNotPluggedIn: - SetState(StateEnum::kNotPluggedIn); - break; - - case StateEnum::kPluggedInNoDemand: - SetState(StateEnum::kPluggedInNoDemand); - break; - - case StateEnum::kPluggedInDemand: - SetState(StateEnum::kPluggedInDemand); - break; - - default: - ChipLogError(AppServer, "Unexpected EVSE hardware state"); - SetState(StateEnum::kFault); - break; - } - - /* update SupplyState */ - SetSupplyState(SupplyStateEnum::kDisabled); - /* update ChargingEnabledUntil & DischargingEnabledUntil to show 0 */ SetChargingEnabledUntil(0); SetDischargingEnabledUntil(0); @@ -83,10 +59,7 @@ Status EnergyEvseDelegate::Disable() /* update MaximumDischargeCurrent to 0 */ SetMaximumDischargeCurrent(0); - NotifyApplicationStateChange(); - // TODO: Generate events - - return Status::Success; + return HandleStateMachineEvent(EVSEStateMachineEvent::DisabledEvent); } /** @@ -128,51 +101,19 @@ Status EnergyEvseDelegate::EnableCharging(const DataModel::Nullable & { /* check chargingEnabledUntil is in the future */ ChipLogError(AppServer, "Charging enabled until: %lu", static_cast(chargingEnabledUntil.Value())); - // TODO + SetChargingEnabledUntil(chargingEnabledUntil.Value()); + // TODO start a timer to disable charging later // if (checkChargingEnabled) } - /* Check current state isn't already enabled */ - - /* If charging is already enabled, check that the parameters may have - changed, these may override an existing charging command */ - switch (mHwState) - { - case StateEnum::kNotPluggedIn: - // TODO handle errors here - SetState(StateEnum::kNotPluggedIn); - break; - - case StateEnum::kPluggedInNoDemand: - // TODO handle errors here - // TODO REFACTOR per Andrei's comment in PR30857 - can we collapse this switch statement? - SetState(StateEnum::kPluggedInNoDemand); - break; - - case StateEnum::kPluggedInDemand: - /* If the EVSE is asking for demand then enable charging */ - SetState(StateEnum::kPluggedInCharging); - break; - - default: - ChipLogError(AppServer, "Unexpected EVSE hardware state"); - SetState(StateEnum::kFault); - break; - } - - /* update SupplyState to say that charging is now enabled */ - SetSupplyState(SupplyStateEnum::kChargingEnabled); - /* If it looks ok, store the min & max charging current */ mMaximumChargingCurrentLimitFromCommand = maximumChargeCurrent; SetMinimumChargeCurrent(minimumChargeCurrent); // TODO persist these to KVS - // TODO: Generate events - - NotifyApplicationStateChange(); + ComputeMaxChargeCurrentLimit(); - return this->ComputeMaxChargeCurrentLimit(); + return HandleStateMachineEvent(EVSEStateMachineEvent::ChargingEnabledEvent); } /** @@ -186,14 +127,10 @@ Status EnergyEvseDelegate::EnableDischarging(const DataModel::Nullable { ChipLogProgress(AppServer, "EnergyEvseDelegate::EnableDischarging() called."); - /* update SupplyState */ - SetSupplyState(SupplyStateEnum::kDischargingEnabled); + // TODO save the maxDischarging Current + // TODO Do something with timestamp - // TODO: Generate events - - NotifyApplicationStateChange(); - - return Status::Success; + return HandleStateMachineEvent(EVSEStateMachineEvent::DischargingEnabledEvent); } /** @@ -209,10 +146,6 @@ Status EnergyEvseDelegate::StartDiagnostics() // TODO: Generate events - // TODO: Notify Application to implement Diagnostics - - NotifyApplicationStateChange(); - return Status::Success; } @@ -344,54 +277,47 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) switch (newState) { case StateEnum::kNotPluggedIn: - if (IsEvPluggedIn(mState)) + if (mHwState == StateEnum::kPluggedInNoDemand || mHwState == StateEnum::kPluggedInDemand) { - /* EV was plugged in, but no longer is */ - mSession.RecalculateSessionDuration(); - if (IsEvTransferringEnergy(mState)) - { - /* - * EV was transferring current - unusual to get to this case without - * first having the state set to kPluggedInNoDemand or kPluggedInDemand - */ - mSession.RecalculateSessionDuration(); - SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kOther); - } - - SendEVNotDetectedEvent(); - SetState(newState); + /* EVSE has been unplugged now */ + HandleStateMachineEvent(EVSEStateMachineEvent::EVNotDetectedEvent); } break; - case StateEnum::kPluggedInNoDemand: /* deliberate fall-thru */ + case StateEnum::kPluggedInNoDemand: + if (mHwState == StateEnum::kNotPluggedIn) + { + /* EV was unplugged, now is plugged in */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVPluggedInEvent); + } + else if (mHwState == StateEnum::kPluggedInDemand) + { + /* EV was plugged in and wanted demand, now doesn't want demand */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent); + } + break; case StateEnum::kPluggedInDemand: - if (IsEvPluggedIn(mState)) + if (mHwState == StateEnum::kNotPluggedIn) { - /* EV was already plugged in before */ - if (IsEvTransferringEnergy(mState)) - { - mSession.RecalculateSessionDuration(); - SendEnergyTransferStoppedEvent(newState == StateEnum::kPluggedInNoDemand - ? EnergyTransferStoppedReasonEnum::kEVStopped - : EnergyTransferStoppedReasonEnum::kEVSEStopped); - } - SetState(newState); + /* EV was unplugged, now is plugged in and wants demand */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVPluggedInEvent); + HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent); } - else + else if (mHwState == StateEnum::kPluggedInNoDemand) { - /* EV was not plugged in - start a new session */ - // TODO get energy meter readings - mSession.StartSession(0, 0); - SendEVConnectedEvent(); - - /* If*/ - - SetState(newState); + /* EV was plugged in and didn't want demand, now does want demand */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent); } break; default: /* All other states should be managed by the Delegate */ + ChipLogError(AppServer, "HwSetState received invalid enum from caller"); + return Status::Failure; break; } @@ -493,6 +419,228 @@ Status EnergyEvseDelegate::HwSetVehicleID(const CharSpan & newValue) * 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() +{ + /* check if we are already plugged in or not */ + if (mState == StateEnum::kNotPluggedIn) + { + /* EV was not plugged in - start a new session */ + // TODO get energy meter readings + mSession.StartSession(0, 0); + SendEVConnectedEvent(); + + /* Set the state to either PluggedInNoDemand or PluggedInDemand as indicated by mHwState */ + SetState(mHwState); + } + // else we are already plugged in - ignore + return Status::Success; +} + +Status EnergyEvseDelegate::HandleEVNotDetectedEvent() +{ + if (mState == StateEnum::kPluggedInCharging || mState == 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); + } + + SendEVNotDetectedEvent(); + SetState(StateEnum::kNotPluggedIn); + return Status::Success; +} + +Status EnergyEvseDelegate::HandleEVNoDemandEvent() +{ + if (mState == StateEnum::kPluggedInCharging || mState == StateEnum::kPluggedInDischarging) + { + /* + * EV was transferring current - EV decided to stop + */ + mSession.RecalculateSessionDuration(); + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVStopped); + } + /* We must still be plugged in to get here - so no need to check if we are plugged in! */ + SetState(StateEnum::kPluggedInNoDemand); + return Status::Success; +} +Status EnergyEvseDelegate::HandleEVDemandEvent() +{ + /* Check to see if the supply is enabled for charging / discharging*/ + switch (mSupplyState) + { + case SupplyStateEnum::kChargingEnabled: + SetState(StateEnum::kPluggedInCharging); + SendEnergyTransferStartedEvent(); + break; + case SupplyStateEnum::kDischargingEnabled: + SetState(StateEnum::kPluggedInDischarging); + 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 */ + SetState(StateEnum::kPluggedInDemand); + break; + default: + break; + } + return Status::Success; +} + +Status EnergyEvseDelegate::HandleChargingEnabledEvent() +{ + /* Check there is no Fault or Diagnostics condition */ + // TODO -! + + /* update SupplyState to say that charging is now enabled */ + SetSupplyState(SupplyStateEnum::kChargingEnabled); + + switch (mState) + { + case StateEnum::kNotPluggedIn: + case StateEnum::kPluggedInNoDemand: + break; + case StateEnum::kPluggedInDemand: + SetState(StateEnum::kPluggedInCharging); + SendEnergyTransferStartedEvent(); + break; + case StateEnum::kPluggedInCharging: + break; + case StateEnum::kPluggedInDischarging: + /* Switched from discharging to charging */ + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped); + + SetState(StateEnum::kPluggedInCharging); + SendEnergyTransferStartedEvent(); + break; + default: + break; + } + return Status::Success; +} +Status EnergyEvseDelegate::HandleDischargingEnabledEvent() +{ + /* Check there is no Fault or Diagnostics condition */ + // TODO -! + + /* update SupplyState to say that charging is now enabled */ + SetSupplyState(SupplyStateEnum::kDischargingEnabled); + + switch (mState) + { + case StateEnum::kNotPluggedIn: + case StateEnum::kPluggedInNoDemand: + break; + case StateEnum::kPluggedInDemand: + SetState(StateEnum::kPluggedInDischarging); + SendEnergyTransferStartedEvent(); + break; + case StateEnum::kPluggedInCharging: + /* Switched from charging to discharging */ + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped); + + SetState(StateEnum::kPluggedInDischarging); + SendEnergyTransferStartedEvent(); + break; + case StateEnum::kPluggedInDischarging: + default: + break; + } + return Status::Success; +} +Status EnergyEvseDelegate::HandleDisabledEvent() +{ + /* Check there is no Fault or Diagnostics condition */ + // TODO -! + + /* update SupplyState to say that charging is now enabled */ + SetSupplyState(SupplyStateEnum::kDisabled); + + switch (mState) + { + case StateEnum::kNotPluggedIn: + case StateEnum::kPluggedInNoDemand: + case StateEnum::kPluggedInDemand: + break; + case StateEnum::kPluggedInCharging: + case StateEnum::kPluggedInDischarging: + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped); + SetState(mHwState); + break; + default: + break; + } + + return Status::Success; +} + +Status EnergyEvseDelegate::HandleFaultRaised() +{ + return Status::Success; +} +Status EnergyEvseDelegate::HandleFaultCleared() +{ + return Status::Success; +} + /** * @brief Called to compute the safe charging current limit * @@ -729,6 +877,7 @@ CHIP_ERROR EnergyEvseDelegate::SetState(StateEnum newValue) { ChipLogDetail(AppServer, "State updated to %d", static_cast(mState)); MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, State::Id); + NotifyApplicationStateChange(); } return CHIP_NO_ERROR; @@ -754,6 +903,7 @@ CHIP_ERROR EnergyEvseDelegate::SetSupplyState(SupplyStateEnum newValue) { ChipLogDetail(AppServer, "SupplyState updated to %d", static_cast(mSupplyState)); MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SupplyState::Id); + NotifyApplicationStateChange(); } return CHIP_NO_ERROR; } diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index d7968f8e6989e5..72eb26ad9b1372 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -204,7 +204,7 @@ async def test_TC_EEVSE_2_2(self): max_charge_current = 60000 # get epoch time for ChargeUntil variable (2 minutes from now) utc_time_charging_end = datetime.datetime.now(pytz.utc) + datetime.timedelta(seconds=charging_duration) - epoch_time = (utc_time_charging_end - datetime.datetime(2000, 1, 1, tzinfo=pytz.utc)).total_seconds() + epoch_time = int((utc_time_charging_end - datetime.datetime(2000, 1, 1, tzinfo=pytz.utc)).total_seconds()) await self.send_enable_charge_command(endpoint=1, charge_until=epoch_time, min_charge=min_charge_current, max_charge=max_charge_current) self.step("6") From a5774dedbbeac63d4b8bb4295ff15a500281b37d Mon Sep 17 00:00:00 2001 From: James Harrow Date: Tue, 2 Jan 2024 21:05:31 +0000 Subject: [PATCH 15/52] Fixed step 6e caused by not setting the cable limit / maxHardwareCurrentLimit in test events --- .../include/EnergyEvseDelegateImpl.h | 2 ++ .../src/EVSEManufacturerImpl.cpp | 12 +++++++++++- .../src/EnergyEvseDelegateImpl.cpp | 12 +++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 2c3fcf8c58d5ac..697e9ed6811ee9 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -139,8 +139,10 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate // ----------------------------------------------------------------- // Internal API to allow an EVSE to change its internal state etc Status HwSetMaxHardwareCurrentLimit(int64_t currentmA); + int64_t HwGetMaxHardwareCurrentLimit() { return mMaxHardwareCurrentLimit; } Status HwSetCircuitCapacity(int64_t currentmA); Status HwSetCableAssemblyLimit(int64_t currentmA); + int64_t HwGetCableAssemblyLimit() { return mCableAssemblyCurrentLimit; } Status HwSetState(StateEnum state); StateEnum HwGetState() { return mHwState; }; Status HwSetFault(FaultStateEnum fault); diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index 1bde91ac89dd83..8d4279601bd92a 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -107,8 +107,10 @@ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_ struct EVSETestEventSaveData { + int64_t mOldMaxHardwareCurrentLimit; int64_t mOldCircuitCapacity; int64_t mOldUserMaximumChargeCurrent; + int64_t mOldCableAssemblyLimit; StateEnum mOldHwStateBasic; /* For storing hwState before Basic Func event */ StateEnum mOldHwStatePluggedIn; /* For storing hwState before PluggedIn event */ StateEnum mOldHwStatePluggedInDemand; /* For storing hwState before PluggedInDemand event */ @@ -130,9 +132,12 @@ void SetTestEventTrigger_BasicFunctionality() { EnergyEvseDelegate * dg = GetEvseDelegate(); + sEVSETestEventSaveData.mOldMaxHardwareCurrentLimit = dg->HwGetMaxHardwareCurrentLimit(); sEVSETestEventSaveData.mOldCircuitCapacity = dg->GetCircuitCapacity(); sEVSETestEventSaveData.mOldUserMaximumChargeCurrent = dg->GetUserMaximumChargeCurrent(); sEVSETestEventSaveData.mOldHwStateBasic = dg->HwGetState(); + + dg->HwSetMaxHardwareCurrentLimit(32000); dg->HwSetCircuitCapacity(32000); dg->SetUserMaximumChargeCurrent(32000); dg->HwSetState(StateEnum::kNotPluggedIn); @@ -141,6 +146,7 @@ void SetTestEventTrigger_BasicFunctionalityClear() { EnergyEvseDelegate * dg = GetEvseDelegate(); + dg->HwSetMaxHardwareCurrentLimit(sEVSETestEventSaveData.mOldMaxHardwareCurrentLimit); dg->HwSetCircuitCapacity(sEVSETestEventSaveData.mOldCircuitCapacity); dg->SetUserMaximumChargeCurrent(sEVSETestEventSaveData.mOldUserMaximumChargeCurrent); dg->HwSetState(sEVSETestEventSaveData.mOldHwStateBasic); @@ -149,12 +155,16 @@ void SetTestEventTrigger_EVPluggedIn() { EnergyEvseDelegate * dg = GetEvseDelegate(); - sEVSETestEventSaveData.mOldHwStatePluggedIn = dg->HwGetState(); + sEVSETestEventSaveData.mOldCableAssemblyLimit = dg->HwGetCableAssemblyLimit(); + sEVSETestEventSaveData.mOldHwStatePluggedIn = dg->HwGetState(); + + dg->HwSetCableAssemblyLimit(63000); dg->HwSetState(StateEnum::kPluggedInNoDemand); } void SetTestEventTrigger_EVPluggedInClear() { EnergyEvseDelegate * dg = GetEvseDelegate(); + dg->HwSetCableAssemblyLimit(sEVSETestEventSaveData.mOldCableAssemblyLimit); dg->HwSetState(sEVSETestEventSaveData.mOldHwStatePluggedIn); } diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 640336e8fdb373..c0c585ed0c0d2a 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -192,7 +192,7 @@ Status EnergyEvseDelegate::HwSetMaxHardwareCurrentLimit(int64_t currentmA) /* there is no attribute to store this so store in private variable */ mMaxHardwareCurrentLimit = currentmA; - return this->ComputeMaxChargeCurrentLimit(); + return ComputeMaxChargeCurrentLimit(); } /** @@ -214,7 +214,7 @@ Status EnergyEvseDelegate::HwSetCircuitCapacity(int64_t currentmA) mCircuitCapacity = currentmA; MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, CircuitCapacity::Id); - return this->ComputeMaxChargeCurrentLimit(); + return ComputeMaxChargeCurrentLimit(); } /** @@ -239,7 +239,7 @@ Status EnergyEvseDelegate::HwSetCableAssemblyLimit(int64_t currentmA) /* there is no attribute to store this so store in private variable */ mCableAssemblyCurrentLimit = currentmA; - return this->ComputeMaxChargeCurrentLimit(); + return ComputeMaxChargeCurrentLimit(); } bool IsEvPluggedIn(StateEnum state) @@ -525,10 +525,12 @@ Status EnergyEvseDelegate::HandleEVDemandEvent() switch (mSupplyState) { case SupplyStateEnum::kChargingEnabled: + ComputeMaxChargeCurrentLimit(); SetState(StateEnum::kPluggedInCharging); SendEnergyTransferStartedEvent(); break; case SupplyStateEnum::kDischargingEnabled: + // TODO ComputeMaxDischargeCurrentLimit() - Needs to be implemented SetState(StateEnum::kPluggedInDischarging); SendEnergyTransferStartedEvent(); break; @@ -559,6 +561,7 @@ Status EnergyEvseDelegate::HandleChargingEnabledEvent() case StateEnum::kPluggedInNoDemand: break; case StateEnum::kPluggedInDemand: + ComputeMaxChargeCurrentLimit(); SetState(StateEnum::kPluggedInCharging); SendEnergyTransferStartedEvent(); break; @@ -568,6 +571,7 @@ Status EnergyEvseDelegate::HandleChargingEnabledEvent() /* Switched from discharging to charging */ SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped); + ComputeMaxChargeCurrentLimit(); SetState(StateEnum::kPluggedInCharging); SendEnergyTransferStartedEvent(); break; @@ -590,6 +594,7 @@ Status EnergyEvseDelegate::HandleDischargingEnabledEvent() case StateEnum::kPluggedInNoDemand: break; case StateEnum::kPluggedInDemand: + // TODO call ComputeMaxDischargeCurrentLimit() SetState(StateEnum::kPluggedInDischarging); SendEnergyTransferStartedEvent(); break; @@ -597,6 +602,7 @@ Status EnergyEvseDelegate::HandleDischargingEnabledEvent() /* Switched from charging to discharging */ SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped); + // TODO call ComputeMaxDischargeCurrentLimit() SetState(StateEnum::kPluggedInDischarging); SendEnergyTransferStartedEvent(); break; From 3f365af6ad88ab9beaa7b4a1adb2c59f35065c7f Mon Sep 17 00:00:00 2001 From: James Harrow Date: Thu, 4 Jan 2024 22:01:59 +0000 Subject: [PATCH 16/52] Added comment to clarify purpose and definition of test eventtrigger field values. --- .../EnergyEvseTestEventTriggerDelegate.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h index 489ca7974eefcb..7f199b21cdc255 100644 --- a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h @@ -22,6 +22,13 @@ namespace chip { +/* + * These Test EventTrigger values are specified in the TC_EEVSE test plan + * and are defined conditions used in test events. + * + * They are sent along with the enableKey (manufacturer defined secret) + * in the General Diagnostic cluster TestEventTrigger command + */ enum class EnergyEvseTrigger : uint64_t { // Scenarios @@ -33,7 +40,6 @@ enum class EnergyEvseTrigger : uint64_t kEVPluggedInClear = 0x0099000000000003, // EV Plugged-in Test Event Clear | Simulate unplugging the EV kEVChargeDemand = 0x0099000000000004, // EV Charge Demand Test Event | Simulate the EV presenting charge demand to the EVSE kEVChargeDemandClear = 0x0099000000000005, // EV Charge Demand Test Event Clear | Simulate the EV becoming fully charged - }; class EnergyEvseTestEventTriggerDelegate : public TestEventTriggerDelegate From 2f49014998adf50406f452fcb8b633e62386521f Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 5 Jan 2024 17:27:17 +0000 Subject: [PATCH 17/52] Fixed several bugs in test script --- src/python_testing/TC_EEVSE_2_2.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index 72eb26ad9b1372..5f8714bf65a77a 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -19,6 +19,7 @@ import time import chip.clusters as Clusters +from chip.clusters.Types import NullValue import pytz from chip.interaction_model import InteractionModelError, Status from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main, TestStep @@ -51,11 +52,11 @@ async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): try: await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( - chargingEnabledUntil=charge_until, - minimumChargeCurrent=6000, - maximumChargeCurrent=60000), - endpoint=1, - timedRequestTimeoutMs=timedRequestTimeoutMs) + chargingEnabledUntil=charge_until, + minimumChargeCurrent=min_charge, + maximumChargeCurrent=max_charge), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") @@ -102,11 +103,11 @@ def steps_TC_EEVSE_2_2(self) -> list[TestStep]: TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 1 (ChargingEnabled)"), TestStep("8c", "TH reads from the DUT the ChargingEnabledUntil attribute. Verify value is the commanded value (NULL)"), TestStep("8d", "TH reads from the DUT the MinimumChargeCurrent attribute. Verify value is the commanded value (6000)"), - TestStep("8d", "TH reads from the DUT the MaximumChargeCurrent attribute. Verify value is the MIN(command value (60000), CircuitCapacity)"), + TestStep("8e", "TH reads from the DUT the MaximumChargeCurrent attribute. Verify value is the MIN(command value (60000), CircuitCapacity)"), TestStep("9", "If the optional attribute is supported TH writes to the DUT UserMaximumChargeCurrent=6000"), TestStep("9a", "After a few seconds TH reads from the DUT the MaximumChargeCurrent. Verify value is UserMaximumChargeCurrent value (6000)"), TestStep("10", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped"), - TestStep("10a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), + TestStep("10a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), TestStep("11", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), TestStep("11a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), TestStep("12", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped"), @@ -234,16 +235,15 @@ async def test_TC_EEVSE_2_2(self): # TODO check EnergyTransferredStoped (EvseStopped) self.step("7a") - await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand) +# await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand) self.step("7b") - await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) +# await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) self.step("8") - charge_until = None + charge_until = NullValue min_charge_current = 6000 max_charge_current = 12000 - await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) self.step("8a") @@ -265,10 +265,11 @@ async def test_TC_EEVSE_2_2(self): self.step("9") # This will only work if the optional UserMaximumChargeCurrent attribute is supported - if Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent.attribute_id in self.get_supported_energy_evse_attributes(): + supported_attributes = await self.get_supported_energy_evse_attributes(endpoint=1) + if Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent.attribute_id in supported_attributes: logging.info("UserMaximumChargeCurrent is supported...") user_max_charge_current = 6000 - self.write_user_max_charge(1, user_max_charge_current) + await self.write_user_max_charge(1, user_max_charge_current) self.step("9a") time.sleep(3) From f2ce4cfa4baff48781de4d9b5683a0d691afc574 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 5 Jan 2024 17:38:52 +0000 Subject: [PATCH 18/52] Made SetChargingEnabledUntil take a nullable type. --- .../include/EnergyEvseDelegateImpl.h | 4 +- .../src/EnergyEvseDelegateImpl.cpp | 46 +++++++++++++------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 697e9ed6811ee9..34ccddfff83e01 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -166,10 +166,10 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate CHIP_ERROR SetFaultState(FaultStateEnum); DataModel::Nullable GetChargingEnabledUntil() override; - CHIP_ERROR SetChargingEnabledUntil(uint32_t); + CHIP_ERROR SetChargingEnabledUntil(DataModel::Nullable); DataModel::Nullable GetDischargingEnabledUntil() override; - CHIP_ERROR SetDischargingEnabledUntil(uint32_t); + CHIP_ERROR SetDischargingEnabledUntil(DataModel::Nullable); int64_t GetCircuitCapacity() override; CHIP_ERROR SetCircuitCapacity(int64_t); diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index c0c585ed0c0d2a..8e1bf0de904300 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -48,9 +48,10 @@ Status EnergyEvseDelegate::Disable() { ChipLogProgress(AppServer, "EnergyEvseDelegate::Disable()"); + DataModel::Nullable disableTime(0); /* update ChargingEnabledUntil & DischargingEnabledUntil to show 0 */ - SetChargingEnabledUntil(0); - SetDischargingEnabledUntil(0); + SetChargingEnabledUntil(disableTime); + SetDischargingEnabledUntil(disableTime); /* update MinimumChargeCurrent & MaximumChargeCurrent to 0 */ SetMinimumChargeCurrent(0); @@ -96,12 +97,13 @@ Status EnergyEvseDelegate::EnableCharging(const DataModel::Nullable & { /* Charging enabled indefinitely */ ChipLogError(AppServer, "Charging enabled indefinitely"); + SetChargingEnabledUntil(chargingEnabledUntil); } else { /* check chargingEnabledUntil is in the future */ ChipLogError(AppServer, "Charging enabled until: %lu", static_cast(chargingEnabledUntil.Value())); - SetChargingEnabledUntil(chargingEnabledUntil.Value()); + SetChargingEnabledUntil(chargingEnabledUntil); // TODO start a timer to disable charging later // if (checkChargingEnabled) } @@ -944,17 +946,25 @@ DataModel::Nullable EnergyEvseDelegate::GetChargingEnabledUntil() return mChargingEnabledUntil; } -CHIP_ERROR EnergyEvseDelegate::SetChargingEnabledUntil(uint32_t newValue) +CHIP_ERROR EnergyEvseDelegate::SetChargingEnabledUntil(DataModel::Nullable newValue) { DataModel::Nullable oldValue = mChargingEnabledUntil; - mChargingEnabledUntil = MakeNullable(newValue); - if ((oldValue.IsNull()) || (oldValue.Value() != newValue)) + mChargingEnabledUntil = newValue; + if (oldValue != newValue) { - ChipLogDetail(AppServer, "ChargingEnabledUntil updated to %lu", - static_cast(mChargingEnabledUntil.Value())); + if (newValue.IsNull()) + { + ChipLogDetail(AppServer, "ChargingEnabledUntil updated to Null"); + } + else + { + ChipLogDetail(AppServer, "ChargingEnabledUntil updated to %lu", + static_cast(mChargingEnabledUntil.Value())); + } MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, ChargingEnabledUntil::Id); } + return CHIP_NO_ERROR; } @@ -964,17 +974,25 @@ DataModel::Nullable EnergyEvseDelegate::GetDischargingEnabledUntil() return mDischargingEnabledUntil; } -CHIP_ERROR EnergyEvseDelegate::SetDischargingEnabledUntil(uint32_t newValue) +CHIP_ERROR EnergyEvseDelegate::SetDischargingEnabledUntil(DataModel::Nullable newValue) { DataModel::Nullable oldValue = mDischargingEnabledUntil; - mDischargingEnabledUntil = MakeNullable(newValue); - if ((oldValue.IsNull()) || (oldValue.Value() != newValue)) + mDischargingEnabledUntil = newValue; + if (oldValue != newValue) { - ChipLogDetail(AppServer, "DischargingEnabledUntil updated to %lu", - static_cast(mDischargingEnabledUntil.Value())); + if (newValue.IsNull()) + { + ChipLogDetail(AppServer, "DischargingEnabledUntil updated to Null"); + } + else + { + ChipLogDetail(AppServer, "DischargingEnabledUntil updated to %lu", + static_cast(mDischargingEnabledUntil.Value())); + } MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, DischargingEnabledUntil::Id); } + return CHIP_NO_ERROR; } @@ -1092,6 +1110,8 @@ CHIP_ERROR EnergyEvseDelegate::SetUserMaximumChargeCurrent(int64_t newValue) if (oldValue != newValue) { ChipLogDetail(AppServer, "UserMaximumChargeCurrent updated to %ld", static_cast(mUserMaximumChargeCurrent)); + + ComputeMaxChargeCurrentLimit(); MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, UserMaximumChargeCurrent::Id); } From b16e1ff57cb2d0c3d02244f1aafb74b068fe9ece Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sun, 7 Jan 2024 18:53:35 +0000 Subject: [PATCH 19/52] Removed Reference to step 5c, and moved reading of SessionID to step 4b. More TC_EEVSE_2_2 bug fixes. Added event checking. Still fails at step 14. Does not have enable timeout timer implemented --- .../src/EnergyEvseDelegateImpl.cpp | 2 +- src/python_testing/TC_EEVSE_2_2.py | 148 +++++++++++++++--- 2 files changed, 125 insertions(+), 25 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 8e1bf0de904300..f1cd57611aaa10 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -297,7 +297,7 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) { /* EV was plugged in and wanted demand, now doesn't want demand */ mHwState = newState; - HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent); + HandleStateMachineEvent(EVSEStateMachineEvent::EVNoDemandEvent); } break; case StateEnum::kPluggedInDemand: diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index 5f8714bf65a77a..f8e9f7759b0ea3 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -14,13 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime + import logging +import queue import time +from datetime import datetime, timedelta, timezone import chip.clusters as Clusters +from chip.clusters import ClusterObjects as ClusterObjects +from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction from chip.clusters.Types import NullValue -import pytz + from chip.interaction_model import InteractionModelError, Status from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main, TestStep from mobly import asserts @@ -28,6 +32,35 @@ logger = logging.getLogger(__name__) +class EventChangeCallback: + def __init__(self, expected_cluster: ClusterObjects): + self._q = queue.Queue() + self._expected_cluster = expected_cluster + + async def start(self, dev_ctrl, nodeid): + self._subscription = await dev_ctrl.ReadEvent(nodeid, + events=[(1, self._expected_cluster, 1)], reportInterval=(1, 5), + fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) + self._subscription.SetEventUpdateCallback(self.__call__) + + def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): + logging.info(f"EVENT RECV'D ---------------------------\n\n\n!!!\n{res}") + if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: + logging.info( + f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') + self._q.put(res) + + def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): + try: + res = self._q.get(block=True, timeout=10) + except queue.Empty: + asserts.fail("Failed to receive a report for the event {}".format(expected_event)) + + asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report") + asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report") + return res.Data + + class TC_EEVSE_2_2(MatterBaseTest): async def read_evse_attribute_expect_success(self, endpoint, attribute): full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) @@ -85,9 +118,9 @@ def steps_TC_EEVSE_2_2(self) -> list[TestStep]: TestStep("3a", "After a few seconds TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), TestStep("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), TestStep("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)"), - TestStep("3d", "TH reads from the DUT the SessionID attribute"), TestStep("4", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event. Verify Event EEVSE.S.E00(EVConnected) sent"), TestStep("4a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), + TestStep("4b", "TH reads from the DUT the SessionID attribute. Value is noted for later"), TestStep("5", "TH sends command EnableCharging with ChargingEnabledUntil=2 minutes in the future, minimumChargeCurrent=6000, maximumChargeCurrent=60000"), TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x3 (PluggedInCharging)"), @@ -109,17 +142,17 @@ def steps_TC_EEVSE_2_2(self) -> list[TestStep]: TestStep("10", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped"), TestStep("10a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), TestStep("11", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), - TestStep("11a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), + TestStep("11a", "TH reads from the DUT the State attribute. Verify value is 0x03 (PluggedInCharging)"), TestStep("12", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped"), - TestStep("12a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), + TestStep("12a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), TestStep("13", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event Clear. Verify Event EEVSE.S.E01(EVNotDetected) sent"), TestStep("13a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), TestStep("13b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x01 (ChargingEnabled)"), - TestStep("13c", "TH reads from the DUT the SessionID attribute. Verify value is the same value noted in 5c"), + TestStep("13c", "TH reads from the DUT the SessionID attribute. Verify value is the same value noted in 4b"), TestStep("13d", "TH reads from the DUT the SessionDuration attribute. Verify value is greater than 120 (and match the time taken for the tests from step 4 to step 13)"), TestStep("14", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event. Verify Event EEVSE.S.E00(EVConnected) sent"), TestStep("14a", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), - TestStep("14b", "TH reads from the DUT the SessionID attribute. Verify value is 1 more than the value noted in 5c"), + TestStep("14b", "TH reads from the DUT the SessionID attribute. Verify value is 1 more than the value noted in 4b"), TestStep("15", "TH sends command Disable. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvseStopped"), TestStep("15a", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), TestStep("16", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear."), @@ -165,11 +198,45 @@ async def send_test_event_trigger_charge_demand(self): async def send_test_event_trigger_charge_demand_clear(self): await self.send_test_event_triggers(eventTrigger=0x0099000000000005) + def validate_energy_transfer_started_event(self, event_data, session_id, expected_state, expected_max_charge): + asserts.assert_equal(session_id, event_data.sessionID, + f"EnergyTransferStarted event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EnergyTransferStarted event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(expected_max_charge, event_data.maximumCurrent, + f"EnergyTransferStarted event maximumCurrent was {event_data.maximumCurrent}, expected {expected_max_charge}") + + def validate_energy_transfer_stopped_event(self, event_data, session_id, expected_state, expected_reason): + asserts.assert_equal(session_id, event_data.sessionID, + f"EnergyTransferStopped event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EnergyTransferStopped event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(expected_reason, event_data.reason, + f"EnergyTransferStopped event reason was {event_data.reason}, expected {expected_reason}") + + def validate_ev_connected_event(self, event_data, session_id): + asserts.assert_equal(session_id, event_data.sessionID, + f"EvConnected event session ID was {event_data.sessionID}, expected {session_id}") + + def validate_ev_not_detected_event(self, event_data, session_id, expected_state, expected_duration, expected_charged): + asserts.assert_equal(session_id, event_data.sessionID, + f"EvNotDetected event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EvNotDetected event event State was {event_data.state} expected {expected_state}") + asserts.assert_greater_equal(event_data.sessionDuration, expected_duration, + f"EvNotDetected event sessionDuration was {event_data.sessionDuration}, expected >= {expected_duration}") + asserts.assert_greater_equal(event_data.sessionEnergyCharged, expected_charged, + f"EvNotDetected event sessionEnergyCharged was {event_data.sessionEnergyCharged}, expected >= {expected_charged}") + @async_test_body async def test_TC_EEVSE_2_2(self): self.step("1") # Commission DUT - already done + # Subscribe to Events and when they are sent push them to a queue for checking later + events_callback = EventChangeCallback(Clusters.EnergyEvse) + await events_callback.start(self.default_controller, self.dut_node_id) + self.step("2") await self.check_test_event_triggers_enabled() @@ -188,32 +255,36 @@ async def test_TC_EEVSE_2_2(self): self.step("3c") await self.check_evse_attribute("FaultState", Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError) - self.step("3d") - # Save Session ID - it may be NULL at this point - session_id = await self.read_evse_attribute_expect_success(endpoint=1, attribute="SessionID") - self.step("4") await self.send_test_event_trigger_pluggedin() - # TODO check PluggedIn Event + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVConnected) self.step("4a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) + self.step("4b") + # Save Session ID for later and check it against the value in the event + session_id = await self.read_evse_attribute_expect_success(endpoint=1, attribute="SessionID") + self.validate_ev_connected_event(event_data, session_id) + self.step("5") charging_duration = 5 # TODO test plan spec says 120s - reduced for now min_charge_current = 6000 max_charge_current = 60000 + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging # get epoch time for ChargeUntil variable (2 minutes from now) - utc_time_charging_end = datetime.datetime.now(pytz.utc) + datetime.timedelta(seconds=charging_duration) - epoch_time = int((utc_time_charging_end - datetime.datetime(2000, 1, 1, tzinfo=pytz.utc)).total_seconds()) + utc_time_charging_end = datetime.now(tz=timezone.utc) + timedelta(seconds=charging_duration) + + # Matter epoch is 0 hours, 0 minutes, 0 seconds on Jan 1, 2000 UTC + epoch_time = int((utc_time_charging_end - datetime(2000, 1, 1, 0, 0, 0, 0, timezone.utc)).total_seconds()) await self.send_enable_charge_command(endpoint=1, charge_until=epoch_time, min_charge=min_charge_current, max_charge=max_charge_current) self.step("6") await self.send_test_event_trigger_charge_demand() - # TODO check EnergyTransferStarted Event + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) self.step("6a") - await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + await self.check_evse_attribute("State", expected_state) self.step("6b") await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) @@ -229,10 +300,15 @@ async def test_TC_EEVSE_2_2(self): expected_max_charge = min(max_charge_current, circuit_capacity) await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) + self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) + self.step("7") # Sleep for the charging duration plus a couple of seconds to check it has stopped time.sleep(charging_duration + 2) # TODO check EnergyTransferredStoped (EvseStopped) + # event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + # expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVSEStopped + # self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) self.step("7a") # await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand) @@ -244,7 +320,9 @@ async def test_TC_EEVSE_2_2(self): charge_until = NullValue min_charge_current = 6000 max_charge_current = 12000 + # TODO reinstate this check await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + # event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) self.step("8a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) @@ -263,6 +341,10 @@ async def test_TC_EEVSE_2_2(self): expected_max_charge = min(max_charge_current, circuit_capacity) await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) + # from step 8 above - validate event + # TODO reinstate this check + # self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) + self.step("9") # This will only work if the optional UserMaximumChargeCurrent attribute is supported supported_attributes = await self.get_supported_energy_evse_attributes(endpoint=1) @@ -281,25 +363,37 @@ async def test_TC_EEVSE_2_2(self): self.step("10") await self.send_test_event_trigger_charge_demand_clear() - # TODO Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVStopped + self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) self.step("10a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) self.step("11") await self.send_test_event_trigger_charge_demand() - # TODO Verify Event EEVSE.S.E03(EnergyTransferStarted) sent + # Check we get EnergyTransferStarted again + await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) + + self.step("11a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) self.step("12") await self.send_test_event_trigger_charge_demand_clear() - # TODO Verify Event EEVSE.S.E03(EnergyTransferStopped with reason EvStopped) sent + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVStopped + self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) self.step("12a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) self.step("13") await self.send_test_event_trigger_pluggedin_clear() - # TODO Verify EVNotDetected sent + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVNotDetected) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand + self.validate_ev_not_detected_event(event_data, session_id, expected_state, expected_duration=0, expected_charged=0) self.step("13a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) @@ -317,18 +411,22 @@ async def test_TC_EEVSE_2_2(self): self.step("14") await self.send_test_event_trigger_pluggedin() - # TODO check PluggedIn Event + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVConnected) + self.validate_ev_connected_event(event_data, session_id + 1) self.step("14a") await self.send_test_event_trigger_charge_demand() - # TODO check EnergyTransferStarted Event + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) self.step("14b") await self.check_evse_attribute("SessionID", session_id + 1) self.step("15") await self.send_disable_command() - # TODO Verify Event EEVSE.S.E03(EnergyTransferStopped with reason EvseStopped) sent + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVSEStopped + self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) self.step("15a") await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) @@ -338,7 +436,9 @@ async def test_TC_EEVSE_2_2(self): self.step("17") await self.send_test_event_trigger_pluggedin_clear() - # TODO Verify EVNotDetected sent + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVNotDetected) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand + self.validate_ev_not_detected_event(event_data, session_id, expected_state, expected_duration=0, expected_charged=0) self.step("18") await self.send_test_event_trigger_basic_clear() From 469f3139c3a1486dd223e761bb04a193d9bd130f Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sun, 7 Jan 2024 22:36:16 +0000 Subject: [PATCH 20/52] Fixed issue with not detecting 2nd plug in event, and session ID not incrementing. Now test case passes all the way. --- .../src/EnergyEvseDelegateImpl.cpp | 4 +++- src/python_testing/TC_EEVSE_2_2.py | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index f1cd57611aaa10..c640a27ea0e07a 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -282,6 +282,7 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) if (mHwState == StateEnum::kPluggedInNoDemand || mHwState == StateEnum::kPluggedInDemand) { /* EVSE has been unplugged now */ + mHwState = newState; HandleStateMachineEvent(EVSEStateMachineEvent::EVNotDetectedEvent); } break; @@ -1276,7 +1277,8 @@ void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMe } else { - mSessionID = MakeNullable(mSessionID.Value()++); + uint32_t sessionID = mSessionID.Value() + 1; + mSessionID = MakeNullable(sessionID); } /* Reset other session values */ diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index f8e9f7759b0ea3..9fce5e1b68544b 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -44,7 +44,6 @@ async def start(self, dev_ctrl, nodeid): self._subscription.SetEventUpdateCallback(self.__call__) def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): - logging.info(f"EVENT RECV'D ---------------------------\n\n\n!!!\n{res}") if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: logging.info( f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') @@ -81,7 +80,7 @@ async def write_user_max_charge(self, endpoint, user_max_charge): Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent(user_max_charge))]) asserts.assert_equal(result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") - async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 60000, + async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 3000, min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): try: await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( @@ -94,9 +93,11 @@ async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - async def send_disable_command(self, endpoint: int = 0, expected_status: Status = Status.Success): + async def send_disable_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): try: - await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), endpoint=1) + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") @@ -411,19 +412,25 @@ async def test_TC_EEVSE_2_2(self): self.step("14") await self.send_test_event_trigger_pluggedin() + # New plug in means session ID should increase by 1 + session_id = session_id + 1 + + # Check we get a new EVConnected event with updated session ID event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVConnected) - self.validate_ev_connected_event(event_data, session_id + 1) + self.validate_ev_connected_event(event_data, session_id) self.step("14a") await self.send_test_event_trigger_charge_demand() + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging # This is the value at the event time event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) self.step("14b") - await self.check_evse_attribute("SessionID", session_id + 1) + await self.check_evse_attribute("SessionID", session_id) self.step("15") await self.send_disable_command() + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging # This is the value prior to stopping event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVSEStopped self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) From ec0a8762e464fcf4b6e93ab27a9e460a4bb10982 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 7 Jan 2024 23:05:30 +0000 Subject: [PATCH 21/52] Restyled by isort --- src/python_testing/TC_EEVSE_2_2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index 9fce5e1b68544b..faac9004e6909b 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -24,9 +24,8 @@ from chip.clusters import ClusterObjects as ClusterObjects from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction from chip.clusters.Types import NullValue - from chip.interaction_model import InteractionModelError, Status -from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main, TestStep +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts logger = logging.getLogger(__name__) From e008095e4f57635684e23cfdb895403bdceb52d4 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Mon, 8 Jan 2024 17:12:47 +0000 Subject: [PATCH 22/52] Made some attributes persisted per spec. --- .../include/EnergyEvseDelegateImpl.h | 2 +- .../include/EnergyEvseManager.h | 2 + .../src/EnergyEvseDelegateImpl.cpp | 46 ++++++++-- .../src/EnergyEvseManager.cpp | 89 ++++++++++++++++++- .../energy-evse-server/energy-evse-server.cpp | 3 +- .../energy-evse-server/energy-evse-server.h | 7 +- 6 files changed, 137 insertions(+), 12 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 34ccddfff83e01..6cd6dd37f8df34 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -198,7 +198,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate DataModel::Nullable GetNextChargeTargetSoC() override; DataModel::Nullable GetApproximateEVEfficiency() override; - CHIP_ERROR SetApproximateEVEfficiency(uint16_t) override; + CHIP_ERROR SetApproximateEVEfficiency(DataModel::Nullable) override; /* SOC attributes */ DataModel::Nullable GetStateOfCharge() override; diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseManager.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseManager.h index 9875c397990ef2..a6a64f35ee9767 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseManager.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseManager.h @@ -46,6 +46,8 @@ class EnergyEvseManager : public Instance CHIP_ERROR Init(); void Shutdown(); + CHIP_ERROR LoadPersistentAttributes(); + EnergyEvseDelegate * GetDelegate() { return mDelegate; }; private: diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index c640a27ea0e07a..d122de7921dac3 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -20,6 +20,7 @@ #include #include #include +#include using namespace chip; using namespace chip::app; @@ -122,7 +123,7 @@ Status EnergyEvseDelegate::EnableCharging(const DataModel::Nullable & * @brief Called when EVSE cluster receives EnableDischarging command * * @param dischargingEnabledUntil (can be null to indefinite discharging) - * @param maximumChargeCurrent (in mA) + * @param maximumDischargeCurrent (in mA) */ Status EnergyEvseDelegate::EnableDischarging(const DataModel::Nullable & dischargingEnabledUntil, const int64_t & maximumDischargeCurrent) @@ -963,6 +964,11 @@ CHIP_ERROR EnergyEvseDelegate::SetChargingEnabledUntil(DataModel::Nullable(mChargingEnabledUntil.Value())); } + + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, ChargingEnabledUntil::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mChargingEnabledUntil); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, ChargingEnabledUntil::Id); } @@ -991,6 +997,10 @@ CHIP_ERROR EnergyEvseDelegate::SetDischargingEnabledUntil(DataModel::Nullable(mDischargingEnabledUntil.Value())); } + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, DischargingEnabledUntil::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mDischargingEnabledUntil); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, DischargingEnabledUntil::Id); } @@ -1113,6 +1123,11 @@ CHIP_ERROR EnergyEvseDelegate::SetUserMaximumChargeCurrent(int64_t newValue) ChipLogDetail(AppServer, "UserMaximumChargeCurrent updated to %ld", static_cast(mUserMaximumChargeCurrent)); ComputeMaxChargeCurrentLimit(); + + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, UserMaximumChargeCurrent::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mUserMaximumChargeCurrent); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, UserMaximumChargeCurrent::Id); } @@ -1138,6 +1153,11 @@ CHIP_ERROR EnergyEvseDelegate::SetRandomizationDelayWindow(uint32_t newValue) { ChipLogDetail(AppServer, "RandomizationDelayWindow updated to %lu", static_cast(mRandomizationDelayWindow)); + + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, RandomizationDelayWindow::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mRandomizationDelayWindow); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, RandomizationDelayWindow::Id); } return CHIP_NO_ERROR; @@ -1175,14 +1195,25 @@ DataModel::Nullable EnergyEvseDelegate::GetApproximateEVEfficiency() return mApproximateEVEfficiency; } -CHIP_ERROR EnergyEvseDelegate::SetApproximateEVEfficiency(uint16_t newValue) +CHIP_ERROR EnergyEvseDelegate::SetApproximateEVEfficiency(DataModel::Nullable newValue) { DataModel::Nullable oldValue = mApproximateEVEfficiency; - mApproximateEVEfficiency = MakeNullable(newValue); - if ((oldValue.IsNull()) || (oldValue.Value() != newValue)) + mApproximateEVEfficiency = newValue; + if ((oldValue != newValue)) { - ChipLogDetail(AppServer, "ApproximateEVEfficiency updated to %d", mApproximateEVEfficiency.Value()); + if (newValue.IsNull()) + { + ChipLogDetail(AppServer, "ApproximateEVEfficiency updated to Null"); + } + else + { + ChipLogDetail(AppServer, "ApproximateEVEfficiency updated to %d", mApproximateEVEfficiency.Value()); + } + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, ApproximateEVEfficiency::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mApproximateEVEfficiency); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, ApproximateEVEfficiency::Id); } @@ -1291,7 +1322,10 @@ void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMe MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyCharged::Id); MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); - // TODO persist mSessionID + // Write values to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, SessionID::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mSessionID); + // TODO persist mStartTime // TODO persist mSessionEnergyChargedAtStart // TODO persist mSessionEnergyDischargedAtStart diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp index 0d84d8856212e0..389a830e8da759 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp @@ -17,14 +17,101 @@ */ #include +#include using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; +CHIP_ERROR EnergyEvseManager::LoadPersistentAttributes() +{ + + SafeAttributePersistenceProvider * aProvider = GetSafeAttributePersistenceProvider(); + EndpointId aEndpointId = mDelegate->GetEndpointId(); + CHIP_ERROR err; + + // Restore ChargingEnabledUntil value + DataModel::Nullable tempChargingEnabledUntil; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::ChargingEnabledUntil::Id), + tempChargingEnabledUntil); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded ChargingEnabledUntil from NVM"); + mDelegate->SetChargingEnabledUntil(tempChargingEnabledUntil); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted ChargingEnabledUntil value"); + } + + // Restore DischargingEnabledUntil value + DataModel::Nullable tempDischargingEnabledUntil; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::DischargingEnabledUntil::Id), + tempDischargingEnabledUntil); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded DischargingEnabledUntil from NVM"); + mDelegate->SetDischargingEnabledUntil(tempDischargingEnabledUntil); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted DischargingEnabledUntil value"); + } + + // Restore UserMaximumChargeCurrent value + uint64_t tempUserMaximumChargeCurrent; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::UserMaximumChargeCurrent::Id), + tempUserMaximumChargeCurrent); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded UserMaximumChargeCurrent from NVM"); + mDelegate->SetUserMaximumChargeCurrent(tempUserMaximumChargeCurrent); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted UserMaximumChargeCurrent value"); + } + + // Restore RandomizationDelayWindow value + uint32_t tempRandomizationDelayWindow; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::RandomizationDelayWindow::Id), + tempRandomizationDelayWindow); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded RandomizationDelayWindow from NVM"); + mDelegate->SetRandomizationDelayWindow(tempRandomizationDelayWindow); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted RandomizationDelayWindow value"); + } + + // Restore ApproximateEVEfficiency value + DataModel::Nullable tempApproxEVEfficiency; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::ApproximateEVEfficiency::Id), + tempApproxEVEfficiency); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded ApproximateEVEfficiency from NVM"); + mDelegate->SetApproximateEVEfficiency(tempApproxEVEfficiency); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted ApproximateEVEfficiency value"); + } + + return CHIP_NO_ERROR; // It is ok to have no value loaded here +} + CHIP_ERROR EnergyEvseManager::Init() { - return Instance::Init(); + CHIP_ERROR err = Instance::Init(); + if (err != CHIP_NO_ERROR) + { + return err; + } + + return LoadPersistentAttributes(); } void EnergyEvseManager::Shutdown() diff --git a/src/app/clusters/energy-evse-server/energy-evse-server.cpp b/src/app/clusters/energy-evse-server/energy-evse-server.cpp index 60f9e24be0f962..bc07ba07eef55a 100644 --- a/src/app/clusters/energy-evse-server/energy-evse-server.cpp +++ b/src/app/clusters/energy-evse-server/energy-evse-server.cpp @@ -23,6 +23,7 @@ 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; @@ -166,7 +167,7 @@ CHIP_ERROR Instance::Write(const ConcreteDataAttributePath & aPath, AttributeVal } uint16_t newValue; ReturnErrorOnFailure(aDecoder.Decode(newValue)); - ReturnErrorOnFailure(mDelegate.SetApproximateEVEfficiency(newValue)); + ReturnErrorOnFailure(mDelegate.SetApproximateEVEfficiency(MakeNullable(newValue))); return CHIP_NO_ERROR; } diff --git a/src/app/clusters/energy-evse-server/energy-evse-server.h b/src/app/clusters/energy-evse-server/energy-evse-server.h index 5df2f0149eb1ed..a194551607c6f4 100644 --- a/src/app/clusters/energy-evse-server/energy-evse-server.h +++ b/src/app/clusters/energy-evse-server/energy-evse-server.h @@ -48,6 +48,7 @@ class Delegate virtual ~Delegate() = default; void SetEndpointId(EndpointId aEndpoint) { mEndpointId = aEndpoint; } + EndpointId GetEndpointId() { return mEndpointId; } /** * @brief Delegate should implement a handler to disable the EVSE. @@ -116,9 +117,9 @@ class Delegate // ------------------------------------------------------------------ // Set attribute methods - virtual CHIP_ERROR SetUserMaximumChargeCurrent(int64_t aNewValue) = 0; - virtual CHIP_ERROR SetRandomizationDelayWindow(uint32_t aNewValue) = 0; - virtual CHIP_ERROR SetApproximateEVEfficiency(uint16_t aNewValue) = 0; + virtual CHIP_ERROR SetUserMaximumChargeCurrent(int64_t aNewValue) = 0; + virtual CHIP_ERROR SetRandomizationDelayWindow(uint32_t aNewValue) = 0; + virtual CHIP_ERROR SetApproximateEVEfficiency(DataModel::Nullable aNewValue) = 0; protected: EndpointId mEndpointId = 0; From b24bb80417aef9d77e424aa3e3b0da5453087191 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Mon, 8 Jan 2024 21:24:21 +0000 Subject: [PATCH 23/52] Fixed incorrect type - not picked up by all compilers. --- .../energy-management-common/src/EnergyEvseManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp index 389a830e8da759..a71474f0913abb 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp @@ -59,7 +59,7 @@ CHIP_ERROR EnergyEvseManager::LoadPersistentAttributes() } // Restore UserMaximumChargeCurrent value - uint64_t tempUserMaximumChargeCurrent; + int64_t tempUserMaximumChargeCurrent; err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::UserMaximumChargeCurrent::Id), tempUserMaximumChargeCurrent); if (err == CHIP_NO_ERROR) From 320b5e1e38a9705bb04bf891e5fa38622a1135da Mon Sep 17 00:00:00 2001 From: James Harrow Date: Mon, 8 Jan 2024 23:33:42 +0000 Subject: [PATCH 24/52] Added provisional handling for Faults --- .../include/EnergyEvseDelegateImpl.h | 6 + .../src/EnergyEvseDelegateImpl.cpp | 148 +++++++++++++----- 2 files changed, 119 insertions(+), 35 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 6cd6dd37f8df34..6eb2241638d84f 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -153,6 +153,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate Status SendEVNotDetectedEvent(); Status SendEnergyTransferStartedEvent(); Status SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum reason); + Status SendFaultEvent(FaultStateEnum newFaultState); // ------------------------------------------------------------------ // Get attribute methods @@ -225,6 +226,10 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate int64_t mActualChargingCurrentLimit = 0; StateEnum mHwState = StateEnum::kNotPluggedIn; /* Hardware state */ + /* Variables to hold State and SupplyState in case a fault is raised */ + StateEnum mStateBeforeFault = StateEnum::kUnknownEnumValue; + SupplyStateEnum mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue; + /* Callback related */ EVSECallbackWrapper mCallbacks = { .handler = nullptr, .arg = 0 }; /* Wrapper to allow callbacks to be registered */ Status NotifyApplicationCurrentLimitChange(int64_t maximumChargeCurrent); @@ -232,6 +237,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate Status GetEVSEEnergyMeterValue(ChargingDischargingType meterType, int64_t & aMeterValue); /* Local State machine handling */ + Status CheckFaultOrDiagnostic(); Status HandleStateMachineEvent(EVSEStateMachineEvent event); Status HandleEVPluggedInEvent(); Status HandleEVNotDetectedEvent(); diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index d122de7921dac3..602d136fe4dd33 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -145,9 +145,11 @@ Status EnergyEvseDelegate::StartDiagnostics() ChipLogProgress(AppServer, "EnergyEvseDelegate::StartDiagnostics()"); /* update SupplyState to indicate we are now in Diagnostics mode */ + // TODO save the value of SupplyState so it can be restored later SetSupplyState(SupplyStateEnum::kDisabledDiagnostics); - // TODO: Generate events + // TODO Application code would need to put the SupplyState back + // once diagnostics have been completed return Status::Success; } @@ -245,23 +247,6 @@ Status EnergyEvseDelegate::HwSetCableAssemblyLimit(int64_t currentmA) return ComputeMaxChargeCurrentLimit(); } -bool IsEvPluggedIn(StateEnum state) -{ - if ((state == StateEnum::kNotPluggedIn) || (state == StateEnum::kFault) || (state == StateEnum::kSessionEnding)) - { - return false; - } - return true; -} - -bool IsEvTransferringEnergy(StateEnum state) -{ - if ((state == StateEnum::kPluggedInCharging) || (state == StateEnum::kPluggedInDischarging)) - { - return true; - } - return false; -} /** * @brief Called by EVSE Hardware to indicate if EV is detected * @@ -333,29 +318,35 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) * * @param FaultStateEnum - the fault condition detected */ -Status EnergyEvseDelegate::HwSetFault(FaultStateEnum fault) +Status EnergyEvseDelegate::HwSetFault(FaultStateEnum newFaultState) { ChipLogProgress(AppServer, "EnergyEvseDelegate::Fault()"); - if (fault == FaultStateEnum::kNoError) + if (mFaultState == newFaultState) { - /* Update State to previous state */ - // TODO: need to work out the logic here! + ChipLogError(AppServer, "No change in fault state, ignoring call"); + return 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); - /* Update SupplyState to previous state */ + /* Updated FaultState before we call into the handlers */ + SetFaultState(newFaultState); + + if (newFaultState == FaultStateEnum::kNoError) + { + /* Fault has been cleared */ + HandleStateMachineEvent(EVSEStateMachineEvent::FaultCleared); } else { - /* Update State & SupplyState */ - SetState(StateEnum::kFault); - SetSupplyState(SupplyStateEnum::kDisabledError); + /* a new Fault has been raised */ + HandleStateMachineEvent(EVSEStateMachineEvent::FaultRaised); } - /* Update FaultState */ - SetFaultState(fault); - - // TODO: Generate events - return Status::Success; } @@ -551,10 +542,30 @@ Status EnergyEvseDelegate::HandleEVDemandEvent() return Status::Success; } +Status EnergyEvseDelegate::CheckFaultOrDiagnostic() +{ + if (mFaultState != FaultStateEnum::kNoError) + { + ChipLogError(AppServer, "EVSE: Trying to handle command when fault is present"); + return Status::Failure; + } + + if (mSupplyState == 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 */ - // TODO -! + Status status = CheckFaultOrDiagnostic(); + if (status != Status::Success) + { + return status; + } /* update SupplyState to say that charging is now enabled */ SetSupplyState(SupplyStateEnum::kChargingEnabled); @@ -587,8 +598,11 @@ Status EnergyEvseDelegate::HandleChargingEnabledEvent() Status EnergyEvseDelegate::HandleDischargingEnabledEvent() { /* Check there is no Fault or Diagnostics condition */ - // TODO -! - + Status status = CheckFaultOrDiagnostic(); + if (status != Status::Success) + { + return status; + } /* update SupplyState to say that charging is now enabled */ SetSupplyState(SupplyStateEnum::kDischargingEnabled); @@ -619,7 +633,11 @@ Status EnergyEvseDelegate::HandleDischargingEnabledEvent() Status EnergyEvseDelegate::HandleDisabledEvent() { /* Check there is no Fault or Diagnostics condition */ - // TODO -! + Status status = CheckFaultOrDiagnostic(); + if (status != Status::Success) + { + return status; + } /* update SupplyState to say that charging is now enabled */ SetSupplyState(SupplyStateEnum::kDisabled); @@ -642,12 +660,53 @@ Status EnergyEvseDelegate::HandleDisabledEvent() 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() { + /* 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 = mState; + } + + if (mSupplyStateBeforeFault == SupplyStateEnum::kUnknownEnumValue) + { + /* No existing fault */ + mSupplyStateBeforeFault = mSupplyState; + } + + /* Update State & SupplyState */ + SetState(StateEnum::kFault); + 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; + } + + /* 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 + */ + SetState(mStateBeforeFault); + SetSupplyState(mSupplyStateBeforeFault); + + /* put back the sentinel to catch new faults if more are raised */ + mStateBeforeFault = StateEnum::kUnknownEnumValue; + mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue; + return Status::Success; } @@ -865,6 +924,25 @@ Status EnergyEvseDelegate::SendEnergyTransferStoppedEvent(EnergyTransferStoppedR return Status::Success; } +Status EnergyEvseDelegate::SendFaultEvent(FaultStateEnum newFaultState) +{ + Events::Fault::Type event; + EventNumber eventNumber; + + event.sessionID = mSession.mSessionID; // Note here the event sessionID can be Null! + event.state = mState; // This is the state prior to the fault being raised + event.faultStatePreviousState = mFaultState; + 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 methods */ From 3a7ace4dc82d6eef93db022b848bec0ef58584b8 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Tue, 9 Jan 2024 01:05:22 +0000 Subject: [PATCH 25/52] Added new test event triggers to help test Fault and Diagnostics --- .../include/EnergyEvseDelegateImpl.h | 7 +-- .../src/EVSEManufacturerImpl.cpp | 44 ++++++++++++++++++- .../src/EnergyEvseDelegateImpl.cpp | 40 +++++++++++++++-- .../EnergyEvseTestEventTriggerDelegate.h | 29 ++++++++---- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 6eb2241638d84f..97da46322fd5d4 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -148,7 +148,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate Status HwSetFault(FaultStateEnum fault); Status HwSetRFID(ByteSpan uid); Status HwSetVehicleID(const CharSpan & vehID); - + Status HwDiagnosticsComplete(); Status SendEVConnectedEvent(); Status SendEVNotDetectedEvent(); Status SendEnergyTransferStartedEvent(); @@ -227,8 +227,9 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate StateEnum mHwState = StateEnum::kNotPluggedIn; /* Hardware state */ /* Variables to hold State and SupplyState in case a fault is raised */ - StateEnum mStateBeforeFault = StateEnum::kUnknownEnumValue; - SupplyStateEnum mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue; + StateEnum mStateBeforeFault = StateEnum::kUnknownEnumValue; + SupplyStateEnum mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue; + SupplyStateEnum mSupplyStateBeforeDiagnostics = SupplyStateEnum::kUnknownEnumValue; /* Callback related */ EVSECallbackWrapper mCallbacks = { .handler = nullptr, .arg = 0 }; /* Wrapper to allow callbacks to be registered */ diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index 8d4279601bd92a..c71db0047b8f20 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -181,6 +181,33 @@ void SetTestEventTrigger_EVChargeDemandClear() dg->HwSetState(sEVSETestEventSaveData.mOldHwStatePluggedInDemand); } +void SetTestEventTrigger_EVSEGroundFault() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetFault(FaultStateEnum::kGroundFault); +} + +void SetTestEventTrigger_EVSEOverTemperatureFault() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetFault(FaultStateEnum::kOverTemperature); +} + +void SetTestEventTrigger_EVSEFaultClear() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetFault(FaultStateEnum::kNoError); +} + +void SetTestEventTrigger_EVSEDiagnosticsComplete() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwDiagnosticsComplete(); +} bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) { @@ -212,9 +239,24 @@ bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV Charge NoDemand"); SetTestEventTrigger_EVChargeDemandClear(); break; + case EnergyEvseTrigger::kEVSEGroundFault: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE has a GroundFault fault"); + SetTestEventTrigger_EVSEGroundFault(); + break; + case EnergyEvseTrigger::kEVSEOverTemperatureFault: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE has a OverTemperature fault"); + SetTestEventTrigger_EVSEOverTemperatureFault(); + break; + case EnergyEvseTrigger::kEVSEFaultClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE faults have cleared"); + SetTestEventTrigger_EVSEFaultClear(); + break; + case EnergyEvseTrigger::kEVSEDiagnosticsComplete: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE Diagnostics Completed"); + SetTestEventTrigger_EVSEDiagnosticsComplete(); + break; default: - return false; } diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 602d136fe4dd33..468b6d5536a678 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -138,18 +138,31 @@ Status EnergyEvseDelegate::EnableDischarging(const DataModel::Nullable /** * @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 (mSupplyState == SupplyStateEnum::kDisabledDiagnostics) + { + ChipLogError(AppServer, "EVSE: Already in diagnostics mode!"); + return Status::Failure; + } + /* update SupplyState to indicate we are now in Diagnostics mode */ - // TODO save the value of SupplyState so it can be restored later - SetSupplyState(SupplyStateEnum::kDisabledDiagnostics); + if (mSupplyStateBeforeDiagnostics != SupplyStateEnum::kUnknownEnumValue) + { + ChipLogError(AppServer, "EVSE: Something wrong trying to go into diagnostics mode"); + return Status::Failure; + } - // TODO Application code would need to put the SupplyState back - // once diagnostics have been completed + mSupplyStateBeforeDiagnostics = mSupplyState; + // Update the SupplyState - this will automatically callback the Application StateChanged callback + SetSupplyState(SupplyStateEnum::kDisabledDiagnostics); return Status::Success; } @@ -410,6 +423,25 @@ Status EnergyEvseDelegate::HwSetVehicleID(const CharSpan & newValue) return Status::Success; } +/** + * @brief Called by EVSE Hardware to indicate that it has finished its diagnostics test + */ +Status EnergyEvseDelegate::HwDiagnosticsComplete() +{ + if (mSupplyState != SupplyStateEnum::kDisabledDiagnostics) + { + ChipLogError(AppServer, "Incorrect state to be completing diagnostics"); + return Status::Failure; + } + + /* Restore the SupplyState to the saved state before diagnostics were triggered */ + SetSupplyState(mSupplyStateBeforeDiagnostics); + + /* Set the sentinel back for checking another diagnostics command */ + mSupplyStateBeforeDiagnostics = SupplyStateEnum::kUnknownEnumValue; + + return Status::Success; +} /* --------------------------------------------------------------------------- * Functions below are private helper functions internal to the delegate */ diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h index 7f199b21cdc255..cb9abd29da77f4 100644 --- a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h @@ -32,14 +32,27 @@ namespace chip { enum class EnergyEvseTrigger : uint64_t { // Scenarios - kBasicFunctionality = 0x0099000000000000, // Basic Functionality Test Event | Simulate installation with - // _{A_CIRCUIT_CAPACITY}_=32A and _{A_USER_MAXIMUM_CHARGE_CURRENT}_=32A - kBasicFunctionalityClear = 0x0099000000000001, // Basic Functionality Test Event Clear | End simulation of installation - kEVPluggedIn = 0x0099000000000002, // EV Plugged-in Test Event | Simulate plugging - // the EV into the EVSE using a cable of 63A capacity - kEVPluggedInClear = 0x0099000000000003, // EV Plugged-in Test Event Clear | Simulate unplugging the EV - kEVChargeDemand = 0x0099000000000004, // EV Charge Demand Test Event | Simulate the EV presenting charge demand to the EVSE - kEVChargeDemandClear = 0x0099000000000005, // EV Charge Demand Test Event Clear | Simulate the EV becoming fully charged + // Basic Functionality Test Event | Simulate installation with _{A_CIRCUIT_CAPACITY}_=32A and + // _{A_USER_MAXIMUM_CHARGE_CURRENT}_=32A + kBasicFunctionality = 0x0099000000000000, + // Basic Functionality Test Event Clear | End simulation of installation + kBasicFunctionalityClear = 0x0099000000000001, + // EV Plugged-in Test Event | Simulate plugging the EV into the EVSE using a cable of 63A capacity + kEVPluggedIn = 0x0099000000000002, + // EV Plugged-in Test Event Clear | Simulate unplugging the EV + kEVPluggedInClear = 0x0099000000000003, + // EV Charge Demand Test Event | Simulate the EV presenting charge demand to the EVSE + kEVChargeDemand = 0x0099000000000004, + // EV Charge Demand Test Event Clear | Simulate the EV becoming fully charged + kEVChargeDemandClear = 0x0099000000000005, + // EVSE has a GroundFault fault + kEVSEGroundFault = 0x0099000000000010, + // EVSE has a OverTemperature fault + kEVSEOverTemperatureFault = 0x0099000000000011, + // EVSE faults have cleared + kEVSEFaultClear = 0x0099000000000012, + // EVSE Diagnostics Complete | Simulate diagnostics have been completed and return to normal + kEVSEDiagnosticsComplete = 0x0099000000000020, }; class EnergyEvseTestEventTriggerDelegate : public TestEventTriggerDelegate From 309b8f6932f6fb2db1471f3235be80bf1fd244a2 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Tue, 9 Jan 2024 14:42:38 +0000 Subject: [PATCH 26/52] Added TC_EEVSE_2_4 --- src/python_testing/TC_EEVSE_2_4.py | 344 +++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 src/python_testing/TC_EEVSE_2_4.py diff --git a/src/python_testing/TC_EEVSE_2_4.py b/src/python_testing/TC_EEVSE_2_4.py new file mode 100644 index 00000000000000..e07b6602a9f000 --- /dev/null +++ b/src/python_testing/TC_EEVSE_2_4.py @@ -0,0 +1,344 @@ +# +# Copyright (c) 2023 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. + + +import logging +import queue +import time +from datetime import datetime, timedelta, timezone + +import chip.clusters as Clusters +from chip.clusters import ClusterObjects as ClusterObjects +from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction +from chip.clusters.Types import NullValue +from chip.interaction_model import InteractionModelError, Status +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + +logger = logging.getLogger(__name__) + + +class EventChangeCallback: + def __init__(self, expected_cluster: ClusterObjects): + self._q = queue.Queue() + self._expected_cluster = expected_cluster + + async def start(self, dev_ctrl, nodeid): + self._subscription = await dev_ctrl.ReadEvent(nodeid, + events=[(1, self._expected_cluster, 1)], reportInterval=(1, 5), + fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) + self._subscription.SetEventUpdateCallback(self.__call__) + + def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): + if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: + logging.info( + f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') + self._q.put(res) + + def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): + try: + res = self._q.get(block=True, timeout=10) + except queue.Empty: + asserts.fail("Failed to receive a report for the event {}".format(expected_event)) + + asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report") + asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report") + return res.Data + + +class TC_EEVSE_2_4(MatterBaseTest): + async def read_evse_attribute_expect_success(self, endpoint, attribute): + full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) + cluster = Clusters.Objects.EnergyEvse + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + async def check_evse_attribute(self, attribute, expected_value): + value = await self.read_evse_attribute_expect_success(endpoint=1, attribute=attribute) + asserts.assert_equal(value, expected_value, + f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") + + async def get_supported_energy_evse_attributes(self, endpoint): + return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") + + async def write_user_max_charge(self, endpoint, user_max_charge): + result = await self.default_controller.WriteAttribute(self.dut_node_id, + [(endpoint, + Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent(user_max_charge))]) + asserts.assert_equal(result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") + + async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 3000, + min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( + chargingEnabledUntil=charge_until, + minimumChargeCurrent=min_charge, + maximumChargeCurrent=max_charge), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + async def send_disable_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + def desc_TC_EEVSE_2_4(self) -> str: + """Returns a description of this test""" + return "5.1.XXX. [TC-EEVSE-2.4] Fault test functionality with DUT as Server" + + def pics_TC_EEVSE_2_4(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + # In this case - there is no feature flags needed to run this test case + return None + + def steps_TC_EEVSE_2_4(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event"), + TestStep("3a", "After a few seconds TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)"), + TestStep("4", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event. Verify Event EEVSE.S.E00(EVConnected) sent"), + TestStep("4a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), + TestStep("4b", "TH reads from the DUT the SessionID attribute. Value is saved for later"), + TestStep("5", "TH sends command EnableCharging with ChargingEnabledUntil=Null, minimumChargeCurrent=6000, maximumChargeCurrent=60000"), + TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), + TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x3 (PluggedInCharging)"), + TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x1 (ChargingEnabled)"), + TestStep("7", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EVSE Ground Fault Test Event. Verify Event EEVSE.S.E04(Fault) sent with SessionID matching value in step 4b, FaultStatePreviousFaultState = 0x00 (NoError), FaultStateCurrentFaultState = 0x07 (GroundFault)"), + TestStep("7a", "TH reads from the DUT the State attribute. Verify value is 0x6 (Fault)"), + TestStep("7b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x4 (DisabledError)"), + TestStep("8", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EVSE Over Temperature Fault Test Event. Verify Event EEVSE.S.E04(Fault) sent with SessionID matching value in step 4b, FaultStatePreviousFaultState = 0x07 (GroundFault), FaultStateCurrentFaultState = 0x0F (OverTemperature)"), + TestStep("8a", "TH reads from the DUT the State attribute. Verify value is 0x6 (Fault)"), + TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x4 (DisabledError)"), + TestStep("9", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EVSE Fault Test Event Clear. Verify Event EEVSE.S.E04(Fault) sent with SessionID matching value in step 4b, FaultStatePreviousFaultState = 0x0F (OverTemperature), FaultStateCurrentFaultState = 0x00 (NoError)"), + TestStep("9a", "TH reads from the DUT the State attribute. Verify value is 0x3 (PluggedInCharging)"), + TestStep("9b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x1 (ChargingEnabled)"), + TestStep("10", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Charge Demand Test Event Clear."), + TestStep("11", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event Clear. Verify Event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("12", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event Clear."), + ] + + return steps + + async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000): + try: + await self.send_single_cmd(endpoint=0, + cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( + enableKey, + eventTrigger) + ) + + except InteractionModelError as e: + asserts.fail(f"Unexpected error returned - {e.status}") + + # TC_EEVSE_2_4 tests steps + async def check_test_event_triggers_enabled(self): + full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled + cluster = Clusters.Objects.GeneralDiagnostics + test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) + asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") + + async def send_test_event_trigger_basic(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000000) + + async def send_test_event_trigger_basic_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000001) + + async def send_test_event_trigger_pluggedin(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000002) + + async def send_test_event_trigger_pluggedin_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000003) + + async def send_test_event_trigger_charge_demand(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000004) + + async def send_test_event_trigger_charge_demand_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000005) + + async def send_test_event_trigger_evse_ground_fault(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000010) + + async def send_test_event_trigger_evse_over_temperature_fault(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000011) + + async def send_test_event_trigger_evse_fault_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000012) + + async def send_test_event_trigger_evse_diagnostics_complete(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000020) + + def validate_energy_transfer_started_event(self, event_data, session_id, expected_state, expected_max_charge): + asserts.assert_equal(session_id, event_data.sessionID, + f"EnergyTransferStarted event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EnergyTransferStarted event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(expected_max_charge, event_data.maximumCurrent, + f"EnergyTransferStarted event maximumCurrent was {event_data.maximumCurrent}, expected {expected_max_charge}") + + def validate_energy_transfer_stopped_event(self, event_data, session_id, expected_state, expected_reason): + asserts.assert_equal(session_id, event_data.sessionID, + f"EnergyTransferStopped event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EnergyTransferStopped event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(expected_reason, event_data.reason, + f"EnergyTransferStopped event reason was {event_data.reason}, expected {expected_reason}") + + def validate_ev_connected_event(self, event_data, session_id): + asserts.assert_equal(session_id, event_data.sessionID, + f"EvConnected event session ID was {event_data.sessionID}, expected {session_id}") + + def validate_ev_not_detected_event(self, event_data, session_id, expected_state, expected_duration, expected_charged): + asserts.assert_equal(session_id, event_data.sessionID, + f"EvNotDetected event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EvNotDetected event event State was {event_data.state} expected {expected_state}") + asserts.assert_greater_equal(event_data.sessionDuration, expected_duration, + f"EvNotDetected event sessionDuration was {event_data.sessionDuration}, expected >= {expected_duration}") + asserts.assert_greater_equal(event_data.sessionEnergyCharged, expected_charged, + f"EvNotDetected event sessionEnergyCharged was {event_data.sessionEnergyCharged}, expected >= {expected_charged}") + + def validate_evse_fault_event(self, event_data, session_id, expected_state, previous_fault, current_fault): + asserts.assert_equal(session_id, event_data.sessionID, + f"Fault event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"Fault event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(event_data.faultStatePreviousState, previous_fault, + f"Fault event faultStatePreviousState was {event_data.faultStatePreviousState}, expected {previous_fault}") + asserts.assert_equal(event_data.faultStateCurrentState, current_fault, + f"Fault event faultStateCurrentState was {event_data.faultStateCurrentState}, expected {current_fault}") + + @async_test_body + async def test_TC_EEVSE_2_4(self): + self.step("1") + # Commission DUT - already done + + # Subscribe to Events and when they are sent push them to a queue for checking later + events_callback = EventChangeCallback(Clusters.EnergyEvse) + await events_callback.start(self.default_controller, self.dut_node_id) + + self.step("2") + await self.check_test_event_triggers_enabled() + + self.step("3") + await self.send_test_event_trigger_basic() + + # After a few seconds... + time.sleep(3) + + self.step("3a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("3b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("3c") + await self.check_evse_attribute("FaultState", Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError) + + self.step("4") + await self.send_test_event_trigger_pluggedin() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVConnected) + + self.step("4a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) + + self.step("4b") + # Save Session ID for later and check it against the value in the event + session_id = await self.read_evse_attribute_expect_success(endpoint=1, attribute="SessionID") + self.validate_ev_connected_event(event_data, session_id) + + self.step("5") + charge_until = NullValue + min_charge_current = 6000 + max_charge_current = 60000 + await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + + self.step("6") + await self.send_test_event_trigger_charge_demand() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + + self.step("6a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("6b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("7") + await self.send_test_event_trigger_evse_ground_fault() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.Fault) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging + previous_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError + current_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kGroundFault + self.validate_evse_fault_event(event_data, session_id, expected_state, previous_fault, current_fault) + + self.step("7a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kFault) + + self.step("7b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledError) + + self.step("8") + await self.send_test_event_trigger_evse_over_temperature_fault() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.Fault) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kFault + previous_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kGroundFault + current_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kOverTemperature + self.validate_evse_fault_event(event_data, session_id, expected_state, previous_fault, current_fault) + + self.step("8a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kFault) + + self.step("8b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledError) + + self.step("9") + await self.send_test_event_trigger_evse_fault_clear() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.Fault) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kFault + previous_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kOverTemperature + current_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError + self.validate_evse_fault_event(event_data, session_id, expected_state, previous_fault, current_fault) + + self.step("9a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("9b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("10") + await self.send_test_event_trigger_charge_demand_clear() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + + self.step("11") + await self.send_test_event_trigger_pluggedin_clear() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVNotDetected) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand + self.validate_ev_not_detected_event(event_data, session_id, expected_state, expected_duration=0, expected_charged=0) + + self.step("12") + await self.send_test_event_trigger_basic_clear() + + +if __name__ == "__main__": + default_matter_test_main() From 9b02ef8c14a49702cf030ca4d6342e6492f98b7b Mon Sep 17 00:00:00 2001 From: James Harrow Date: Tue, 9 Jan 2024 17:00:08 +0000 Subject: [PATCH 27/52] Fix lint issue - unused datetime modules. --- src/python_testing/TC_EEVSE_2_4.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python_testing/TC_EEVSE_2_4.py b/src/python_testing/TC_EEVSE_2_4.py index e07b6602a9f000..a686f4a657f572 100644 --- a/src/python_testing/TC_EEVSE_2_4.py +++ b/src/python_testing/TC_EEVSE_2_4.py @@ -18,7 +18,6 @@ import logging import queue import time -from datetime import datetime, timedelta, timezone import chip.clusters as Clusters from chip.clusters import ClusterObjects as ClusterObjects From 925a701a12e76b6587027973737294a42fae084b Mon Sep 17 00:00:00 2001 From: James Harrow Date: Wed, 10 Jan 2024 22:58:03 +0000 Subject: [PATCH 28/52] Added TC_EEVSE_2_5.py to support DiagnosticsCommand testing. Also changed the SupplyState reverting to Disabled once diagnostics is complete to match the spec. --- .../include/EnergyEvseDelegateImpl.h | 5 +- .../src/EnergyEvseDelegateImpl.cpp | 16 +- src/python_testing/TC_EEVSE_2_5.py | 216 ++++++++++++++++++ 3 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 src/python_testing/TC_EEVSE_2_5.py diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 97da46322fd5d4..dc46e58d5021a7 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -227,9 +227,8 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate StateEnum mHwState = StateEnum::kNotPluggedIn; /* Hardware state */ /* Variables to hold State and SupplyState in case a fault is raised */ - StateEnum mStateBeforeFault = StateEnum::kUnknownEnumValue; - SupplyStateEnum mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue; - SupplyStateEnum mSupplyStateBeforeDiagnostics = SupplyStateEnum::kUnknownEnumValue; + StateEnum mStateBeforeFault = StateEnum::kUnknownEnumValue; + SupplyStateEnum mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue; /* Callback related */ EVSECallbackWrapper mCallbacks = { .handler = nullptr, .arg = 0 }; /* Wrapper to allow callbacks to be registered */ diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 468b6d5536a678..095d8914660cd4 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -153,14 +153,6 @@ Status EnergyEvseDelegate::StartDiagnostics() return Status::Failure; } - /* update SupplyState to indicate we are now in Diagnostics mode */ - if (mSupplyStateBeforeDiagnostics != SupplyStateEnum::kUnknownEnumValue) - { - ChipLogError(AppServer, "EVSE: Something wrong trying to go into diagnostics mode"); - return Status::Failure; - } - - mSupplyStateBeforeDiagnostics = mSupplyState; // Update the SupplyState - this will automatically callback the Application StateChanged callback SetSupplyState(SupplyStateEnum::kDisabledDiagnostics); @@ -434,11 +426,9 @@ Status EnergyEvseDelegate::HwDiagnosticsComplete() return Status::Failure; } - /* Restore the SupplyState to the saved state before diagnostics were triggered */ - SetSupplyState(mSupplyStateBeforeDiagnostics); - - /* Set the sentinel back for checking another diagnostics command */ - mSupplyStateBeforeDiagnostics = SupplyStateEnum::kUnknownEnumValue; + /* Restore the SupplyState to Disabled (per spec) - client will need to + * re-enable charging or discharging to get out of this state */ + SetSupplyState(SupplyStateEnum::kDisabled); return Status::Success; } diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py new file mode 100644 index 00000000000000..78737af4b6637a --- /dev/null +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -0,0 +1,216 @@ +# +# Copyright (c) 2023 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. + + +import logging +import queue +import time + +import chip.clusters as Clusters +from chip.clusters import ClusterObjects as ClusterObjects +from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction +from chip.clusters.Types import NullValue +from chip.interaction_model import InteractionModelError, Status +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + +logger = logging.getLogger(__name__) + + +class EventChangeCallback: + def __init__(self, expected_cluster: ClusterObjects): + self._q = queue.Queue() + self._expected_cluster = expected_cluster + + async def start(self, dev_ctrl, nodeid): + self._subscription = await dev_ctrl.ReadEvent(nodeid, + events=[(1, self._expected_cluster, 1)], reportInterval=(1, 5), + fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) + self._subscription.SetEventUpdateCallback(self.__call__) + + def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): + if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: + logging.info( + f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') + self._q.put(res) + + def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): + try: + res = self._q.get(block=True, timeout=10) + except queue.Empty: + asserts.fail("Failed to receive a report for the event {}".format(expected_event)) + + asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report") + asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report") + return res.Data + + +class TC_EEVSE_2_5(MatterBaseTest): + async def read_evse_attribute_expect_success(self, endpoint, attribute): + full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) + cluster = Clusters.Objects.EnergyEvse + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + async def check_evse_attribute(self, attribute, expected_value): + value = await self.read_evse_attribute_expect_success(endpoint=1, attribute=attribute) + asserts.assert_equal(value, expected_value, + f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") + + async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 3000, + min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( + chargingEnabledUntil=charge_until, + minimumChargeCurrent=min_charge, + maximumChargeCurrent=max_charge), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + async def send_start_diagnostics_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, + expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.StartDiagnostics(), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + def desc_TC_EEVSE_2_5(self) -> str: + """Returns a description of this test""" + return "5.1.XXX. [TC-EEVSE-2.4] Fault test functionality with DUT as Server" + + def pics_TC_EEVSE_2_5(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + # In this case - there is no feature flags needed to run this test case + return None + + def steps_TC_EEVSE_2_5(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event"), + TestStep("3a", "After a few seconds TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)"), + TestStep("4", "TH sends command EnableCharging with ChargingEnabledUntil=Null, minimumChargeCurrent=6000, maximumChargeCurrent=60000"), + TestStep("4a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("4b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (ChargingEnabled)"), + TestStep("5", "TH sends command StartDiagnostics"), + TestStep("5a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("5b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (DisabledDiagnostics)"), + TestStep("6", "A few seconds later TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EVSE Diagnostics Complete Event"), + TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (Disabled)"), + TestStep("7", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event Clear."), + ] + + return steps + + async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000): + try: + await self.send_single_cmd(endpoint=0, + cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( + enableKey, + eventTrigger) + ) + + except InteractionModelError as e: + asserts.fail(f"Unexpected error returned - {e.status}") + + # TC_EEVSE_2_5 tests steps + async def check_test_event_triggers_enabled(self): + full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled + cluster = Clusters.Objects.GeneralDiagnostics + test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) + asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") + + async def send_test_event_trigger_basic(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000000) + + async def send_test_event_trigger_basic_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000001) + + async def send_test_event_trigger_evse_diagnostics_complete(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000020) + + @async_test_body + async def test_TC_EEVSE_2_5(self): + self.step("1") + # Commission DUT - already done + + # Subscribe to Events and when they are sent push them to a queue for checking later + events_callback = EventChangeCallback(Clusters.EnergyEvse) + await events_callback.start(self.default_controller, self.dut_node_id) + + self.step("2") + await self.check_test_event_triggers_enabled() + + self.step("3") + await self.send_test_event_trigger_basic() + + # After a few seconds... + time.sleep(1) + + self.step("3a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("3b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("3c") + await self.check_evse_attribute("FaultState", Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError) + + self.step("4") + charge_until = NullValue + min_charge_current = 6000 + max_charge_current = 60000 + await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + + self.step("4a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("4b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("5") + await self.send_start_diagnostics_command(endpoint=1) + + self.step("5a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("5b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledDiagnostics) + + self.step("6") + await self.send_test_event_trigger_evse_diagnostics_complete() + + self.step("6a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("6b") + # It should go to disabled after a diagnostics session + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("7") + await self.send_test_event_trigger_basic_clear() + + +if __name__ == "__main__": + default_matter_test_main() From bd83ba4f2dd8fbf49e508e7af59d4750a6c495c4 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Thu, 11 Jan 2024 01:39:24 +0000 Subject: [PATCH 29/52] Created a helper EEVSE base class to avoid repetition in the different test cases. --- src/python_testing/TC_EEVSE_2_2.py | 135 +------------------ src/python_testing/TC_EEVSE_2_4.py | 160 +--------------------- src/python_testing/TC_EEVSE_2_5.py | 91 +------------ src/python_testing/TC_EEVSE_Utils.py | 191 +++++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 375 deletions(-) create mode 100644 src/python_testing/TC_EEVSE_Utils.py diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index faac9004e6909b..f92e577a9315ae 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -28,50 +28,12 @@ from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts -logger = logging.getLogger(__name__) - - -class EventChangeCallback: - def __init__(self, expected_cluster: ClusterObjects): - self._q = queue.Queue() - self._expected_cluster = expected_cluster - - async def start(self, dev_ctrl, nodeid): - self._subscription = await dev_ctrl.ReadEvent(nodeid, - events=[(1, self._expected_cluster, 1)], reportInterval=(1, 5), - fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) - self._subscription.SetEventUpdateCallback(self.__call__) - - def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): - if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: - logging.info( - f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') - self._q.put(res) - - def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): - try: - res = self._q.get(block=True, timeout=10) - except queue.Empty: - asserts.fail("Failed to receive a report for the event {}".format(expected_event)) - - asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report") - asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report") - return res.Data - +from TC_EEVSE_Utils import EventChangeCallback, EEVSEBaseTestHelper -class TC_EEVSE_2_2(MatterBaseTest): - async def read_evse_attribute_expect_success(self, endpoint, attribute): - full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) - cluster = Clusters.Objects.EnergyEvse - return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) +logger = logging.getLogger(__name__) - async def check_evse_attribute(self, attribute, expected_value): - value = await self.read_evse_attribute_expect_success(endpoint=1, attribute=attribute) - asserts.assert_equal(value, expected_value, - f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") - async def get_supported_energy_evse_attributes(self, endpoint): - return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") +class TC_EEVSE_2_2(MatterBaseTest, EEVSEBaseTestHelper): async def write_user_max_charge(self, endpoint, user_max_charge): result = await self.default_controller.WriteAttribute(self.dut_node_id, @@ -79,28 +41,6 @@ async def write_user_max_charge(self, endpoint, user_max_charge): Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent(user_max_charge))]) asserts.assert_equal(result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") - async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 3000, - min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): - try: - await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( - chargingEnabledUntil=charge_until, - minimumChargeCurrent=min_charge, - maximumChargeCurrent=max_charge), - endpoint=1, - timedRequestTimeoutMs=timedRequestTimeoutMs) - - except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - - async def send_disable_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): - try: - await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), - endpoint=1, - timedRequestTimeoutMs=timedRequestTimeoutMs) - - except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - def desc_TC_EEVSE_2_2(self) -> str: """Returns a description of this test""" return "5.1.3. [TC-EEVSE-2.2] Primary functionality with DUT as Server" @@ -162,74 +102,9 @@ def steps_TC_EEVSE_2_2(self) -> list[TestStep]: return steps - async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000): - try: - await self.send_single_cmd(endpoint=0, - cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( - enableKey, - eventTrigger) - ) - - except InteractionModelError as e: - asserts.fail(f"Unexpected error returned - {e.status}") - - # TC_EEVSE_2_2 tests steps - async def check_test_event_triggers_enabled(self): - full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled - cluster = Clusters.Objects.GeneralDiagnostics - test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) - asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") - - async def send_test_event_trigger_basic(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000000) - - async def send_test_event_trigger_basic_clear(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000001) - - async def send_test_event_trigger_pluggedin(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000002) - - async def send_test_event_trigger_pluggedin_clear(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000003) - - async def send_test_event_trigger_charge_demand(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000004) - - async def send_test_event_trigger_charge_demand_clear(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000005) - - def validate_energy_transfer_started_event(self, event_data, session_id, expected_state, expected_max_charge): - asserts.assert_equal(session_id, event_data.sessionID, - f"EnergyTransferStarted event session ID was {event_data.sessionID}, expected {session_id}") - asserts.assert_equal(expected_state, event_data.state, - f"EnergyTransferStarted event State was {event_data.state} expected {expected_state}") - asserts.assert_equal(expected_max_charge, event_data.maximumCurrent, - f"EnergyTransferStarted event maximumCurrent was {event_data.maximumCurrent}, expected {expected_max_charge}") - - def validate_energy_transfer_stopped_event(self, event_data, session_id, expected_state, expected_reason): - asserts.assert_equal(session_id, event_data.sessionID, - f"EnergyTransferStopped event session ID was {event_data.sessionID}, expected {session_id}") - asserts.assert_equal(expected_state, event_data.state, - f"EnergyTransferStopped event State was {event_data.state} expected {expected_state}") - asserts.assert_equal(expected_reason, event_data.reason, - f"EnergyTransferStopped event reason was {event_data.reason}, expected {expected_reason}") - - def validate_ev_connected_event(self, event_data, session_id): - asserts.assert_equal(session_id, event_data.sessionID, - f"EvConnected event session ID was {event_data.sessionID}, expected {session_id}") - - def validate_ev_not_detected_event(self, event_data, session_id, expected_state, expected_duration, expected_charged): - asserts.assert_equal(session_id, event_data.sessionID, - f"EvNotDetected event session ID was {event_data.sessionID}, expected {session_id}") - asserts.assert_equal(expected_state, event_data.state, - f"EvNotDetected event event State was {event_data.state} expected {expected_state}") - asserts.assert_greater_equal(event_data.sessionDuration, expected_duration, - f"EvNotDetected event sessionDuration was {event_data.sessionDuration}, expected >= {expected_duration}") - asserts.assert_greater_equal(event_data.sessionEnergyCharged, expected_charged, - f"EvNotDetected event sessionEnergyCharged was {event_data.sessionEnergyCharged}, expected >= {expected_charged}") - @async_test_body async def test_TC_EEVSE_2_2(self): + self.step("1") # Commission DUT - already done @@ -244,7 +119,7 @@ async def test_TC_EEVSE_2_2(self): await self.send_test_event_trigger_basic() # After a few seconds... - time.sleep(3) + time.sleep(1) self.step("3a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) diff --git a/src/python_testing/TC_EEVSE_2_4.py b/src/python_testing/TC_EEVSE_2_4.py index a686f4a657f572..3e7e947c60d9fb 100644 --- a/src/python_testing/TC_EEVSE_2_4.py +++ b/src/python_testing/TC_EEVSE_2_4.py @@ -27,78 +27,12 @@ from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts +from TC_EEVSE_Utils import EventChangeCallback, EEVSEBaseTestHelper + logger = logging.getLogger(__name__) -class EventChangeCallback: - def __init__(self, expected_cluster: ClusterObjects): - self._q = queue.Queue() - self._expected_cluster = expected_cluster - - async def start(self, dev_ctrl, nodeid): - self._subscription = await dev_ctrl.ReadEvent(nodeid, - events=[(1, self._expected_cluster, 1)], reportInterval=(1, 5), - fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) - self._subscription.SetEventUpdateCallback(self.__call__) - - def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): - if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: - logging.info( - f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') - self._q.put(res) - - def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): - try: - res = self._q.get(block=True, timeout=10) - except queue.Empty: - asserts.fail("Failed to receive a report for the event {}".format(expected_event)) - - asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report") - asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report") - return res.Data - - -class TC_EEVSE_2_4(MatterBaseTest): - async def read_evse_attribute_expect_success(self, endpoint, attribute): - full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) - cluster = Clusters.Objects.EnergyEvse - return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) - - async def check_evse_attribute(self, attribute, expected_value): - value = await self.read_evse_attribute_expect_success(endpoint=1, attribute=attribute) - asserts.assert_equal(value, expected_value, - f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") - - async def get_supported_energy_evse_attributes(self, endpoint): - return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") - - async def write_user_max_charge(self, endpoint, user_max_charge): - result = await self.default_controller.WriteAttribute(self.dut_node_id, - [(endpoint, - Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent(user_max_charge))]) - asserts.assert_equal(result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") - - async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 3000, - min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): - try: - await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( - chargingEnabledUntil=charge_until, - minimumChargeCurrent=min_charge, - maximumChargeCurrent=max_charge), - endpoint=1, - timedRequestTimeoutMs=timedRequestTimeoutMs) - - except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - - async def send_disable_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): - try: - await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), - endpoint=1, - timedRequestTimeoutMs=timedRequestTimeoutMs) - - except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") +class TC_EEVSE_2_4(MatterBaseTest, EEVSEBaseTestHelper): def desc_TC_EEVSE_2_4(self) -> str: """Returns a description of this test""" @@ -140,94 +74,6 @@ def steps_TC_EEVSE_2_4(self) -> list[TestStep]: return steps - async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000): - try: - await self.send_single_cmd(endpoint=0, - cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( - enableKey, - eventTrigger) - ) - - except InteractionModelError as e: - asserts.fail(f"Unexpected error returned - {e.status}") - - # TC_EEVSE_2_4 tests steps - async def check_test_event_triggers_enabled(self): - full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled - cluster = Clusters.Objects.GeneralDiagnostics - test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) - asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") - - async def send_test_event_trigger_basic(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000000) - - async def send_test_event_trigger_basic_clear(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000001) - - async def send_test_event_trigger_pluggedin(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000002) - - async def send_test_event_trigger_pluggedin_clear(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000003) - - async def send_test_event_trigger_charge_demand(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000004) - - async def send_test_event_trigger_charge_demand_clear(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000005) - - async def send_test_event_trigger_evse_ground_fault(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000010) - - async def send_test_event_trigger_evse_over_temperature_fault(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000011) - - async def send_test_event_trigger_evse_fault_clear(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000012) - - async def send_test_event_trigger_evse_diagnostics_complete(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000020) - - def validate_energy_transfer_started_event(self, event_data, session_id, expected_state, expected_max_charge): - asserts.assert_equal(session_id, event_data.sessionID, - f"EnergyTransferStarted event session ID was {event_data.sessionID}, expected {session_id}") - asserts.assert_equal(expected_state, event_data.state, - f"EnergyTransferStarted event State was {event_data.state} expected {expected_state}") - asserts.assert_equal(expected_max_charge, event_data.maximumCurrent, - f"EnergyTransferStarted event maximumCurrent was {event_data.maximumCurrent}, expected {expected_max_charge}") - - def validate_energy_transfer_stopped_event(self, event_data, session_id, expected_state, expected_reason): - asserts.assert_equal(session_id, event_data.sessionID, - f"EnergyTransferStopped event session ID was {event_data.sessionID}, expected {session_id}") - asserts.assert_equal(expected_state, event_data.state, - f"EnergyTransferStopped event State was {event_data.state} expected {expected_state}") - asserts.assert_equal(expected_reason, event_data.reason, - f"EnergyTransferStopped event reason was {event_data.reason}, expected {expected_reason}") - - def validate_ev_connected_event(self, event_data, session_id): - asserts.assert_equal(session_id, event_data.sessionID, - f"EvConnected event session ID was {event_data.sessionID}, expected {session_id}") - - def validate_ev_not_detected_event(self, event_data, session_id, expected_state, expected_duration, expected_charged): - asserts.assert_equal(session_id, event_data.sessionID, - f"EvNotDetected event session ID was {event_data.sessionID}, expected {session_id}") - asserts.assert_equal(expected_state, event_data.state, - f"EvNotDetected event event State was {event_data.state} expected {expected_state}") - asserts.assert_greater_equal(event_data.sessionDuration, expected_duration, - f"EvNotDetected event sessionDuration was {event_data.sessionDuration}, expected >= {expected_duration}") - asserts.assert_greater_equal(event_data.sessionEnergyCharged, expected_charged, - f"EvNotDetected event sessionEnergyCharged was {event_data.sessionEnergyCharged}, expected >= {expected_charged}") - - def validate_evse_fault_event(self, event_data, session_id, expected_state, previous_fault, current_fault): - asserts.assert_equal(session_id, event_data.sessionID, - f"Fault event session ID was {event_data.sessionID}, expected {session_id}") - asserts.assert_equal(expected_state, event_data.state, - f"Fault event State was {event_data.state} expected {expected_state}") - asserts.assert_equal(event_data.faultStatePreviousState, previous_fault, - f"Fault event faultStatePreviousState was {event_data.faultStatePreviousState}, expected {previous_fault}") - asserts.assert_equal(event_data.faultStateCurrentState, current_fault, - f"Fault event faultStateCurrentState was {event_data.faultStateCurrentState}, expected {current_fault}") - @async_test_body async def test_TC_EEVSE_2_4(self): self.step("1") diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py index 78737af4b6637a..112c6552627053 100644 --- a/src/python_testing/TC_EEVSE_2_5.py +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -27,70 +27,12 @@ from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts +from TC_EEVSE_Utils import EventChangeCallback, EEVSEBaseTestHelper + logger = logging.getLogger(__name__) -class EventChangeCallback: - def __init__(self, expected_cluster: ClusterObjects): - self._q = queue.Queue() - self._expected_cluster = expected_cluster - - async def start(self, dev_ctrl, nodeid): - self._subscription = await dev_ctrl.ReadEvent(nodeid, - events=[(1, self._expected_cluster, 1)], reportInterval=(1, 5), - fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) - self._subscription.SetEventUpdateCallback(self.__call__) - - def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): - if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: - logging.info( - f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') - self._q.put(res) - - def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): - try: - res = self._q.get(block=True, timeout=10) - except queue.Empty: - asserts.fail("Failed to receive a report for the event {}".format(expected_event)) - - asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report") - asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report") - return res.Data - - -class TC_EEVSE_2_5(MatterBaseTest): - async def read_evse_attribute_expect_success(self, endpoint, attribute): - full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) - cluster = Clusters.Objects.EnergyEvse - return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) - - async def check_evse_attribute(self, attribute, expected_value): - value = await self.read_evse_attribute_expect_success(endpoint=1, attribute=attribute) - asserts.assert_equal(value, expected_value, - f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") - - async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 3000, - min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): - try: - await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( - chargingEnabledUntil=charge_until, - minimumChargeCurrent=min_charge, - maximumChargeCurrent=max_charge), - endpoint=1, - timedRequestTimeoutMs=timedRequestTimeoutMs) - - except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - - async def send_start_diagnostics_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, - expected_status: Status = Status.Success): - try: - await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.StartDiagnostics(), - endpoint=1, - timedRequestTimeoutMs=timedRequestTimeoutMs) - - except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") +class TC_EEVSE_2_5(MatterBaseTest, EEVSEBaseTestHelper): def desc_TC_EEVSE_2_5(self) -> str: """Returns a description of this test""" @@ -123,33 +65,6 @@ def steps_TC_EEVSE_2_5(self) -> list[TestStep]: return steps - async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000): - try: - await self.send_single_cmd(endpoint=0, - cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( - enableKey, - eventTrigger) - ) - - except InteractionModelError as e: - asserts.fail(f"Unexpected error returned - {e.status}") - - # TC_EEVSE_2_5 tests steps - async def check_test_event_triggers_enabled(self): - full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled - cluster = Clusters.Objects.GeneralDiagnostics - test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) - asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") - - async def send_test_event_trigger_basic(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000000) - - async def send_test_event_trigger_basic_clear(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000001) - - async def send_test_event_trigger_evse_diagnostics_complete(self): - await self.send_test_event_triggers(eventTrigger=0x0099000000000020) - @async_test_body async def test_TC_EEVSE_2_5(self): self.step("1") diff --git a/src/python_testing/TC_EEVSE_Utils.py b/src/python_testing/TC_EEVSE_Utils.py new file mode 100644 index 00000000000000..dfd788b6e3b111 --- /dev/null +++ b/src/python_testing/TC_EEVSE_Utils.py @@ -0,0 +1,191 @@ +# +# Copyright (c) 2023 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. + + +import logging +import queue + +import chip.clusters as Clusters +from chip.clusters import ClusterObjects as ClusterObjects +from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction +from chip.interaction_model import InteractionModelError, Status +from matter_testing_support import MatterBaseTest +from mobly import asserts + +logger = logging.getLogger(__name__) + + +class EventChangeCallback: + def __init__(self, expected_cluster: ClusterObjects): + self._q = queue.Queue() + self._expected_cluster = expected_cluster + + async def start(self, dev_ctrl, nodeid): + self._subscription = await dev_ctrl.ReadEvent(nodeid, + events=[(1, self._expected_cluster, 1)], reportInterval=(1, 5), + fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) + self._subscription.SetEventUpdateCallback(self.__call__) + + def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): + if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: + logging.info( + f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') + self._q.put(res) + + def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): + try: + res = self._q.get(block=True, timeout=10) + except queue.Empty: + asserts.fail("Failed to receive a report for the event {}".format(expected_event)) + + asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report") + asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report") + return res.Data + + +class EEVSEBaseTestHelper: + + async def read_evse_attribute_expect_success(self, endpoint, attribute): + full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) + cluster = Clusters.Objects.EnergyEvse + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + async def check_evse_attribute(self, attribute, expected_value): + value = await self.read_evse_attribute_expect_success(endpoint=1, attribute=attribute) + asserts.assert_equal(value, expected_value, + f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") + + async def get_supported_energy_evse_attributes(self, endpoint): + return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") + + async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 3000, + min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( + chargingEnabledUntil=charge_until, + minimumChargeCurrent=min_charge, + maximumChargeCurrent=max_charge), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + async def send_disable_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + async def send_start_diagnostics_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, + expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.StartDiagnostics(), + endpoint=1, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000): + try: + await self.send_single_cmd(endpoint=0, + cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( + enableKey, + eventTrigger) + ) + + except InteractionModelError as e: + asserts.fail(f"Unexpected error returned - {e.status}") + + async def check_test_event_triggers_enabled(self): + full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled + cluster = Clusters.Objects.GeneralDiagnostics + test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) + asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") + + async def send_test_event_trigger_basic(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000000) + + async def send_test_event_trigger_basic_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000001) + + async def send_test_event_trigger_pluggedin(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000002) + + async def send_test_event_trigger_pluggedin_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000003) + + async def send_test_event_trigger_charge_demand(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000004) + + async def send_test_event_trigger_charge_demand_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000005) + + async def send_test_event_trigger_evse_ground_fault(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000010) + + async def send_test_event_trigger_evse_over_temperature_fault(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000011) + + async def send_test_event_trigger_evse_fault_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000012) + + async def send_test_event_trigger_evse_diagnostics_complete(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000020) + + def validate_energy_transfer_started_event(self, event_data, session_id, expected_state, expected_max_charge): + asserts.assert_equal(session_id, event_data.sessionID, + f"EnergyTransferStarted event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EnergyTransferStarted event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(expected_max_charge, event_data.maximumCurrent, + f"EnergyTransferStarted event maximumCurrent was {event_data.maximumCurrent}, expected {expected_max_charge}") + + def validate_energy_transfer_stopped_event(self, event_data, session_id, expected_state, expected_reason): + asserts.assert_equal(session_id, event_data.sessionID, + f"EnergyTransferStopped event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EnergyTransferStopped event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(expected_reason, event_data.reason, + f"EnergyTransferStopped event reason was {event_data.reason}, expected {expected_reason}") + + def validate_ev_connected_event(self, event_data, session_id): + asserts.assert_equal(session_id, event_data.sessionID, + f"EvConnected event session ID was {event_data.sessionID}, expected {session_id}") + + def validate_ev_not_detected_event(self, event_data, session_id, expected_state, expected_duration, expected_charged): + asserts.assert_equal(session_id, event_data.sessionID, + f"EvNotDetected event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EvNotDetected event event State was {event_data.state} expected {expected_state}") + asserts.assert_greater_equal(event_data.sessionDuration, expected_duration, + f"EvNotDetected event sessionDuration was {event_data.sessionDuration}, expected >= {expected_duration}") + asserts.assert_greater_equal(event_data.sessionEnergyCharged, expected_charged, + f"EvNotDetected event sessionEnergyCharged was {event_data.sessionEnergyCharged}, expected >= {expected_charged}") + + def validate_evse_fault_event(self, event_data, session_id, expected_state, previous_fault, current_fault): + asserts.assert_equal(session_id, event_data.sessionID, + f"Fault event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"Fault event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(event_data.faultStatePreviousState, previous_fault, + f"Fault event faultStatePreviousState was {event_data.faultStatePreviousState}, expected {previous_fault}") + asserts.assert_equal(event_data.faultStateCurrentState, current_fault, + f"Fault event faultStateCurrentState was {event_data.faultStateCurrentState}, expected {current_fault}") From 514e18e0fc09405c0c3aee0964ed13b318e3695a Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Thu, 11 Jan 2024 01:40:19 +0000 Subject: [PATCH 30/52] Restyled by isort --- src/python_testing/TC_EEVSE_2_2.py | 3 +-- src/python_testing/TC_EEVSE_2_4.py | 3 +-- src/python_testing/TC_EEVSE_2_5.py | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index f92e577a9315ae..a27d8cf52ebbf9 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -27,8 +27,7 @@ from chip.interaction_model import InteractionModelError, Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts - -from TC_EEVSE_Utils import EventChangeCallback, EEVSEBaseTestHelper +from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback logger = logging.getLogger(__name__) diff --git a/src/python_testing/TC_EEVSE_2_4.py b/src/python_testing/TC_EEVSE_2_4.py index 3e7e947c60d9fb..92e4d12409a541 100644 --- a/src/python_testing/TC_EEVSE_2_4.py +++ b/src/python_testing/TC_EEVSE_2_4.py @@ -26,8 +26,7 @@ from chip.interaction_model import InteractionModelError, Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts - -from TC_EEVSE_Utils import EventChangeCallback, EEVSEBaseTestHelper +from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback logger = logging.getLogger(__name__) diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py index 112c6552627053..9ea74065fd10bc 100644 --- a/src/python_testing/TC_EEVSE_2_5.py +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -26,8 +26,7 @@ from chip.interaction_model import InteractionModelError, Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts - -from TC_EEVSE_Utils import EventChangeCallback, EEVSEBaseTestHelper +from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback logger = logging.getLogger(__name__) From 21cd6d7bfa2fd459437b728f05f9da525f3f8bbb Mon Sep 17 00:00:00 2001 From: James Harrow Date: Thu, 11 Jan 2024 17:59:05 +0000 Subject: [PATCH 31/52] Fixed Lint issues --- src/python_testing/TC_EEVSE_2_2.py | 5 +---- src/python_testing/TC_EEVSE_2_4.py | 5 ----- src/python_testing/TC_EEVSE_2_5.py | 5 ----- src/python_testing/TC_EEVSE_Utils.py | 1 - 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index a27d8cf52ebbf9..83bc676ed75b7c 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -16,15 +16,12 @@ import logging -import queue import time from datetime import datetime, timedelta, timezone import chip.clusters as Clusters -from chip.clusters import ClusterObjects as ClusterObjects -from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction from chip.clusters.Types import NullValue -from chip.interaction_model import InteractionModelError, Status +from chip.interaction_model import Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback diff --git a/src/python_testing/TC_EEVSE_2_4.py b/src/python_testing/TC_EEVSE_2_4.py index 92e4d12409a541..8a28b562635390 100644 --- a/src/python_testing/TC_EEVSE_2_4.py +++ b/src/python_testing/TC_EEVSE_2_4.py @@ -16,16 +16,11 @@ import logging -import queue import time import chip.clusters as Clusters -from chip.clusters import ClusterObjects as ClusterObjects -from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction from chip.clusters.Types import NullValue -from chip.interaction_model import InteractionModelError, Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main -from mobly import asserts from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback logger = logging.getLogger(__name__) diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py index 9ea74065fd10bc..a9da6c4df8bf94 100644 --- a/src/python_testing/TC_EEVSE_2_5.py +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -16,16 +16,11 @@ import logging -import queue import time import chip.clusters as Clusters -from chip.clusters import ClusterObjects as ClusterObjects -from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction from chip.clusters.Types import NullValue -from chip.interaction_model import InteractionModelError, Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main -from mobly import asserts from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback logger = logging.getLogger(__name__) diff --git a/src/python_testing/TC_EEVSE_Utils.py b/src/python_testing/TC_EEVSE_Utils.py index dfd788b6e3b111..f4916b0900f033 100644 --- a/src/python_testing/TC_EEVSE_Utils.py +++ b/src/python_testing/TC_EEVSE_Utils.py @@ -22,7 +22,6 @@ from chip.clusters import ClusterObjects as ClusterObjects from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction from chip.interaction_model import InteractionModelError, Status -from matter_testing_support import MatterBaseTest from mobly import asserts logger = logging.getLogger(__name__) From 9ab79d154b8892b039c2f99411b81a6d6200991f Mon Sep 17 00:00:00 2001 From: James Harrow Date: Thu, 11 Jan 2024 18:48:34 +0000 Subject: [PATCH 32/52] Revamped TC_EEVSE_2_5 to match spec behaviour (cannot start diagnostics unless Disabled). Also removed hard-coded endpoint ids in Utils --- .../src/EnergyEvseDelegateImpl.cpp | 4 +- src/python_testing/TC_EEVSE_2_5.py | 47 ++++++++++++------- src/python_testing/TC_EEVSE_Utils.py | 20 ++++---- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 095d8914660cd4..98dc99ac22f156 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -147,9 +147,9 @@ Status EnergyEvseDelegate::StartDiagnostics() /* For EVSE manufacturers to customize */ ChipLogProgress(AppServer, "EnergyEvseDelegate::StartDiagnostics()"); - if (mSupplyState == SupplyStateEnum::kDisabledDiagnostics) + if (mSupplyState != SupplyStateEnum::kDisabled) { - ChipLogError(AppServer, "EVSE: Already in diagnostics mode!"); + ChipLogError(AppServer, "EVSE: cannot be put into diagnostics mode if it is not Disabled!"); return Status::Failure; } diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py index a9da6c4df8bf94..27dfca05ced439 100644 --- a/src/python_testing/TC_EEVSE_2_5.py +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -21,6 +21,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from chip.interaction_model import Status from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback logger = logging.getLogger(__name__) @@ -42,19 +43,22 @@ def steps_TC_EEVSE_2_5(self) -> list[TestStep]: TestStep("1", "Commissioning, already done", is_commissioning=True), TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event"), - TestStep("3a", "After a few seconds TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("3a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), TestStep("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), TestStep("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)"), TestStep("4", "TH sends command EnableCharging with ChargingEnabledUntil=Null, minimumChargeCurrent=6000, maximumChargeCurrent=60000"), TestStep("4a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), - TestStep("4b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (ChargingEnabled)"), - TestStep("5", "TH sends command StartDiagnostics"), - TestStep("5a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), - TestStep("5b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (DisabledDiagnostics)"), - TestStep("6", "A few seconds later TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EVSE Diagnostics Complete Event"), + TestStep("4b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x01 (ChargingEnabled)"), + TestStep("5", "TH sends command StartDiagnostics. Verify that command is rejected with Failure"), + TestStep("6", "TH sends command Disable."), TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (Disabled)"), - TestStep("7", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event Clear."), + TestStep("7", "TH sends command StartDiagnostics. Verify that command is accepted with Success"), + TestStep("7a", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (DisabledDiagnostics)"), + TestStep("8", "A few seconds later TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EVSE Diagnostics Complete Event"), + TestStep("8a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (Disabled)"), + TestStep("9", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event Clear."), ] return steps @@ -74,9 +78,6 @@ async def test_TC_EEVSE_2_5(self): self.step("3") await self.send_test_event_trigger_basic() - # After a few seconds... - time.sleep(1) - self.step("3a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) @@ -99,25 +100,35 @@ async def test_TC_EEVSE_2_5(self): await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) self.step("5") - await self.send_start_diagnostics_command(endpoint=1) + # Check we get a failure because the state needs to be Disabled to run a Diagnostic + await self.send_start_diagnostics_command(expected_status=Status.Failure) + + self.step("6") + await self.send_disable_command() - self.step("5a") + self.step("6a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) - self.step("5b") + self.step("6b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("7") + await self.send_start_diagnostics_command() + + self.step("7a") await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledDiagnostics) - self.step("6") + self.step("8") await self.send_test_event_trigger_evse_diagnostics_complete() - self.step("6a") + self.step("8a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) - self.step("6b") - # It should go to disabled after a diagnostics session + self.step("8b") + # It should stay disabled after a diagnostics session await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) - self.step("7") + self.step("9") await self.send_test_event_trigger_basic_clear() diff --git a/src/python_testing/TC_EEVSE_Utils.py b/src/python_testing/TC_EEVSE_Utils.py index f4916b0900f033..d2d5873c37d0b4 100644 --- a/src/python_testing/TC_EEVSE_Utils.py +++ b/src/python_testing/TC_EEVSE_Utils.py @@ -32,9 +32,9 @@ def __init__(self, expected_cluster: ClusterObjects): self._q = queue.Queue() self._expected_cluster = expected_cluster - async def start(self, dev_ctrl, nodeid): + async def start(self, dev_ctrl, nodeid, endpoint: int = 1): self._subscription = await dev_ctrl.ReadEvent(nodeid, - events=[(1, self._expected_cluster, 1)], reportInterval=(1, 5), + events=[(endpoint, self._expected_cluster, 1)], reportInterval=(1, 5), fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) self._subscription.SetEventUpdateCallback(self.__call__) @@ -62,41 +62,41 @@ async def read_evse_attribute_expect_success(self, endpoint, attribute): cluster = Clusters.Objects.EnergyEvse return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) - async def check_evse_attribute(self, attribute, expected_value): - value = await self.read_evse_attribute_expect_success(endpoint=1, attribute=attribute) + async def check_evse_attribute(self, attribute, expected_value, endpoint: int = 1): + value = await self.read_evse_attribute_expect_success(endpoint=endpoint, attribute=attribute) asserts.assert_equal(value, expected_value, f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") async def get_supported_energy_evse_attributes(self, endpoint): return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") - async def send_enable_charge_command(self, endpoint: int = 0, charge_until: int = None, timedRequestTimeoutMs: int = 3000, + async def send_enable_charge_command(self, endpoint: int = 1, charge_until: int = None, timedRequestTimeoutMs: int = 3000, min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): try: await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( chargingEnabledUntil=charge_until, minimumChargeCurrent=min_charge, maximumChargeCurrent=max_charge), - endpoint=1, + endpoint=endpoint, timedRequestTimeoutMs=timedRequestTimeoutMs) except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - async def send_disable_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): + async def send_disable_command(self, endpoint: int = 1, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): try: await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), - endpoint=1, + endpoint=endpoint, timedRequestTimeoutMs=timedRequestTimeoutMs) except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - async def send_start_diagnostics_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 3000, + async def send_start_diagnostics_command(self, endpoint: int = 1, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): try: await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.StartDiagnostics(), - endpoint=1, + endpoint=endpoint, timedRequestTimeoutMs=timedRequestTimeoutMs) except InteractionModelError as e: From 2e774fe53de8ca01405aaca364ac36a11a98128f Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 00:41:19 +0000 Subject: [PATCH 33/52] Implemented timer to disable the EVSE automatically. --- .../include/EnergyEvseDelegateImpl.h | 26 +++++- .../src/EVSEManufacturerImpl.cpp | 5 + .../src/EnergyEvseDelegateImpl.cpp | 93 ++++++++++++++++++- src/python_testing/TC_EEVSE_2_2.py | 19 ++-- 4 files changed, 127 insertions(+), 16 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index dc46e58d5021a7..86a85363ed31a3 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -136,6 +136,15 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate */ Status HwRegisterEvseCallbackHandler(EVSECallbackFunc handler, intptr_t arg); + /** + * @brief This is used to start a check on if the Enabled timer needs to be started + * + * It should be called after the EVSE is initialised and the persisted attributes + * have been loaded, and time has been synchronised. If time isn't sync'd + * yet it will call itself back periodically (if required). + */ + Status ScheduleCheckOnEnabledTimeout(); + // ----------------------------------------------------------------- // Internal API to allow an EVSE to change its internal state etc Status HwSetMaxHardwareCurrentLimit(int64_t currentmA); @@ -214,10 +223,11 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate private: /* Constants */ - static constexpr int DEFAULT_MIN_CHARGE_CURRENT = 6000; /* 6A */ - static constexpr int DEFAULT_USER_MAXIMUM_CHARGE_CURRENT = kMaximumChargeCurrent; /* 80A */ - static constexpr int DEFAULT_RANDOMIZATION_DELAY_WINDOW = 600; /* 600s */ - static constexpr int kMaxVehicleIDBufSize = 32; + static constexpr int DEFAULT_MIN_CHARGE_CURRENT = 6000; /* 6A */ + static constexpr int DEFAULT_USER_MAXIMUM_CHARGE_CURRENT = kMaximumChargeCurrent; /* 80A */ + static constexpr int DEFAULT_RANDOMIZATION_DELAY_WINDOW = 600; /* 600s */ + static constexpr int kMaxVehicleIDBufSize = 32; + static constexpr int PERIODIC_CHECK_INTERVAL_REAL_TIME_CLOCK_NOT_SYNCED = 30; /* private variables for controlling the hardware - these are not attributes */ int64_t mMaxHardwareCurrentLimit = 0; /* Hardware current limit in mA */ @@ -254,6 +264,14 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate */ Status ComputeMaxChargeCurrentLimit(); + /** + * @brief This checks if the charging or discharging needs to be disabled + * + * @params pointer to SystemLayer + * @params pointer to EnergyEvseDelegate + */ + static void EvseCheckTimerExpiry(System::Layer * systemLayer, void * delegate); + /* Attributes */ StateEnum mState = StateEnum::kNotPluggedIn; SupplyStateEnum mSupplyState = SupplyStateEnum::kDisabled; diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index c71db0047b8f20..183e8a176a4a45 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -59,6 +59,11 @@ CHIP_ERROR EVSEManufacturer::Init() // uint8_t uid[10] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; // dg->HwSetRFID(ByteSpan(uid)); + /* Once the system is initialised then check to see if the state was restored + * (e.g. after a power outage), and if the Enable timer check needs to be started + */ + dg->ScheduleCheckOnEnabledTimeout(); + return CHIP_NO_ERROR; } diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 98dc99ac22f156..fdbd1c73638548 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -42,6 +42,13 @@ EnergyEvseDelegate::~EnergyEvseDelegate() } } +/** + * @brief Helper function to get current timestamp in Epoch format + * + * @param chipEpoch reference to hold return timestamp + */ +CHIP_ERROR GetEpochTS(uint32_t & chipEpoch); + /** * @brief Called when EVSE cluster receives Disable command */ @@ -105,8 +112,6 @@ Status EnergyEvseDelegate::EnableCharging(const DataModel::Nullable & /* check chargingEnabledUntil is in the future */ ChipLogError(AppServer, "Charging enabled until: %lu", static_cast(chargingEnabledUntil.Value())); SetChargingEnabledUntil(chargingEnabledUntil); - // TODO start a timer to disable charging later - // if (checkChargingEnabled) } /* If it looks ok, store the min & max charging current */ @@ -136,6 +141,76 @@ Status EnergyEvseDelegate::EnableDischarging(const DataModel::Nullable return HandleStateMachineEvent(EVSEStateMachineEvent::DischargingEnabledEvent); } +/** + * @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() +{ + + uint32_t chipEpoch = 0; + DataModel::Nullable enabledUntilTime; + + if (mSupplyState == SupplyStateEnum::kChargingEnabled) + { + enabledUntilTime = GetChargingEnabledUntil(); + } + else if (mSupplyState == SupplyStateEnum::kDischargingEnabled) + { + enabledUntilTime = GetDischargingEnabledUntil(); + } + else + { + // In all other states the EVSE is disabled + return Status::Success; + } + + if (enabledUntilTime.IsNull()) + { + /* This is enabled indefinitely so don't schedule a callback */ + return Status::Success; + } + + CHIP_ERROR err = GetEpochTS(chipEpoch); + if (err == CHIP_NO_ERROR) + { + /* time is sync'd */ + int32_t delta = static_cast(enabledUntilTime.Value() - chipEpoch); + if (delta > 0) + { + /* The timer hasn't expired yet - set a timer to check in the future */ + ChipLogDetail(AppServer, "Setting EVSE Enable check timer for %d seconds", delta); + DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(delta), EvseCheckTimerExpiry, this); + } + else + { + /* we have gone past the enabledUntilTime - so we need to disable */ + ChipLogDetail(AppServer, "EVSE enable time expired, disabling charging"); + Disable(); + } + } + else if (err == CHIP_ERROR_REAL_TIME_NOT_SYNCED) + { + /* Real time isn't sync'd -lets check again in 30 seconds - otherwise keep the charger enabled */ + DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(PERIODIC_CHECK_INTERVAL_REAL_TIME_CLOCK_NOT_SYNCED), + EvseCheckTimerExpiry, this); + } + return Status::Success; +} + +void EnergyEvseDelegate::EvseCheckTimerExpiry(System::Layer * systemLayer, void * delegate) +{ + EnergyEvseDelegate * dg = reinterpret_cast(delegate); + + dg->ScheduleCheckOnEnabledTimeout(); +} + /** * @brief Called when EVSE cluster receives StartDiagnostics command * @@ -615,6 +690,9 @@ Status EnergyEvseDelegate::HandleChargingEnabledEvent() default: break; } + + ScheduleCheckOnEnabledTimeout(); + return Status::Success; } Status EnergyEvseDelegate::HandleDischargingEnabledEvent() @@ -650,6 +728,9 @@ Status EnergyEvseDelegate::HandleDischargingEnabledEvent() default: break; } + + ScheduleCheckOnEnabledTimeout(); + return Status::Success; } Status EnergyEvseDelegate::HandleDisabledEvent() @@ -1366,6 +1447,14 @@ CHIP_ERROR GetEpochTS(uint32_t & chipEpoch) System::Clock::Milliseconds64 cTMs; CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(cTMs); + + /* If the GetClock_RealTimeMS returns CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE, then + * This platform cannot ever report real time ! + * This should not be certifiable since getting time is a Mandatory + * feature of EVSE Cluster + */ + VerifyOrDie(err != CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + if (err != CHIP_NO_ERROR) { ChipLogError(Zcl, "EVSE: Unable to get current time - err:%" CHIP_ERROR_FORMAT, err.Format()); diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index 83bc676ed75b7c..ce4b543bc2d9c1 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -176,24 +176,24 @@ async def test_TC_EEVSE_2_2(self): self.step("7") # Sleep for the charging duration plus a couple of seconds to check it has stopped time.sleep(charging_duration + 2) - # TODO check EnergyTransferredStoped (EvseStopped) - # event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) - # expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVSEStopped - # self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) + # check EnergyTransferredStoped (EvseStopped) + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVSEStopped + self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) self.step("7a") -# await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand) + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand) self.step("7b") -# await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) self.step("8") charge_until = NullValue min_charge_current = 6000 max_charge_current = 12000 - # TODO reinstate this check + await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) - # event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) self.step("8a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) @@ -213,8 +213,7 @@ async def test_TC_EEVSE_2_2(self): await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) # from step 8 above - validate event - # TODO reinstate this check - # self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) + self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) self.step("9") # This will only work if the optional UserMaximumChargeCurrent attribute is supported From cafe1b45c026876a8f1804a025a94d0ebb1eec3a Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 00:54:37 +0000 Subject: [PATCH 34/52] Added documentation to cover concern about long-lived bytespan in enableKey --- .../EnergyEvseTestEventTriggerDelegate.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h index cb9abd29da77f4..1828d30d19eed0 100644 --- a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h @@ -62,7 +62,17 @@ class EnergyEvseTestEventTriggerDelegate : public TestEventTriggerDelegate mEnableKey(enableKey), mOtherDelegate(otherDelegate) {} + /** + * This function expects the enableKey ByteSpan to be valid forever. + * Typically this feature is only enabled in certification testing + * and uses a static secret key in the device for testing (e.g. in factory data) + */ bool DoesEnableKeyMatch(const ByteSpan & enableKey) const override; + + /** This function must return True if the eventTrigger is recognised and handled + * It must return False to allow a higher level TestEvent handler to check other + * clusters that may handle it. + */ CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override; private: From c52fe69c1a9a28f8b909f47d7cc538fb313782a2 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 08:11:47 +0000 Subject: [PATCH 35/52] Fixed Lint and build issues on other platforms --- .../energy-management-common/src/EnergyEvseDelegateImpl.cpp | 2 +- src/python_testing/TC_EEVSE_2_5.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index fdbd1c73638548..63498e204e850a 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -185,7 +185,7 @@ Status EnergyEvseDelegate::ScheduleCheckOnEnabledTimeout() if (delta > 0) { /* The timer hasn't expired yet - set a timer to check in the future */ - ChipLogDetail(AppServer, "Setting EVSE Enable check timer for %d seconds", delta); + ChipLogDetail(AppServer, "Setting EVSE Enable check timer for %ld seconds", static_cast(delta)); DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(delta), EvseCheckTimerExpiry, this); } else diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py index 27dfca05ced439..0179df90892687 100644 --- a/src/python_testing/TC_EEVSE_2_5.py +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -16,7 +16,6 @@ import logging -import time import chip.clusters as Clusters from chip.clusters.Types import NullValue From f50ef1e7be5f15f24c67c9ef45686458551bd055 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Fri, 12 Jan 2024 08:19:47 +0000 Subject: [PATCH 36/52] Restyled by isort --- src/python_testing/TC_EEVSE_2_5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py index 0179df90892687..a4b35a7e9feae9 100644 --- a/src/python_testing/TC_EEVSE_2_5.py +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -19,8 +19,8 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue -from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from chip.interaction_model import Status +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback logger = logging.getLogger(__name__) From bd975624e0745853acaf96e3bbaa96781cdc2020 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 18:45:58 +0000 Subject: [PATCH 37/52] Implemented some of the feedback on PR --- .../include/EnergyEvseDelegateImpl.h | 16 +++--- .../src/EVSEManufacturerImpl.cpp | 52 +++++++++++++------ .../src/EnergyEvseDelegateImpl.cpp | 3 +- .../src/EnergyEvseManager.cpp | 7 +-- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 86a85363ed31a3..1b97fef5a8e54e 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -223,11 +223,11 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate private: /* Constants */ - static constexpr int DEFAULT_MIN_CHARGE_CURRENT = 6000; /* 6A */ - static constexpr int DEFAULT_USER_MAXIMUM_CHARGE_CURRENT = kMaximumChargeCurrent; /* 80A */ - static constexpr int DEFAULT_RANDOMIZATION_DELAY_WINDOW = 600; /* 600s */ - static constexpr int kMaxVehicleIDBufSize = 32; - static constexpr int PERIODIC_CHECK_INTERVAL_REAL_TIME_CLOCK_NOT_SYNCED = 30; + static constexpr int kDefaultMinChargeCurrent = 6000; /* 6A */ + static constexpr int kDefaultUserMaximumChargeCurrent = kMaximumChargeCurrent; /* 80A */ + static constexpr int kDefaultRandomizationDelayWindow = 600; /* 600s */ + static constexpr int kMaxVehicleIDBufSize = 32; + static constexpr int kPeriodicCheckIntervalRealTimeClockNotSynced = 30; /* private variables for controlling the hardware - these are not attributes */ int64_t mMaxHardwareCurrentLimit = 0; /* Hardware current limit in mA */ @@ -279,11 +279,11 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate DataModel::Nullable mChargingEnabledUntil; // TODO Default to 0 to indicate disabled DataModel::Nullable mDischargingEnabledUntil; // TODO Default to 0 to indicate disabled int64_t mCircuitCapacity = 0; - int64_t mMinimumChargeCurrent = DEFAULT_MIN_CHARGE_CURRENT; + int64_t mMinimumChargeCurrent = kDefaultMinChargeCurrent; int64_t mMaximumChargeCurrent = 0; int64_t mMaximumDischargeCurrent = 0; - int64_t mUserMaximumChargeCurrent = DEFAULT_USER_MAXIMUM_CHARGE_CURRENT; // TODO update spec - uint32_t mRandomizationDelayWindow = DEFAULT_RANDOMIZATION_DELAY_WINDOW; + int64_t mUserMaximumChargeCurrent = kDefaultUserMaximumChargeCurrent; // TODO update spec + uint32_t mRandomizationDelayWindow = kDefaultRandomizationDelayWindow; /* PREF attributes */ uint8_t mNumberOfWeeklyTargets = 0; uint8_t mNumberOfDailyTargets = 1; diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index 183e8a176a4a45..6e646a8dad42ee 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -42,22 +42,16 @@ CHIP_ERROR EVSEManufacturer::Init() dg->HwRegisterEvseCallbackHandler(ApplicationCallbackHandler, reinterpret_cast(this)); - // For Manufacturer to specify the hardware capability in mA - // dg->HwSetMaxHardwareCurrentLimit(32000); - - // For Manufacturer to specify the CircuitCapacity (e.g. from DIP switches) - // dg->HwSetCircuitCapacity(20000); - - // For now let's pretend the EV is plugged in, and asking for demand - // dg->HwSetState(StateEnum::kPluggedInDemand); - // dg->HwSetCableAssemblyLimit(63000); - - // For now let's pretend the vehicle ID is set - // dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_123456789")); - - // For now let's pretend the RFID sensor was triggered - send an event - // uint8_t uid[10] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; - // dg->HwSetRFID(ByteSpan(uid)); + /* + * This is an example implementation for manufacturers to consider + * + * For Manufacturer to specify the hardware capability in mA: + * dg->HwSetMaxHardwareCurrentLimit(32000); // 32A + * + * For Manufacturer to specify the CircuitCapacity in mA (e.g. from DIP switches) + * dg->HwSetCircuitCapacity(20000); // 20A + * + */ /* Once the system is initialised then check to see if the state was restored * (e.g. after a power outage), and if the Enable timer check needs to be started @@ -67,6 +61,32 @@ CHIP_ERROR EVSEManufacturer::Init() return CHIP_NO_ERROR; } +/* + * When the EV is plugged in, and asking for demand change the state + * and set the CableAssembly current limit + * + * EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + * if (dg == nullptr) + * { + * ChipLogError(AppServer, "Delegate is not initialized"); + * return CHIP_ERROR_UNINITIALIZED; + * } + * + * dg->HwSetState(StateEnum::kPluggedInDemand); + * dg->HwSetCableAssemblyLimit(63000); // 63A = 63000mA + * + * + * If the vehicle ID can be retrieved (e.g. over Powerline) + * dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_123456789")); + * + * + * If the EVSE has an RFID sensor, the RFID value read can cause an event to be sent + * (e.g. can be used to indicate if a user as tried to activate the charging) + * + * uint8_t uid[10] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; + * dg->HwSetRFID(ByteSpan(uid)); + */ + CHIP_ERROR EVSEManufacturer::Shutdown() { return CHIP_NO_ERROR; diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 63498e204e850a..dc3385c850c85f 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -198,7 +198,7 @@ Status EnergyEvseDelegate::ScheduleCheckOnEnabledTimeout() else if (err == CHIP_ERROR_REAL_TIME_NOT_SYNCED) { /* Real time isn't sync'd -lets check again in 30 seconds - otherwise keep the charger enabled */ - DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(PERIODIC_CHECK_INTERVAL_REAL_TIME_CLOCK_NOT_SYNCED), + DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(kPeriodicCheckIntervalRealTimeClockNotSynced), EvseCheckTimerExpiry, this); } return Status::Success; @@ -387,7 +387,6 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) /* All other states should be managed by the Delegate */ ChipLogError(AppServer, "HwSetState received invalid enum from caller"); return Status::Failure; - break; } return Status::Success; diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp index a71474f0913abb..710b13f9d342b8 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp @@ -105,12 +105,7 @@ CHIP_ERROR EnergyEvseManager::LoadPersistentAttributes() CHIP_ERROR EnergyEvseManager::Init() { - CHIP_ERROR err = Instance::Init(); - if (err != CHIP_NO_ERROR) - { - return err; - } - + ReturnErrorOnFailure(Instance::Init()); return LoadPersistentAttributes(); } From 895b55238ccb4183542e413801bb3e13d390d048 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 19:16:25 +0000 Subject: [PATCH 38/52] Refactored HwSetState to use nested switch statements to be clear that all enums are caught. --- .../src/EnergyEvseDelegateImpl.cpp | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index dc3385c850c85f..14486d9528512f 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -345,41 +345,71 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) switch (newState) { case StateEnum::kNotPluggedIn: - if (mHwState == StateEnum::kPluggedInNoDemand || mHwState == StateEnum::kPluggedInDemand) + 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 + break; } break; case StateEnum::kPluggedInNoDemand: - if (mHwState == StateEnum::kNotPluggedIn) + switch (mHwState) { + case StateEnum::kNotPluggedIn: /* EV was unplugged, now is plugged in */ mHwState = newState; HandleStateMachineEvent(EVSEStateMachineEvent::EVPluggedInEvent); - } - else if (mHwState == StateEnum::kPluggedInDemand) - { + 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(kNotPluggedIn) - Invalid value for mHwState"); + mHwState = newState; // set it to the new state + break; } break; case StateEnum::kPluggedInDemand: - if (mHwState == StateEnum::kNotPluggedIn) + switch (mHwState) { + case StateEnum::kNotPluggedIn: /* EV was unplugged, now is plugged in and wants demand */ mHwState = newState; HandleStateMachineEvent(EVSEStateMachineEvent::EVPluggedInEvent); HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent); - } - else if (mHwState == StateEnum::kPluggedInNoDemand) - { + 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(kNotPluggedIn) - Invalid value for mHwState"); + mHwState = newState; // set it to the new state + break; } break; From 87fe6cca8cc63d0b595994dfcdeb72a9dc1aedf3 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 19:55:32 +0000 Subject: [PATCH 39/52] Fixed error messages --- .../src/EnergyEvseDelegateImpl.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 14486d9528512f..6704eda6fa0158 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -360,7 +360,7 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) default: // invalid value for mHwState ChipLogError(AppServer, "HwSetState newstate(kNotPluggedIn) - Invalid value for mHwState"); - mHwState = newState; // set it to the new state + mHwState = newState; // set it to the new state anyway break; } break; @@ -383,8 +383,8 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) break; default: // invalid value for mHwState - ChipLogError(AppServer, "HwSetState newstate(kNotPluggedIn) - Invalid value for mHwState"); - mHwState = newState; // set it to the new state + ChipLogError(AppServer, "HwSetState newstate(kPluggedInNoDemand) - Invalid value for mHwState"); + mHwState = newState; // set it to the new state anyway break; } break; @@ -407,8 +407,8 @@ Status EnergyEvseDelegate::HwSetState(StateEnum newState) break; default: // invalid value for mHwState - ChipLogError(AppServer, "HwSetState newstate(kNotPluggedIn) - Invalid value for mHwState"); - mHwState = newState; // set it to the new state + ChipLogError(AppServer, "HwSetState newstate(kPluggedInDemand) - Invalid value for mHwState"); + mHwState = newState; // set it to the new state anyway break; } break; From 6b12abe16ef943d4c8ef05aa3b20bd20690050c0 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 20:54:51 +0000 Subject: [PATCH 40/52] Test scripts: Removed hardcoded endpoint 1 (use --endpoint 1 in args), allowed the enableKey to be passed in using --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --- src/python_testing/TC_EEVSE_2_2.py | 24 +++++++---------- src/python_testing/TC_EEVSE_2_4.py | 8 +++--- src/python_testing/TC_EEVSE_2_5.py | 6 +++-- src/python_testing/TC_EEVSE_Utils.py | 39 +++++++++++++++++++++------- 4 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index ce4b543bc2d9c1..fd5a3a042f5fa8 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -31,12 +31,6 @@ class TC_EEVSE_2_2(MatterBaseTest, EEVSEBaseTestHelper): - async def write_user_max_charge(self, endpoint, user_max_charge): - result = await self.default_controller.WriteAttribute(self.dut_node_id, - [(endpoint, - Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent(user_max_charge))]) - asserts.assert_equal(result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") - def desc_TC_EEVSE_2_2(self) -> str: """Returns a description of this test""" return "5.1.3. [TC-EEVSE-2.2] Primary functionality with DUT as Server" @@ -106,7 +100,9 @@ async def test_TC_EEVSE_2_2(self): # Subscribe to Events and when they are sent push them to a queue for checking later events_callback = EventChangeCallback(Clusters.EnergyEvse) - await events_callback.start(self.default_controller, self.dut_node_id) + await events_callback.start(self.default_controller, + self.dut_node_id, + self.matter_test_config.endpoint) self.step("2") await self.check_test_event_triggers_enabled() @@ -135,7 +131,7 @@ async def test_TC_EEVSE_2_2(self): self.step("4b") # Save Session ID for later and check it against the value in the event - session_id = await self.read_evse_attribute_expect_success(endpoint=1, attribute="SessionID") + session_id = await self.read_evse_attribute_expect_success(attribute="SessionID") self.validate_ev_connected_event(event_data, session_id) self.step("5") @@ -167,7 +163,7 @@ async def test_TC_EEVSE_2_2(self): await self.check_evse_attribute("MinimumChargeCurrent", min_charge_current) self.step("6e") - circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute="CircuitCapacity") + circuit_capacity = await self.read_evse_attribute_expect_success(attribute="CircuitCapacity") expected_max_charge = min(max_charge_current, circuit_capacity) await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) @@ -192,7 +188,7 @@ async def test_TC_EEVSE_2_2(self): min_charge_current = 6000 max_charge_current = 12000 - await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) self.step("8a") @@ -208,7 +204,7 @@ async def test_TC_EEVSE_2_2(self): await self.check_evse_attribute("MinimumChargeCurrent", min_charge_current) self.step("8e") - circuit_capacity = await self.read_evse_attribute_expect_success(endpoint=1, attribute="CircuitCapacity") + circuit_capacity = await self.read_evse_attribute_expect_success(attribute="CircuitCapacity") expected_max_charge = min(max_charge_current, circuit_capacity) await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) @@ -217,7 +213,7 @@ async def test_TC_EEVSE_2_2(self): self.step("9") # This will only work if the optional UserMaximumChargeCurrent attribute is supported - supported_attributes = await self.get_supported_energy_evse_attributes(endpoint=1) + supported_attributes = await self.get_supported_energy_evse_attributes() if Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent.attribute_id in supported_attributes: logging.info("UserMaximumChargeCurrent is supported...") user_max_charge_current = 6000 @@ -243,7 +239,7 @@ async def test_TC_EEVSE_2_2(self): self.step("11") await self.send_test_event_trigger_charge_demand() # Check we get EnergyTransferStarted again - await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) @@ -275,7 +271,7 @@ async def test_TC_EEVSE_2_2(self): await self.check_evse_attribute("SessionID", session_id) self.step("13d") - session_duration = await self.read_evse_attribute_expect_success(endpoint=1, attribute="SessionDuration") + session_duration = await self.read_evse_attribute_expect_success(attribute="SessionDuration") asserts.assert_greater_equal(session_duration, charging_duration, f"Unexpected 'SessionDuration' value - expected >= {charging_duration}, was {session_duration}") diff --git a/src/python_testing/TC_EEVSE_2_4.py b/src/python_testing/TC_EEVSE_2_4.py index 8a28b562635390..9379e23332e813 100644 --- a/src/python_testing/TC_EEVSE_2_4.py +++ b/src/python_testing/TC_EEVSE_2_4.py @@ -75,7 +75,9 @@ async def test_TC_EEVSE_2_4(self): # Subscribe to Events and when they are sent push them to a queue for checking later events_callback = EventChangeCallback(Clusters.EnergyEvse) - await events_callback.start(self.default_controller, self.dut_node_id) + await events_callback.start(self.default_controller, + self.dut_node_id, + self.matter_test_config.endpoint) self.step("2") await self.check_test_event_triggers_enabled() @@ -104,14 +106,14 @@ async def test_TC_EEVSE_2_4(self): self.step("4b") # Save Session ID for later and check it against the value in the event - session_id = await self.read_evse_attribute_expect_success(endpoint=1, attribute="SessionID") + session_id = await self.read_evse_attribute_expect_success(attribute="SessionID") self.validate_ev_connected_event(event_data, session_id) self.step("5") charge_until = NullValue min_charge_current = 6000 max_charge_current = 60000 - await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) self.step("6") await self.send_test_event_trigger_charge_demand() diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py index a4b35a7e9feae9..1a647c20e0bcef 100644 --- a/src/python_testing/TC_EEVSE_2_5.py +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -69,7 +69,9 @@ async def test_TC_EEVSE_2_5(self): # Subscribe to Events and when they are sent push them to a queue for checking later events_callback = EventChangeCallback(Clusters.EnergyEvse) - await events_callback.start(self.default_controller, self.dut_node_id) + await events_callback.start(self.default_controller, + self.dut_node_id, + self.matter_test_config.endpoint) self.step("2") await self.check_test_event_triggers_enabled() @@ -90,7 +92,7 @@ async def test_TC_EEVSE_2_5(self): charge_until = NullValue min_charge_current = 6000 max_charge_current = 60000 - await self.send_enable_charge_command(endpoint=1, charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) self.step("4a") await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) diff --git a/src/python_testing/TC_EEVSE_Utils.py b/src/python_testing/TC_EEVSE_Utils.py index d2d5873c37d0b4..fb6bfa1f714d13 100644 --- a/src/python_testing/TC_EEVSE_Utils.py +++ b/src/python_testing/TC_EEVSE_Utils.py @@ -32,9 +32,9 @@ def __init__(self, expected_cluster: ClusterObjects): self._q = queue.Queue() self._expected_cluster = expected_cluster - async def start(self, dev_ctrl, nodeid, endpoint: int = 1): - self._subscription = await dev_ctrl.ReadEvent(nodeid, - events=[(endpoint, self._expected_cluster, 1)], reportInterval=(1, 5), + async def start(self, dev_ctrl, node_id: int, endpoint: int): + self._subscription = await dev_ctrl.ReadEvent(node_id, + events=[(endpoint, self._expected_cluster, True)], reportInterval=(1, 5), fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) self._subscription.SetEventUpdateCallback(self.__call__) @@ -57,20 +57,28 @@ def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): class EEVSEBaseTestHelper: - async def read_evse_attribute_expect_success(self, endpoint, attribute): + async def read_evse_attribute_expect_success(self, endpoint: int = None, attribute: str = ""): full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) cluster = Clusters.Objects.EnergyEvse return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) - async def check_evse_attribute(self, attribute, expected_value, endpoint: int = 1): + async def check_evse_attribute(self, attribute, expected_value, endpoint: int = None): value = await self.read_evse_attribute_expect_success(endpoint=endpoint, attribute=attribute) asserts.assert_equal(value, expected_value, f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") - async def get_supported_energy_evse_attributes(self, endpoint): + async def get_supported_energy_evse_attributes(self, endpoint: int = None): return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") - async def send_enable_charge_command(self, endpoint: int = 1, charge_until: int = None, timedRequestTimeoutMs: int = 3000, + async def write_user_max_charge(self, endpoint: int = None, user_max_charge: int = 0): + if endpoint is None: + endpoint = self.matter_test_config.endpoint + result = await self.default_controller.WriteAttribute(self.dut_node_id, + [(endpoint, + Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent(user_max_charge))]) + asserts.assert_equal(result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") + + async def send_enable_charge_command(self, endpoint: int = None, charge_until: int = None, timedRequestTimeoutMs: int = 3000, min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): try: await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( @@ -83,7 +91,7 @@ async def send_enable_charge_command(self, endpoint: int = 1, charge_until: int except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - async def send_disable_command(self, endpoint: int = 1, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): + async def send_disable_command(self, endpoint: int = None, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): try: await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), endpoint=endpoint, @@ -92,7 +100,7 @@ async def send_disable_command(self, endpoint: int = 1, timedRequestTimeoutMs: i except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - async def send_start_diagnostics_command(self, endpoint: int = 1, timedRequestTimeoutMs: int = 3000, + async def send_start_diagnostics_command(self, endpoint: int = None, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): try: await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.StartDiagnostics(), @@ -102,8 +110,18 @@ async def send_start_diagnostics_command(self, endpoint: int = 1, timedRequestTi except InteractionModelError as e: asserts.assert_equal(e.status, expected_status, "Unexpected error returned") - async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000): + async def send_test_event_triggers(self, enableKey: bytes = None, eventTrigger=0x0099000000000000): + # get the test event enable key or assume the default + # This can be passed in on command line using + # --hex-arg enableKey:000102030405060708090a0b0c0d0e0f + if enableKey is None: + if 'enableKey' not in self.matter_test_config.global_test_params: + enableKey = bytes([b for b in range(16)]) + else: + enableKey = self.matter_test_config.global_test_params['enableKey'] + try: + # GeneralDiagnosics cluster is meant to be on Endpoint 0 (Root) await self.send_single_cmd(endpoint=0, cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( enableKey, @@ -116,6 +134,7 @@ async def send_test_event_triggers(self, enableKey=bytes([b for b in range(16)]) async def check_test_event_triggers_enabled(self): full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled cluster = Clusters.Objects.GeneralDiagnostics + # GeneralDiagnosics cluster is meant to be on Endpoint 0 (Root) test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") From 303ca2af24d85deea20612109f9377140a7bc057 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 21:31:08 +0000 Subject: [PATCH 41/52] Made enum class for callbacks and improved documentation comments based on feedback. --- .../include/EVSECallbacks.h | 16 +++++++++++----- .../include/EnergyEvseDelegateImpl.h | 16 ++++++++++++---- .../EnergyEvseTestEventTriggerDelegate.h | 11 +++++++---- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h index e72ee80ef9fcca..15578405552d78 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h +++ b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h @@ -30,31 +30,37 @@ using namespace chip::app::Clusters::EnergyEvse; * This is not specific to the EnergyEVSE cluster, but includes DeviceEnergyManagement * and potential future clusters. */ -enum EVSECallbackType + +enum class EVSECallbackType : uint8_t { /* * The State has changed (e.g. from Disabled to Charging, or vice-versa) */ StateChanged, /* - * ChargeCurrent has changed + * ChargeCurrent has changed (e.g. maxChargingCurrent so requires an + update to advertise a different charging current to the EV) */ ChargeCurrentChanged, /* * Charging Preferences have changed + * The daily charging target time, SoC / Added Energy schedules have changed + * and may require the local optimiser to re-run. */ ChargingPreferencesChanged, /* - * Energy Meter Reading requested + * Energy Meter Reading requested from the hardware, e.g. so that the session + * information can be updated. */ EnergyMeterReadingRequested, /* - * DeviceEnergyManagement has changed + * The associated DeviceEnergyManagement cluster has changed. This may mean + * that the start time, or power profile or power levels have been adjusted */ DeviceEnergyManagementChanged, }; -enum ChargingDischargingType +enum class ChargingDischargingType : uint8_t { kCharging, kDischarging diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 1b97fef5a8e54e..8cf76f1b7a29bf 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -137,11 +137,19 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate Status HwRegisterEvseCallbackHandler(EVSECallbackFunc handler, intptr_t arg); /** - * @brief This is used to start a check on if the Enabled timer needs to be started + * @brief Decides if a timer is needed based on EVSE state and sets a callback if needed * - * It should be called after the EVSE is initialised and the persisted attributes - * have been loaded, and time has been synchronised. If time isn't sync'd - * yet it will call itself back periodically (if required). + * In order to ensure the EVSE restarts charging (if enabled) after power loss + * this should be called after the EVSE is initialised + * (e.g. HwSetMaxHardwareCurrentLimit and HwSetCircuitCapacity have been called) + * and the persisted attributes have been loaded, and time has been synchronised. + * + * If time isn't sync'd yet it will call itself back periodically (if required) + * until time is sync'd. + * + * It is also called when a EnableCharging or EnableDischarging command + * is recv'd to schedule when the EVSE should be automatically disabled based + * on ChargingEnabledUntil / DischargingEnabledUntil expiring. */ Status ScheduleCheckOnEnabledTimeout(); diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h index 1828d30d19eed0..3c1dff4f5e7f81 100644 --- a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h @@ -58,14 +58,17 @@ enum class EnergyEvseTrigger : uint64_t class EnergyEvseTestEventTriggerDelegate : public TestEventTriggerDelegate { public: + /** + * This class expects the enableKey ByteSpan to be valid forever. + * Typically this feature is only enabled in certification testing + * and uses a static secret key in the device for testing (e.g. in factory data) + */ explicit EnergyEvseTestEventTriggerDelegate(const ByteSpan & enableKey, TestEventTriggerDelegate * otherDelegate) : mEnableKey(enableKey), mOtherDelegate(otherDelegate) {} - /** - * This function expects the enableKey ByteSpan to be valid forever. - * Typically this feature is only enabled in certification testing - * and uses a static secret key in the device for testing (e.g. in factory data) + /* This function returns True if the enableKey received in the TestEventTrigger command + * matches the value passed into the constructor. */ bool DoesEnableKeyMatch(const ByteSpan & enableKey) const override; From 34c077d0d015762e5f7d8c98e6a03bf5bb93c473 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 22:14:05 +0000 Subject: [PATCH 42/52] Fixed another python lint issue. --- src/python_testing/TC_EEVSE_2_2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py index fd5a3a042f5fa8..8ebc78a22e94cd 100644 --- a/src/python_testing/TC_EEVSE_2_2.py +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -21,7 +21,6 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue -from chip.interaction_model import Status from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback From 50793c3a86e76de039fa5c6be68f93310823b006 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Fri, 12 Jan 2024 23:58:28 +0000 Subject: [PATCH 43/52] Updated README.md with help on how to build for test event triggers, using chip-repl and python testing. --- .../energy-management-app/linux/README.md | 366 +++++++++++++++++- 1 file changed, 364 insertions(+), 2 deletions(-) diff --git a/examples/energy-management-app/linux/README.md b/examples/energy-management-app/linux/README.md index 9671ab073b12a8..a6a9e9671f622c 100644 --- a/examples/energy-management-app/linux/README.md +++ b/examples/energy-management-app/linux/README.md @@ -13,10 +13,17 @@ To cross-compile this example on x64 host and run on **NXP i.MX 8M Mini** - [CHIP Linux Energy Management Example](#chip-linux-energy-management-example) - [Building](#building) - - [Commandline Arguments](#commandline-arguments) + - [Commandline arguments](#commandline-arguments) - [Running the Complete Example on Raspberry Pi 4](#running-the-complete-example-on-raspberry-pi-4) - - [Running RPC console](#running-rpc-console) + - [Running RPC Console](#running-rpc-console) - [Device Tracing](#device-tracing) + - [Python TestCases](#python-testcases) + - [Running the test cases:](#running-the-test-cases) + - [CHIP-REPL Interaction](#chip-repl-interaction) + - [Building chip-repl:](#building-chip-repl) + - [Activating python virtual env](#activating-python-virtual-env) + - [Interacting with CHIP-REPL and the example app](#interacting-with-chip-repl-and-the-example-app) + - [Using chip-repl to Fake a charging session](#using-chip-repl-to-fake-a-charging-session)
@@ -141,3 +148,358 @@ Obtain tracing json file. $ ./{PIGWEED_REPO}/pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py -s localhost:33000 \ -o {OUTPUT_FILE} -t {ELF_FILE} {PIGWEED_REPO}/pw_trace_tokenized/pw_trace_protos/trace_rpc.proto ``` + +## Python TestCases + +When you want to test this cluster you can use chip-repl or chip-tool by hand. +CHIP-REPL is slightly easier to interact with when dealing with some of the +complex structures. + +There are several test scripts provided for EVSE (in +[src/python_testing](src/python_testing)): + +- TC_EEVSE_2_2: This validates the primary functionality +- TC_EEVSE_2_3: This validates Get/Set/Clear target commands +- TC_EEVSE_2_4: This validates Faults +- TC_EEVSE_2_5: This validates EVSE diagnostic command (optional) + +These scripts require the use of TestEventsTriggers via the GeneralDiagnostics +cluster on Endpoint 0. This requires an enableKey (16 bytes) and a set of +reserved int64_t TestEvent trigger codes. + +By default the test event support is not enabled, and when compiling the example +app you need to add `chip_enable_energy_evse_trigger=true` to the gn args. + + $ gn gen out/debug + $ ninja -C out/debug --args='chip_enable_energy_evse_trigger=true` + +Once the application is built you also need to tell it at runtime what the +chosen enable key is using the `--enable-key` command line option. + + $ ./chip-energy-management-app --enable-key 000102030405060708090a0b0c0d0e0f + +### Running the test cases: + +From the top-level of the connectedhomeip repo type: + +```bash + $ python src/python_testing/TC_EEVSE_2_2.py --endpoint 1 -m on-network -n 1234 -p 20202021 -d 3840 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f +``` + +- Note that the `--endpoint 1` must be used with the example, since the EVSE + cluster is on endpoint 1. The `--hex-arg enableKey:` value must match + the `--enable-key ` used on chip-energy-management-app args. + +## CHIP-REPL Interaction + +- See chip-repl documentation in + [Matter_REPL_Intro](../../../docs/guides/repl/Matter_REPL_Intro.ipynb) + +### Building chip-repl: + +```bash + $ ./build_python.sh -i +``` + +### Activating python virtual env + +- You need to repeat this step each time you start a new shell. + +```bash + $ source /bin/activate +``` + +### Interacting with CHIP-REPL and the example app + +- Step 1: Launch the example app + +```bash + $ ./chip-energy-management-app --enable-key 000102030405060708090a0b0c0d0e0f +``` + +- Step 2: Launch CHIP-REPL + +```bash + $ chip-repl +``` + +- Step 3: (In chip-repl) Commissioning OnNetwork + +```python + devCtrl.CommissionOnNetwork(1234,20202021) # Commission with NodeID 1234 +Established secure session with Device +Commissioning complete +Out[2]: +``` + +- Step 4: (In chip-repl) Read EVSE attributes + +```python + # Read from NodeID 1234, Endpoint 1, all attributes on EnergyEvse cluster + await devCtrl.ReadAttribute(1234,[(1, chip.clusters.EnergyEvse)]) + +{ +│ 1: { +│ │ : { +│ │ │ : 3790455237, +│ │ │ : Null, +│ │ │ : , +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : 0, +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : [ +... │ │ ], +│ │ │ : 6000, +│ │ │ : Null, +│ │ │ : 758415333, +│ │ │ : 0, +│ │ │ : 1, +│ │ │ : [ +... +│ │ │ ], +│ │ │ : , +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : [ +... │ │ ], +│ │ │ : Null, +│ │ │ : 0, +│ │ │ : Null, +│ │ │ : , +│ │ │ : 600, +│ │ │ : 0, +│ │ │ : 1, +│ │ │ : 80000, +│ │ │ : 2 +│ │ } +│ } +} + +``` + +- Step 5: Setting up a subscription so that attributes updates are sent + automatically + +```python + reportingTimingParams = (3, 60) # MinInterval = 3s, MaxInterval = 60s + subscription = await devCtrl.ReadAttribute(1234,[(1, chip.clusters.EnergyEvse)], reportInterval=reportingTimingParams) +``` + +- Step 6: Send an EnableCharging command which lasts for 60 seconds The + EnableCharging takes an optional parameter which allows the charger to + automatically disable itself at some preset time in the future. Note that it + uses Epoch_s (which is from Jan 1 2000) which is a uint32_t in seconds. + +```python + from datetime import datetime, timezone, timedelta + epoch_end = int((datetime.now(tz=timezone.utc) + timedelta(seconds=60) - datetime(2000, 1, 1, 0, 0, 0, 0, timezone.utc)).total_seconds()) + + await devCtrl.SendCommand(1234, 1, chip.clusters.EnergyEvse.Commands.EnableCharging(chargingEnabledUntil=epoch_end,minimumChargeCurrent=2000,maximumChargeCurrent=25000),timedRequestTimeoutMs=3000) +``` + +The output should look like: + +``` +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 2000 +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 758416066 +} +``` + +After 60 seconds the charging should automatically become disabled: + +```python +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 0 +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 0 +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 0 +} +``` + +Note that you can omit the `chargingEnabledUntil` argument and it will charge +indefinitely. + +### Using chip-repl to Fake a charging session + +If you haven't implemented a real EVSE but want to simulate plugging in an EV +then you can use a few of the test event triggers to simulate these scenarios. + +The test event triggers values can be found in: +[EnergyEvseTestEventTriggerDelegate.h](../../../src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h) + +- 0x0099000000000000 - Simulates the EVSE being installed on a 32A supply +- 0x0099000000000002 - Simulates the EVSE being plugged in (this should + generate an EVConnected event) +- 0x0099000000000004 - Simulates the EVSE requesting power + +To send a test event trigger to the app, use the following commands (in +chip-repl): + +```python + # Read the events + await devCtrl.ReadEvent(1234,[(1, chip.clusters.EnergyEvse,1)]) +[] + + # send 1st event trigger to 'install' the EVSE on a 32A supply + await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000)) + + # send 2nd event trigger to plug the EV in + await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000002)) + +``` + +Now send the enable charging command (omit the chargingEnabledUntil arg this +time): + +```python + await devCtrl.SendCommand(1234, 1, chip.clusters.EnergyEvse.Commands.EnableCharging(minimumChargeCurrent=2000,maximumChargeCurrent=25000),timedRequestTimeoutMs=3000) +``` + +Now send the test event trigger to simulate the EV asking for demand: + +```python + # send 2nd event trigger to plug the EV in + await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000004)) + + # Read the events + await devCtrl.ReadEvent(1234,[(1, chip.clusters.EnergyEvse,1)]) +[ +│ EventReadResult( +│ │ Header=EventHeader( +│ │ │ EndpointId=1, +│ │ │ ClusterId=153, +│ │ │ EventId=0, +│ │ │ EventNumber=65538, +│ │ │ Priority=, +│ │ │ Timestamp=1705102500069, +│ │ │ TimestampType= +│ │ ), +│ │ Status=, +│ │ Data=EVConnected( +│ │ │ sessionID=0 +│ │ ) +│ ), +│ EventReadResult( +│ │ Header=EventHeader( +│ │ │ EndpointId=1, +│ │ │ ClusterId=153, +│ │ │ EventId=2, +│ │ │ EventNumber=65539, +│ │ │ Priority=, +│ │ │ Timestamp=1705102801764, +│ │ │ TimestampType= +│ │ ), +│ │ Status=, +│ │ Data=EnergyTransferStarted( +│ │ │ sessionID=0, +│ │ │ state=, +│ │ │ maximumCurrent=25000 +│ │ ) +│ ) +] +``` + +- We can see that the `EventNumber 65538` was sent when the vehicle was + plugged in, and a new `sessionID=0` was created. +- We can also see that the EnergyTransferStarted was sent in + `EventNumber 65539` + +What happens when we unplug the vehicle? + +```python + await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000001)) +``` + +When we re-read the events: + +```python +[ +│ EventReadResult( +│ │ Header=EventHeader( +│ │ │ EndpointId=1, +│ │ │ ClusterId=153, +│ │ │ EventId=3, +│ │ │ EventNumber=65540, +│ │ │ Priority=, +│ │ │ Timestamp=1705102996749, +│ │ │ TimestampType= +│ │ ), +│ │ Status=, +│ │ Data=EnergyTransferStopped( +│ │ │ sessionID=0, +│ │ │ state=, +│ │ │ reason=, +│ │ │ energyTransferred=0 +│ │ ) +│ ), +│ EventReadResult( +│ │ Header=EventHeader( +│ │ │ EndpointId=1, +│ │ │ ClusterId=153, +│ │ │ EventId=1, +│ │ │ EventNumber=65541, +│ │ │ Priority=, +│ │ │ Timestamp=1705102996749, +│ │ │ TimestampType= +│ │ ), +│ │ Status=, +│ │ Data=EVNotDetected( +│ │ │ sessionID=0, +│ │ │ state=, +│ │ │ sessionDuration=0, +│ │ │ sessionEnergyCharged=0, +│ │ │ sessionEnergyDischarged=0 +│ │ ) +│ ) +] + +``` + +- In `EventNumber 65540` we had an EnergyTransferStopped event with reason + `kOther`. + + This was a rather abrupt end to a charging session (normally we would see + the EVSE or EV decide to stop charging), but this demonstrates the cable + being pulled out without a graceful charging shutdown. + +- In `EventNumber 65541` we had an EvNotDetected event showing that the state + was `kPluggedInCharging` prior to the EV being not detected (normally in a + graceful shutdown this would be `kPluggedInNoDemand` or `kPluggedInDemand`). From f6293264611e8cb7433c9ee0e1622bbc8fd71b4a Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sat, 13 Jan 2024 13:30:52 +0000 Subject: [PATCH 44/52] Tweaks to README.md to avoid Myst syntax highlighting issues. --- examples/energy-management-app/linux/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/energy-management-app/linux/README.md b/examples/energy-management-app/linux/README.md index a6a9e9671f622c..fbf2e83d730970 100644 --- a/examples/energy-management-app/linux/README.md +++ b/examples/energy-management-app/linux/README.md @@ -237,7 +237,9 @@ Out[2]: ```python # Read from NodeID 1234, Endpoint 1, all attributes on EnergyEvse cluster await devCtrl.ReadAttribute(1234,[(1, chip.clusters.EnergyEvse)]) +``` +``` { │ 1: { │ │ : { @@ -327,7 +329,7 @@ Attribute Changed: After 60 seconds the charging should automatically become disabled: -```python +``` Attribute Changed: { │ 'Endpoint': 1, @@ -374,10 +376,6 @@ To send a test event trigger to the app, use the following commands (in chip-repl): ```python - # Read the events - await devCtrl.ReadEvent(1234,[(1, chip.clusters.EnergyEvse,1)]) -[] - # send 1st event trigger to 'install' the EVSE on a 32A supply await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000)) @@ -401,6 +399,9 @@ Now send the test event trigger to simulate the EV asking for demand: # Read the events await devCtrl.ReadEvent(1234,[(1, chip.clusters.EnergyEvse,1)]) +``` + +``` [ │ EventReadResult( │ │ Header=EventHeader( @@ -450,7 +451,7 @@ What happens when we unplug the vehicle? When we re-read the events: -```python +``` [ │ EventReadResult( │ │ Header=EventHeader( From 4d2108bec230b3f1669c4fee7747c7af1119df54 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sat, 13 Jan 2024 13:41:22 +0000 Subject: [PATCH 45/52] Improved error logging around GetEpochTS() --- .../src/EnergyEvseDelegateImpl.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index 6704eda6fa0158..2b4a6e44f5fcea 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -1510,9 +1510,12 @@ void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMe { /* Get Timestamp */ uint32_t chipEpoch = 0; - if (GetEpochTS(chipEpoch) != CHIP_NO_ERROR) + CHIP_ERROR err = GetEpochTS(chipEpoch); + if (err != CHIP_NO_ERROR) { - ChipLogError(AppServer, "EVSE: Failed to get timestamp when starting session"); + /* Note that the error will be also be logged inside GetErrorTS() - + * adding context here to help debugging */ + ChipLogError(AppServer, "EVSE: Unable to get current time when starting session - err:%" CHIP_ERROR_FORMAT, err.Format()); return; } mStartTime = chipEpoch; @@ -1556,9 +1559,13 @@ void EvseSession::RecalculateSessionDuration() { /* Get Timestamp */ uint32_t chipEpoch = 0; - if (GetEpochTS(chipEpoch) != CHIP_NO_ERROR) + CHIP_ERROR err = GetEpochTS(chipEpoch); + if (err != CHIP_NO_ERROR) { - ChipLogError(AppServer, "EVSE: Failed to get timestamp when updating session duration"); + /* Note that the error will be also be logged inside GetErrorTS() - + * adding context here to help debugging */ + ChipLogError(AppServer, "EVSE: Unable to get current time when updating session duration - err:%" CHIP_ERROR_FORMAT, + err.Format()); return; } From 1f16b6c3b5d64720cb9f7a824c684bfdb513d8e2 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sat, 13 Jan 2024 14:32:37 +0000 Subject: [PATCH 46/52] Made main use std::unique_ptr instead of using new/delete per PR comments. Also moved GetEVSEManufacturer declaration to header file. --- .../include/EVSEManufacturerImpl.h | 11 +++ .../src/EVSEManufacturerImpl.cpp | 3 - examples/energy-management-app/linux/main.cpp | 79 ++++++++----------- 3 files changed, 44 insertions(+), 49 deletions(-) diff --git a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h index 01f46a9d717e19..5bf253aef81d9b 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h @@ -66,6 +66,17 @@ class EVSEManufacturer int64_t mLastDischargingEnergyMeter = 0; }; +/** @brief Helper function to return the singleton EVSEManufacturer instance + * + * This is needed by the EVSEManufacturer class to support TestEventTriggers + * which are called outside of any class context. This allows the EVSEManufacturer + * class to return the relevant Delegate instance in which to invoke the test + * events on. + * + * This function is typically found in main.cpp or wherever the singleton is created. + */ +EVSEManufacturer * GetEvseManufacturer(); + } // namespace EnergyEvse } // namespace Clusters } // namespace app diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index 6e646a8dad42ee..da21a51cf7eb5d 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -25,9 +25,6 @@ using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; -/* Function prototype - this should be implemented somewhere in main.cpp or similar */ -EVSEManufacturer * GetEvseManufacturer(); - CHIP_ERROR EVSEManufacturer::Init() { /* Manufacturers should modify this to do any custom initialisation */ diff --git a/examples/energy-management-app/linux/main.cpp b/examples/energy-management-app/linux/main.cpp index 83cdecbe83db15..faf7651875fa94 100644 --- a/examples/energy-management-app/linux/main.cpp +++ b/examples/energy-management-app/linux/main.cpp @@ -35,48 +35,47 @@ using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; -static EnergyEvseDelegate * gDelegate = nullptr; -static EnergyEvseManager * gInstance = nullptr; -static EVSEManufacturer * gEvseManufacturer = nullptr; +static std::unique_ptr gDelegate; +static std::unique_ptr gInstance; +static std::unique_ptr gEvseManufacturer; -EVSEManufacturer * GetEvseManufacturer() +EVSEManufacturer * EnergyEvse::GetEvseManufacturer() { - return gEvseManufacturer; + return gEvseManufacturer.get(); } void ApplicationInit() { CHIP_ERROR err; - if ((gDelegate != nullptr) || (gInstance != nullptr) || (gEvseManufacturer != nullptr)) + if (gDelegate || gInstance || gEvseManufacturer) { ChipLogError(AppServer, "EVSE Instance or Delegate, EvseManufacturer already exist."); return; } - gDelegate = new EnergyEvseDelegate(); - if (gDelegate == nullptr) + gDelegate = std::make_unique(); + if (!gDelegate) { ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseDelegate"); return; } /* Manufacturer may optionally not support all features, commands & attributes */ - gInstance = - new EnergyEvseManager(EndpointId(ENERGY_EVSE_ENDPOINT), *gDelegate, - BitMask( - EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, - EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, EnergyEvse::Feature::kV2x), - BitMask(OptionalAttributes::kSupportsUserMaximumChargingCurrent, - OptionalAttributes::kSupportsRandomizationWindow, - OptionalAttributes::kSupportsApproximateEvEfficiency), - BitMask(OptionalCommands::kSupportsStartDiagnostics)); - - if (gInstance == nullptr) + gInstance = std::make_unique( + EndpointId(ENERGY_EVSE_ENDPOINT), *gDelegate, + BitMask(EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, + EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, + EnergyEvse::Feature::kV2x), + BitMask(OptionalAttributes::kSupportsUserMaximumChargingCurrent, + OptionalAttributes::kSupportsRandomizationWindow, + OptionalAttributes::kSupportsApproximateEvEfficiency), + BitMask(OptionalCommands::kSupportsStartDiagnostics)); + + if (!gInstance) { ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseManager"); - delete gDelegate; - gDelegate = nullptr; + gDelegate.reset(); return; } @@ -84,22 +83,18 @@ void ApplicationInit() if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "Init failed on gInstance"); - delete gInstance; - delete gDelegate; - gInstance = nullptr; - gDelegate = nullptr; + gInstance.reset(); + gDelegate.reset(); return; } /* Now create EVSEManufacturer*/ - gEvseManufacturer = new EVSEManufacturer(gInstance); - if (gEvseManufacturer == nullptr) + gEvseManufacturer = std::make_unique(gInstance.get()); + if (!gEvseManufacturer) { ChipLogError(AppServer, "Failed to allocate memory for EvseManufacturer"); - delete gInstance; - delete gDelegate; - gInstance = nullptr; - gDelegate = nullptr; + gInstance.reset(); + gDelegate.reset(); return; } @@ -108,12 +103,9 @@ void ApplicationInit() if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "Init failed on gEvseManufacturer"); - delete gEvseManufacturer; - delete gInstance; - delete gDelegate; - gEvseManufacturer = nullptr; - gInstance = nullptr; - gDelegate = nullptr; + gEvseManufacturer.reset(); + gInstance.reset(); + gDelegate.reset(); return; } } @@ -123,17 +115,12 @@ void ApplicationShutdown() ChipLogDetail(AppServer, "Energy Management App: ApplicationShutdown()"); /* Shutdown the EVSEManufacturer*/ - gEvseManufacturer->Shutdown(); + if (gEvseManufacturer) + gEvseManufacturer->Shutdown(); /* Shutdown the Instance - deregister attribute & command handler */ - gInstance->Shutdown(); - - delete gEvseManufacturer; - delete gInstance; - delete gDelegate; - gEvseManufacturer = nullptr; - gInstance = nullptr; - gDelegate = nullptr; + if (gInstance) + gInstance->Shutdown(); } int main(int argc, char * argv[]) From 13f16a87259f6c3579c8e3fe8ba95337c8c12644 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sat, 13 Jan 2024 16:41:36 +0000 Subject: [PATCH 47/52] Fixing MISSPELL issues in README.md --- .github/.wordlist.txt | 2 + .../energy-management-app/linux/README.md | 44 ++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 44470a612f42c7..07f3df6b7a9822 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -519,8 +519,10 @@ EthyleneOxideConcentrationMeasurement EvalCode EvalCodeWithName EvalFrameDefault +EV EVB evk +EVSE exceptfds ExchangeContext exe diff --git a/examples/energy-management-app/linux/README.md b/examples/energy-management-app/linux/README.md index fbf2e83d730970..be4e265e45b37d 100644 --- a/examples/energy-management-app/linux/README.md +++ b/examples/energy-management-app/linux/README.md @@ -17,7 +17,7 @@ To cross-compile this example on x64 host and run on **NXP i.MX 8M Mini** - [Running the Complete Example on Raspberry Pi 4](#running-the-complete-example-on-raspberry-pi-4) - [Running RPC Console](#running-rpc-console) - [Device Tracing](#device-tracing) - - [Python TestCases](#python-testcases) + - [Python Test Cases](#python-test-cases) - [Running the test cases:](#running-the-test-cases) - [CHIP-REPL Interaction](#chip-repl-interaction) - [Building chip-repl:](#building-chip-repl) @@ -149,7 +149,7 @@ Obtain tracing json file. -o {OUTPUT_FILE} -t {ELF_FILE} {PIGWEED_REPO}/pw_trace_tokenized/pw_trace_protos/trace_rpc.proto ``` -## Python TestCases +## Python Test Cases When you want to test this cluster you can use chip-repl or chip-tool by hand. CHIP-REPL is slightly easier to interact with when dealing with some of the @@ -158,20 +158,20 @@ complex structures. There are several test scripts provided for EVSE (in [src/python_testing](src/python_testing)): -- TC_EEVSE_2_2: This validates the primary functionality -- TC_EEVSE_2_3: This validates Get/Set/Clear target commands -- TC_EEVSE_2_4: This validates Faults -- TC_EEVSE_2_5: This validates EVSE diagnostic command (optional) +- `TC_EEVSE_2_2`: This validates the primary functionality +- `TC_EEVSE_2_3`: This validates Get/Set/Clear target commands +- `TC_EEVSE_2_4`: This validates Faults +- `TC_EEVSE_2_5`: This validates EVSE diagnostic command (optional) -These scripts require the use of TestEventsTriggers via the GeneralDiagnostics -cluster on Endpoint 0. This requires an enableKey (16 bytes) and a set of -reserved int64_t TestEvent trigger codes. +These scripts require the use of Test Event Triggers via the GeneralDiagnostics +cluster on Endpoint 0. This requires an `enableKey` (16 bytes) and a set of +reserved int64_t test event trigger codes. By default the test event support is not enabled, and when compiling the example app you need to add `chip_enable_energy_evse_trigger=true` to the gn args. $ gn gen out/debug - $ ninja -C out/debug --args='chip_enable_energy_evse_trigger=true` + $ ninja -C out/debug --args='chip_enable_energy_evse_trigger=true' Once the application is built you also need to tell it at runtime what the chosen enable key is using the `--enable-key` command line option. @@ -292,10 +292,11 @@ Out[2]: subscription = await devCtrl.ReadAttribute(1234,[(1, chip.clusters.EnergyEvse)], reportInterval=reportingTimingParams) ``` -- Step 6: Send an EnableCharging command which lasts for 60 seconds The - EnableCharging takes an optional parameter which allows the charger to - automatically disable itself at some preset time in the future. Note that it - uses Epoch_s (which is from Jan 1 2000) which is a uint32_t in seconds. +- Step 6: Send an `EnableCharging` command which lasts for 60 seconds The + `EnableCharging` takes an optional `chargingEnabledUntil` parameter which + allows the charger to automatically disable itself at some preset time in + the future. Note that it uses Epoch_s (which is from Jan 1 2000) which is a + uint32_t in seconds. ```python from datetime import datetime, timezone, timedelta @@ -369,7 +370,7 @@ The test event triggers values can be found in: - 0x0099000000000000 - Simulates the EVSE being installed on a 32A supply - 0x0099000000000002 - Simulates the EVSE being plugged in (this should - generate an EVConnected event) + generate an `EVConnected` event) - 0x0099000000000004 - Simulates the EVSE requesting power To send a test event trigger to the app, use the following commands (in @@ -384,7 +385,7 @@ chip-repl): ``` -Now send the enable charging command (omit the chargingEnabledUntil arg this +Now send the enable charging command (omit the `chargingEnabledUntil` arg this time): ```python @@ -440,7 +441,7 @@ Now send the test event trigger to simulate the EV asking for demand: - We can see that the `EventNumber 65538` was sent when the vehicle was plugged in, and a new `sessionID=0` was created. -- We can also see that the EnergyTransferStarted was sent in +- We can also see that the `EnergyTransferStarted` was sent in `EventNumber 65539` What happens when we unplug the vehicle? @@ -494,13 +495,14 @@ When we re-read the events: ``` -- In `EventNumber 65540` we had an EnergyTransferStopped event with reason +- In `EventNumber 65540` we had an `EnergyTransferStopped` event with reason `kOther`. This was a rather abrupt end to a charging session (normally we would see the EVSE or EV decide to stop charging), but this demonstrates the cable being pulled out without a graceful charging shutdown. -- In `EventNumber 65541` we had an EvNotDetected event showing that the state - was `kPluggedInCharging` prior to the EV being not detected (normally in a - graceful shutdown this would be `kPluggedInNoDemand` or `kPluggedInDemand`). +- In `EventNumber 65541` we had an `EvNotDetected` event showing that the + state was `kPluggedInCharging` prior to the EV being not detected (normally + in a graceful shutdown this would be `kPluggedInNoDemand` or + `kPluggedInDemand`). From a1b379e79740c930175757c905ffe55b0227d032 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Sat, 13 Jan 2024 23:46:26 +0000 Subject: [PATCH 48/52] Fixes #31061 Updated DEVICE_TYPE to 0x050C now this has been allocated --- .../linux/include/CHIPProjectAppConfig.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/energy-management-app/linux/include/CHIPProjectAppConfig.h b/examples/energy-management-app/linux/include/CHIPProjectAppConfig.h index f22628d0f69def..e091fa9cf7c91c 100644 --- a/examples/energy-management-app/linux/include/CHIPProjectAppConfig.h +++ b/examples/energy-management-app/linux/include/CHIPProjectAppConfig.h @@ -32,15 +32,13 @@ #define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 0 -// Bulbs do not typically use this - enabled so we can use shell to discover commissioners #define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT 1 #define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 #define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONABLE_DEVICE_TYPE 1 -// TODO We don’t have one yet - but we’d need to change this -#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 257 // 0x0101 = 257 = Dimmable Bulb +#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 0x050C // Energy EVSE #define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONABLE_DEVICE_NAME 1 From 495d3478e8edff6069936c1f3abc7f39f3d0d038 Mon Sep 17 00:00:00 2001 From: James Harrow Date: Mon, 15 Jan 2024 19:58:49 +0000 Subject: [PATCH 49/52] Small correction to description in test case. --- src/python_testing/TC_EEVSE_2_5.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py index 1a647c20e0bcef..34ca0151935f93 100644 --- a/src/python_testing/TC_EEVSE_2_5.py +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -51,12 +51,12 @@ def steps_TC_EEVSE_2_5(self) -> list[TestStep]: TestStep("5", "TH sends command StartDiagnostics. Verify that command is rejected with Failure"), TestStep("6", "TH sends command Disable."), TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), - TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (Disabled)"), + TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), TestStep("7", "TH sends command StartDiagnostics. Verify that command is accepted with Success"), TestStep("7a", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (DisabledDiagnostics)"), TestStep("8", "A few seconds later TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EVSE Diagnostics Complete Event"), TestStep("8a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), - TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (Disabled)"), + TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), TestStep("9", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event Clear."), ] From 69241b73791455b6713b857ff83dd4e447b8841b Mon Sep 17 00:00:00 2001 From: jamesharrow <93921463+jamesharrow@users.noreply.github.com> Date: Tue, 16 Jan 2024 19:06:27 +0000 Subject: [PATCH 50/52] Update examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h Co-authored-by: Boris Zbarsky --- .../energy-management-common/include/EnergyEvseDelegateImpl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 8cf76f1b7a29bf..d27f35ee63201c 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -53,7 +53,7 @@ class EvseSession public: EvseSession(EndpointId aEndpoint) { mEndpointId = aEndpoint; } /** - * @brief This function samples the start-time, and energy meter to hold the session info + * @brief This function records the start time and provided energy meter values as part of the new session. * * @param chargingMeterValue - The current value of the energy meter (charging) in mWh * @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh From 0c2a8cdf4c0b6605fdf807878ed9ce00f67fc30a Mon Sep 17 00:00:00 2001 From: jamesharrow <93921463+jamesharrow@users.noreply.github.com> Date: Tue, 16 Jan 2024 19:30:29 +0000 Subject: [PATCH 51/52] Touched file to retrigger restyled job --- .../energy-management-common/include/EVSECallbacks.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h index 15578405552d78..124326d1e260cf 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h +++ b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h @@ -30,7 +30,7 @@ using namespace chip::app::Clusters::EnergyEvse; * This is not specific to the EnergyEVSE cluster, but includes DeviceEnergyManagement * and potential future clusters. */ - + enum class EVSECallbackType : uint8_t { /* From fa454722179b2e682ae0f4bdd74777a9b656defe Mon Sep 17 00:00:00 2001 From: jamesharrow <93921463+jamesharrow@users.noreply.github.com> Date: Tue, 16 Jan 2024 19:32:11 +0000 Subject: [PATCH 52/52] Removed whitespace which was added to trigger restyled to rerun --- .../energy-management-common/include/EVSECallbacks.h | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h index 124326d1e260cf..7d288809952ee3 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h +++ b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h @@ -30,7 +30,6 @@ using namespace chip::app::Clusters::EnergyEvse; * This is not specific to the EnergyEVSE cluster, but includes DeviceEnergyManagement * and potential future clusters. */ - enum class EVSECallbackType : uint8_t { /*