@@ -46,6 +46,8 @@ using namespace chip::app::Clusters::PowerSource::Attributes;
4646
4747using Protocols::InteractionModel::Status;
4848
49+ constexpr int64_t kMaxRequiredEnergy_mWh = 1000000000000 ; // 1000 MWh
50+
4951CHIP_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
124129CHIP_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