Skip to content

Commit

Permalink
Basic charging: use state F to signal error
Browse files Browse the repository at this point in the history
- Set state F for 300ms (configurable) after fault when in state C.
- Unlock connector in F after 5s to avoid lock/unlock in all temporary t_step_EFs

Signed-off-by: Cornelius Claussen <[email protected]>
  • Loading branch information
corneliusclaussen committed Oct 1, 2024
1 parent 4b18f46 commit 7aef5b4
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 40 deletions.
43 changes: 39 additions & 4 deletions modules/EvseManager/Charger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,19 @@ void Charger::run_state_machine() {
}
}

if (config_context.state_F_after_fault_ms > 0 and not shared_context.hlc_charging_active) {
// First time we see that a fatal error became active, signal F for a short time.
// Only use in basic charging mode.
if (entered_fatal_error_state()) {
pwm_F();
}

if (internal_context.pwm_F_active and
time_in_fatal_error_state_ms() > config_context.state_F_after_fault_ms) {
pwm_off();
}
}

// Wait here until all errors are cleared
if (stop_charging_on_fatal_error_internal()) {
break;
Expand Down Expand Up @@ -796,6 +809,7 @@ void Charger::process_event(CPEvent cp_event) {
case CPEvent::CarRequestedStopPower:
case CPEvent::CarUnplugged:
case CPEvent::BCDtoEF:
case CPEvent::BCDtoE:
case CPEvent::EFtoBCD:
session_log.car(false, fmt::format("Event {}", cpevent_to_string(cp_event)));
break;
Expand Down Expand Up @@ -866,10 +880,10 @@ void Charger::process_cp_events_state(CPEvent cp_event) {
signal_hlc_stop_charging();
session_log.evse(false, "CP state transition C->B at this stage violates ISO15118-2");
}
} else if (cp_event == CPEvent::BCDtoEF) {
} else if (cp_event == CPEvent::BCDtoE) {
shared_context.iec_allow_close_contactor = false;
shared_context.current_state = EvseState::StoppingCharging;
// Tell HLC stack to stop the session in case of an E/F event while charging.
// Tell HLC stack to stop the session in case of an E event while charging.
if (shared_context.hlc_charging_active) {
signal_hlc_stop_charging();
session_log.evse(false, "CP state transition C->E/F at this stage violates ISO15118-2");
Expand Down Expand Up @@ -958,7 +972,7 @@ void Charger::update_pwm_now(float dc) {
"Set PWM On ({}%) took {} ms", dc * 100.,
(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start)).count()));
internal_context.last_pwm_update = std::chrono::steady_clock::now();

internal_context.pwm_F_active = false;
bsp->set_pwm(dc);
}

Expand All @@ -981,6 +995,7 @@ void Charger::pwm_off() {
shared_context.pwm_running = false;
internal_context.update_pwm_last_dc = 1.;
internal_context.pwm_set_last_ampere = 0.;
internal_context.pwm_F_active = false;
bsp->set_pwm_off();
}

Expand All @@ -989,6 +1004,7 @@ void Charger::pwm_F() {
shared_context.pwm_running = false;
internal_context.update_pwm_last_dc = 0.;
internal_context.pwm_set_last_ampere = 0.;
internal_context.pwm_F_active = true;
bsp->set_pwm_F();
}

Expand Down Expand Up @@ -1308,7 +1324,7 @@ void Charger::setup(bool has_ventilation, const ChargeMode _charge_mode, bool _a
bool _ac_hlc_use_5percent, bool _ac_enforce_hlc, bool _ac_with_soc_timeout,
float _soft_over_current_tolerance_percent, float _soft_over_current_measurement_noise_A,
const int _switch_3ph1ph_delay_s, const std::string _switch_3ph1ph_cp_state,
const int _soft_over_current_timeout_ms) {
const int _soft_over_current_timeout_ms, const int _state_F_after_fault_ms) {
// set up board support package
bsp->setup(has_ventilation);

Expand All @@ -1327,6 +1343,8 @@ void Charger::setup(bool has_ventilation, const ChargeMode _charge_mode, bool _a
config_context.switch_3ph1ph_delay_s = _switch_3ph1ph_delay_s;
config_context.switch_3ph1ph_cp_state_F = _switch_3ph1ph_cp_state == "F";

config_context.state_F_after_fault_ms = _state_F_after_fault_ms;

if (config_context.charge_mode == ChargeMode::AC and config_context.ac_hlc_enabled)
EVLOG_info << "AC HLC mode enabled.";
}
Expand Down Expand Up @@ -1868,14 +1886,31 @@ bool Charger::stop_charging_on_fatal_error() {
return stop_charging_on_fatal_error_internal();
}

bool Charger::entered_fatal_error_state() {
return shared_context.error_prevent_charging_flag and not shared_context.last_error_prevent_charging_flag;
}

int Charger::time_in_fatal_error_state_ms() {
if (not internal_context.fatal_error_timer_running) {
return 0;
} else {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
internal_context.fatal_error_became_active)
.count();
}
}

bool Charger::stop_charging_on_fatal_error_internal() {
bool err = false;
if (shared_context.error_prevent_charging_flag) {
if (not shared_context.last_error_prevent_charging_flag) {
internal_context.fatal_error_became_active = std::chrono::steady_clock::now();

graceful_stop_charging();
}
err = true;
}
internal_context.fatal_error_timer_running = err;
shared_context.last_error_prevent_charging_flag = shared_context.error_prevent_charging_flag;
return err;
}
Expand Down
11 changes: 10 additions & 1 deletion modules/EvseManager/Charger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ class Charger {
void setup(bool has_ventilation, const ChargeMode charge_mode, bool ac_hlc_enabled, bool ac_hlc_use_5percent,
bool ac_enforce_hlc, bool ac_with_soc_timeout, float soft_over_current_tolerance_percent,
float soft_over_current_measurement_noise_A, const int switch_3ph1ph_delay_s,
const std::string switch_3ph1ph_cp_state, const int soft_over_current_timeout_ms);
const std::string switch_3ph1ph_cp_state, const int soft_over_current_timeout_ms,
const int _state_F_after_fault_ms);

bool enable_disable(int connector_id, const types::evse_manager::EnableDisableSource& source);

Expand Down Expand Up @@ -192,6 +193,8 @@ class Charger {
void set_hlc_allow_close_contactor(bool on);

bool stop_charging_on_fatal_error();
bool entered_fatal_error_state();
int time_in_fatal_error_state_ms();

/// @brief Returns the OCMF start data.
///
Expand Down Expand Up @@ -322,6 +325,8 @@ class Charger {
bool switch_3ph1ph_cp_state_F{false};
// Tolerate soft over current for given time
int soft_over_current_timeout_ms{7000};
// Switch to F for configured ms after a fatal error
int state_F_after_fault_ms{300};
} config_context;

// Used by different threads, but requires no complete state machine locking
Expand Down Expand Up @@ -362,6 +367,10 @@ class Charger {
bool no_energy_warning_printed{false};
float pwm_set_last_ampere{0};
bool t_step_ef_x1_pause{false};
bool pwm_F_active{false};

std::chrono::time_point<std::chrono::steady_clock> fatal_error_became_active;
bool fatal_error_timer_running{false};
} internal_context;

// main Charger thread
Expand Down
2 changes: 1 addition & 1 deletion modules/EvseManager/ErrorHandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ void ErrorHandling::raise_inoperative_error(const std::string& caused_by) {
}

if (r_hlc.size() > 0) {
r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction);
r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_EmergencyShutdown);
}

// raise externally
Expand Down
15 changes: 8 additions & 7 deletions modules/EvseManager/EvseManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -818,11 +818,12 @@ void EvseManager::ready() {
if (config.ac_with_soc) {
setup_fake_DC_mode();
} else {
charger->setup(
config.has_ventilation, (config.charge_mode == "DC" ? Charger::ChargeMode::DC : Charger::ChargeMode::AC),
hlc_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, false,
config.soft_over_current_tolerance_percent, config.soft_over_current_measurement_noise_A,
config.switch_3ph1ph_delay_s, config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms);
charger->setup(config.has_ventilation,
(config.charge_mode == "DC" ? Charger::ChargeMode::DC : Charger::ChargeMode::AC), hlc_enabled,
config.ac_hlc_use_5percent, config.ac_enforce_hlc, false,
config.soft_over_current_tolerance_percent, config.soft_over_current_measurement_noise_A,
config.switch_3ph1ph_delay_s, config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms,
config.state_F_after_fault_ms);
}

telemetryThreadHandle = std::thread([this]() {
Expand Down Expand Up @@ -1008,7 +1009,7 @@ void EvseManager::setup_fake_DC_mode() {
charger->setup(config.has_ventilation, Charger::ChargeMode::DC, hlc_enabled, config.ac_hlc_use_5percent,
config.ac_enforce_hlc, false, config.soft_over_current_tolerance_percent,
config.soft_over_current_measurement_noise_A, config.switch_3ph1ph_delay_s,
config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms);
config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms, config.state_F_after_fault_ms);

types::iso15118_charger::EVSEID evseid = {config.evse_id, config.evse_id_din};

Expand Down Expand Up @@ -1046,7 +1047,7 @@ void EvseManager::setup_AC_mode() {
charger->setup(config.has_ventilation, Charger::ChargeMode::AC, hlc_enabled, config.ac_hlc_use_5percent,
config.ac_enforce_hlc, true, config.soft_over_current_tolerance_percent,
config.soft_over_current_measurement_noise_A, config.switch_3ph1ph_delay_s,
config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms);
config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms, config.state_F_after_fault_ms);

types::iso15118_charger::EVSEID evseid = {config.evse_id, config.evse_id_din};

Expand Down
1 change: 1 addition & 0 deletions modules/EvseManager/EvseManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ struct Conf {
std::string switch_3ph1ph_cp_state;
int soft_over_current_timeout_ms;
bool lock_connector_in_state_b;
int state_F_after_fault_ms;
};

class EvseManager : public Everest::ModuleBase {
Expand Down
51 changes: 39 additions & 12 deletions modules/EvseManager/IECStateMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ const std::string cpevent_to_string(CPEvent e) {
return "EFtoBCD";
case CPEvent::BCDtoEF:
return "BCDtoEF";
case CPEvent::BCDtoE:
return "BCDtoE";
case CPEvent::EvseReplugStarted:
return "EvseReplugStarted";
case CPEvent::EvseReplugFinished:
Expand All @@ -82,6 +84,7 @@ IECStateMachine::IECStateMachine(const std::unique_ptr<evse_board_supportIntf>&
r_bsp(r_bsp_), lock_connector_in_state_b(lock_connector_in_state_b_) {
// feed the state machine whenever the timer expires
timeout_state_c1.signal_reached.connect(&IECStateMachine::feed_state_machine_no_thread, this);
timeout_unlock_state_F.signal_reached.connect(&IECStateMachine::feed_state_machine_no_thread, this);

// Subscribe to bsp driver to receive BspEvents from the hardware
r_bsp->subscribe_event([this](const types::board_support_common::BspEvent event) {
Expand Down Expand Up @@ -142,21 +145,26 @@ void IECStateMachine::feed_state_machine_no_thread() {
std::queue<CPEvent> IECStateMachine::state_machine() {

std::queue<CPEvent> events;
auto timer = TimerControl::do_nothing;
auto timer_state_C1 = TimerControl::do_nothing;
auto timer_unlock_state_F = TimerControl::do_nothing;

{
// mutex protected section

Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_state_machine);

if (cp_state not_eq RawCPState::F and last_cp_state == RawCPState::F) {
timer_unlock_state_F = TimerControl::stop;
}

switch (cp_state) {

case RawCPState::Disabled:
if (last_cp_state != RawCPState::Disabled) {
pwm_running = false;
r_bsp->call_pwm_off();
ev_simplified_mode = false;
timer = TimerControl::stop;
timer_state_C1 = TimerControl::stop;
call_allow_power_on_bsp(false);
connector_unlock();
}
Expand All @@ -169,7 +177,7 @@ std::queue<CPEvent> IECStateMachine::state_machine() {
ev_simplified_mode = false;
car_plugged_in = false;
call_allow_power_on_bsp(false);
timer = TimerControl::stop;
timer_state_C1 = TimerControl::stop;
connector_unlock();
}

Expand All @@ -196,13 +204,14 @@ std::queue<CPEvent> IECStateMachine::state_machine() {
// Need to switch off according to Table A.6 Sequence 8.1
// within 100ms
call_allow_power_on_bsp(false);
timer = TimerControl::stop;
timer_state_C1 = TimerControl::stop;
}

// Table A.6: Sequence 1.1 Plug-in
if (last_cp_state == RawCPState::A || last_cp_state == RawCPState::Disabled ||
(!car_plugged_in && last_cp_state == RawCPState::F)) {
events.push(CPEvent::CarPluggedIn);
car_plugged_in = true;
ev_simplified_mode = false;
}

Expand All @@ -217,7 +226,7 @@ std::queue<CPEvent> IECStateMachine::state_machine() {
// If state D is not supported switch off.
if (not has_ventilation) {
call_allow_power_on_bsp(false);
timer = TimerControl::stop;
timer_state_C1 = TimerControl::stop;
break;
}
// no break, intended fall through: If we support state D it is handled the same way as state C
Expand All @@ -229,6 +238,7 @@ std::queue<CPEvent> IECStateMachine::state_machine() {
if (last_cp_state == RawCPState::A || last_cp_state == RawCPState::Disabled ||
(!car_plugged_in && last_cp_state == RawCPState::F)) {
events.push(CPEvent::CarPluggedIn);
car_plugged_in = true;
EVLOG_info << "Detected simplified mode.";
ev_simplified_mode = true;
} else if (last_cp_state == RawCPState::B) {
Expand All @@ -238,13 +248,13 @@ std::queue<CPEvent> IECStateMachine::state_machine() {
if (!pwm_running && last_pwm_running) { // X2->C1
// Table A.6 Sequence 10.2: EV does not stop drawing power
// even if PWM stops. Stop within 6 seconds (E.g. Kona1!)
timer = TimerControl::start;
timer_state_C1 = TimerControl::start;
}

// PWM switches on while in state C
if (pwm_running && !last_pwm_running) {
// when resuming after a pause before the EV goes to state B, stop the timer.
timer = TimerControl::stop;
timer_state_C1 = TimerControl::stop;

// If we resume charging and the EV never left state C during pause we allow non-compliant EVs to switch
// on again.
Expand Down Expand Up @@ -285,27 +295,32 @@ std::queue<CPEvent> IECStateMachine::state_machine() {
case RawCPState::E:
connector_unlock();
if (last_cp_state != RawCPState::E) {
timer = TimerControl::stop;
timer_state_C1 = TimerControl::stop;
call_allow_power_on_bsp(false);
pwm_running = false;
r_bsp->call_pwm_off();
if (last_cp_state == RawCPState::B || last_cp_state == RawCPState::C ||
last_cp_state == RawCPState::D) {
events.push(CPEvent::BCDtoEF);
events.push(CPEvent::BCDtoE);
}
}
break;

case RawCPState::F:
connector_unlock();
timer = TimerControl::stop;
timer_state_C1 = TimerControl::stop;
call_allow_power_on_bsp(false);
if (last_cp_state not_eq RawCPState::F) {
timer_unlock_state_F = TimerControl::start;
pwm_running = false;
}
if (last_cp_state == RawCPState::B || last_cp_state == RawCPState::C || last_cp_state == RawCPState::D) {
events.push(CPEvent::BCDtoEF);
}

if (timeout_unlock_state_F.reached()) {
connector_unlock();
}
break;
}

Expand All @@ -320,9 +335,9 @@ std::queue<CPEvent> IECStateMachine::state_machine() {

// stopping the timer could lead to a deadlock when called from the
// mutex protected section
switch (timer) {
switch (timer_state_C1) {
case TimerControl::start:
timeout_state_c1.start(std::chrono::seconds(6));
timeout_state_c1.start(power_off_under_load_in_c1_timeout);
break;
case TimerControl::stop:
timeout_state_c1.stop();
Expand All @@ -332,6 +347,18 @@ std::queue<CPEvent> IECStateMachine::state_machine() {
break;
}

switch (timer_unlock_state_F) {
case TimerControl::start:
timeout_unlock_state_F.start(unlock_in_state_f_timeout);
break;
case TimerControl::stop:
timeout_unlock_state_F.stop();
break;
case TimerControl::do_nothing:
default:
break;
}

return events;
}

Expand Down
Loading

0 comments on commit 7aef5b4

Please sign in to comment.