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 00000000..596e4ca7 Binary files /dev/null and b/manager/demo-patch-scripts/.apply-runtime-patches.sh.swp differ 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 +]