Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 121 additions & 33 deletions modules/EVSE/EvseManager/Charger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ void Charger::run_state_machine() {
if (initialize_state) {
internal_context.pp_warning_printed = false;
internal_context.no_energy_warning_printed = false;
internal_context.ac_x1_fallback_nominal_timeout_running = false;
internal_context.auth_received_printed = false;

bsp->allow_power_on(false, types::evse_board_support::Reason::PowerOff);

Expand Down Expand Up @@ -277,8 +279,15 @@ void Charger::run_state_machine() {
// enabling PWM.
std::this_thread::sleep_for(
std::chrono::milliseconds(config_context.sleep_before_enabling_pwm_hlc_mode_ms));
update_pwm_now(PWM_5_PERCENT);
stopwatch.mark("HLC_PWM_5%_ON");

// If already authorized before plugin, do not use 5% PWM on AC HLC
if (shared_context.authorized and config_context.charge_mode == ChargeMode::AC and
ac_hlc_enabled_current_session and not config_context.ac_enforce_hlc) {
hlc_use_5percent_current_session = false;
} else {
update_pwm_now(PWM_5_PERCENT);
stopwatch.mark("HLC_PWM_5%_ON");
}
}
}

Expand Down Expand Up @@ -338,7 +347,10 @@ void Charger::run_state_machine() {
// in DC mode: go to error_slac for this session

if (shared_context.authorized and not shared_context.authorized_pnc) {
session_log.evse(false, "EIM Authorization received");
if (not internal_context.auth_received_printed) {
internal_context.auth_received_printed = true;
session_log.evse(false, "EIM Authorization received");
}

// If we are restarting, the transaction may already be active
if (not shared_context.transaction_active) {
Expand Down Expand Up @@ -371,29 +383,71 @@ void Charger::run_state_machine() {
"disable 5 percent if it was enabled before: {}",
(bool)hlc_use_5percent_current_session));

// Figure 3 of ISO15118-3: 5 percent start, PnC and EIM
// Figure 4 of ISO15118-3: X1 start, PnC and EIM
internal_context.t_step_EF_return_state = target_state;
internal_context.t_step_EF_return_pwm = 0.;
internal_context.t_step_EF_return_ampere = 0.;
// fall back to nominal PWM after the t_step_EF break. Note that
// ac_hlc_enabled_current_session remains untouched as HLC can still start later in
// nominal PWM mode
hlc_use_5percent_current_session = false;
shared_context.current_state = EvseState::T_step_EF;
if (hlc_use_5percent_current_session) {
// Figure 3 of ISO15118-3: 5 percent start, PnC and EIM
internal_context.t_step_EF_return_state = target_state;
internal_context.t_step_EF_return_pwm = 0.;
internal_context.t_step_EF_return_ampere = 0.;
shared_context.current_state = EvseState::T_step_EF;

// fall back to nominal PWM after the t_step_EF break. Note that
// ac_hlc_enabled_current_session remains untouched as HLC can still start later in
// nominal PWM mode
hlc_use_5percent_current_session = false;
} else {
// Figure 4 of ISO15118-3: X1 start, PnC and EIM
// This figure requires a T_step_F for X1->Nominal. This does not really make sense.
// Also this is basically the same case as auth before plugin (which here in Everest
// technically is auth very shortly after plugin as the auch module assigns auth
// only on plug events) For auth before plugin -3 requires no T_step_EF. Also normal
// BC does X1>nomainal without F. We deviate from the standard here and perform no
// T_step_EF.

shared_context.current_state = target_state;
}

} else {
// SLAC matching was started already when EIM arrived
if (hlc_use_5percent_current_session) {
// Figure 5 of ISO15118-3: 5 percent start, PnC and EIM, matching already started
// when EIM was done
session_log.evse(
false, "AC mode, HLC enabled(5percent), matching already started. Go through "
"t_step_X1 and disable 5 percent.");
internal_context.t_step_X1_return_state = target_state;
internal_context.t_step_X1_return_pwm = 0.;
internal_context.t_step_EF_return_ampere = 0.;
hlc_use_5percent_current_session = false;
shared_context.current_state = EvseState::T_step_X1;
// This has the following real world issue: The X1 will kill the ISO-2 session and
// the EV will send a session stop. On EVs that actually support AC charge loop
// (such as VW), this would mean the end of it and they require a replug. To fix
// this, we should not go straight to X1 after EIM, but give the ISO stack some time
// to actually go into chargeloop. If that happens, we should keep 5% to not kill
// the ISO session, and if it does not within a timeout, we can still perform
// X1->nominal for EVs that performed the ISO auth loop but do not support ISO
// chargeloop (or non-HLC cars)

// If EV already sent a PowerDeliver.req
if (shared_context.hlc_charging_active) {
session_log.evse(
false,
"AC mode, HLC enabled(5percent), v2g_session_setup_finished shortly after "
"EIM. Keep 5% enabled to not accidentially kill the ISO session.");
shared_context.current_state = target_state;
} else {
if (internal_context.ac_x1_fallback_nominal_timeout_running) {
if (std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() -
internal_context.ac_x1_fallback_nominal_timeout_started)
.count() > AC_X1_FALLBACK_TO_NOMINAL_TIMEOUT_MS) {
session_log.evse(false, "AC mode, HLC enabled(5percent), matching "
"already started. Go through "
"t_step_X1 and disable 5 percent.");
internal_context.t_step_X1_return_state = target_state;
internal_context.t_step_X1_return_pwm = 0.;
internal_context.t_step_EF_return_ampere = 0.;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internal_context.t_step_EF_return_ampere = 0.;
internal_context.t_step_X1_return_ampere = 0.;

hlc_use_5percent_current_session = false;
shared_context.current_state = EvseState::T_step_X1;
}
} else {
internal_context.ac_x1_fallback_nominal_timeout_running = true;
internal_context.ac_x1_fallback_nominal_timeout_started =
std::chrono::steady_clock::now();
}
}
} else {
// Figure 6 of ISO15118-3: X1 start, PnC and EIM, matching already started when EIM
// was done. We can go directly to PrepareCharging, as we do not need to switch from
Expand Down Expand Up @@ -427,23 +481,58 @@ void Charger::run_state_machine() {
}
} else if (shared_context.authorized and shared_context.authorized_pnc) {

if (!start_transaction()) {
break;
if (not shared_context.transaction_active) {
if (!start_transaction()) {
break;
}
}

const EvseState target_state(EvseState::PrepareCharging);

// We got authorization by Plug and Charge
session_log.evse(false, "PnC Authorization received");
if (not internal_context.auth_received_printed) {
internal_context.auth_received_printed = true;
session_log.evse(false, "PnC Authorization received");
}

if (config_context.charge_mode == ChargeMode::AC) {
// Figures 3,4,5,6 of ISO15118-3: Independent on how we started we can continue with 5 percent
// signalling once we got PnC authorization without going through t_step_EF or t_step_X1.
// Figures 3,4,5,6 of ISO15118-3: Independent on how we started we can continue with 5 percent or
// switch to nominal signalling once we got PnC authorization without going through t_step_EF or
// t_step_X1. Some EVs implement a non-standard way for PnC with AC: They perform ISO until Auth
// loop, and once authorization is provided, they close TCP (or send SessionStop). The intention is
// to use only the PnC part of ISO (which is the same as DC to that point) and then do normal BC
// instead of ISO charging loop. To support this we wait a short timeout to see if the chargeloop is
// entered, and if not, we switch back to nominal. To get a similar behaviour for PnC and EIM we
// still perform a T_step_EF here even if the standard says differently.
if (shared_context.hlc_charging_active) {
// AC HLC charging loop started
session_log.evse(false, "AC mode, HLC, PnC auth received. We will continue with 5percent as "
"HLC charing loop was started.");
hlc_use_5percent_current_session = true;
shared_context.current_state = target_state;

session_log.evse(
false, "AC mode, HLC enabled, PnC auth received. We will continue with 5percent independent on "
"how we started.");
hlc_use_5percent_current_session = true;
shared_context.current_state = target_state;
} else {
// AC HLC charging loop not yet started
if (internal_context.ac_x1_fallback_nominal_timeout_running) {
if (std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() -
internal_context.ac_x1_fallback_nominal_timeout_started)
.count() > AC_X1_FALLBACK_TO_NOMINAL_TIMEOUT_MS) {
session_log.evse(
false,
"AC mode, HLC enabled, PnC authorized, but charging loop did not start. Go through "
"t_step_EF and disable 5 percent.");
internal_context.t_step_EF_return_state = target_state;
internal_context.t_step_EF_return_pwm = 0.;
internal_context.t_step_EF_return_ampere = 0.;
hlc_use_5percent_current_session = false;
shared_context.current_state = EvseState::T_step_EF;
}
} else {
internal_context.ac_x1_fallback_nominal_timeout_running = true;
internal_context.ac_x1_fallback_nominal_timeout_started = std::chrono::steady_clock::now();
}
}

} else if (config_context.charge_mode == ChargeMode::DC) {
// Figure 8 of ISO15118-3: DC with EIM before or after plugin or PnC
Expand Down Expand Up @@ -843,7 +932,6 @@ void Charger::run_state_machine() {
<< evse_state_to_string(internal_context.last_state_detect_state_change)
<< " current_state: " << evse_state_to_string(shared_context.current_state);
}

} while (internal_context.last_state_detect_state_change not_eq shared_context.current_state);
}

Expand Down Expand Up @@ -1243,7 +1331,6 @@ void Charger::stop_session() {
bool Charger::start_transaction() {
shared_context.stop_transaction_id_token.reset();
shared_context.last_stop_transaction_reason.reset();
shared_context.transaction_active = true;

types::powermeter::TransactionReq req;
req.evse_id = evse_id;
Expand Down Expand Up @@ -1275,6 +1362,7 @@ bool Charger::start_transaction() {

store->store_session(shared_context.session_uuid);
signal_transaction_started_event(shared_context.id_token);
shared_context.transaction_active = true;
return true;
}

Expand Down
5 changes: 5 additions & 0 deletions modules/EVSE/EvseManager/Charger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ class Charger {
bool t_step_ef_x1_pause{false};
bool pwm_F_active{false};

bool ac_x1_fallback_nominal_timeout_running{false};
std::chrono::time_point<std::chrono::steady_clock> ac_x1_fallback_nominal_timeout_started;
bool auth_received_printed{false};

std::chrono::time_point<std::chrono::steady_clock> fatal_error_became_active;
bool fatal_error_timer_running{false};
} internal_context;
Expand Down Expand Up @@ -433,6 +437,7 @@ class Charger {
// after the wake-up sequence.
static constexpr int STAY_IN_X1_AFTER_TSTEP_EF_MS = 750;
static constexpr int WAIT_FOR_ENERGY_IN_AUTHLOOP_TIMEOUT_MS = 5000;
static constexpr int AC_X1_FALLBACK_TO_NOMINAL_TIMEOUT_MS = 3000;

types::evse_manager::EnableDisableSource active_enable_disable_source{
types::evse_manager::Enable_source::Unspecified, types::evse_manager::Enable_state::Unassigned, 10000};
Expand Down
16 changes: 8 additions & 8 deletions modules/EVSE/EvseManager/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,17 @@ config:
default: false
ac_hlc_use_5percent:
description: >-
Use 5 percent PWM signalling to try to enforce HLC on AC. Note that if EIM arrives before SLAC matching, we will
fall back to nominal PWM charging. So most cars will never use HLC in this mode, especially on a free service
where EIM is always available, but that is what ISO15118-2/-3 requires to be compliant - it wants to use HLC only
for PnC and not for EIM.
If set to true, start with 5% PWM if HLC is enabled on AC. If set to false, start with X1. Note that
almost no EV will perform SLAC/HLC on X1. Recommendation is to set this to true.
type: boolean
default: false
default: true
ac_enforce_hlc:
description: >-
Combine with 5percent option to really enforce HLC even with EIM. It is not ISO15118-2/-3 compliant as it waits
for matching even if EIM is available before SLAC reaches matched state. On cars that do not support ISO15118 on
AC this will take a very long time to timeout and fall back to basic nominal PWM charging, but it will eventually.
Combine with 5percent option to really enforce HLC even with EIM. ISO15118-3 forbids the usage of 5% if Auth happens before plugin,
but most EVs will not perform HLC with nominal PWM. For the home use case (always authorized or auth before plugin) it may still be desirable
to use HLC e.g. for better energy management.
In this case you can use this option, but be aware that EVs that do not support HLC may take a long time to start (or never start) charging.
This mode is not ISO15118-2/-3 compliant. Do not use for public charging stations.
type: boolean
default: false
ac_with_soc:
Expand Down
Loading