Skip to content

Commit abfbf11

Browse files
jamesharrowrestyled-commitssoares-sergio
authored
[EEVSE] Add test script and SDK updates for TC_EEVSE_2.7 (project-chip#39565)
* Added new TestEvent trigger codes to python and C++ * Added TE trigger support for SoC * Updated Energy Management App zap file to enable SoC, BatteryCapacity and other attributes needed in 1.5 * Initial version of TC_EEVSE_2_7 basic steps 1-7 * regen_all to get .matter file update * Restyled by clang-format * Added more test steps to 2.7 and used feature_guard and mark_all_remaining_steps_skipped() if not present * Enabled SoC and other features in Delegate example. * Added steps 9-10d (passing) * Moved charging target functions from EEVSE_2_3.py into EEVSE_Utils.py * fixed import issues missed in the move * Added steps 11a-d (failing due to delegate implementation - spec says if SoC is present then the nextEnergyRequired should be null) * Restyled by isort * Fix cast issue seen on some platforms * Refactored ComputeChargingSchedule to split out DetermineRequiredEnergy and ComputeStartTime to better support SoC based charging. * Fixed incorrect constraint check now that SoC reporting is enabled TC_EEVSE_2_3 was failing. * Fixed major typo! * Added steps 12a-14d. Fixed setting of NextStartTime to Null if SoC > TargetSoC * Finished test steps for 2.7 * Enforce TargetSoC to be null if we fall back to AddedEnergy charging mode (Step 17d failed) * Fixed Lint failures * Things spotted during self-review, and re-fix 2.3 test which was failing. Removed TODO around setting voltage with sensible EU default * Noticed that kMinimumChargeCurrent appears in autogen field index so renamed variable to kMinimumChargeCurrentLimit * Apply suggestions from code review Co-authored-by: Sergio Soares <[email protected]> * Fixed things which got broken after applying code review changes via GitHub * Corrected battery capacity to be 70kWh not 7kWh * Added comments per review about Matter Epoch date * Added Gemini code review suggestions * Revert "Fixed incorrect constraint check now that SoC reporting is enabled TC_EEVSE_2_3 was failing." This reverts commit 3dad7c3. * Per review comment - this probably should have been InvalidCommand. * Reworked TC_EEVSE_2.3 to handle kSoCReporting which started failing if TargetSoC was not included in SetTargets. Added new matter mark_step_range_skipped() into matter_testing.py * Added decorator and FeatureMap logging * Updated TestSteps for TC_EEVSE_2.7 * Restyled by isort * fixed lint error 2.3 & 2.7 * Restyled by isort --------- Co-authored-by: Restyled.io <[email protected]> Co-authored-by: Sergio Soares <[email protected]>
1 parent 15430e7 commit abfbf11

File tree

15 files changed

+1083
-214
lines changed

15 files changed

+1083
-214
lines changed

examples/energy-management-app/energy-management-common/energy-evse/include/EVSEManufacturerImpl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ class EVSEManufacturer : public DEMManufacturerDelegate
129129
*/
130130
static void ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg);
131131

132+
/**
133+
* @brief Helper functions used by ComputeChargingSchedule
134+
*/
135+
CHIP_ERROR DetermineRequiredEnergy(EnergyEvseDelegate * dg, int64_t & requiredEnergy_mWh,
136+
DataModel::Nullable<Percent> & targetSoC,
137+
DataModel::Nullable<int64_t> & targetAddedEnergy_mWh);
138+
139+
CHIP_ERROR ComputeStartTime(EnergyEvseDelegate * dg, DataModel::Nullable<uint32_t> & startTime_epoch_s,
140+
uint32_t targetTime_epoch_s, uint32_t now_epoch_s, int64_t requiredEnergy_mWh);
132141
/**
133142
* @brief Simple example to demonstrate how an EVSE can compute the start time
134143
* and duration of a charging schedule

examples/energy-management-app/energy-management-common/energy-evse/include/EnergyEvseDelegateImpl.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ namespace EnergyEvse {
3636
// chip::app::Clusters::EnergyEvse::TargetDayOfWeekBitmap)
3737
constexpr uint8_t kAllTargetDaysMask = 0x7f;
3838

39+
// A sensible minimum limit for mains voltage (100V) to avoid accidental use
40+
// of 100mV instead of 100000mV
41+
constexpr int64_t kMinimumMainsVoltage_mV = 100000;
42+
3943
/* Local state machine Events to allow simpler handling of state transitions */
4044
enum EVSEStateMachineEvent
4145
{
@@ -214,6 +218,8 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate
214218
// Internal API to allow an EVSE to change its internal state etc
215219
Status HwSetMaxHardwareCurrentLimit(int64_t currentmA);
216220
int64_t HwGetMaxHardwareCurrentLimit() { return mMaxHardwareCurrentLimit; }
221+
Status HwSetNominalMainsVoltage(int64_t voltage_mV);
222+
int64_t HwGetNominalMainsVoltage() { return mNominalMainsVoltage; }
217223
Status HwSetCircuitCapacity(int64_t currentmA);
218224
Status HwSetCableAssemblyLimit(int64_t currentmA);
219225
int64_t HwGetCableAssemblyLimit() { return mCableAssemblyCurrentLimit; }
@@ -282,7 +288,10 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate
282288

283289
/* SOC attributes */
284290
DataModel::Nullable<Percent> GetStateOfCharge() override;
291+
CHIP_ERROR SetStateOfCharge(DataModel::Nullable<Percent>);
285292
DataModel::Nullable<int64_t> GetBatteryCapacity() override;
293+
CHIP_ERROR SetBatteryCapacity(DataModel::Nullable<int64_t>);
294+
286295
/* PNC attributes*/
287296
DataModel::Nullable<CharSpan> GetVehicleID() override;
288297
/* Session SESS attributes */
@@ -304,7 +313,9 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate
304313
int64_t mCableAssemblyCurrentLimit = 0; /* Cable limit detected when cable is plugged in, in mA */
305314
int64_t mMaximumChargingCurrentLimitFromCommand = 0; /* Value of current maximum limit when charging enabled */
306315
int64_t mActualChargingCurrentLimit = 0;
307-
StateEnum mHwState = StateEnum::kNotPluggedIn; /* Hardware state */
316+
int64_t mNominalMainsVoltage = 230000; /* Assume a sensible default mains voltage */
317+
318+
StateEnum mHwState = StateEnum::kNotPluggedIn; /* Hardware state */
308319

309320
/* Variables to hold State and SupplyState in case a fault is raised */
310321
StateEnum mStateBeforeFault = StateEnum::kUnknownEnumValue;

examples/energy-management-app/energy-management-common/energy-evse/src/EVSEManufacturerImpl.cpp

Lines changed: 151 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ using namespace chip::app::Clusters::PowerSource::Attributes;
4646

4747
using Protocols::InteractionModel::Status;
4848

49+
constexpr int64_t kMaxRequiredEnergy_mWh = 1000000000000; // 1000 MWh
50+
4951
CHIP_ERROR EVSEManufacturer::Init(chip::EndpointId powerSourceEndpointId)
5052
{
5153
/* Manufacturers should modify this to do any custom initialisation */
@@ -78,6 +80,9 @@ CHIP_ERROR EVSEManufacturer::Init(chip::EndpointId powerSourceEndpointId)
7880
/*
7981
* This is an example implementation for manufacturers to consider
8082
*
83+
* For Manufacturer to specify the hardware mains voltage in mV:
84+
* dg->HwSetNominalMainsVoltage(230000); // 230V
85+
*
8186
* For Manufacturer to specify the hardware capability in mA:
8287
* dg->HwSetMaxHardwareCurrentLimit(32000); // 32A
8388
*
@@ -123,7 +128,7 @@ CHIP_ERROR EVSEManufacturer::Shutdown()
123128

124129
CHIP_ERROR FindNextTarget(const BitMask<EnergyEvse::TargetDayOfWeekBitmap> dayOfWeekMap, uint16_t minutesPastMidnightNow_m,
125130
uint16_t & targetTimeMinutesPastMidnight_m, DataModel::Nullable<Percent> & targetSoC,
126-
DataModel::Nullable<int64_t> & addedEnergy_mWh, bool bAllowTargetsInPast)
131+
DataModel::Nullable<int64_t> & targetAddedEnergy_mWh, bool bAllowTargetsInPast)
127132
{
128133
EnergyEvse::Structs::ChargingTargetScheduleStruct::Type entry;
129134

@@ -170,11 +175,11 @@ CHIP_ERROR FindNextTarget(const BitMask<EnergyEvse::TargetDayOfWeekBitmap> dayOf
170175

171176
if (chargingTarget.addedEnergy.HasValue())
172177
{
173-
addedEnergy_mWh.SetNonNull(chargingTarget.addedEnergy.Value());
178+
targetAddedEnergy_mWh.SetNonNull(chargingTarget.addedEnergy.Value());
174179
}
175180
else
176181
{
177-
addedEnergy_mWh.SetNull();
182+
targetAddedEnergy_mWh.SetNull();
178183
}
179184
}
180185
}
@@ -190,6 +195,141 @@ CHIP_ERROR FindNextTarget(const BitMask<EnergyEvse::TargetDayOfWeekBitmap> dayOf
190195
return bFound ? CHIP_NO_ERROR : CHIP_ERROR_NOT_FOUND;
191196
}
192197

198+
/**
199+
* @brief DetermineRequiredEnergy based on if we know vehicle SoC, BatteryCapacity
200+
* and TargetSoC or fallback to addedEnergy target.
201+
*
202+
* Output: requiredEnergy_mWh - how much energy is needed to charge
203+
* Note this could be 0 if the vehicleSoC > targetSoC
204+
*/
205+
CHIP_ERROR EVSEManufacturer::DetermineRequiredEnergy(EnergyEvseDelegate * dg, int64_t & requiredEnergy_mWh,
206+
DataModel::Nullable<Percent> & targetSoC,
207+
DataModel::Nullable<int64_t> & targetAddedEnergy_mWh)
208+
{
209+
DataModel::Nullable<Percent> vehicleSoC;
210+
DataModel::Nullable<int64_t> batteryCapacity_mWh;
211+
212+
if (!targetSoC.IsNull())
213+
{
214+
if (GetEvseInstance()->HasFeature(Feature::kSoCReporting))
215+
{
216+
// We support SoCReporting, but it doesn't mean the Vehicle supports it
217+
vehicleSoC = dg->GetStateOfCharge();
218+
batteryCapacity_mWh = dg->GetBatteryCapacity();
219+
if (!vehicleSoC.IsNull() && !batteryCapacity_mWh.IsNull())
220+
{
221+
// If the current SoC is already >= targetSoC - we don't need to charge, set to 0.
222+
requiredEnergy_mWh = std::max<int64_t>(
223+
0, static_cast<int64_t>((targetSoC.Value() - vehicleSoC.Value()) * batteryCapacity_mWh.Value() / 100));
224+
ChipLogProgress(AppServer, "EVSE: Vehicle reports current SoC: %d ----- target SoC: %d", vehicleSoC.Value(),
225+
targetSoC.Value());
226+
// Since we are charging using SoC then always null out the NextAddedEnergy target
227+
// to indicate that the SoC target is being followed (per spec)
228+
// NextChargeAddedEnergy is set by the caller based on targetAddedEnergy_mWh
229+
targetAddedEnergy_mWh.SetNull();
230+
231+
return CHIP_NO_ERROR;
232+
}
233+
// ELSE we don't have VehicleSoC and Battery Capacity so we have to
234+
// fallback to AddedEnergy charging below
235+
}
236+
else
237+
{
238+
// Cluster does not support SoC Reporting so target SoC is only allowed to be 100%
239+
if (targetSoC.Value() != 100)
240+
{
241+
ChipLogError(AppServer, "EVSE WARNING: TargetSoC is not 100%% and we don't know the EV SoC!");
242+
}
243+
244+
// We don't know the Vehicle SoC so we must charge now
245+
// Let's assume an unreasonably large battery size which
246+
// ComputeStartTime uses to recognise it needs to start now
247+
requiredEnergy_mWh = kMaxRequiredEnergy_mWh;
248+
249+
return CHIP_NO_ERROR;
250+
}
251+
}
252+
253+
// Unable to charge using TargetSoC - Fallback to using AddedEnergy method
254+
// Check we have a AddedEnergy target
255+
if (targetAddedEnergy_mWh.IsNull())
256+
{
257+
ChipLogError(AppServer,
258+
"EVSE ERROR: Cannot use TargetSoC (maybe missing VehicleSoC/BatteryCapacity?) or AddedEnergy has not been "
259+
"provided - assume large battery!");
260+
261+
requiredEnergy_mWh = kMaxRequiredEnergy_mWh;
262+
return CHIP_NO_ERROR;
263+
}
264+
265+
// Otherwise just use targetAddedEnergy_mWh
266+
requiredEnergy_mWh = targetAddedEnergy_mWh.Value();
267+
268+
// According to spec if we can't use SoC for charging (falling back to AddedEnergy)
269+
// then NextTargetSoC should be Null is set by the caller based on targetSoC
270+
targetSoC.SetNull();
271+
272+
return CHIP_NO_ERROR;
273+
}
274+
/**
275+
* @brief Compute the start time based on required energy
276+
*
277+
* Simple optimizer - assume a flat tariff throughout the day
278+
*/
279+
CHIP_ERROR EVSEManufacturer::ComputeStartTime(EnergyEvseDelegate * dg, DataModel::Nullable<uint32_t> & startTime_epoch_s,
280+
uint32_t targetTime_epoch_s, uint32_t now_epoch_s, int64_t requiredEnergy_mWh)
281+
{
282+
283+
uint32_t chargingDuration_s;
284+
uint32_t tempStartTime_epoch_s;
285+
286+
if (requiredEnergy_mWh == 0)
287+
{
288+
// If we don't need to charge, then ensure we do not set a NextStartTime
289+
// (spec says this should be NULL if we don't plan to schedule a charge)
290+
startTime_epoch_s.SetNull();
291+
return CHIP_NO_ERROR;
292+
}
293+
294+
// Compute power from nominal voltage and maxChargingRate
295+
// GetMaximumChargeCurrent returns mA, but to help avoid overflow
296+
// We use V (not mV) and compute power to the nearest Watt
297+
uint32_t power_W = static_cast<uint32_t>((dg->HwGetNominalMainsVoltage() * dg->GetMaximumChargeCurrent()) / 1000000);
298+
if (power_W == 0)
299+
{
300+
ChipLogError(AppServer, "EVSE Error: MaxCurrent = 0Amp - Can't schedule charging");
301+
startTime_epoch_s.SetNull();
302+
return CHIP_ERROR_INTERNAL;
303+
}
304+
305+
// Time to charge(seconds) = (3600 * Energy(mWh) / Power(W)) / 1000
306+
// to avoid using floats we multiply by 36 and then divide by 10 (instead of x3600 and dividing by 1000)
307+
chargingDuration_s =
308+
static_cast<uint32_t>((static_cast<uint64_t>(requiredEnergy_mWh) * 36) / (static_cast<uint64_t>(power_W) * 10));
309+
310+
// Add in 15 minutes leeway to account for slow starting vehicles
311+
// that need to condition the battery or if it is cold etc
312+
chargingDuration_s += (15 * 60);
313+
314+
// A price optimizer can look for cheapest time of day
315+
// However for now we'll start charging as late as possible
316+
tempStartTime_epoch_s = targetTime_epoch_s - chargingDuration_s;
317+
318+
if (tempStartTime_epoch_s < now_epoch_s)
319+
{
320+
// we need to turn on the EVSE now - it won't have enough time to reach the target
321+
startTime_epoch_s.SetNonNull(now_epoch_s);
322+
// TODO call function to turn on the EV
323+
}
324+
else
325+
{
326+
// we turn off the EVSE for now
327+
startTime_epoch_s.SetNonNull(tempStartTime_epoch_s);
328+
// TODO have a periodic timer which checks if we should turn on the charger now
329+
}
330+
return CHIP_NO_ERROR;
331+
}
332+
193333
/**
194334
* @brief Simple example to demonstrate how an EVSE can compute the start time
195335
* and duration of a charging schedule
@@ -215,18 +355,16 @@ CHIP_ERROR EVSEManufacturer::ComputeChargingSchedule()
215355
DataModel::Nullable<uint32_t> startTime_epoch_s;
216356
DataModel::Nullable<uint32_t> targetTime_epoch_s;
217357
DataModel::Nullable<Percent> targetSoC;
218-
DataModel::Nullable<int64_t> addedEnergy_mWh;
358+
DataModel::Nullable<int64_t> targetAddedEnergy_mWh;
219359

220-
uint32_t power_W;
221-
uint32_t chargingDuration_s;
222360
uint32_t tempTargetTime_epoch_s;
223-
uint32_t tempStartTime_epoch_s;
361+
int64_t requiredEnergy_mWh;
224362
uint16_t targetTimeMinutesPastMidnight_m;
225363

226364
// Initialise the values to Null - if the FindNextTarget finds one, then it will update the value
227365
targetTime_epoch_s.SetNull();
228366
targetSoC.SetNull();
229-
addedEnergy_mWh.SetNull();
367+
targetAddedEnergy_mWh.SetNull();
230368
startTime_epoch_s.SetNull(); // If we FindNextTarget this will be computed below and set to a non null value
231369

232370
/* We can only compute charging schedules if the EV is plugged in and the charging is enabled
@@ -239,7 +377,7 @@ CHIP_ERROR EVSEManufacturer::ComputeChargingSchedule()
239377
while (searchDay < 2)
240378
{
241379
err = FindNextTarget(dayOfWeekMap, minutesPastMidnightNow_m, targetTimeMinutesPastMidnight_m, targetSoC,
242-
addedEnergy_mWh, (searchDay != 0));
380+
targetAddedEnergy_mWh, (searchDay != 0));
243381
if (err == CHIP_ERROR_NOT_FOUND)
244382
{
245383
// We didn't find one for today, try tomorrow
@@ -265,68 +403,18 @@ CHIP_ERROR EVSEManufacturer::ComputeChargingSchedule()
265403
((now_epoch_s / 60) + targetTimeMinutesPastMidnight_m + (searchDay * 1440) - minutesPastMidnightNow_m) * 60;
266404
targetTime_epoch_s.SetNonNull(tempTargetTime_epoch_s);
267405

268-
if (!targetSoC.IsNull())
406+
/* Determine requiredEnergy based on if we support SoC Reporting or fallback to AddedEnergy target */
407+
if (DetermineRequiredEnergy(dg, requiredEnergy_mWh, targetSoC, targetAddedEnergy_mWh) == CHIP_NO_ERROR)
269408
{
270-
if (targetSoC.Value() != 100)
271-
{
272-
ChipLogError(AppServer, "EVSE WARNING: TargetSoC is not 100%% and we don't know the EV SoC!");
273-
}
274-
// We don't know the Vehicle SoC so we must charge now
275-
// TODO make this use the SoC featureMap to determine if this is an error
276-
startTime_epoch_s.SetNonNull(now_epoch_s);
277-
}
278-
else
279-
{
280-
// We expect to use AddedEnergy to determine the charging start time
281-
if (addedEnergy_mWh.IsNull())
282-
{
283-
ChipLogError(AppServer, "EVSE ERROR: Neither TargetSoC or AddedEnergy has been provided");
284-
return CHIP_ERROR_INTERNAL;
285-
}
286-
// Simple optimizer - assume a flat tariff throughout the day
287-
// Compute power from nominal voltage and maxChargingRate
288-
// GetMaximumChargeCurrent returns mA, but to help avoid overflow
289-
// We use V (not mV) and compute power to the nearest Watt
290-
power_W = static_cast<uint32_t>((230 * dg->GetMaximumChargeCurrent()) /
291-
1000); // TODO don't use 230V - not all markets will use that
292-
if (power_W == 0)
293-
{
294-
ChipLogError(AppServer, "EVSE Error: MaxCurrent = 0Amp - Can't schedule charging");
295-
return CHIP_ERROR_INTERNAL;
296-
}
297-
298-
// Time to charge(seconds) = (3600 * Energy(mWh) / Power(W)) / 1000
299-
// to avoid using floats we multiply by 36 and then divide by 10 (instead of x3600 and dividing by 1000)
300-
chargingDuration_s = static_cast<uint32_t>(((addedEnergy_mWh.Value() / power_W) * 36) / 10);
301-
302-
// Add in 15 minutes leeway to account for slow starting vehicles
303-
// that need to condition the battery or if it is cold etc
304-
chargingDuration_s += (15 * 60);
305-
306-
// A price optimizer can look for cheapest time of day
307-
// However for now we'll start charging as late as possible
308-
tempStartTime_epoch_s = tempTargetTime_epoch_s - chargingDuration_s;
309-
310-
if (tempStartTime_epoch_s < now_epoch_s)
311-
{
312-
// we need to turn on the EVSE now - it won't have enough time to reach the target
313-
startTime_epoch_s.SetNonNull(now_epoch_s);
314-
// TODO call function to turn on the EV
315-
}
316-
else
317-
{
318-
// we turn off the EVSE for now
319-
startTime_epoch_s.SetNonNull(tempStartTime_epoch_s);
320-
// TODO have a periodic timer which checks if we should turn on the charger now
321-
}
409+
ComputeStartTime(dg, startTime_epoch_s, tempTargetTime_epoch_s, now_epoch_s, requiredEnergy_mWh);
322410
}
323411
}
324412
}
325413

326414
// Update the attributes to allow a UI to inform the user
327415
dg->SetNextChargeStartTime(startTime_epoch_s);
328416
dg->SetNextChargeTargetTime(targetTime_epoch_s);
329-
dg->SetNextChargeRequiredEnergy(addedEnergy_mWh);
417+
dg->SetNextChargeRequiredEnergy(targetAddedEnergy_mWh);
330418
dg->SetNextChargeTargetSoC(targetSoC);
331419

332420
return err;

0 commit comments

Comments
 (0)