Skip to content

Commit

Permalink
Make the EV-computed curve more meaningful
Browse files Browse the repository at this point in the history
- Do not throttle the SASchedule from the EV based on departure time, only
  transmit the composite pmax schedule. The EV will deareate based on
  departure time
- Show the values in the current power delivery request instead of the
  progress towards the eamount so that we can see the impact of curtailing the
  pmax
- Pass in the pmax to the power curve computation algo, although it is
  currently a NOP

This fixes: #92 (comment)

Signed-off-by: Shankari <[email protected]>
  • Loading branch information
Shankari committed Nov 20, 2024
1 parent 74e1a72 commit 047318c
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 95 deletions.
98 changes: 24 additions & 74 deletions manager/demo-patches/enable_iso_dt.patch
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
--- /ext/source/modules/EvseV2G/iso_server.cpp
+++ /ext/source/modules/EvseV2G/iso_server.cpp
@@ -37,6 +37,7 @@
#define MQTT_MAX_PAYLOAD_SIZE 268435455
#define V2G_SECC_MSG_CERTINSTALL_TIME 4500
#define GEN_CHALLENGE_SIZE 16
+#define CEIL(x, y) (x)/(y) + ((x) % (y) != 0);

constexpr uint16_t SAE_V2H = 28472;
constexpr uint16_t SAE_V2G = 28473;
@@ -1072,6 +1073,7 @@
}
res->EVSEProcessing = (iso2_EVSEProcessingType)conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH];

+
if (conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH] != iso2_EVSEProcessingType_Finished) {
if (((is_payment_option_contract == false) && (conn->ctx->session.auth_timeout_eim == 0)) ||
((is_payment_option_contract == true) && (conn->ctx->session.auth_timeout_pnc == 0))) {
@@ -1130,32 +1132,84 @@
@@ -1130,32 +1130,55 @@ static enum v2g_event handle_iso_charge_parameter_discovery(struct v2g_connectio
res->EVSEChargeParameter_isUsed = 0;
res->EVSEProcessing = (iso2_EVSEProcessingType)conn->ctx->evse_v2g_data.evse_processing[PHASE_PARAMETER];

+ struct linked_ac_params {
+ float max_current;
+ float max_current;
+ int64_t voltage;
+ int64_t pmax;
+ };
Expand All @@ -39,75 +23,48 @@
/* Determin max current and nominal voltage */
- float max_current = conn->ctx->basic_config.evse_ac_current_limit;
- int64_t nom_voltage =
+ linked_ac_params default_params;
+ linked_ac_params default_params;
+ /* Setup default params (before the departure time overrides) */
+ default_params.max_current = conn->ctx->basic_config.evse_ac_current_limit;
+ default_params.voltage =
conn->ctx->evse_v2g_data.evse_nominal_voltage.Value *
pow(10, conn->ctx->evse_v2g_data.evse_nominal_voltage.Multiplier); /* nominal voltage */
/* Calculate pmax based on max current, nominal voltage and phase count (which the car has selected
* above) */
-
- /* Calculate pmax based on max current, nominal voltage and phase count (which the car has selected
- * above) */
- int64_t pmax =
- max_current * nom_voltage *
+ default_params.pmax =
+ default_params.pmax =
+ default_params.max_current * default_params.voltage *
((req->RequestedEnergyTransferMode == iso2_EnergyTransferModeType_AC_single_phase_core) ? 1 : 3);
- populate_physical_value(&conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
+ dlog(DLOG_LEVEL_WARNING, "before adjusting for departure time, max_current %f, nom_voltage %d, pmax %d, departure_duration %d",
+
+ dlog(DLOG_LEVEL_WARNING, "before adjusting for departure time, max_current %f, nom_voltage %d, pmax %d, departure_duration %d",
+ default_params.max_current, default_params.voltage,
+ default_params.pmax, departure_time_duration);
+ double req_eamount = calc_physical_value(req->AC_EVChargeParameter.EAmount.Value,
+ double req_eamount = calc_physical_value(req->AC_EVChargeParameter.EAmount.Value,
+ req->AC_EVChargeParameter.EAmount.Multiplier);
+ dlog(DLOG_LEVEL_WARNING, "Requested departure time %u, requested energy %f",
+ dlog(DLOG_LEVEL_WARNING, "Requested departure time %u, requested energy %f",
+ departure_time_duration, req_eamount);
+ if (departure_time_duration == 0) {
+ dlog(DLOG_LEVEL_WARNING, "No departure time specified, not modifying defaults");
+ sel_params = default_params;
+ } else {
+ dlog(DLOG_LEVEL_WARNING, "Departure time specified, checking to see if we can lower requirements");
+ int64_t departure_time_duration_hours = departure_time_duration / 3600;
+ float min_hours_to_charge = req_eamount / default_params.pmax;
+ float min_secs_to_charge = min_hours_to_charge * 60 * 60;
+ if (min_secs_to_charge >= departure_time_duration) {
+ dlog(DLOG_LEVEL_WARNING,
+ "Min hours to charge %f, requested departure time in hours %f, pmax is unchanged",
+ min_hours_to_charge, departure_time_duration_hours);
+ sel_params = default_params;
+ } else {
+ /* In general, while computing these values, we use integer division
+ which rounds down. We then check if there is a reminder and add one to round up.
+ The rationale is that, from a user perspective, it is better to finish charging
+ a teeny bit early rather than not provide all the energy requested */
+ sel_params.pmax = (int)std::ceil(req_eamount / departure_time_duration_hours);
+ sel_params.voltage = default_params.voltage;
+ sel_params.max_current = CEIL(sel_params.pmax, (sel_params.voltage *
+ ((req->RequestedEnergyTransferMode == iso2_EnergyTransferModeType_AC_single_phase_core) ? 1 : 3)));
+ dlog(DLOG_LEVEL_WARNING,
+ "Min hours to charge %f, requested departure time in hours %d, plenty of time to charge",
+ min_hours_to_charge, departure_time_duration_hours);
+ dlog(DLOG_LEVEL_WARNING, "lowering pmax = %d, max_current = %f, pmax float = %f",
+ sel_params.pmax, sel_params.max_current, ((float)req_eamount / (float)departure_time_duration_hours));
+ }
+ }
+ populate_physical_value(&conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
+ sel_params = default_params;
+
populate_physical_value(&conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
.PMaxSchedule.PMaxScheduleEntry.array[0]
.PMax,
- pmax, iso2_unitSymbolType_W);
+ sel_params.pmax, iso2_unitSymbolType_W);
+ sel_params.pmax, iso2_unitSymbolType_W);
} else {
conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
.PMaxSchedule.PMaxScheduleEntry.array[0]
.PMax = conn->ctx->evse_v2g_data.evse_maximum_power_limit;
}
+ if (departure_time_duration == 0) {
+ if (departure_time_duration == 0) {
+ departure_time_duration = SA_SCHEDULE_DURATION; // one day, per spec
+ }
+ }
+
conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
.PMaxSchedule.PMaxScheduleEntry.array[0]
.RelativeTimeInterval.start = 0;
@@ -1164,7 +1218,7 @@
@@ -1164,7 +1187,7 @@ static enum v2g_event handle_iso_charge_parameter_discovery(struct v2g_connectio
.RelativeTimeInterval.duration_isUsed = 1;
conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
.PMaxSchedule.PMaxScheduleEntry.array[0]
Expand All @@ -116,22 +73,15 @@
conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0]
.PMaxSchedule.PMaxScheduleEntry.arrayLen = 1;
conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.arrayLen = 1;
@@ -1207,19 +1261,15 @@

populate_ac_evse_status(conn->ctx, &res->AC_EVSEChargeParameter.AC_EVSEStatus);

+ // We have already calculated all of these above, so let's not duplicate code here
/* Max current */
- float max_current = conn->ctx->basic_config.evse_ac_current_limit;
- populate_physical_value_float(&res->AC_EVSEChargeParameter.EVSEMaxCurrent, max_current, 1,
+ populate_physical_value_float(&res->AC_EVSEChargeParameter.EVSEMaxCurrent, sel_params.max_current, 1,
@@ -1212,14 +1235,8 @@ static enum v2g_event handle_iso_charge_parameter_discovery(struct v2g_connectio
populate_physical_value_float(&res->AC_EVSEChargeParameter.EVSEMaxCurrent, max_current, 1,
iso2_unitSymbolType_A);
-
/* Nominal voltage */
res->AC_EVSEChargeParameter.EVSENominalVoltage = conn->ctx->evse_v2g_data.evse_nominal_voltage;
- /* Nominal voltage */
- res->AC_EVSEChargeParameter.EVSENominalVoltage = conn->ctx->evse_v2g_data.evse_nominal_voltage;
- int64_t nom_voltage = conn->ctx->evse_v2g_data.evse_nominal_voltage.Value *
- pow(10, conn->ctx->evse_v2g_data.evse_nominal_voltage.Multiplier);
-
/* Calculate pmax based on max current, nominal voltage and phase count (which the car has selected above) */
- int64_t pmax = max_current * nom_voltage *
- ((iso2_EnergyTransferModeType_AC_single_phase_core == req->RequestedEnergyTransferMode) ? 1 : 3);
Expand Down
14 changes: 7 additions & 7 deletions manager/demo-patches/power_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@author: ANAND
"""
# KS is btwn 1 and 20
def LQRChargeCurve(DepTime, EAmount, KS):
def LQRChargeCurve(DepTime, EAmount, PMax, KS):
# system matrices
A=np.array([[0]])
B=np.array([[1]])
Expand Down Expand Up @@ -53,6 +53,7 @@ def LQRChargeCurve(DepTime, EAmount, KS):
# define the input for closed-loop simulation
inputCL=np.zeros(shape=(1,numberSamples))
inputCL[0,:]=xd*np.ones(numberSamples)
print(f"Created input array with {EAmount=} and {numberSamples=}")
returnSimulationCL = ct.forced_response(sysStateSpaceCl,
timeVector,
inputCL,
Expand All @@ -76,14 +77,13 @@ def LQRChargeCurve(DepTime, EAmount, KS):
@author Katie
'''
def formatCurveData(yc, uc, tc):
def formatCurveData(profile_entry_list):
# Node-RED expects watts & miliseconds
yc_curve = [{"x": float(x), "y": float(y) * 1000} for x, y in zip(yc, tc)]
uc_curve = [{"x": float(x), "y": float(y) * 1000} for x, y in zip(uc, tc)]
yc_curve = [{"x": float(ped.start), "y": float(ped.max_power.value)} for ped in profile_entry_list]
return {
"series": ["A", "B"],
"data": [yc_curve, uc_curve],
"labels": ["r1", "r2"]
"series": ["A"],
"data": [yc_curve],
"labels": ["r1"]
}


Expand Down
33 changes: 19 additions & 14 deletions manager/demo-patches/simulator_enabledt.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--- /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py
+++ /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py
--- /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py 2024-11-20 09:51:36.878132628 +0000
+++ /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py 2024-11-20 09:51:36.878132628 +0000
@@ -7,6 +7,7 @@
import logging
import random
Expand Down Expand Up @@ -32,7 +32,7 @@
from iso15118.shared.settings import get_PKI_PATH

logger = logging.getLogger(__name__)
@@ -283,7 +294,7 @@
@@ -290,7 +301,7 @@
dc_charge_params = None

if (await self.get_energy_transfer_mode(protocol)).startswith("AC"):
Expand All @@ -41,7 +41,7 @@
unit=UnitSymbol.WATT_HOURS)
ev_max_voltage = PVEVMaxVoltage(
multiplier=0, value=400, unit=UnitSymbol.VOLTAGE
@@ -295,7 +306,7 @@
@@ -302,7 +313,7 @@
multiplier=0, value=10, unit=UnitSymbol.AMPERE
)
ac_charge_params = ACEVChargeParameter(
Expand All @@ -50,7 +50,7 @@
e_amount=e_amount,
ev_max_voltage=ev_max_voltage,
ev_max_current=ev_max_current,
@@ -306,7 +317,7 @@
@@ -313,7 +324,7 @@
multiplier=1, value=6000, unit=UnitSymbol.WATT_HOURS
)
dc_charge_params = DCEVChargeParameter(
Expand All @@ -59,7 +59,7 @@
dc_ev_status=await self.get_dc_ev_status(),
ev_maximum_current_limit=self.dc_ev_charge_params.dc_max_current_limit,
ev_maximum_power_limit=self.dc_ev_charge_params.dc_max_power_limit,
@@ -422,7 +433,7 @@
@@ -429,7 +440,7 @@
)

scheduled_params = ScheduledScheduleExchangeReqParams(
Expand All @@ -68,7 +68,7 @@
ev_target_energy_request=RationalNumber(exponent=3, value=10),
ev_max_energy_request=RationalNumber(exponent=3, value=20),
ev_min_energy_request=RationalNumber(exponent=-2, value=5),
@@ -436,7 +447,7 @@
@@ -443,7 +454,7 @@
) -> DynamicScheduleExchangeReqParams:
"""Overrides EVControllerInterface.get_dynamic_se_params()."""
dynamic_params = DynamicScheduleExchangeReqParams(
Expand All @@ -77,7 +77,7 @@
min_soc=30,
target_soc=80,
ev_target_energy_request=RationalNumber(exponent=3, value=40),
@@ -577,12 +588,15 @@
@@ -584,12 +595,15 @@
return schedule.sa_schedule_tuple_id

async def process_sa_schedules_v2(
Expand All @@ -94,18 +94,22 @@
# The charging schedule coming from the SECC is called 'schedule', the
# pendant coming from the EVCC (after having processed the offered
# schedule(s)) is called 'profile'. Therefore, we use the prefix
@@ -610,13 +624,43 @@
@@ -617,13 +631,48 @@
)
evcc_profile_entry_list.append(last_profile_entry_details)

+ print("Done processing schedules...")
+ # Set Curve Variables...
+ print("About to handle pmax schedule %s" % secc_schedule.p_max_schedule.schedule_entries[0])
+ p_max = secc_schedule.p_max_schedule.schedule_entries[0].p_max
+ pmax:float = p_max.value * pow(10, p_max.multiplier)
+ print(f"{pmax=}")
+ departure_time = secc_schedule.p_max_schedule.schedule_entries[0].time_interval.duration
+ new_schedule = evcc_profile_entry_list
+ print("Abt to generate Curve...", time_elapsed)
+ if (time_elapsed > departure_time):
+ print("End of Profile! Defaulting to EVCC profile enteries")
+ elif (departure_time != 86400):
+ else:
+ ks = 1
+ # Check EAmount
+ mqtt_publish.single("everest_external/nodered/{}/evcc/check_eamount", "test", hostname="mqtt-server")
Expand All @@ -119,13 +123,14 @@
+ ks = 10
+ else: # == algorithm_two
+ ks = 1
+ power_draw_progress, power_draw, time_vector = LQRChargeCurve(departure_time, eamount, ks)
+ formatted_curve = formatCurveData(power_draw_progress,power_draw,time_vector)
+ power_draw_progress, power_draw, time_vector = LQRChargeCurve(departure_time, eamount, pmax, ks)
+ print(f"About to generate a new schedule with a EVCC_Profile {evcc_profile_entry_list}")
+ new_schedule = generate_new_schedule(evcc_profile_entry_list, power_draw, time_vector, departure_time, time_elapsed)
+ print(f"New schedule of length {len(new_schedule)} created")
+
+ formatted_curve = formatCurveData(new_schedule)
+ # Then Re-Publish the chosen curve as the final selection
+ print(f"About to publish {str(formatted_curve)=}")
+ mqtt_publish.single("everest_external/nodered/{}/evcc/active_powercurve", str(formatted_curve), hostname="mqtt-server")
+
# TODO If a SalesTariff is present and digitally signed (and TLS is used),
Expand All @@ -139,7 +144,7 @@
)

async def continue_charging(self) -> bool:
@@ -753,7 +797,7 @@
@@ -760,7 +809,7 @@
else:
# Dynamic Mode
dynamic_params = DynamicACChargeLoopReqParams(
Expand All @@ -148,7 +153,7 @@
ev_target_energy_request=RationalNumber(exponent=3, value=40),
ev_max_energy_request=RationalNumber(exponent=3, value=60),
ev_min_energy_request=RationalNumber(exponent=3, value=-20),
@@ -905,7 +949,7 @@
@@ -912,7 +961,7 @@
multiplier=1, value=6000, unit=UnitSymbol.WATT_HOURS
)
dc_charge_params = DCEVChargeParameter(
Expand Down

0 comments on commit 047318c

Please sign in to comment.