From 0ddd024a961fd9ff487385ac66e12ebe75635857 Mon Sep 17 00:00:00 2001 From: Shankari Date: Sat, 16 Nov 2024 10:16:11 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=E2=8F=B1=EF=B8=8F=20Initial=20set?= =?UTF-8?q?=20of=20patches=20for=20the=20departure=20time=20demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are primarily from @the-bay-kay's fork The main changes here are: - move them from the demo script to the dockerfile so that the compilation is done upfront, and not at runtime. this makes startup much faster and is more consistent with the ethos of containerization - create new scripts to apply the patches, since there are *so many* of them - move the existing patches for the auth method from the demo script to the new mechanism for consistency - these patches were not being applied earlier, so we also had to change the node-red flow to pass in the payment method - add new python packages to support generating curves properly https://github.com/EVerest/everest-demo/issues/74#issuecomment-2480665897 - fix the paths for the packages so that they work properly https://github.com/EVerest/everest-demo/issues/74#issuecomment-2480447920 Signed-off-by: Shankari --- .env | 2 +- demo-iso15118-2-ocpp-201.sh | 18 +- manager/Dockerfile | 39 +++- .../.apply-runtime-patches.sh.swp | Bin 0 -> 12288 bytes .../apply-compile-patches.sh | 5 + .../apply-library-patches.sh | 5 + .../apply-runtime-patches.sh | 24 +++ .../comm_session_handler_enabledt.patch | 24 +++ .../enable_evcc_logging.cfg | 0 manager/demo-patches/enable_iso_dt.patch | 141 ++++++++++++++ .../demo-patches/enable_ocpp_logging.patch | 33 ++++ .../enable_payment_method_in_python.patch | 64 +++---- manager/demo-patches/ev_state_enabledt.patch | 19 ++ .../iso15118_2_states_enabledt.patch | 109 +++++++++++ .../jsevmanager_index_enabledt.patch | 39 ++++ manager/demo-patches/power_curve.py | 172 ++++++++++++++++++ .../pyjosev_module_enabledt.patch | 13 ++ manager/demo-patches/simulator_enabledt.patch | 159 ++++++++++++++++ .../support_payment_in_jsevmanager.patch | 45 +++++ manager/support_payment_in_jsevmanager.patch | 48 ----- .../config/config-sil-iso15118-ac-flow.json | 4 +- 21 files changed, 853 insertions(+), 110 deletions(-) create mode 100644 manager/demo-patch-scripts/.apply-runtime-patches.sh.swp create mode 100644 manager/demo-patch-scripts/apply-compile-patches.sh create mode 100644 manager/demo-patch-scripts/apply-library-patches.sh create mode 100644 manager/demo-patch-scripts/apply-runtime-patches.sh create mode 100644 manager/demo-patches/comm_session_handler_enabledt.patch rename manager/{ => demo-patches}/enable_evcc_logging.cfg (100%) create mode 100644 manager/demo-patches/enable_iso_dt.patch create mode 100644 manager/demo-patches/enable_ocpp_logging.patch rename manager/{ => demo-patches}/enable_payment_method_in_python.patch (66%) create mode 100644 manager/demo-patches/ev_state_enabledt.patch create mode 100644 manager/demo-patches/iso15118_2_states_enabledt.patch create mode 100644 manager/demo-patches/jsevmanager_index_enabledt.patch create mode 100644 manager/demo-patches/power_curve.py create mode 100644 manager/demo-patches/pyjosev_module_enabledt.patch create mode 100644 manager/demo-patches/simulator_enabledt.patch create mode 100644 manager/demo-patches/support_payment_in_jsevmanager.patch delete mode 100644 manager/support_payment_in_jsevmanager.patch diff --git a/.env b/.env index 9e50fcad..50cb6303 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -TAG=0.0.19 +TAG=0.0.20 EVEREST_MANAGER_CPUS='1.0' EVEREST_MANAGER_MEMORY='1024mb' diff --git a/demo-iso15118-2-ocpp-201.sh b/demo-iso15118-2-ocpp-201.sh index 38d3e4c1..4d43820b 100755 --- a/demo-iso15118-2-ocpp-201.sh +++ b/demo-iso15118-2-ocpp-201.sh @@ -130,25 +130,9 @@ docker exec everest-ac-demo-manager-1 rm /ext/dist/share/everest/modules/OCPP201 docker exec everest-ac-demo-manager-1 rm /ext/dist/share/everest/modules/OCPP201/component_config/custom/Connector_2_1.json echo "Configuring and restarting nodered" -docker cp nodered/config/config-sil-iso15118-ac-flow.json everest-ac-demo-nodered-1:/config/config-sil-two-evse-flow.json +docker cp nodered/config/config-sil-iso15118-ac-flow.json everest-ac-demo-nodered-1:/config/config-sil-iso15118-ac-flow.json docker restart everest-ac-demo-nodered-1 -echo "Installing patch and vim and cleaning up the cache" -docker exec everest-ac-demo-manager-1 /bin/bash -c "apt-get -qq -o=Dpkg::Use-Pty=0 update \ - && apt-get install -y -qq -o=Dpkg::Use-Pty=0 patch \ - && apt-get install -y -qq -o=Dpkg::Use-Pty=0 vim \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*" - -echo "Copying over EVerest patches" -docker cp manager/enable_payment_method_in_python.patch everest-ac-demo-manager-1:/tmp/ -docker cp manager/support_payment_in_jsevmanager.patch everest-ac-demo-manager-1:/tmp/ - -echo "Now applying the patches" -docker cp manager/enable_evcc_logging.cfg everest-ac-demo-manager-1:/ext/dist/etc/everest/default_logging.cfg -docker exec everest-ac-demo-manager-1 /bin/bash -c "cd /ext && patch -p0 -i /tmp/enable_payment_method_in_python.patch" -docker exec everest-ac-demo-manager-1 /bin/bash -c "cd /ext/dist/libexec/everest && patch -p1 -i /tmp/support_payment_in_jsevmanager.patch" - if [[ "$DEMO_VERSION" =~ sp2 || "$DEMO_VERSION" =~ sp3 ]]; then docker cp manager/cached_certs_correct_name_emaid.tar.gz everest-ac-demo-manager-1:/ext/source/build docker exec everest-ac-demo-manager-1 /bin/bash -c "pushd /ext/source/build && tar xf cached_certs_correct_name_emaid.tar.gz" diff --git a/manager/Dockerfile b/manager/Dockerfile index 820917e6..c00ec118 100644 --- a/manager/Dockerfile +++ b/manager/Dockerfile @@ -3,6 +3,16 @@ FROM ghcr.io/everest/everest-ci/build-kit-base:v1.4.2 ARG EVEREST_VERSION=2024.9.0 ENV EVEREST_VERSION=${EVEREST_VERSION} +RUN echo "Copying compile-time patches before starting compile" + +COPY demo-patches/ /tmp/demo-patches/ +COPY demo-patch-scripts/ /tmp/demo-patch-scripts/ + +RUN echo "Installing patch and vim" +RUN apt-get -y -qq update +RUN apt-get install -y -qq patch +RUN apt-get install -y -qq vim + # Cloning the repo now and copying files over RUN git clone https://github.com/EVerest/everest-core.git \ && cd everest-core \ @@ -11,10 +21,37 @@ RUN git clone https://github.com/EVerest/everest-core.git \ && mkdir -p /ext/scripts \ && cp -r everest-core/.ci/build-kit/scripts/* /ext/scripts/ \ && mv everest-core /ext/source \ + && bash /tmp/demo-patch-scripts/apply-compile-patches.sh \ && /entrypoint.sh run-script compile \ && /entrypoint.sh run-script install -# Copy over the custom config *after* compilation and installation +# The previous approach works for code patches to the +# modules in everest-core, which are checked out as part +# of the build. However, it does not work for patches to the +# libraries that the modules use because the modules are +# downloaded as part of the build + +# so we need to apply them post-build and then recompile and +# re-install. If there was a way to split the prep and the +# build (e.g. between the cmake and the ninja, we could apply +# it there. But this is what we have to work with :( + +RUN bash /tmp/demo-patch-scripts/apply-library-patches.sh +RUN /entrypoint.sh run-script compile + +# cleanup +RUN apt-get -y remove --purge build-essential +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /var/tmp/* + +# Copy over the non-compiled patches *after* compilation and installation +RUN echo "Applying Post-Build patches..." +RUN bash /tmp/demo-patch-scripts/apply-runtime-patches.sh + +# Setup python stuff for the more complex simulator +# and for testing if needed +RUN pip install --break-system-packages numpy==2.1.3 +RUN pip install --break-system-packages control==0.10.1 +RUN pip install --break-system-packages paho-mqtt==2.1.0 COPY run-test.sh /ext/source/tests/run-test.sh diff --git a/manager/demo-patch-scripts/.apply-runtime-patches.sh.swp b/manager/demo-patch-scripts/.apply-runtime-patches.sh.swp new file mode 100644 index 0000000000000000000000000000000000000000..596e4ca72fbbf668b96ba5b923ae3b995b1145cf GIT binary patch literal 12288 zcmeI2KX21O7{=eSAf+uuurRu*)CuRBMpeVeScxJQ7!Z)3{O1Xi=kA@S z%!Uat0Vco%m;e)C0!)AjFoFMwfYod8n$~-xTJycCUAxd$x7;uRCcp%k025#WOn?b6 z0Vco%m;e)C0@sj$^Z`D#0FKuwJpKP)fB*m30{Bk)NP0;+BHbi?z6J1x^qjOs`mzb| zmZV88=|1V_2EaK1SaVobh~{@q?9mLT3Q1%=F-U-rk={u33hbuyZ})C+_Ek>IlH^g ztQ^;Ib=Pr&22>`~I!P_%$s>%d7sTEvWvVKAJG0S%YLqzbheatbp>(b?rd2= zq1vuGLWFjd_(c+()33NWL^-GcSq5(&)h+>D2=TZ%hY6xAlr~AdYI56beObS9%~$i% z`E2jKDDku6sLoETe~ID3&a1f2Z7uQjak|8KS^rhM>r?LSd*u;d;=Qc@D&9CLJ(pNs p(rpzdl|+$e(3l{JJ*vd_Lwa8q@9q_b&@tZG7K0!b14*^>zW~751|0wZ literal 0 HcmV?d00001 diff --git a/manager/demo-patch-scripts/apply-compile-patches.sh b/manager/demo-patch-scripts/apply-compile-patches.sh new file mode 100644 index 00000000..b23195dd --- /dev/null +++ b/manager/demo-patch-scripts/apply-compile-patches.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "Applying compile-time patches" + +cd / && patch -p0 -i /tmp/demo-patches/enable_iso_dt.patch diff --git a/manager/demo-patch-scripts/apply-library-patches.sh b/manager/demo-patch-scripts/apply-library-patches.sh new file mode 100644 index 00000000..abc14d66 --- /dev/null +++ b/manager/demo-patch-scripts/apply-library-patches.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "Applying library patches" + +cd / && patch -p0 -i /tmp/demo-patches/enable_ocpp_logging.patch diff --git a/manager/demo-patch-scripts/apply-runtime-patches.sh b/manager/demo-patch-scripts/apply-runtime-patches.sh new file mode 100644 index 00000000..ab138825 --- /dev/null +++ b/manager/demo-patch-scripts/apply-runtime-patches.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +echo "Applying comm_session_handler_enabledt.patch" +cd / && patch -p0 -N -i /tmp/demo-patches/comm_session_handler_enabledt.patch +echo "Applying ev_state_enabledt.patch" +cd / && patch -p0 -N -i /tmp/demo-patches/ev_state_enabledt.patch +echo "Applying iso15118_2_states_enabledt.patch" +cd / && patch -p0 -N -i /tmp/demo-patches/iso15118_2_states_enabledt.patch +echo "Applying jsevmanager_index_enabledt.patch" +cd / && patch -p0 -N -i /tmp/demo-patches/jsevmanager_index_enabledt.patch +echo "Applying pyjosev_module_enabledt.patch" +cd / && patch -p0 -N -i /tmp/demo-patches/pyjosev_module_enabledt.patch +echo "Applying simulator_enabledt.patch" +cd / && patch -p0 -N -i /tmp/demo-patches/simulator_enabledt.patch + +echo "Applying enabled_payment_method_in_python.patch" +cd /ext && patch -p0 -i /tmp/demo-patches/enable_payment_method_in_python.patch +echo "Applying support_payment_in_jsevmanager.patch" +cd /ext/dist/libexec/everest && patch -p1 -i /tmp/demo-patches/support_payment_in_jsevmanager.patch + +cp /tmp/demo-patches/power_curve.py \ +/ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/ + +cp /tmp/demo-patches/enable_evcc_logging.cfg /ext/dist/etc/everest/default_logging.cfg diff --git a/manager/demo-patches/comm_session_handler_enabledt.patch b/manager/demo-patches/comm_session_handler_enabledt.patch new file mode 100644 index 00000000..71204b35 --- /dev/null +++ b/manager/demo-patches/comm_session_handler_enabledt.patch @@ -0,0 +1,24 @@ +--- /ext/source/build/dist/libexec/everest/3rd_party/josev/iso15118/evcc/comm_session_handler.py ++++ /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/comm_session_handler.py +@@ -114,6 +114,21 @@ + # Once the timer is up, the EV will terminate the communication session. + # A value >= 0 means the timer is running, a value < 0 means it stopped. + self.ongoing_timer: float = -1 ++ ++ # The Charge timer (in seconds) starts running once the EVCC ++ # receives a PowerDeliveryRes with EVSEProcessing set to 'Finished'. ++ # This timer counts up during a charge session, recording the duration. ++ # When a charge session is paused or stopped, the timer is reset. ++ # A value >= 0 means the timer is running, a value < 0 means it stopped. ++ self.charging_session_timer: float = -1 ++ # The end of profile schedule marks the final departure time within ++ # the profile_entry_schedule created for a PowerDeliveryReq. This ++ # value, is used to mark when the 24 entry schedule has terminated. ++ # See ISO 15118-2 Subclause 8.5.2.10 for details ++ self.end_of_profile_schedule: int= -1 ++ self.sim_speed: int = -1 ++ self.departure_time: int = -1 ++ + # Temporarily save the ScheduleExchangeReq, which need to be resent to the SECC + # if the response message's EVSEProcessing field is set to "Ongoing" + self.ongoing_schedule_exchange_req: Optional[ScheduleExchangeReq] = None diff --git a/manager/enable_evcc_logging.cfg b/manager/demo-patches/enable_evcc_logging.cfg similarity index 100% rename from manager/enable_evcc_logging.cfg rename to manager/demo-patches/enable_evcc_logging.cfg diff --git a/manager/demo-patches/enable_iso_dt.patch b/manager/demo-patches/enable_iso_dt.patch new file mode 100644 index 00000000..ee39155b --- /dev/null +++ b/manager/demo-patches/enable_iso_dt.patch @@ -0,0 +1,141 @@ +--- /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 @@ + res->EVSEChargeParameter_isUsed = 0; + res->EVSEProcessing = (iso2_EVSEProcessingType)conn->ctx->evse_v2g_data.evse_processing[PHASE_PARAMETER]; + ++ struct linked_ac_params { ++ float max_current; ++ int64_t voltage; ++ int64_t pmax; ++ }; ++ ++ linked_ac_params sel_params; ++ + /* Configure SA-schedules*/ + if (res->EVSEProcessing == iso2_EVSEProcessingType_Finished) { + /* If processing is finished, configure SASchedule list */ + if (conn->ctx->evse_v2g_data.evse_sa_schedule_list_is_used == false) { ++ int64_t departure_time_duration = req->AC_EVChargeParameter.DepartureTime; ++ + /* If not configured, configure SA-schedule automatically for AC charging */ + if (conn->ctx->is_dc_charger == false) { + /* 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; ++ /* 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) */ +- int64_t pmax = +- max_current * nom_voltage * ++ 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", ++ default_params.max_current, default_params.voltage, ++ default_params.pmax, departure_time_duration); ++ 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", ++ 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] + .PMaxSchedule.PMaxScheduleEntry.array[0] + .PMax, +- 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) { ++ 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 @@ + .RelativeTimeInterval.duration_isUsed = 1; + conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0] + .PMaxSchedule.PMaxScheduleEntry.array[0] +- .RelativeTimeInterval.duration = SA_SCHEDULE_DURATION; ++ .RelativeTimeInterval.duration = departure_time_duration; + 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, + iso2_unitSymbolType_A); +- + /* 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); ++ int64_t pmax = sel_params.pmax; + + /* Check the SASchedule */ + if (res->SAScheduleList_isUsed == (unsigned int)1) { diff --git a/manager/demo-patches/enable_ocpp_logging.patch b/manager/demo-patches/enable_ocpp_logging.patch new file mode 100644 index 00000000..1d2e587c --- /dev/null +++ b/manager/demo-patches/enable_ocpp_logging.patch @@ -0,0 +1,33 @@ +--- /ext/cache/cpm/libocpp/56452082640eeee05feec42f2f502d6beb8e684c/libocpp/lib/ocpp/v201/charge_point.cpp ++++ /ext/cache/cpm/libocpp/56452082640eeee05feec42f2f502d6beb8e684c/libocpp/lib/ocpp/v201/charge_point.cpp +@@ -3358,7 +3358,7 @@ + } + + void ChargePoint::handle_set_charging_profile_req(Call call) { +- EVLOG_debug << "Received SetChargingProfileRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; ++ EVLOG_info << "Received SetChargingProfileRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; + auto msg = call.msg; + SetChargingProfileResponse response; + response.status = ChargingProfileStatusEnum::Rejected; +@@ -3383,7 +3383,7 @@ + response.statusInfo = StatusInfo(); + response.statusInfo->reasonCode = "InvalidValue"; + response.statusInfo->additionalInfo = "ChargingStationExternalConstraintsInSetChargingProfileRequest"; +- EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get() ++ EVLOG_info << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get() + << "\nadditionalInfo: " << response.statusInfo->additionalInfo->get(); + + ocpp::CallResult call_result(response, call.uniqueId); +@@ -3394,10 +3394,10 @@ + + response = this->smart_charging_handler->validate_and_add_profile(msg.chargingProfile, msg.evseId); + if (response.status == ChargingProfileStatusEnum::Accepted) { +- EVLOG_debug << "Accepting SetChargingProfileRequest"; ++ EVLOG_info << "Accepting SetChargingProfileRequest"; + this->callbacks.set_charging_profiles_callback(); + } else { +- EVLOG_debug << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get() ++ EVLOG_info << "Rejecting SetChargingProfileRequest:\n reasonCode: " << response.statusInfo->reasonCode.get() + << "\nadditionalInfo: " << response.statusInfo->additionalInfo->get(); + } + diff --git a/manager/enable_payment_method_in_python.patch b/manager/demo-patches/enable_payment_method_in_python.patch similarity index 66% rename from manager/enable_payment_method_in_python.patch rename to manager/demo-patches/enable_payment_method_in_python.patch index 5a1ae12b..ce5236e0 100644 --- a/manager/enable_payment_method_in_python.patch +++ b/manager/demo-patches/enable_payment_method_in_python.patch @@ -1,6 +1,7 @@ ---- ext-switchev-iso15118/iso15118/evcc/controller/interface.py -+++ dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py -@@ -109,6 +109,15 @@ +diff -r -uw /tmp/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py +--- /tmp/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py 2024-11-16 07:27:59.908302470 +0000 ++++ dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/interface.py 2024-11-16 07:29:33.510264217 +0000 +@@ -112,6 +112,15 @@ raise NotImplementedError @abstractmethod @@ -16,17 +17,11 @@ async def get_energy_transfer_mode( self, protocol: Protocol ) -> EnergyTransferModeEnum: ---- ext-switchev-iso15118/iso15118/evcc/controller/simulator.py -+++ dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py -@@ -53,6 +53,7 @@ - SAScheduleTupleEntry as SAScheduleTupleEntryDINSPEC, - ) - from iso15118.shared.messages.enums import ( -+ AuthEnum, - ControlMode, - DCEVErrorCode, - EnergyTransferModeEnum, -@@ -233,11 +234,18 @@ +Only in dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller: interface.py.orig +diff -r -uw /tmp/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py +--- /tmp/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py 2024-11-16 07:27:59.909302470 +0000 ++++ dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py 2024-11-16 07:29:33.516264216 +0000 +@@ -248,12 +248,19 @@ logger.error(f"Invalid protocol '{protocol}', can't determine EVCCID") raise InvalidProtocolError @@ -41,13 +36,16 @@ ) -> EnergyTransferModeEnum: """Overrides EVControllerInterface.get_energy_transfer_mode().""" return EnergyTransferModeEnum(EVEREST_EV_STATE.EnergyTransferMode) -+ ++ async def get_supported_energy_services(self) -> List[ServiceV20]: """Overrides EVControllerInterface.get_energy_transfer_service().""" ---- ext-switchev-iso15118/iso15118/evcc/states/iso15118_2_states.py -+++ dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py -@@ -193,8 +193,9 @@ + return self.config.supported_energy_services +Only in dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller: simulator.py.orig +diff -r -uw /tmp/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py +--- /tmp/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py 2024-11-16 07:27:59.933302468 +0000 ++++ dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py 2024-11-16 07:29:33.517264216 +0000 +@@ -196,8 +196,9 @@ self.stop_state_machine("ChargeService not offered") return @@ -58,7 +56,7 @@ await self.select_energy_transfer_mode() charge_service: ChargeService = service_discovery_res.charge_service -@@ -262,12 +263,13 @@ +@@ -265,12 +266,13 @@ self.comm_session.selected_energy_mode.value.startswith("AC") ) @@ -73,17 +71,10 @@ if evcc_settings.ev_session_context.selected_auth_option: logger.debug( "Reusing authorization option " -@@ -279,15 +281,30 @@ +@@ -282,6 +284,21 @@ ) evcc_settings.ev_session_context.selected_auth_option = None else: -- # Choose Plug & Charge (pnc) or External Identification Means (eim) -- # as the selected authorization option. The car manufacturer might -- # have a mechanism to determine a user-defined or default -- # authorization option. This implementation favors pnc, but -- # feel free to change if need be. -- if AuthEnum.PNC_V2 in auth_option_list and self.comm_session.is_tls: -- self.comm_session.selected_auth_option = AuthEnum.PNC_V2 + logger.warn("V2G_PAYMENT: about to read value from state") + self.comm_session.selected_auth_option = ( + await self.comm_session.ev_controller.get_selected_auth_option( @@ -95,20 +86,11 @@ + logger.debug( + "V2G_PAYMENT: Found Payment Option %s passed in from the PyJoseV module, using it" % self.comm_session.selected_auth_option + ) - else: -- self.comm_session.selected_auth_option = AuthEnum.EIM_V2 ++ else: + logger.debug( + "V2G_PAYMENT: No previous paused session, no PaymentOption set, using TLS flag %s to decide auth method" % self.comm_session.is_tls + ) -+ # Choose Plug & Charge (pnc) or External Identification Means (eim) -+ # as the selected authorization option. The car manufacturer might -+ # have a mechanism to determine a user-defined or default -+ # authorization option. This implementation favors pnc, but -+ # feel free to change if need be. -+ if AuthEnum.PNC_V2 in auth_option_list and self.comm_session.is_tls: -+ self.comm_session.selected_auth_option = AuthEnum.PNC_V2 -+ else: -+ self.comm_session.selected_auth_option = AuthEnum.EIM_V2 - - async def select_services(self, service_discovery_res: ServiceDiscoveryRes): - """ + # Choose Plug & Charge (pnc) or External Identification Means (eim) + # as the selected authorization option. The car manufacturer might + # have a mechanism to determine a user-defined or default +Only in dist/libexec/everest/3rd_party/josev/iso15118/evcc/states: iso15118_2_states.py.orig diff --git a/manager/demo-patches/ev_state_enabledt.patch b/manager/demo-patches/ev_state_enabledt.patch new file mode 100644 index 00000000..b102432f --- /dev/null +++ b/manager/demo-patches/ev_state_enabledt.patch @@ -0,0 +1,19 @@ +--- /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/everest/ev_state.py ++++ /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/everest/ev_state.py +@@ -1,6 +1,7 @@ + # SPDX-License-Identifier: Apache-2.0 + # Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest + from dataclasses import dataclass, field ++from typing import Optional + + DEFAULT_DC_MAX_CURRENT_LIMIT_A = 300 + DEFAULT_DC_MAX_POWER_LIMIT_W = 150000 +@@ -13,6 +14,8 @@ DEFAULT_TARGET_VOLTAGE_V = 200 + class EVState: + # Common + PaymentOption: str = '' ++ EAmount: int = 60 ++ DepartureTime: Optional[int] = None + EnergyTransferMode: str = '' + StopCharging = False + Pause = False diff --git a/manager/demo-patches/iso15118_2_states_enabledt.patch b/manager/demo-patches/iso15118_2_states_enabledt.patch new file mode 100644 index 00000000..69ada58a --- /dev/null +++ b/manager/demo-patches/iso15118_2_states_enabledt.patch @@ -0,0 +1,109 @@ +--- /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py ++++ /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/states/iso15118_2_states.py +@@ -4,6 +4,8 @@ + SessionStopRes. + """ + ++import paho.mqtt.publish as mqtt_publish ++import paho.mqtt.subscribe as mqtt_subscribe + import logging + from time import time + from typing import Any, List, Union +@@ -63,6 +65,7 @@ + PowerDeliveryReq, + PowerDeliveryRes, + PreChargeReq, ++ ResponseCode, + PreChargeRes, + ServiceDetailReq, + ServiceDetailRes, +@@ -715,7 +718,7 @@ + if authorization_res.evse_processing == EVSEProcessing.FINISHED: + # Reset the Ongoing timer + self.comm_session.ongoing_timer = -1 +- ++ + charge_params = await self.comm_session.ev_controller.get_charge_params_v2( + Protocol.ISO_15118_2 + ) +@@ -726,6 +729,7 @@ + dc_ev_charge_parameter=charge_params.dc_parameters, + ) + ++ + self.create_next_message( + ChargeParameterDiscovery, + charge_parameter_discovery_req, +@@ -785,10 +789,21 @@ + ) + ev_controller = self.comm_session.ev_controller + ++ print('SAScheduleTuples are:', charge_params_res.sa_schedule_list.schedule_tuples) + if charge_params_res.evse_processing == EVSEProcessing.FINISHED: + # Reset the Ongoing timer + self.comm_session.ongoing_timer = -1 ++ if(self.comm_session.charging_session_timer < 0): ++ self.comm_session.charging_session_timer = time() + ++ mqtt_publish.single("everest_external/nodered/{}/evcc/check_sim_speed", "test", hostname="mqtt-server") ++ sim_speed_msg = mqtt_subscribe.simple("everest_external/nodered/evcc/confirm_sim_speed", hostname="mqtt-server") ++ self.comm_session.sim_speed = int(str(sim_speed_msg.payload)[2:-1]) ++ print("Sim speed is now ", self.comm_session.sim_speed) ++ print(time(), self.comm_session.charging_session_timer, self.comm_session.sim_speed) ++ time_elapsed = (time() - self.comm_session.charging_session_timer) * self.comm_session.sim_speed ++ print('Charging Session Time Elapsed... ', time_elapsed) ++ + # TODO Look at EVSEStatus and EVSENotification and react accordingly + # if e.g. EVSENotification is set to STOP_CHARGING or if RCD + # is True. But let's do that after the testival +@@ -798,12 +813,22 @@ + schedule_id, + charging_profile, + ) = await ev_controller.process_sa_schedules_v2( +- charge_params_res.sa_schedule_list.schedule_tuples ++ charge_params_res.sa_schedule_list.schedule_tuples, +++ time_elapsed, + ) +- ++ # time_elapsed, + # EVerest code start # ++ self.comm_session.end_of_profile_schedule = charging_profile.profile_entries[-1].start ++ ++ mqtt_publish.single("everest_external/nodered/{}/evcc/check_departure_time", "test", hostname="mqtt-server") ++ dt_speed_msg = mqtt_subscribe.simple("everest_external/nodered/evcc/confirm_departure_time", hostname="mqtt-server") ++ # If end of profile > end of SECC schedule or no DT (dt==0), end renegotiation... ++ if (self.comm_session.end_of_profile_schedule >= int(str(dt_speed_msg .payload)[2:-1]) or 0 == int(str(dt_speed_msg .payload)[2:-1])): ++ self.comm_session.end_of_profile_schedule = 86400 ++ + EVEREST_CTX.publish('AC_EVPowerReady', True) + # EVerest code end # ++ + await self.comm_session.ev_controller.enable_charging(True) + if self.comm_session.selected_charging_type_is_ac: + power_delivery_req = PowerDeliveryReq( +@@ -1171,6 +1196,16 @@ + if charging_status_res.evse_max_current: + evse_max_current = charging_status_res.evse_max_current.value * pow(10, charging_status_res.evse_max_current.multiplier) + EVEREST_CTX.publish('AC_EVSEMaxCurrent', evse_max_current) ++ ++ time_elapsed = (time() - self.comm_session.charging_session_timer) * self.comm_session.sim_speed ++ print('End Of Schedule:: ', self.comm_session.end_of_profile_schedule) ++ print('NewClockValue:: ', time_elapsed) ++ print(self.comm_session.end_of_profile_schedule) ++ ++ is_end_of_profile = (time_elapsed > self.comm_session.end_of_profile_schedule) and (self.comm_session.end_of_profile_schedule <= 86400) ++ if is_end_of_profile: ++ print('Passed the end of the schedule!') ++ + # EVerest code end # + + if charging_status_res.receipt_required and self.comm_session.is_tls: +@@ -1213,7 +1248,7 @@ + f"MeteringReceiptReq: {exc}" + ) + return +- elif ac_evse_status.evse_notification == EVSENotification.RE_NEGOTIATION: ++ elif ac_evse_status.evse_notification == EVSENotification.RE_NEGOTIATION or is_end_of_profile: + self.comm_session.renegotiation_requested = True + power_delivery_req = PowerDeliveryReq( + charge_progress=ChargeProgress.RENEGOTIATE, diff --git a/manager/demo-patches/jsevmanager_index_enabledt.patch b/manager/demo-patches/jsevmanager_index_enabledt.patch new file mode 100644 index 00000000..132e1de5 --- /dev/null +++ b/manager/demo-patches/jsevmanager_index_enabledt.patch @@ -0,0 +1,39 @@ +--- /ext/dist/libexec/everest/modules/JsEvManager/index.js ++++ /ext/dist/libexec/everest/modules/JsEvManager/index.js +@@ -13,6 +13,8 @@ + mod.state = 'unplugged'; + + mod.v2g_finished = false; ++ mod.iso_eamount = 85; ++ mod.iso_departure_time = null; + mod.iso_stopped = false; + mod.evse_maxcurrent = 0; + mod.maxCurrent = 0; +@@ -361,6 +363,17 @@ + return false; + }); + ++ // c.args[0] == DepartureTime, c.args[1] == eamount ++ registerCmd(mod, 'iso_set_departure_time', 2, (mod, c) => { ++ mod.iso_departure_time = (Number(c.args[0])) ? Number(c.args[0]) : null; ++ mod.iso_eamount = (Number(c.args[1])) ? Number(c.args[1]) : 60 ++ ++ evlog.debug(`iso_departure_time is: ${mod.iso_departure_time}`); ++ evlog.debug(`iso_eamount ime is: ${mod.iso_eamount}`); ++ return true; ++ ++ }); ++ + if (mod.uses_list.ev.length > 0) { + registerCmd(mod, 'iso_start_v2g_session', 1, (mod, c) => { + switch (c.args[0]) { +@@ -372,7 +385,8 @@ + default: return false; + } + +- mod.uses_list.ev[0].call.start_charging({ EnergyTransferMode: mod.energymode }); ++ args = { EnergyTransferMode: mod.energymode, EAmount: mod.iso_eamount, DepartureTime: mod.iso_departure_time }; ++ mod.uses_list.ev[0].call.start_charging(args); + + return true; + }); diff --git a/manager/demo-patches/power_curve.py b/manager/demo-patches/power_curve.py new file mode 100644 index 00000000..bf0a58b3 --- /dev/null +++ b/manager/demo-patches/power_curve.py @@ -0,0 +1,172 @@ +import sys +import json +import math +from time import time +import numpy as np +import control as ct +from iso15118.shared.messages.iso15118_2.datatypes import ProfileEntryDetails +from iso15118.shared.messages.datatypes import PVPMax +from iso15118.shared.messages.enums import UnitSymbol + +"""beginning_of_profile_schedule: int= -1 +Created on Fri Aug 9 00:37:56 2024 +LQRchargeCurve + +@author: ANAND +""" +# KS is btwn 1 and 20 +def LQRChargeCurve(DepTime, EAmount, KS): + # system matrices + A=np.array([[0]]) + B=np.array([[1]]) + C=np.array([[1]]) + D=np.array([[0]]) + + #define the initial condition + x0=np.array([[0]]) + + # define the time vector for simulation + startTime=0 + endTime = round(DepTime/60)*60 + numberSamples = round(endTime/60) + timeVector=np.linspace(startTime,endTime,numberSamples) + + # state weighting matrix + Q=KS/1000 + + # input weighting matrix + R=KS*1000 + + # system matrices + + sysStateSpace=ct.ss(A,B,C,D) + xd=np.array([[EAmount]]) + + K, S, E = ct.lqr(sysStateSpace, Q, R) + + Acl=A-np.matmul(B,K) + Bcl=-Acl + + # define the state-space model + sysStateSpaceCl=ct.ss(Acl,Bcl,C,D) + + # define the input for closed-loop simulation + inputCL=np.zeros(shape=(1,numberSamples)) + inputCL[0,:]=xd*np.ones(numberSamples) + returnSimulationCL = ct.forced_response(sysStateSpaceCl, + timeVector, + inputCL, + x0) + + + # YC is state of charge of the vehicle (progress to eamount) + # UC is power + # TC is the timevector + Yc = returnSimulationCL.states[0,:] + Uc= np.transpose(-K*(returnSimulationCL.states[0,:]-inputCL)) + Tc = returnSimulationCL.time + + return Yc, Uc, Tc + + +''' + formatCurveData takes the output of the LQR ChargeCurve below, and formats + it into a JSON string that will be accepted by Node-RED's `chart` + module. + + @author Katie +''' +def formatCurveData(yc, uc, tc): + # 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)] + return { + "series": ["A", "B"], + "data": [yc_curve, uc_curve], + "labels": ["r1", "r2"] + } + + +''' + generate_new_schedule is used in `evcc/simulator.py`'s `process_sa_schedules_v2` to + create a profile_entry list from both the existing `secc_schedule` and a schedule + generated by the above LQR Charging Optimizer + + @author Katie +''' +def generate_new_schedule(secc_schedule, uc, tc, departure_time, time_elapsed): + # time_offset = 24 * # max enteries is 24, so refresh every <24 -ish seconds? + # Define some helper functions... + # Evenly sample from the `curve_schedule`< up to the end timestamp + def sample_schedule(schedule): + + sliced_array = [x for x in schedule if (time_elapsed <= x[1])] + return sliced_array[0:23] + + # Generates a ProfileEntryDetails obj for the final schedule + def make_entry(val, timestamp, next_ts): + # First, convert kWh to kW + time_delta = (float(next_ts) - float(timestamp)) / 3600 + watts = (1000 * val) / time_delta + # Then, create the ProfileEntryDetails... + return ProfileEntryDetails( + start=int(timestamp), + max_power=PVPMax( + multiplier=0, + value=watts, # Convert miliwattsHours to WattHours + unit=UnitSymbol.WATT + ), + max_phases_in_use = None + ) + + def convert_tuple_schedule(curve_arr): + final_ts = curve_arr[-1][1] + schedule_arr = [] + for i in range(0, len(curve_arr) - 2): + schedule_arr.append(make_entry(curve_arr[i][0], curve_arr[i][1], curve_arr[i+1][1])) + print('Done') + if(len(schedule_arr)): + schedule_arr.pop(-1) + + return(schedule_arr) + + + def check_new_schedule(curve_arr): + for schedule in secc_schedule: + for new_s in curve_arr: + print(f"Is {schedule.max_power.value} < {new_s.max_power.value}?") + if new_s.max_power.value > schedule.max_power.value: + print('Schedule Creation Error: ', new_s.max_power.value) + + curve_schedule = [(x[0],y) for x, y, in zip(uc, tc)] # UC is in kWh, not kW + # We get 24 from ISO 15118-2, Table 71. This is the max number of profile enteries. + # For some reason, EVerest only accepts 23... Investigate later + curve_schedule = sample_schedule(curve_schedule) + + if(len(curve_schedule) <= 2): # Check to see if we're done... + print("Done with profile, defaulting to SECC Schedule") + return secc_schedule + + curve_schedule = convert_tuple_schedule(curve_schedule) + print("Returning a curve schedule of:", curve_schedule) + + return curve_schedule + +def generate_dummy_schedule(): + watt_vals = [3330, 2300, 1300, 500] + temp = [] + + for i in range(len(watt_vals)): + temp.append(ProfileEntryDetails( + start=10 * i, + max_power=PVPMax( + multiplier=0, + value=watt_vals[i], + unit=UnitSymbol.WATT + ), + max_phases_in_use = None + ) + ) + print('Katie:', temp) + return temp + diff --git a/manager/demo-patches/pyjosev_module_enabledt.patch b/manager/demo-patches/pyjosev_module_enabledt.patch new file mode 100644 index 00000000..17243444 --- /dev/null +++ b/manager/demo-patches/pyjosev_module_enabledt.patch @@ -0,0 +1,13 @@ +--- /ext/dist/libexec/everest/modules/PyEvJosev/module.py ++++ /ext/dist/libexec/everest/modules/PyEvJosev/module.py +@@ -94,7 +94,10 @@ + # implementation handlers + + def _handler_start_charging(self, args) -> bool: ++ log.info('_handler_start_charging() args: %s' % str(args)) + ++ self._es.DepartureTime = args['DepartureTime'] ++ self._es.EAmount = args['EAmount'] + self._es.EnergyTransferMode = args['EnergyTransferMode'] + + self._ready_event.set() diff --git a/manager/demo-patches/simulator_enabledt.patch b/manager/demo-patches/simulator_enabledt.patch new file mode 100644 index 00000000..e09bd3e6 --- /dev/null +++ b/manager/demo-patches/simulator_enabledt.patch @@ -0,0 +1,159 @@ +--- /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py ++++ /ext/dist/libexec/everest/3rd_party/josev/iso15118/evcc/controller/simulator.py +@@ -7,6 +7,7 @@ + import logging + import random + from typing import List, Optional, Tuple, Union ++from time import time + import os + from pathlib import Path + +@@ -53,6 +54,7 @@ + SAScheduleTupleEntry as SAScheduleTupleEntryDINSPEC, + ) + from iso15118.shared.messages.enums import ( ++ AuthEnum, + ControlMode, + DCEVErrorCode, + EnergyTransferModeEnum, +@@ -130,6 +132,15 @@ + to_ec_priv_key, + ) + ++from iso15118.evcc.states.power_curve import ( ++ LQRChargeCurve, ++ formatCurveData, ++ generate_new_schedule, ++ generate_dummy_schedule ++) ++import paho.mqtt.publish as mqtt_publish ++import paho.mqtt.subscribe as mqtt_subscribe ++ + from iso15118.shared.settings import get_PKI_PATH + + logger = logging.getLogger(__name__) +@@ -283,7 +294,7 @@ + dc_charge_params = None + + if (await self.get_energy_transfer_mode(protocol)).startswith("AC"): +- e_amount = PVEAmount(multiplier=0, value=60, ++ e_amount = PVEAmount(multiplier=3, value=EVEREST_EV_STATE.EAmount, + unit=UnitSymbol.WATT_HOURS) + ev_max_voltage = PVEVMaxVoltage( + multiplier=0, value=400, unit=UnitSymbol.VOLTAGE +@@ -295,7 +306,7 @@ + multiplier=0, value=10, unit=UnitSymbol.AMPERE + ) + ac_charge_params = ACEVChargeParameter( +- departure_time=0, ++ departure_time=EVEREST_EV_STATE.DepartureTime, + e_amount=e_amount, + ev_max_voltage=ev_max_voltage, + ev_max_current=ev_max_current, +@@ -306,7 +317,7 @@ + multiplier=1, value=6000, unit=UnitSymbol.WATT_HOURS + ) + dc_charge_params = DCEVChargeParameter( +- departure_time=0, ++ departure_time=EVEREST_EV_STATE.DepartureTime, + 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 @@ + ) + + scheduled_params = ScheduledScheduleExchangeReqParams( +- departure_time=7200, ++ departure_time=EVEREST_EV_STATE.DepartureTime, + 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 @@ + ) -> DynamicScheduleExchangeReqParams: + """Overrides EVControllerInterface.get_dynamic_se_params().""" + dynamic_params = DynamicScheduleExchangeReqParams( +- departure_time=7200, ++ departure_time=EVEREST_EV_STATE.DepartureTime, + min_soc=30, + target_soc=80, + ev_target_energy_request=RationalNumber(exponent=3, value=40), +@@ -577,12 +588,15 @@ + return schedule.sa_schedule_tuple_id + + async def process_sa_schedules_v2( +- self, sa_schedules: List[SAScheduleTuple] ++ self, sa_schedules: List[SAScheduleTuple], time_elapsed + ) -> Tuple[ChargeProgressV2, int, ChargingProfile]: ++ print("In ProcessSchedules") + """Overrides EVControllerInterface.process_sa_schedules().""" + secc_schedule = sa_schedules.pop() + evcc_profile_entry_list: List[ProfileEntryDetails] = [] + ++ print(f"Processing SASchedules! ${sa_schedules}") ++ print(f"Processing SASchedules! ${time_elapsed}") + # 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 @@ + ) + evcc_profile_entry_list.append(last_profile_entry_details) + ++ print("Done processing schedules...") ++ # Set Curve Variables... ++ 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): ++ ks = 1 ++ # Check EAmount ++ mqtt_publish.single("everest_external/nodered/{}/evcc/check_eamount", "test", hostname="mqtt-server") ++ eamount_msg = mqtt_subscribe.simple("everest_external/nodered/evcc/confirm_eamount", hostname="mqtt-server") ++ eamount = int(str(eamount_msg.payload)[2:-1]) ++ # Check which algorithm is being used ++ mqtt_publish.single("everest_external/nodered/{}/evcc/check_algorithm", "test", hostname="mqtt-server") ++ msg = mqtt_subscribe.simple("everest_external/nodered/evcc/confirm_algorithm", hostname="mqtt-server") ++ algorithm_choice = str(msg.payload)[2:-1] # convert bytestring ++ if(algorithm_choice == 'algorithm_one'): ++ 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) ++ 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") ++ ++ # Then Re-Publish the chosen curve as the final selection ++ 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), + # verify each sales tariff with the mobility operator sub 2 certificate + + return ( + ChargeProgressV2.START, + secc_schedule.sa_schedule_tuple_id, +- ChargingProfile(profile_entries=evcc_profile_entry_list), ++ ChargingProfile(profile_entries=new_schedule), + ) + + async def continue_charging(self) -> bool: +@@ -753,7 +797,7 @@ + else: + # Dynamic Mode + dynamic_params = DynamicACChargeLoopReqParams( +- departure_time=2000, ++ departure_time=EVEREST_EV_STATE.DepartureTime, + 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 @@ + multiplier=1, value=6000, unit=UnitSymbol.WATT_HOURS + ) + dc_charge_params = DCEVChargeParameter( +- departure_time=0, ++ departure_time=EVEREST_EV_STATE.DepartureTime, + dc_ev_status=await self.get_dc_ev_status(), + ev_maximum_current_limit=self.dc_ev_discharge_params.dc_max_current_limit, + ev_maximum_power_limit=self.dc_ev_discharge_params.dc_max_power_limit, diff --git a/manager/demo-patches/support_payment_in_jsevmanager.patch b/manager/demo-patches/support_payment_in_jsevmanager.patch new file mode 100644 index 00000000..c87701f1 --- /dev/null +++ b/manager/demo-patches/support_payment_in_jsevmanager.patch @@ -0,0 +1,45 @@ +diff -r -uw /tmp/dist/libexec/everest/modules/JsEvManager/index.js ./modules/JsEvManager/index.js +--- /tmp/dist/libexec/everest/modules/JsEvManager/index.js 2024-11-16 07:33:36.399948014 +0000 ++++ ./modules/JsEvManager/index.js 2024-11-16 07:39:43.045930659 +0000 +@@ -375,17 +375,26 @@ + }); + + if (mod.uses_list.ev.length > 0) { +- registerCmd(mod, 'iso_start_v2g_session', 1, (mod, c) => { +- switch (c.args[0]) { ++ registerCmd(mod, 'iso_start_v2g_session', 2, (mod, c) => { ++ if (c.args[0] === 'externalpayment') mod.payment = 'ExternalPayment'; ++ else if (c.args[0] === 'contract') mod.payment = 'Contract'; ++ else { ++ evlog.error('Found invalid payment method' + c.args[0]); ++ return false; ++ } ++ ++ switch (c.args[1]) { + case 'ac': + if (mod.config.module.three_phases !== true) mod.energymode = 'AC_single_phase_core'; + else mod.energymode = 'AC_three_phase_core'; + break; + case 'dc': mod.energymode = 'DC_extended'; break; +- default: return false; ++ default: ++ evlog.error('Found invalid payment method' + c.args[1]); ++ return false; + } + +- args = { EnergyTransferMode: mod.energymode, EAmount: mod.iso_eamount, DepartureTime: mod.iso_departure_time }; ++ args = { PaymentOption: mod.payment, EnergyTransferMode: mod.energymode, EAmount: mod.iso_eamount, DepartureTime: mod.iso_departure_time }; + mod.uses_list.ev[0].call.start_charging(args); + + return true; +diff -r -uw /tmp/dist/libexec/everest/modules/PyEvJosev/module.py ./modules/PyEvJosev/module.py +--- /tmp/dist/libexec/everest/modules/PyEvJosev/module.py 2024-11-16 07:33:36.597948000 +0000 ++++ ./modules/PyEvJosev/module.py 2024-11-16 07:35:09.774714889 +0000 +@@ -96,6 +96,7 @@ + def _handler_start_charging(self, args) -> bool: + log.info('_handler_start_charging() args: %s' % str(args)) + ++ self._es.PaymentOption =args['PaymentOption'] + self._es.DepartureTime = args['DepartureTime'] + self._es.EAmount = args['EAmount'] + self._es.EnergyTransferMode = args['EnergyTransferMode'] diff --git a/manager/support_payment_in_jsevmanager.patch b/manager/support_payment_in_jsevmanager.patch deleted file mode 100644 index ec78c903..00000000 --- a/manager/support_payment_in_jsevmanager.patch +++ /dev/null @@ -1,48 +0,0 @@ -diff --git a/modules/PyEvJosev/module.py b/modules/PyEvJosev/module.py -index fabc388..c8961fc 100644 ---- a/modules/PyEvJosev/module.py -+++ b/modules/PyEvJosev/module.py -@@ -95,6 +95,7 @@ class PyEVJosevModule(): - - def _handler_start_charging(self, args) -> bool: - -+ self._es.PaymentOption =args['PaymentOption'] - self._es.EnergyTransferMode = args['EnergyTransferMode'] - - self._ready_event.set() -diff --git a/modules/simulation/JsEvManager/index.js b/modules/simulation/JsEvManager/index.js -index e5b4e9c..4bce5f5 100644 ---- a/modules/JsEvManager/index.js -+++ b/modules/JsEvManager/index.js -@@ -362,17 +362,27 @@ function registerAllCmds(mod) { - }); - - if (mod.uses_list.ev.length > 0) { -- registerCmd(mod, 'iso_start_v2g_session', 1, (mod, c) => { -- switch (c.args[0]) { -+ registerCmd(mod, 'iso_start_v2g_session', 2, (mod, c) => { -+ if (c.args[0] === 'externalpayment') mod.payment = 'ExternalPayment'; -+ else if (c.args[0] === 'contract') mod.payment = 'Contract'; -+ else { -+ evlog.debug('Found invalid payment method' + c.args[0]); -+ return false; -+ } -+ -+ switch (c.args[1]) { - case 'ac': - if (mod.config.module.three_phases !== true) mod.energymode = 'AC_single_phase_core'; - else mod.energymode = 'AC_three_phase_core'; - break; - case 'dc': mod.energymode = 'DC_extended'; break; -- default: return false; -+ default: -+ evlog.debug('Found invalid payment method' + c.args[1]); -+ return false; - } - -- mod.uses_list.ev[0].call.start_charging({ EnergyTransferMode: mod.energymode }); -+ args = { PaymentOption: mod.payment, EnergyTransferMode: mod.energymode }; -+ mod.uses_list.ev[0].call.start_charging(args); - - return true; - }); diff --git a/nodered/config/config-sil-iso15118-ac-flow.json b/nodered/config/config-sil-iso15118-ac-flow.json index 8a5ed0b2..e3a22387 100644 --- a/nodered/config/config-sil-iso15118-ac-flow.json +++ b/nodered/config/config-sil-iso15118-ac-flow.json @@ -2140,7 +2140,7 @@ "options": [ { "label": "AC ISO15118-2", - "value": "sleep 1;iso_wait_slac_matched;iso_set_departure_time null null;iso_start_v2g_session ac;iso_wait_pwr_ready;iso_draw_power_regulated 16,3;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug#iso_pause_charging;iso_wait_for_resume#iso_start_bcb_toogle 3;iso_wait_pwm_is_running;iso_start_v2g_session ExternalPayment,AC_three_phase_core;iso_wait_pwr_ready;iso_draw_power_regulated 16,3;sleep 36000", + "value": "sleep 1;iso_wait_slac_matched;iso_set_departure_time null null;iso_start_v2g_session externalpayment,ac;iso_wait_pwr_ready;iso_draw_power_regulated 16,3;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug#iso_pause_charging;iso_wait_for_resume#iso_start_bcb_toogle 3;iso_wait_pwm_is_running;iso_start_v2g_session ExternalPayment,AC_three_phase_core;iso_wait_pwr_ready;iso_draw_power_regulated 16,3;sleep 36000", "type": "str" } ], @@ -3577,4 +3577,4 @@ ] ] } -] \ No newline at end of file +]