Skip to content

Commit b2942cf

Browse files
committed
Add HLC no‑energy timeout and reuse power_available()
Signed-off-by: Piet Gömpel <pietgoempel@gmail.com>
1 parent f5e6eaf commit b2942cf

File tree

6 files changed

+86
-32
lines changed

6 files changed

+86
-32
lines changed

modules/EVSE/EvseManager/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ target_sources(${MODULE_NAME}
1919
ErrorHandling.cpp
2020
backtrace.cpp
2121
PersistentStore.cpp
22+
over_voltage/OverVoltageMonitor.cpp
2223
)
2324

2425
target_link_libraries(${MODULE_NAME}
@@ -56,7 +57,6 @@ target_sources(${MODULE_NAME}
5657
"token_provider/auth_token_providerImpl.cpp"
5758
"random_delay/uk_random_delayImpl.cpp"
5859
"dc_external_derate/dc_external_derateImpl.cpp"
59-
"over_voltage/OverVoltageMonitor.cpp"
6060
)
6161

6262
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

modules/EVSE/EvseManager/Charger.cpp

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ void Charger::run_state_machine() {
307307
if (config_context.charge_mode == ChargeMode::DC) {
308308
// Create a copy of the atomic struct
309309
types::iso15118::DcEvseMaximumLimits evse_limit = shared_context.current_evse_max_limits;
310-
if (not(evse_limit.evse_maximum_current_limit > 0 and evse_limit.evse_maximum_power_limit > 0)) {
310+
if (not power_available()) {
311311

312312
// Wait some time here in this state to see if we get energy from the EnergyManager...
313313
if (time_in_current_state < WAIT_FOR_ENERGY_IN_AUTHLOOP_TIMEOUT_MS) {
@@ -593,11 +593,12 @@ void Charger::run_state_machine() {
593593
if (initialize_state) {
594594
signal_simple_event(types::evse_manager::SessionEventEnum::PrepareCharging);
595595
bcb_toggle_reset();
596+
internal_context.hlc_charge_loop_no_energy_timeout_running = false;
596597

597598
if (config_context.charge_mode == ChargeMode::DC) {
598599
// Create a copy of the atomic struct
599600
types::iso15118::DcEvseMaximumLimits evse_limit = shared_context.current_evse_max_limits;
600-
if (not(evse_limit.evse_maximum_current_limit > 0 and evse_limit.evse_maximum_power_limit > 0)) {
601+
if (not power_available()) {
601602
signal_hlc_no_energy_available();
602603
}
603604
}
@@ -628,18 +629,17 @@ void Charger::run_state_machine() {
628629
// used as the car can do BASIC and HLC charging any time. In AC HLC with 5 percent mode, we need to
629630
// wait for both iec_allow and hlc_allow.
630631
if (not power_available() and not hlc_use_5percent_current_session) {
631-
//For AC BC: it is ok to wait here.
632-
// For AC and DC ISO, continue in case we are in 5% mode. This allows us to go into
633-
// Charge loop and report 0A/0W to the EV
632+
// For AC BC: it is ok to wait here.
633+
// For AC and DC ISO, continue in case we are in 5% mode. This allows us to go into
634+
// Charge loop and report 0A/0W to the EV
634635
break;
635636
}
636637

637638
// Power is available or we are in HLC, PWM is already enabled. Check if we can go to charging
638639
if ((shared_context.iec_allow_close_contactor and not hlc_use_5percent_current_session) or
639640
(shared_context.iec_allow_close_contactor and shared_context.hlc_allow_close_contactor and
640641
hlc_use_5percent_current_session)) {
641-
642-
shared_context.current_state = EvseState::Charging;
642+
set_state(EvseState::Charging);
643643
} else {
644644
// We have power and PWM is on, but EV did not proceed to state C yet (and/or HLC is not
645645
// ready)
@@ -661,6 +661,8 @@ void Charger::run_state_machine() {
661661
}
662662
}
663663

664+
// For DC we go into charging when CurrentDemand started (notify_currentdemand_started)
665+
664666
// if (charge_mode == ChargeMode::DC) {
665667
// DC: wait until car requests power on CP (B->C/D).
666668
// Then we close contactor and wait for instructions from HLC.
@@ -695,25 +697,54 @@ void Charger::run_state_machine() {
695697
break;
696698
}
697699

700+
if (not power_available()) {
701+
const bool hlc_session_active =
702+
shared_context.hlc_charging_active or hlc_use_5percent_current_session;
703+
if (hlc_session_active) {
704+
if (config_context.hlc_charge_loop_without_energy_timeout_s > 0) {
705+
if (not internal_context.hlc_charge_loop_no_energy_timeout_running) {
706+
internal_context.hlc_charge_loop_no_energy_timeout_running = true;
707+
internal_context.iso_charge_loop_no_energy_timeout_started =
708+
std::chrono::steady_clock::now();
709+
session_log.evse(false, fmt::format("No energy available, starting ISO charge loop timeout of {} seconds",
710+
config_context.hlc_charge_loop_without_energy_timeout_s));
711+
} else {
712+
const auto no_energy_duration_ms =
713+
std::chrono::duration_cast<std::chrono::milliseconds>(
714+
std::chrono::steady_clock::now() -
715+
internal_context.iso_charge_loop_no_energy_timeout_started)
716+
.count();
717+
if (no_energy_duration_ms >=
718+
config_context.hlc_charge_loop_without_energy_timeout_s * 1000) {
719+
session_log.evse(false,
720+
"No energy available, ISO charge loop timeout reached");
721+
internal_context.hlc_charge_loop_no_energy_timeout_running = false;
722+
set_state(EvseState::StoppingCharging);
723+
break;
724+
}
725+
}
726+
} else {
727+
session_log.evse(false, "No energy available, but no timeout configured, stopping charging immediately");
728+
set_state(EvseState::StoppingCharging);
729+
break;
730+
}
731+
} else if (config_context.charge_mode == ChargeMode::AC) {
732+
EVLOG_info << "HLC charging not active";
733+
// Stop immediately in basic AC mode
734+
set_state(EvseState::StoppingCharging); // Proxy state to ChargingPausedEVSE
735+
break;
736+
}
737+
} else {
738+
internal_context.hlc_charge_loop_no_energy_timeout_running = false;
739+
}
740+
698741
if (config_context.charge_mode == ChargeMode::DC) {
699742
if (initialize_state) {
700743
bsp->allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging);
701744
}
702745
} else {
703746
check_soft_over_current();
704747

705-
if (not power_available()) {
706-
if (shared_context.hlc_charging_active or hlc_use_5percent_current_session) {
707-
// We are in HLC and have no energy
708-
// TODO: start timer to stop charging
709-
} else {
710-
EVLOG_info << "HLC charging not active";
711-
// Stop immediately in basic AC mode
712-
set_state(EvseState::StoppingCharging); // Proxy state to ChargingPausedEVSE
713-
break;
714-
}
715-
}
716-
717748
if (initialize_state) {
718749
// Allow another wake-up sequence
719750
shared_context.legacy_wakeup_done = false;
@@ -1406,7 +1437,8 @@ void Charger::setup(bool has_ventilation, const ChargeMode _charge_mode, bool _a
14061437
const int _switch_3ph1ph_delay_s, const std::string _switch_3ph1ph_cp_state,
14071438
const int _soft_over_current_timeout_ms, const int _state_F_after_fault_ms,
14081439
const bool fail_on_powermeter_errors, const bool raise_mrec9,
1409-
const int sleep_before_enabling_pwm_hlc_mode_ms, const utils::SessionIdType session_id_type) {
1440+
const int sleep_before_enabling_pwm_hlc_mode_ms, const utils::SessionIdType session_id_type,
1441+
const int hlc_charge_loop_without_energy_timeout_s) {
14101442
// set up board support package
14111443
bsp->setup(has_ventilation);
14121444

@@ -1430,6 +1462,7 @@ void Charger::setup(bool has_ventilation, const ChargeMode _charge_mode, bool _a
14301462
config_context.raise_mrec9 = raise_mrec9;
14311463
config_context.sleep_before_enabling_pwm_hlc_mode_ms = sleep_before_enabling_pwm_hlc_mode_ms;
14321464
config_context.session_id_type = session_id_type;
1465+
config_context.hlc_charge_loop_without_energy_timeout_s = hlc_charge_loop_without_energy_timeout_s;
14331466

14341467
if (config_context.charge_mode == ChargeMode::AC and config_context.ac_hlc_enabled)
14351468
EVLOG_info << "AC HLC mode enabled.";
@@ -1810,15 +1843,21 @@ void Charger::check_soft_over_current() {
18101843
// returns whether power is actually available from EnergyManager
18111844
// i.e. max_current is in valid range
18121845
bool Charger::power_available() {
1813-
const auto overrun = duration_cast<seconds>(steady_clock::now() - shared_context.max_current_valid_until).count();
1814-
if (overrun > 0) {
1815-
EVLOG_warning << "Power budget expired, falling back to 0. Last update: " << overrun << " seconds ago";
1816-
if (shared_context.max_current > 0.) {
1817-
shared_context.max_current = 0.;
1818-
signal_max_current(shared_context.max_current);
1846+
if (config_context.charge_mode == ChargeMode::AC) {
1847+
const auto overrun =
1848+
duration_cast<seconds>(steady_clock::now() - shared_context.max_current_valid_until).count();
1849+
if (overrun > 0) {
1850+
EVLOG_warning << "Power budget expired, falling back to 0. Last update: " << overrun << " seconds ago";
1851+
if (shared_context.max_current > 0.) {
1852+
shared_context.max_current = 0.;
1853+
signal_max_current(shared_context.max_current);
1854+
}
18191855
}
1856+
return get_max_current_internal() > 5.9;
1857+
} else {
1858+
const auto evse_limit = shared_context.current_evse_max_limits;
1859+
return evse_limit.evse_maximum_current_limit > 0 and evse_limit.evse_maximum_power_limit > 0;
18201860
}
1821-
return get_max_current_internal() > 5.9;
18221861
}
18231862

18241863
void Charger::request_error_sequence() {

modules/EVSE/EvseManager/Charger.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ class Charger {
105105
float soft_over_current_measurement_noise_A, const int switch_3ph1ph_delay_s,
106106
const std::string switch_3ph1ph_cp_state, const int soft_over_current_timeout_ms,
107107
const int _state_F_after_fault_ms, const bool fail_on_powermeter_errors, const bool raise_mrec9,
108-
const int sleep_before_enabling_pwm_hlc_mode_ms, const utils::SessionIdType session_id_type);
108+
const int sleep_before_enabling_pwm_hlc_mode_ms, const utils::SessionIdType session_id_type,
109+
const int hlc_charge_loop_without_energy_timeout_s);
109110

110111
void enable_disable_initial_state_publish();
111112
bool enable_disable(int connector_id, const types::evse_manager::EnableDisableSource& source);
@@ -345,6 +346,8 @@ class Charger {
345346
int sleep_before_enabling_pwm_hlc_mode_ms{1000};
346347
// type used to generate session ids
347348
utils::SessionIdType session_id_type{utils::SessionIdType::UUID};
349+
// Timeout in seconds that defines for how long the EVSE allows the ISO charge loop (AC: ChargingStatus, DC: CurrentDemand)
350+
int hlc_charge_loop_without_energy_timeout_s{300};
348351
} config_context;
349352

350353
// Used by different threads, but requires no complete state machine locking
@@ -390,6 +393,9 @@ class Charger {
390393
std::chrono::time_point<std::chrono::steady_clock> ac_x1_fallback_nominal_timeout_started;
391394
bool auth_received_printed{false};
392395

396+
bool hlc_charge_loop_no_energy_timeout_running{false};
397+
std::chrono::time_point<std::chrono::steady_clock> iso_charge_loop_no_energy_timeout_started;
398+
393399
std::chrono::time_point<std::chrono::steady_clock> fatal_error_became_active;
394400
bool fatal_error_timer_running{false};
395401

modules/EVSE/EvseManager/EvseManager.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,7 +1357,8 @@ void EvseManager::ready() {
13571357
config.switch_3ph1ph_delay_s, config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms,
13581358
config.state_F_after_fault_ms, config.fail_on_powermeter_errors, config.raise_mrec9,
13591359
config.sleep_before_enabling_pwm_hlc_mode_ms,
1360-
utils::get_session_id_type_from_string(config.session_id_type));
1360+
utils::get_session_id_type_from_string(config.session_id_type),
1361+
config.hlc_charge_loop_without_energy_timeout_s);
13611362
}
13621363

13631364
telemetryThreadHandle = std::thread([this]() {
@@ -1546,7 +1547,8 @@ void EvseManager::setup_fake_DC_mode() {
15461547
config.soft_over_current_measurement_noise_A, config.switch_3ph1ph_delay_s,
15471548
config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms, config.state_F_after_fault_ms,
15481549
config.fail_on_powermeter_errors, config.raise_mrec9, config.sleep_before_enabling_pwm_hlc_mode_ms,
1549-
utils::get_session_id_type_from_string(config.session_id_type));
1550+
utils::get_session_id_type_from_string(config.session_id_type),
1551+
config.hlc_charge_loop_without_energy_timeout_s);
15501552

15511553
types::iso15118::EVSEID evseid = {config.evse_id, config.evse_id_din};
15521554

@@ -1587,7 +1589,7 @@ void EvseManager::setup_AC_mode() {
15871589
config.soft_over_current_measurement_noise_A, config.switch_3ph1ph_delay_s,
15881590
config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms, config.state_F_after_fault_ms,
15891591
config.fail_on_powermeter_errors, config.raise_mrec9, config.sleep_before_enabling_pwm_hlc_mode_ms,
1590-
utils::get_session_id_type_from_string(config.session_id_type));
1592+
utils::get_session_id_type_from_string(config.session_id_type), config.hlc_charge_loop_without_energy_timeout_s);
15911593

15921594
types::iso15118::EVSEID evseid = {config.evse_id, config.evse_id_din};
15931595

modules/EVSE/EvseManager/EvseManager.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ struct Conf {
117117
std::string bpt_channel;
118118
std::string bpt_generator_mode;
119119
std::string bpt_grid_code_island_method;
120+
int hlc_charge_loop_without_energy_timeout_s;
120121
};
121122

122123
class EvseManager : public Everest::ModuleBase {

modules/EVSE/EvseManager/manifest.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,12 @@ config:
379379
- Active
380380
- Passive
381381
default: None
382+
hlc_charge_loop_without_energy_timeout_s:
383+
description: >-
384+
Timeout (s) to allow HLC charge loop to continue without available energy;
385+
when exceeded, the HLC session is stopped and PWM is disabled until energy returns.
386+
type: integer
387+
default: 300
382388
provides:
383389
evse:
384390
interface: evse_manager

0 commit comments

Comments
 (0)