Skip to content

Commit cd96d4d

Browse files
authored
fix(EvseManager): Apply EVSE limits on DC target values if EV doesnt update its target values (#1893)
* fix(EvseManager):Apply EVSE limits on DC target values if EV doesnt update its target values The ISO15118 stack may only publish EV target voltage/current on change, so updated EVSE limits from energy management or BSP caps were never enforced when the EV kept requesting the same values. Store the raw EV targets as atomics and extract the clamping logic into process_dc_ev_target_voltage_current(). The Charger state machine now now calls process_dc_ev_target_voltage_current via signal_dc_enforce_target_limits to re-clamp against current EVSE limits, in case apply_new_target_voltage_current has no been called for 5 seconds Signed-off-by: Piet Gömpel <pietgoempel@gmail.com> * Only update power supply targets if values changed compared to last values Signed-off-by: Piet Gömpel <pietgoempel@gmail.com> --------- Signed-off-by: Piet Gömpel <pietgoempel@gmail.com>
1 parent b9039d0 commit cd96d4d

File tree

4 files changed

+120
-65
lines changed

4 files changed

+120
-65
lines changed

modules/EVSE/EvseManager/Charger.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,14 @@ void Charger::run_state_machine() {
734734
if (initialize_state) {
735735
bsp->allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging);
736736
}
737+
// Enforce EVSE target limits so that energy management
738+
// changes are applied even when the EV does not send new targets
739+
const auto now = std::chrono::steady_clock::now();
740+
if (now - last_dc_enforce_target_limits.load() >=
741+
std::chrono::milliseconds(DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS)) {
742+
last_dc_enforce_target_limits = now;
743+
signal_dc_enforce_target_limits(shared_context.current_evse_max_limits);
744+
}
737745
} else {
738746
check_soft_over_current();
739747

@@ -1933,6 +1941,10 @@ void Charger::set_matching_started(bool m) {
19331941
shared_context.matching_started = m;
19341942
}
19351943

1944+
void Charger::reset_dc_enforce_target_limits_timer() {
1945+
last_dc_enforce_target_limits = std::chrono::steady_clock::now();
1946+
}
1947+
19361948
void Charger::notify_currentdemand_started() {
19371949
Everest::scoped_lock_timeout lock(state_machine_mutex,
19381950
Everest::MutexDescription::Charger_notify_currentdemand_started);

modules/EVSE/EvseManager/Charger.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ class Charger {
162162
sigslot::signal<> signal_ac_with_soc_timeout;
163163

164164
sigslot::signal<> signal_dc_supply_off;
165+
sigslot::signal<types::iso15118::DcEvseMaximumLimits> signal_dc_enforce_target_limits;
165166
sigslot::signal<> signal_slac_reset;
166167
sigslot::signal<> signal_slac_start;
167168

@@ -179,6 +180,7 @@ class Charger {
179180
void set_matching_started(bool m);
180181

181182
void notify_currentdemand_started();
183+
void reset_dc_enforce_target_limits_timer();
182184

183185
std::string evse_state_to_string(EvseState s);
184186

@@ -398,12 +400,15 @@ class Charger {
398400

399401
std::chrono::time_point<std::chrono::steady_clock> fatal_error_became_active;
400402
bool fatal_error_timer_running{false};
403+
401404
} internal_context;
402405

403406
// main Charger thread
404407
Everest::Thread main_thread_handle;
405408
Everest::Thread error_thread_handle;
406409

410+
std::atomic<std::chrono::steady_clock::time_point> last_dc_enforce_target_limits{};
411+
407412
const std::unique_ptr<IECStateMachine>& bsp;
408413
const std::unique_ptr<ErrorHandling>& error_handling;
409414
const std::vector<std::unique_ptr<powermeterIntf>>& r_powermeter_billing;
@@ -440,6 +445,12 @@ class Charger {
440445
static constexpr int STAY_IN_X1_AFTER_TSTEP_EF_MS = 750;
441446
static constexpr int WAIT_FOR_ENERGY_IN_AUTHLOOP_TIMEOUT_MS = 5000;
442447
static constexpr int AC_X1_FALLBACK_TO_NOMINAL_TIMEOUT_MS = 3000;
448+
// Ensures apply_new_target_voltage_current() is called at least every DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS
449+
// during DC charging. This re-applies EVSE limits to the power supply even when the EV does not send
450+
// new target values or ignores updated limits from energy management.
451+
// The timer is reset whenever apply_new_target_voltage_current() is called from any source
452+
// (subscribe callback, energy manager, or this periodic signal).
453+
static constexpr int DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS = 5000;
443454

444455
types::evse_manager::EnableDisableSource active_enable_disable_source{
445456
types::evse_manager::Enable_source::Unspecified, types::evse_manager::Enable_state::Unassigned, 10000};

modules/EVSE/EvseManager/EvseManager.cpp

Lines changed: 88 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -719,69 +719,16 @@ void EvseManager::ready() {
719719
});
720720
}
721721

722-
// Car requests a target voltage and current limit
723-
r_hlc[0]->subscribe_dc_ev_target_voltage_current(
724-
[this](types::iso15118::DcEvTargetValues v) {
725-
bool target_changed = false;
726-
727-
// Hack for Skoda Enyaq that should be fixed in a different way
728-
if (config.hack_skoda_enyaq and (v.dc_ev_target_voltage < 300 or v.dc_ev_target_current < 0))
729-
return;
730-
731-
// Limit voltage/current for broken EV implementations
732-
const auto ev = get_ev_info();
733-
if (ev.maximum_current_limit.has_value() and
734-
v.dc_ev_target_current > ev.maximum_current_limit.value()) {
735-
v.dc_ev_target_current = ev.maximum_current_limit.value();
736-
}
737-
738-
if (ev.maximum_voltage_limit.has_value() and
739-
v.dc_ev_target_voltage > ev.maximum_voltage_limit.value()) {
740-
v.dc_ev_target_voltage = ev.maximum_voltage_limit.value();
741-
}
742-
743-
bool car_breaks_limit{false};
744-
const auto hlc_limits = charger->get_evse_max_hlc_limits();
745-
if (v.dc_ev_target_current > hlc_limits.evse_maximum_current_limit) {
746-
v.dc_ev_target_current = hlc_limits.evse_maximum_current_limit;
747-
car_breaks_limit = true;
748-
}
749-
750-
const auto actual_voltage =
751-
ev_info.present_voltage.has_value() ? ev_info.present_voltage.value() : v.dc_ev_target_voltage;
752-
753-
const auto target_power = v.dc_ev_target_current * actual_voltage;
754-
if (target_power > hlc_limits.evse_maximum_power_limit) {
755-
v.dc_ev_target_current = hlc_limits.evse_maximum_power_limit / actual_voltage;
756-
car_breaks_limit = true;
757-
}
758-
759-
if (v.dc_ev_target_voltage not_eq latest_target_voltage or
760-
v.dc_ev_target_current not_eq latest_target_current) {
761-
latest_target_voltage = v.dc_ev_target_voltage;
762-
latest_target_current = v.dc_ev_target_current;
763-
target_changed = true;
764-
}
765-
766-
if (target_changed) {
767-
apply_new_target_voltage_current();
768-
if (not contactor_open) {
769-
powersupply_DC_on();
770-
}
771-
if (car_breaks_limit) {
772-
EVLOG_warning
773-
<< "EV ignores new EVSE max limits. Setting target current to new EVSE max limits";
774-
}
775-
776-
{
777-
Everest::scoped_lock_timeout lock(ev_info_mutex,
778-
Everest::MutexDescription::EVSE_publish_ev_info);
779-
ev_info.target_voltage = latest_target_voltage;
780-
ev_info.target_current = latest_target_current;
781-
p_evse->publish_ev_info(ev_info);
782-
}
783-
}
784-
});
722+
// Car requests a target voltage and current limit.
723+
// Store raw EV values and immediately clamp against EVSE limits.
724+
// Clamping is also re-applied by the Charger state machine
725+
// (signal_dc_enforce_target_limits) in case the EV doesnt respect the
726+
// limits or does not change the target values for some time.
727+
r_hlc[0]->subscribe_dc_ev_target_voltage_current([this](types::iso15118::DcEvTargetValues v) {
728+
raw_ev_target_voltage = v.dc_ev_target_voltage;
729+
raw_ev_target_current = v.dc_ev_target_current;
730+
process_dc_ev_target_voltage_current(charger->get_evse_max_hlc_limits());
731+
});
785732

786733
r_hlc[0]->subscribe_d20_dc_dynamic_charge_mode([this](types::iso15118::DcChargeDynamicModeValues values) {
787734
static bool last_is_actually_exporting_to_grid{false};
@@ -899,6 +846,11 @@ void EvseManager::ready() {
899846
imd_stop();
900847
});
901848

849+
// Re-evaluate DC target limits so that updated EVSE
850+
// limits are enforced even when the EV does not send new target values
851+
charger->signal_dc_enforce_target_limits.connect(
852+
[this](types::iso15118::DcEvseMaximumLimits limits) { process_dc_ev_target_voltage_current(limits); });
853+
902854
// Current demand has finished - switch off DC supply
903855
r_hlc[0]->subscribe_current_demand_finished([this] { powersupply_DC_off(); });
904856

@@ -2338,6 +2290,10 @@ void EvseManager::powersupply_DC_on() {
23382290
// input voltage/current is what the evse/car would like to set.
23392291
// if it is more then what the energymanager gave us, we can limit it here.
23402292
bool EvseManager::powersupply_DC_set(double _voltage, double _current) {
2293+
if (last_power_supply_voltage == _voltage and last_power_supply_current == _current) {
2294+
return true;
2295+
}
2296+
23412297
double voltage = _voltage;
23422298
double current = _current;
23432299
static bool last_is_actually_exporting_to_grid{false};
@@ -2392,6 +2348,8 @@ bool EvseManager::powersupply_DC_set(double _voltage, double _current) {
23922348

23932349
// set the new limits for the DC output
23942350
r_powersupply_DC[0]->call_setImportVoltageCurrent(voltage, current);
2351+
last_power_supply_voltage = voltage;
2352+
last_power_supply_current = current;
23952353
return true;
23962354
}
23972355
EVLOG_critical << fmt::format("DC voltage/current out of limits requested: Voltage {:.2f} Current {:.2f}.",
@@ -2426,6 +2384,8 @@ bool EvseManager::powersupply_DC_set(double _voltage, double _current) {
24262384

24272385
// set the new limits for the DC output
24282386
r_powersupply_DC[0]->call_setExportVoltageCurrent(voltage, current);
2387+
last_power_supply_voltage = voltage;
2388+
last_power_supply_current = current;
24292389
return true;
24302390
}
24312391
EVLOG_critical << fmt::format("DC voltage/current out of limits requested: Voltage {:.2f} Current {:.2f}.", voltage,
@@ -2577,10 +2537,75 @@ types::evse_manager::EVInfo EvseManager::get_ev_info() {
25772537
return ev_info;
25782538
}
25792539

2540+
void EvseManager::process_dc_ev_target_voltage_current(const types::iso15118::DcEvseMaximumLimits& hlc_limits) {
2541+
double clamped_voltage = raw_ev_target_voltage.load();
2542+
double clamped_current = raw_ev_target_current.load();
2543+
2544+
// Hack for Skoda Enyaq that should be fixed in a different way
2545+
if (config.hack_skoda_enyaq and (clamped_voltage < 300 or clamped_current < 0)) {
2546+
return;
2547+
}
2548+
2549+
// Limit voltage/current for broken EV implementations
2550+
const auto ev_info_snapshot = get_ev_info();
2551+
if (ev_info_snapshot.maximum_current_limit.has_value() and
2552+
clamped_current > ev_info_snapshot.maximum_current_limit.value()) {
2553+
clamped_current = ev_info_snapshot.maximum_current_limit.value();
2554+
}
2555+
if (ev_info_snapshot.maximum_voltage_limit.has_value() and
2556+
clamped_voltage > ev_info_snapshot.maximum_voltage_limit.value()) {
2557+
clamped_voltage = ev_info_snapshot.maximum_voltage_limit.value();
2558+
}
2559+
2560+
bool car_breaks_limit{false};
2561+
if (clamped_current > hlc_limits.evse_maximum_current_limit) {
2562+
clamped_current = hlc_limits.evse_maximum_current_limit;
2563+
car_breaks_limit = true;
2564+
}
2565+
2566+
const auto actual_voltage =
2567+
ev_info_snapshot.present_voltage.has_value() ? ev_info_snapshot.present_voltage.value() : clamped_voltage;
2568+
2569+
const auto target_power = clamped_current * actual_voltage;
2570+
if (target_power > hlc_limits.evse_maximum_power_limit) {
2571+
clamped_current = hlc_limits.evse_maximum_power_limit / actual_voltage;
2572+
car_breaks_limit = true;
2573+
}
2574+
2575+
bool target_changed = false;
2576+
if (clamped_voltage not_eq latest_target_voltage or clamped_current not_eq latest_target_current) {
2577+
latest_target_voltage = clamped_voltage;
2578+
latest_target_current = clamped_current;
2579+
target_changed = true;
2580+
}
2581+
2582+
apply_new_target_voltage_current();
2583+
2584+
if (target_changed) {
2585+
if (not contactor_open) {
2586+
powersupply_DC_on();
2587+
}
2588+
if (car_breaks_limit) {
2589+
EVLOG_warning << "EV ignores new EVSE max limits. Setting target current to new EVSE max limits";
2590+
}
2591+
2592+
{
2593+
Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_publish_ev_info);
2594+
ev_info.target_voltage = latest_target_voltage;
2595+
ev_info.target_current = latest_target_current;
2596+
p_evse->publish_ev_info(ev_info);
2597+
}
2598+
}
2599+
}
2600+
25802601
void EvseManager::apply_new_target_voltage_current() {
25812602
if (latest_target_voltage > 0) {
25822603
powersupply_DC_set(latest_target_voltage, latest_target_current);
25832604
}
2605+
2606+
// We allow the EV to adjust the voltage/current for a few seconds
2607+
// so we reset the timer on every new target value received.
2608+
charger->reset_dc_enforce_target_limits_timer();
25842609
}
25852610

25862611
bool EvseManager::session_is_iso_d20_ac_bpt() {

modules/EVSE/EvseManager/EvseManager.hpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ class EvseManager : public Everest::ModuleBase {
232232

233233
types::evse_manager::EVInfo get_ev_info();
234234
void apply_new_target_voltage_current();
235+
void process_dc_ev_target_voltage_current(const types::iso15118::DcEvseMaximumLimits& hlc_limits);
235236

236237
std::string selected_protocol = "Unknown";
237238

@@ -339,8 +340,14 @@ class EvseManager : public Everest::ModuleBase {
339340
static constexpr std::chrono::seconds MIN_TIME_BETWEEN_FIRST_AND_LAST_FAILURE{2};
340341
static constexpr int REQUIRED_CONSECUTIVE_FAILURES{2};
341342

342-
double latest_target_voltage;
343-
double latest_target_current;
343+
std::atomic<double> latest_target_voltage{0.};
344+
std::atomic<double> latest_target_current{0.};
345+
std::atomic<double> last_power_supply_voltage{0.};
346+
std::atomic<double> last_power_supply_current{0.};
347+
348+
// Raw EV target values as received from ISO15118 stack
349+
std::atomic<double> raw_ev_target_voltage{0.};
350+
std::atomic<double> raw_ev_target_current{0.};
344351

345352
types::authorization::ProvidedIdToken autocharge_token;
346353

0 commit comments

Comments
 (0)