From dcab5e2a5ff6bfb2516fe12aa45a9c3ffc05a147 Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Wed, 31 Jan 2024 10:58:29 +0100 Subject: [PATCH 1/6] EvseManager Charger timed mutexes, cleanup Add timed mutexes that print stacktraces if they detect a deadlock Clean up Charger member variables Use steady clock where possible Signed-off-by: Cornelius Claussen --- modules/EvseManager/CMakeLists.txt | 11 + modules/EvseManager/Charger.cpp | 769 +++++++++--------- modules/EvseManager/Charger.hpp | 234 +++--- modules/EvseManager/EvseManager.cpp | 94 +-- modules/EvseManager/EvseManager.hpp | 9 +- modules/EvseManager/IECStateMachine.cpp | 32 +- modules/EvseManager/IECStateMachine.hpp | 4 +- modules/EvseManager/backtrace.cpp | 43 + modules/EvseManager/backtrace.hpp | 16 + .../EvseManager/energy_grid/energyImpl.cpp | 3 +- modules/EvseManager/evse/evse_managerImpl.cpp | 148 ++-- modules/EvseManager/scoped_lock_timeout.hpp | 64 ++ 12 files changed, 807 insertions(+), 620 deletions(-) create mode 100644 modules/EvseManager/backtrace.cpp create mode 100644 modules/EvseManager/backtrace.hpp create mode 100644 modules/EvseManager/scoped_lock_timeout.hpp diff --git a/modules/EvseManager/CMakeLists.txt b/modules/EvseManager/CMakeLists.txt index ecf54561d..19358f472 100644 --- a/modules/EvseManager/CMakeLists.txt +++ b/modules/EvseManager/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources(${MODULE_NAME} CarManufacturer.cpp IECStateMachine.cpp ErrorHandling.cpp + backtrace.cpp ) target_link_libraries(${MODULE_NAME} @@ -32,6 +33,16 @@ target_link_libraries(${MODULE_NAME} ) endif() +target_compile_options(${MODULE_NAME} +PRIVATE + "-rdynamic" +) + +target_link_options(${MODULE_NAME} +PRIVATE + "-rdynamic" +) + # needed for std::filesystem target_compile_features(${MODULE_NAME} PUBLIC cxx_std_17) # ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index 75b37512e..c0cc66655 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -15,51 +15,63 @@ #include +#include "scoped_lock_timeout.hpp" + namespace module { Charger::Charger(const std::unique_ptr& bsp, const std::unique_ptr& error_handling, const types::evse_board_support::Connector_type& connector_type) : bsp(bsp), error_handling(error_handling), connector_type(connector_type) { - connector_enabled = true; - max_current = 6.0; + + Everest::install_backtrace_handler(); + + shared_context.connector_enabled = true; + shared_context.max_current = 6.0; if (connector_type == types::evse_board_support::Connector_type::IEC62196Type2Socket) { - max_current_cable = bsp->read_pp_ampacity(); + shared_context.max_current_cable = bsp->read_pp_ampacity(); } - authorized = false; + shared_context.authorized = false; - update_pwm_last_dc = 0.; + internal_context.update_pwm_last_dc = 0.; - current_state = EvseState::Idle; - last_state = EvseState::Disabled; + shared_context.current_state = EvseState::Idle; + internal_context.last_state = EvseState::Disabled; - current_drawn_by_vehicle[0] = 0.; - current_drawn_by_vehicle[1] = 0.; - current_drawn_by_vehicle[2] = 0.; + shared_context.current_drawn_by_vehicle[0] = 0.; + shared_context.current_drawn_by_vehicle[1] = 0.; + shared_context.current_drawn_by_vehicle[2] = 0.; - t_step_EF_return_state = EvseState::Idle; - t_step_X1_return_state = EvseState::Idle; + internal_context.t_step_EF_return_state = EvseState::Idle; + internal_context.t_step_X1_return_state = EvseState::Idle; - matching_started = false; + shared_context.matching_started = false; - transaction_active = false; - session_active = false; + shared_context.transaction_active = false; + shared_context.session_active = false; hlc_use_5percent_current_session = false; // Register callbacks for errors/error clearings error_handling->signal_error.connect([this](const types::evse_manager::Error e, const bool prevent_charging) { if (prevent_charging) { - std::scoped_lock lock(state_machine_mutex); - error_prevent_charging_flag = true; + std::thread error_thread([this]() { + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: error_handling->signal_error"); + shared_context.error_prevent_charging_flag = true; + }); + error_thread.detach(); } }); error_handling->signal_all_errors_cleared.connect([this]() { EVLOG_info << "All errors cleared"; - signal_event(types::evse_manager::SessionEventEnum::AllErrorsCleared); + signal_simple_event(types::evse_manager::SessionEventEnum::AllErrorsCleared); { - std::scoped_lock lock(state_machine_mutex); - error_prevent_charging_flag = false; + std::thread error_thread([this]() { + Everest::scoped_lock_timeout lock(state_machine_mutex, + "Charger.cpp: error_handling->signal_all_errors_cleared"); + shared_context.error_prevent_charging_flag = false; + }); + error_thread.detach(); } }); } @@ -74,7 +86,7 @@ void Charger::main_thread() { // publish initial values signal_max_current(get_max_current_internal()); - signal_state(current_state); + signal_state(shared_context.current_state); while (true) { if (main_thread_handle.shouldExit()) { @@ -84,7 +96,7 @@ void Charger::main_thread() { std::this_thread::sleep_for(MAINLOOP_UPDATE_RATE); { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: mainloop"); // update power limits power_available(); // Run our own state machine update (i.e. run everything that needs @@ -96,53 +108,58 @@ void Charger::main_thread() { void Charger::run_state_machine() { + constexpr int max_mainloop_runs = 10; + int mainloop_runs = 0; + // run over state machine loop until current_state does not change anymore do { + mainloop_runs++; // If a state change happened or an error recovered during a state we reinitialize the state - bool initialize_state = (last_state_detect_state_change not_eq current_state) or - (last_error_prevent_charging_flag not_eq error_prevent_charging_flag); + bool initialize_state = + (internal_context.last_state_detect_state_change not_eq shared_context.current_state) or + (internal_context.last_error_prevent_charging_flag not_eq shared_context.error_prevent_charging_flag); if (initialize_state) { - session_log.evse(false, - fmt::format("Charger state: {}->{}", evse_state_to_string(last_state_detect_state_change), - evse_state_to_string(current_state))); + session_log.evse(false, fmt::format("Charger state: {}->{}", + evse_state_to_string(internal_context.last_state_detect_state_change), + evse_state_to_string(shared_context.current_state))); } - last_state = last_state_detect_state_change; - last_state_detect_state_change = current_state; - last_error_prevent_charging_flag = error_prevent_charging_flag; + internal_context.last_state = internal_context.last_state_detect_state_change; + internal_context.last_state_detect_state_change = shared_context.current_state; + internal_context.last_error_prevent_charging_flag = shared_context.error_prevent_charging_flag; auto now = std::chrono::system_clock::now(); - if (ac_with_soc_timeout and (ac_with_soc_timer -= 50) < 0) { - ac_with_soc_timeout = false; - ac_with_soc_timer = 3600000; + if (shared_context.ac_with_soc_timeout and (shared_context.ac_with_soc_timer -= 50) < 0) { + shared_context.ac_with_soc_timeout = false; + shared_context.ac_with_soc_timer = 3600000; signal_ac_with_soc_timeout(); return; } if (initialize_state) { - current_state_started = now; - signal_state(current_state); + internal_context.current_state_started = now; + signal_state(shared_context.current_state); } auto timeInCurrentState = - std::chrono::duration_cast(now - current_state_started).count(); + std::chrono::duration_cast(now - internal_context.current_state_started).count(); - switch (current_state) { + switch (shared_context.current_state) { case EvseState::Disabled: if (initialize_state) { - signal_event(types::evse_manager::SessionEventEnum::Disabled); + signal_simple_event(types::evse_manager::SessionEventEnum::Disabled); pwm_F(); } break; case EvseState::Replug: if (initialize_state) { - signal_event(types::evse_manager::SessionEventEnum::ReplugStarted); + signal_simple_event(types::evse_manager::SessionEventEnum::ReplugStarted); // start timer in case we need to - if (ac_with_soc_timeout) { - ac_with_soc_timer = 120000; + if (shared_context.ac_with_soc_timeout) { + shared_context.ac_with_soc_timer = 120000; } } // simply wait here until BSP informs us that replugging was finished @@ -152,14 +169,14 @@ void Charger::run_state_machine() { // make sure we signal availability to potential new cars if (initialize_state) { bcb_toggle_reset(); - iec_allow_close_contactor = false; - hlc_charging_active = false; - hlc_allow_close_contactor = false; - max_current_cable = 0; - hlc_charging_terminate_pause = HlcTerminatePause::Unknown; + shared_context.iec_allow_close_contactor = false; + shared_context.hlc_charging_active = false; + shared_context.hlc_allow_close_contactor = false; + shared_context.max_current_cable = 0; + shared_context.hlc_charging_terminate_pause = HlcTerminatePause::Unknown; pwm_off(); deauthorize_internal(); - transaction_active = false; + shared_context.transaction_active = false; clear_errors_on_unplug(); } break; @@ -172,25 +189,25 @@ void Charger::run_state_machine() { if (initialize_state) { bsp->allow_power_on(false, types::evse_board_support::Reason::PowerOff); - if (last_state == EvseState::Replug) { - signal_event(types::evse_manager::SessionEventEnum::ReplugFinished); + if (internal_context.last_state == EvseState::Replug) { + signal_simple_event(types::evse_manager::SessionEventEnum::ReplugFinished); } else { // First user interaction was plug in of car? Start session here. - if (not session_active) { + if (not shared_context.session_active) { start_session(false); } // External signal on MQTT - signal_event(types::evse_manager::SessionEventEnum::AuthRequired); + signal_simple_event(types::evse_manager::SessionEventEnum::AuthRequired); } hlc_use_5percent_current_session = false; // switch on HLC if configured. May be switched off later on after retries for this session only. - if (charge_mode == ChargeMode::AC) { - ac_hlc_enabled_current_session = ac_hlc_enabled; + if (config_context.charge_mode == ChargeMode::AC) { + ac_hlc_enabled_current_session = config_context.ac_hlc_enabled; if (ac_hlc_enabled_current_session) { - hlc_use_5percent_current_session = ac_hlc_use_5percent; + hlc_use_5percent_current_session = config_context.ac_hlc_use_5percent; } - } else if (charge_mode == ChargeMode::DC) { + } else if (config_context.charge_mode == ChargeMode::DC) { hlc_use_5percent_current_session = true; } else { // unsupported charging mode, give up here. @@ -207,19 +224,22 @@ void Charger::run_state_machine() { // Read PP value in case of AC socket if (connector_type == types::evse_board_support::Connector_type::IEC62196Type2Socket and - max_current_cable == 0) { - max_current_cable = bsp->read_pp_ampacity(); + shared_context.max_current_cable == 0) { + shared_context.max_current_cable = bsp->read_pp_ampacity(); // retry if the value is not yet available. Some BSPs may take some time to measure the PP. - if (max_current_cable == 0) { + if (shared_context.max_current_cable == 0) { break; } } // Wait for Energy Manager to supply some power, otherwise wait here. // If we have zero power, some cars will not like the ChargingParameter message. - if (charge_mode == ChargeMode::DC and not(current_evse_max_limits.EVSEMaximumCurrentLimit > 0 and - current_evse_max_limits.EVSEMaximumPowerLimit > 0)) { - break; + if (config_context.charge_mode == ChargeMode::DC) { + // Create a copy of the atomic struct + types::iso15118_charger::DC_EVSEMaximumLimits evse_limit = shared_context.current_evse_max_limits; + if (not(evse_limit.EVSEMaximumCurrentLimit > 0 and evse_limit.EVSEMaximumPowerLimit > 0)) { + break; + } } // SLAC is running in the background trying to setup a PLC connection. @@ -241,11 +261,11 @@ void Charger::run_state_machine() { // in AC mode: go back to non HLC nominal PWM mode // in DC mode: go to error_slac for this session - if (authorized and not authorized_pnc) { + if (shared_context.authorized and not shared_context.authorized_pnc) { session_log.evse(false, "EIM Authorization received"); // If we are restarting, the transaction may already be active - if (not transaction_active) { + if (not shared_context.transaction_active) { start_transaction(); } @@ -253,17 +273,17 @@ void Charger::run_state_machine() { // EIM done and matching process not started -> we need to go through t_step_EF and fall back to nominal // PWM. This is a complete waste of 4 precious seconds. - if (charge_mode == ChargeMode::AC) { + if (config_context.charge_mode == ChargeMode::AC) { if (ac_hlc_enabled_current_session) { - if (ac_enforce_hlc) { + if (config_context.ac_enforce_hlc) { // non standard compliant mode: we just keep 5 percent running all the time like in DC session_log.evse( false, "AC mode, HLC enabled(ac_enforce_hlc), keeping 5 percent on until a dlink error " "is signalled."); hlc_use_5percent_current_session = true; - current_state = target_state; + shared_context.current_state = target_state; } else { - if (not get_matching_started()) { + if (not shared_context.matching_started) { // SLAC matching was not started when EIM arrived session_log.evse( @@ -271,17 +291,17 @@ void Charger::run_state_machine() { fmt::format( "AC mode, HLC enabled, matching not started yet. Go through t_step_EF and " "disable 5 percent if it was enabled before: {}", - hlc_use_5percent_current_session)); + (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 - t_step_EF_return_state = target_state; - t_step_EF_return_pwm = 0.; + internal_context.t_step_EF_return_state = target_state; + internal_context.t_step_EF_return_pwm = 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; - current_state = EvseState::T_step_EF; + shared_context.current_state = EvseState::T_step_EF; } else { // SLAC matching was started already when EIM arrived if (hlc_use_5percent_current_session) { @@ -290,10 +310,10 @@ void Charger::run_state_machine() { session_log.evse( false, "AC mode, HLC enabled(5percent), matching already started. Go through " "t_step_X1 and disable 5 percent."); - t_step_X1_return_state = target_state; - t_step_X1_return_pwm = 0.; + internal_context.t_step_X1_return_state = target_state; + internal_context.t_step_X1_return_pwm = 0.; hlc_use_5percent_current_session = false; - current_state = EvseState::T_step_X1; + shared_context.current_state = EvseState::T_step_X1; } 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 @@ -302,7 +322,7 @@ void Charger::run_state_machine() { false, "AC mode, HLC enabled(X1), matching already started. We are in X1 so we can " "go directly to nominal PWM."); - current_state = target_state; + shared_context.current_state = target_state; } } } @@ -313,19 +333,19 @@ void Charger::run_state_machine() { // wants to. session_log.evse(false, "AC mode, HLC disabled. We are in X1 so we can " "go directly to nominal PWM."); - current_state = target_state; + shared_context.current_state = target_state; } - } else if (charge_mode == ChargeMode::DC) { + } else if (config_context.charge_mode == ChargeMode::DC) { // Figure 8 of ISO15118-3: DC with EIM before or after plugin or PnC // simple here as we always stay within 5 percent mode anyway. session_log.evse(false, "DC mode. We are in 5percent mode so we can continue without further action."); - current_state = target_state; + shared_context.current_state = target_state; } else { // unsupported charging mode, give up here. error_handling->raise_internal_error("Unsupported charging mode."); } - } else if (authorized and authorized_pnc) { + } else if (shared_context.authorized and shared_context.authorized_pnc) { start_transaction(); @@ -333,7 +353,7 @@ void Charger::run_state_machine() { // We got authorization by Plug and Charge session_log.evse(false, "PnC Authorization received"); - if (charge_mode == ChargeMode::AC) { + 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. @@ -341,14 +361,14 @@ void Charger::run_state_machine() { false, "AC mode, HLC enabled, PnC auth received. We will continue with 5percent independent on " "how we started."); hlc_use_5percent_current_session = true; - current_state = target_state; + shared_context.current_state = target_state; - } else if (charge_mode == ChargeMode::DC) { + } else if (config_context.charge_mode == ChargeMode::DC) { // Figure 8 of ISO15118-3: DC with EIM before or after plugin or PnC // simple here as we always stay within 5 percent mode anyway. session_log.evse(false, "DC mode. We are in 5percent mode so we can continue without further action."); - current_state = target_state; + shared_context.current_state = target_state; } else { // unsupported charging mode, give up here. error_handling->raise_internal_error("Unsupported charging mode."); @@ -362,14 +382,14 @@ void Charger::run_state_machine() { session_log.evse(false, "Enter T_step_EF"); pwm_F(); } - if (timeInCurrentState >= t_step_EF) { + if (timeInCurrentState >= T_STEP_EF) { session_log.evse(false, "Exit T_step_EF"); - if (t_step_EF_return_pwm == 0.) { + if (internal_context.t_step_EF_return_pwm == 0.) { pwm_off(); } else { - update_pwm_now(t_step_EF_return_pwm); + update_pwm_now(internal_context.t_step_EF_return_pwm); } - current_state = t_step_EF_return_state; + shared_context.current_state = internal_context.t_step_EF_return_state; } break; @@ -378,26 +398,25 @@ void Charger::run_state_machine() { session_log.evse(false, "Enter T_step_X1"); pwm_off(); } - if (timeInCurrentState >= t_step_X1) { + if (timeInCurrentState >= T_STEP_X1) { session_log.evse(false, "Exit T_step_X1"); - if (t_step_X1_return_pwm == 0.) { + if (internal_context.t_step_X1_return_pwm == 0.) { pwm_off(); } else { - update_pwm_now(t_step_X1_return_pwm); + update_pwm_now(internal_context.t_step_X1_return_pwm); } - current_state = t_step_X1_return_state; + shared_context.current_state = internal_context.t_step_X1_return_state; } break; case EvseState::PrepareCharging: - if (initialize_state) { - signal_event(types::evse_manager::SessionEventEnum::PrepareCharging); + signal_simple_event(types::evse_manager::SessionEventEnum::PrepareCharging); bcb_toggle_reset(); } - if (charge_mode == ChargeMode::DC) { - if (hlc_allow_close_contactor and iec_allow_close_contactor) { + if (config_context.charge_mode == ChargeMode::DC) { + if (shared_context.hlc_allow_close_contactor and shared_context.iec_allow_close_contactor) { bsp->allow_power_on(true, types::evse_board_support::Reason::DCCableCheck); } } @@ -409,36 +428,39 @@ void Charger::run_state_machine() { // make sure we are enabling PWM if (not hlc_use_5percent_current_session) { - update_pwm_now_if_changed(ampere_to_duty_cycle(get_max_current_internal())); + auto m = get_max_current_internal(); + update_pwm_now_if_changed(ampere_to_duty_cycle(m)); } else { update_pwm_now_if_changed(PWM_5_PERCENT); } - if (charge_mode == ChargeMode::AC) { + if (config_context.charge_mode == ChargeMode::AC) { // In AC mode BASIC, iec_allow is sufficient. The same is true for HLC mode when nominal PWM is used as // the car can do BASIC and HLC charging any time. In AC HLC with 5 percent mode, we need to wait for // both iec_allow and hlc_allow. - if ((iec_allow_close_contactor and not hlc_use_5percent_current_session) or - (iec_allow_close_contactor and hlc_allow_close_contactor and hlc_use_5percent_current_session)) { + if ((shared_context.iec_allow_close_contactor and not hlc_use_5percent_current_session) or + (shared_context.iec_allow_close_contactor and shared_context.hlc_allow_close_contactor and + hlc_use_5percent_current_session)) { - signal_event(types::evse_manager::SessionEventEnum::ChargingStarted); + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingStarted); if (power_available()) { - current_state = EvseState::Charging; + shared_context.current_state = EvseState::Charging; } else { - current_state = EvseState::Charging; + shared_context.current_state = EvseState::Charging; pause_charging_wait_for_power_internal(); } } - if (not hlc_charging_active and not legacy_wakeup_done and timeInCurrentState > legacy_wakeup_timeout) { + if (not shared_context.hlc_charging_active and not shared_context.legacy_wakeup_done and + timeInCurrentState > LEGACY_WAKEUP_TIMEOUT) { session_log.evse( false, "EV did not transition to state C, trying one legacy wakeup according to IEC61851-1 A.5.3"); - legacy_wakeup_done = true; - t_step_EF_return_state = EvseState::PrepareCharging; - t_step_EF_return_pwm = ampere_to_duty_cycle(get_max_current_internal()); - current_state = EvseState::T_step_EF; + shared_context.legacy_wakeup_done = true; + internal_context.t_step_EF_return_state = EvseState::PrepareCharging; + internal_context.t_step_EF_return_pwm = ampere_to_duty_cycle(get_max_current_internal()); + shared_context.current_state = EvseState::T_step_EF; } } @@ -454,7 +476,7 @@ void Charger::run_state_machine() { case EvseState::Charging: if (initialize_state) { - hlc_charging_terminate_pause = HlcTerminatePause::Unknown; + shared_context.hlc_charging_terminate_pause = HlcTerminatePause::Unknown; } // Wait here until all errors are cleared @@ -462,7 +484,7 @@ void Charger::run_state_machine() { break; } - if (charge_mode == ChargeMode::DC) { + if (config_context.charge_mode == ChargeMode::DC) { // FIXME: handle DC pause/resume here // FIXME: handle DC no power available from Energy management } else { @@ -474,12 +496,12 @@ void Charger::run_state_machine() { } if (initialize_state) { - if (last_state not_eq EvseState::PrepareCharging) { - signal_event(types::evse_manager::SessionEventEnum::ChargingResumed); + if (internal_context.last_state not_eq EvseState::PrepareCharging) { + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingResumed); } // Allow another wake-up sequence - legacy_wakeup_done = false; + shared_context.legacy_wakeup_done = false; bsp->allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging); // make sure we are enabling PWM @@ -499,7 +521,7 @@ void Charger::run_state_machine() { case EvseState::ChargingPausedEV: - if (charge_mode == ChargeMode::AC) { + if (config_context.charge_mode == ChargeMode::AC) { check_soft_over_current(); } @@ -513,34 +535,34 @@ void Charger::run_state_machine() { // This is also true for nominal PWM AC HLC charging, so an EV that does HLC AC and pauses can only // resume in HLC mode and not in BASIC charging. - if (hlc_charging_active) { + if (shared_context.hlc_charging_active) { // This is for HLC charging (both AC and DC) if (initialize_state) { bcb_toggle_reset(); bsp->allow_power_on(false, types::evse_board_support::Reason::PowerOff); - if (charge_mode == ChargeMode::DC) { + if (config_context.charge_mode == ChargeMode::DC) { signal_dc_supply_off(); } - signal_event(types::evse_manager::SessionEventEnum::ChargingPausedEV); + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingPausedEV); } if (bcb_toggle_detected()) { - current_state = EvseState::PrepareCharging; + shared_context.current_state = EvseState::PrepareCharging; } // We come here by a state C->B transition but the ISO message may not have arrived yet, // so we wait here until we know wether it is Terminate or Pause. Until we leave PWM on (should not be // shut down before SessionStop.req) - if (hlc_charging_terminate_pause == HlcTerminatePause::Terminate) { + if (shared_context.hlc_charging_terminate_pause == HlcTerminatePause::Terminate) { // EV wants to terminate session - current_state = EvseState::StoppingCharging; - if (pwm_running) { + shared_context.current_state = EvseState::StoppingCharging; + if (shared_context.pwm_running) { pwm_off(); } - } else if (hlc_charging_terminate_pause == HlcTerminatePause::Pause) { + } else if (shared_context.hlc_charging_terminate_pause == HlcTerminatePause::Pause) { // EV wants an actual pause - if (pwm_running) { + if (shared_context.pwm_running) { pwm_off(); } } @@ -556,7 +578,7 @@ void Charger::run_state_machine() { } if (initialize_state) { - signal_event(types::evse_manager::SessionEventEnum::ChargingPausedEV); + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingPausedEV); } else { // update PWM if it has changed and 5 seconds have passed since last update if (not errors_prevent_charging_internal()) { @@ -568,10 +590,10 @@ void Charger::run_state_machine() { case EvseState::ChargingPausedEVSE: if (initialize_state) { - signal_event(types::evse_manager::SessionEventEnum::ChargingPausedEVSE); - if (hlc_charging_active) { + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingPausedEVSE); + if (shared_context.hlc_charging_active) { // currentState = EvseState::StoppingCharging; - last_stop_transaction_reason = types::evse_manager::StopTransactionReason::Local; + shared_context.last_stop_transaction_reason = types::evse_manager::StopTransactionReason::Local; // tell HLC stack to stop the session signal_hlc_stop_charging(); pwm_off(); @@ -583,7 +605,7 @@ void Charger::run_state_machine() { case EvseState::WaitingForEnergy: if (initialize_state) { - signal_event(types::evse_manager::SessionEventEnum::WaitingForEnergy); + signal_simple_event(types::evse_manager::SessionEventEnum::WaitingForEnergy); if (not hlc_use_5percent_current_session) { pwm_off(); } @@ -593,12 +615,12 @@ void Charger::run_state_machine() { case EvseState::StoppingCharging: if (initialize_state) { bcb_toggle_reset(); - if (transaction_active or session_active) { - signal_event(types::evse_manager::SessionEventEnum::StoppingCharging); + if (shared_context.transaction_active or shared_context.session_active) { + signal_simple_event(types::evse_manager::SessionEventEnum::StoppingCharging); } - if (hlc_charging_active) { - if (charge_mode == ChargeMode::DC) { + if (shared_context.hlc_charging_active) { + if (config_context.charge_mode == ChargeMode::DC) { // DC supply off - actually this is after relais switched off // this is a backup switch off, normally it should be switched off earlier by ISO protocol. signal_dc_supply_off(); @@ -608,7 +630,7 @@ void Charger::run_state_machine() { } else { // For AC BASIC charging, we reached StoppingCharging because an unplug happend. pwm_off(); - current_state = EvseState::Finished; + shared_context.current_state = EvseState::Finished; } } @@ -616,9 +638,9 @@ void Charger::run_state_machine() { // Only allow that if the transaction is still running. If it was cancelled externally with // cancel_transaction(), we do not allow restart. If OCPP cancels a transaction it assumes it cannot be // restarted. In all other cases, e.g. the EV stopping the transaction it may resume with a BCB toggle. - if (hlc_charging_active and bcb_toggle_detected()) { - if (transaction_active) { - current_state = EvseState::PrepareCharging; + if (shared_context.hlc_charging_active and bcb_toggle_detected()) { + if (shared_context.transaction_active) { + shared_context.current_state = EvseState::PrepareCharging; // wake up SLAC as well signal_slac_start(); } else { @@ -633,23 +655,31 @@ void Charger::run_state_machine() { if (initialize_state) { // Transaction may already be stopped when it was cancelled earlier. // In that case, do not sent a second transactionFinished event. - if (transaction_active) { + if (shared_context.transaction_active) { stop_transaction(); } + // We may come here from an error state, so a session was maybe not active. - if (session_active) { + if (shared_context.session_active) { stop_session(); } - if (charge_mode == ChargeMode::DC) { + if (config_context.charge_mode == ChargeMode::DC) { signal_dc_supply_off(); } } - current_state = EvseState::Idle; + shared_context.current_state = EvseState::Idle; break; } - } while (last_state_detect_state_change not_eq current_state); + + if (mainloop_runs > max_mainloop_runs) { + EVLOG_warning << "Charger main loop exceeded maximum number of runs, last_state " + << 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); } void Charger::process_event(CPEvent cp_event) { @@ -667,14 +697,14 @@ void Charger::process_event(CPEvent cp_event) { break; } - std::scoped_lock lock(state_machine_mutex); - if (cp_event == CPEvent::PowerOn) { contactors_closed = true; } else if (cp_event == CPEvent::PowerOff) { contactors_closed = false; } + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: process_event"); + run_state_machine(); // Process all event actions that are independent of the current state @@ -689,59 +719,60 @@ void Charger::process_event(CPEvent cp_event) { } void Charger::process_cp_events_state(CPEvent cp_event) { - switch (current_state) { + switch (shared_context.current_state) { case EvseState::Idle: if (cp_event == CPEvent::CarPluggedIn) { - current_state = EvseState::WaitingForAuthentication; + shared_context.current_state = EvseState::WaitingForAuthentication; } break; case EvseState::WaitingForAuthentication: if (cp_event == CPEvent::CarRequestedPower) { session_log.car(false, "B->C transition before PWM is enabled at this stage violates IEC61851-1"); - iec_allow_close_contactor = true; + shared_context.iec_allow_close_contactor = true; } else if (cp_event == CPEvent::CarRequestedStopPower) { session_log.car(false, "C->B transition at this stage violates IEC61851-1"); - iec_allow_close_contactor = false; + shared_context.iec_allow_close_contactor = false; } break; case EvseState::PrepareCharging: if (cp_event == CPEvent::CarRequestedPower) { - iec_allow_close_contactor = true; + shared_context.iec_allow_close_contactor = true; } else if (cp_event == CPEvent::CarRequestedStopPower) { - iec_allow_close_contactor = false; + shared_context.iec_allow_close_contactor = false; // current_state = EvseState::StoppingCharging; } break; case EvseState::Charging: if (cp_event == CPEvent::CarRequestedStopPower) { - iec_allow_close_contactor = false; - current_state = EvseState::ChargingPausedEV; + shared_context.iec_allow_close_contactor = false; + shared_context.current_state = EvseState::ChargingPausedEV; } break; case EvseState::ChargingPausedEV: if (cp_event == CPEvent::CarRequestedPower) { - iec_allow_close_contactor = true; + shared_context.iec_allow_close_contactor = true; // For BASIC charging we can simply switch back to Charging - if (charge_mode == ChargeMode::AC and not hlc_charging_active) { - current_state = EvseState::Charging; - } else if (not pwm_running) { + if (config_context.charge_mode == ChargeMode::AC and not shared_context.hlc_charging_active) { + shared_context.current_state = EvseState::Charging; + } else if (not shared_context.pwm_running) { bcb_toggle_detect_start_pulse(); } } - if (cp_event == CPEvent::CarRequestedStopPower and not pwm_running and hlc_charging_active) { + if (cp_event == CPEvent::CarRequestedStopPower and not shared_context.pwm_running and + shared_context.hlc_charging_active) { bcb_toggle_detect_stop_pulse(); } break; case EvseState::StoppingCharging: // Allow session restart from EV after SessionStop.terminate with BCB toggle - if (hlc_charging_active and not pwm_running) { + if (shared_context.hlc_charging_active and not shared_context.pwm_running) { if (cp_event == CPEvent::CarRequestedPower) { bcb_toggle_detect_start_pulse(); } else if (cp_event == CPEvent::CarRequestedStopPower) { @@ -758,16 +789,16 @@ void Charger::process_cp_events_state(CPEvent cp_event) { void Charger::process_cp_events_independent(CPEvent cp_event) { switch (cp_event) { case CPEvent::EvseReplugStarted: - current_state = EvseState::Replug; + shared_context.current_state = EvseState::Replug; break; case CPEvent::EvseReplugFinished: - current_state = EvseState::WaitingForAuthentication; + shared_context.current_state = EvseState::WaitingForAuthentication; break; case CPEvent::CarUnplugged: - if (not hlc_charging_active) { - current_state = EvseState::StoppingCharging; + if (not shared_context.hlc_charging_active) { + shared_context.current_state = EvseState::StoppingCharging; } else { - current_state = EvseState::Finished; + shared_context.current_state = EvseState::Finished; } break; default: @@ -776,9 +807,10 @@ void Charger::process_cp_events_independent(CPEvent cp_event) { } void Charger::update_pwm_max_every_5seconds(float dc) { - if (dc not_eq update_pwm_last_dc) { - auto now = date::utc_clock::now(); - auto timeSinceLastUpdate = std::chrono::duration_cast(now - last_pwm_update).count(); + if (dc not_eq internal_context.update_pwm_last_dc) { + auto now = std::chrono::steady_clock::now(); + auto timeSinceLastUpdate = + std::chrono::duration_cast(now - internal_context.last_pwm_update).count(); if (timeSinceLastUpdate >= 5000) { update_pwm_now(dc); } @@ -786,35 +818,37 @@ void Charger::update_pwm_max_every_5seconds(float dc) { } void Charger::update_pwm_now(float dc) { - auto start = date::utc_clock::now(); - update_pwm_last_dc = dc; - pwm_running = true; - bsp->set_pwm(dc); + auto start = std::chrono::steady_clock::now(); + internal_context.update_pwm_last_dc = dc; + shared_context.pwm_running = true; session_log.evse( false, - fmt::format("Set PWM On ({}%) took {} ms", dc * 100., - (std::chrono::duration_cast(date::utc_clock::now() - start)).count())); - last_pwm_update = date::utc_clock::now(); + fmt::format( + "Set PWM On ({}%) took {} ms", dc * 100., + (std::chrono::duration_cast(std::chrono::steady_clock::now() - start)).count())); + internal_context.last_pwm_update = std::chrono::steady_clock::now(); + + bsp->set_pwm(dc); } void Charger::update_pwm_now_if_changed(float dc) { - if (update_pwm_last_dc not_eq dc) { + if (internal_context.update_pwm_last_dc not_eq dc) { update_pwm_now(dc); } } void Charger::pwm_off() { session_log.evse(false, "Set PWM Off"); - pwm_running = false; - update_pwm_last_dc = 1.; + shared_context.pwm_running = false; + internal_context.update_pwm_last_dc = 1.; bsp->set_pwm_off(); } void Charger::pwm_F() { session_log.evse(false, "Set PWM F"); - pwm_running = false; - update_pwm_last_dc = 0.; + shared_context.pwm_running = false; + internal_context.update_pwm_last_dc = 0.; bsp->set_pwm_F(); } @@ -855,8 +889,8 @@ bool Charger::set_max_current(float c, std::chrono::time_point if (validUntil > date::utc_clock::now()) { { std::lock_guard lock(state_machine_mutex); - max_current = c; - max_current_valid_until = validUntil; + shared_context.max_current = c; + shared_context.max_current_valid_until = validUntil; } bsp->set_overcurrent_limit(c); signal_max_current(c); @@ -868,25 +902,26 @@ bool Charger::set_max_current(float c, std::chrono::time_point // pause if currently charging, else do nothing. bool Charger::pause_charging() { - std::scoped_lock lock(state_machine_mutex); - if (current_state == EvseState::Charging) { - legacy_wakeup_done = false; - current_state = EvseState::ChargingPausedEVSE; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: pause_charging"); + if (shared_context.current_state == EvseState::Charging) { + shared_context.legacy_wakeup_done = false; + shared_context.current_state = EvseState::ChargingPausedEVSE; return true; } return false; } bool Charger::resume_charging() { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: resume_charging"); - if (hlc_charging_active and transaction_active and current_state == EvseState::ChargingPausedEVSE) { - current_state = EvseState::PrepareCharging; + if (shared_context.hlc_charging_active and shared_context.transaction_active and + shared_context.current_state == EvseState::ChargingPausedEVSE) { + shared_context.current_state = EvseState::PrepareCharging; // wake up SLAC as well signal_slac_start(); return true; - } else if (transaction_active and current_state == EvseState::ChargingPausedEVSE) { - current_state = EvseState::WaitingForEnergy; + } else if (shared_context.transaction_active and shared_context.current_state == EvseState::ChargingPausedEVSE) { + shared_context.current_state = EvseState::WaitingForEnergy; return true; } @@ -895,14 +930,14 @@ bool Charger::resume_charging() { // pause charging since no power is available at the moment bool Charger::pause_charging_wait_for_power() { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: pause_charging_wait_for_power"); return pause_charging_wait_for_power_internal(); } // pause charging since no power is available at the moment bool Charger::pause_charging_wait_for_power_internal() { - if (current_state == EvseState::Charging) { - current_state = EvseState::WaitingForEnergy; + if (shared_context.current_state == EvseState::Charging) { + shared_context.current_state = EvseState::WaitingForEnergy; return true; } return false; @@ -910,10 +945,11 @@ bool Charger::pause_charging_wait_for_power_internal() { // resume charging since power became available. Does not resume if user paused charging. bool Charger::resume_charging_power_available() { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: resume_charging_power_available"); - if (transaction_active and current_state == EvseState::WaitingForEnergy and power_available()) { - current_state = EvseState::Charging; + if (shared_context.transaction_active and shared_context.current_state == EvseState::WaitingForEnergy and + power_available()) { + shared_context.current_state = EvseState::Charging; return true; } return false; @@ -932,70 +968,58 @@ bool Charger::evse_replug() { // Cancel transaction/charging from external EvseManager interface (e.g. via OCPP) bool Charger::cancel_transaction(const types::evse_manager::StopTransactionRequest& request) { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: cancel_transaction"); - if (transaction_active) { - if (hlc_charging_active) { - current_state = EvseState::StoppingCharging; + if (shared_context.transaction_active) { + if (shared_context.hlc_charging_active) { + shared_context.current_state = EvseState::StoppingCharging; signal_hlc_stop_charging(); } else { - current_state = EvseState::ChargingPausedEVSE; + shared_context.current_state = EvseState::ChargingPausedEVSE; } - transaction_active = false; - last_stop_transaction_reason = request.reason; + shared_context.transaction_active = false; + shared_context.last_stop_transaction_reason = request.reason; if (request.id_tag) { - stop_transaction_id_token = request.id_tag.value(); + shared_context.stop_transaction_id_token = request.id_tag.value(); } - signal_event(types::evse_manager::SessionEventEnum::ChargingFinished); - signal_event(types::evse_manager::SessionEventEnum::TransactionFinished); + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingFinished); + signal_transaction_finished_event(shared_context.last_stop_transaction_reason, + shared_context.stop_transaction_id_token); return true; } return false; } void Charger::start_session(bool authfirst) { - session_active = true; - authorized = false; - if (authfirst) - last_start_session_reason = types::evse_manager::StartSessionReason::Authorized; - else - last_start_session_reason = types::evse_manager::StartSessionReason::EVConnected; - signal_event(types::evse_manager::SessionEventEnum::SessionStarted); + shared_context.session_active = true; + shared_context.authorized = false; + if (authfirst) { + shared_context.last_start_session_reason = types::evse_manager::StartSessionReason::Authorized; + } else { + shared_context.last_start_session_reason = types::evse_manager::StartSessionReason::EVConnected; + } + signal_session_started_event(shared_context.last_start_session_reason); } void Charger::stop_session() { - session_active = false; - authorized = false; - signal_event(types::evse_manager::SessionEventEnum::SessionFinished); + shared_context.session_active = false; + shared_context.authorized = false; + signal_simple_event(types::evse_manager::SessionEventEnum::SessionFinished); } void Charger::start_transaction() { - stop_transaction_id_token.reset(); - transaction_active = true; - signal_event(types::evse_manager::SessionEventEnum::TransactionStarted); + shared_context.stop_transaction_id_token.reset(); + shared_context.transaction_active = true; + signal_transaction_started_event(shared_context.id_token); } void Charger::stop_transaction() { - transaction_active = false; - last_stop_transaction_reason = types::evse_manager::StopTransactionReason::EVDisconnected; - signal_event(types::evse_manager::SessionEventEnum::ChargingFinished); - signal_event(types::evse_manager::SessionEventEnum::TransactionFinished); -} - -std::optional Charger::get_stop_transaction_id_token() { - std::lock_guard lock(state_machine_mutex); - return stop_transaction_id_token; -} - -types::evse_manager::StopTransactionReason Charger::get_transaction_finished_reason() { - std::scoped_lock lock(state_machine_mutex); - return last_stop_transaction_reason; -} - -types::evse_manager::StartSessionReason Charger::get_session_started_reason() { - std::scoped_lock lock(state_machine_mutex); - return last_start_session_reason; + shared_context.transaction_active = false; + shared_context.last_stop_transaction_reason = types::evse_manager::StopTransactionReason::EVDisconnected; + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingFinished); + signal_transaction_finished_event(shared_context.last_stop_transaction_reason, + shared_context.stop_transaction_id_token); } bool Charger::switch_three_phases_while_charging(bool n) { @@ -1010,93 +1034,93 @@ void Charger::setup(bool three_phases, bool has_ventilation, const std::string& // set up board support package bsp->setup(three_phases, has_ventilation, country_code); - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: setup"); // cache our config variables - charge_mode = _charge_mode; - ac_hlc_enabled_current_session = ac_hlc_enabled = _ac_hlc_enabled; - ac_hlc_use_5percent = _ac_hlc_use_5percent; - ac_enforce_hlc = _ac_enforce_hlc; - ac_with_soc_timeout = _ac_with_soc_timeout; - ac_with_soc_timer = 3600000; + config_context.charge_mode = _charge_mode; + ac_hlc_enabled_current_session = config_context.ac_hlc_enabled = _ac_hlc_enabled; + config_context.ac_hlc_use_5percent = _ac_hlc_use_5percent; + config_context.ac_enforce_hlc = _ac_enforce_hlc; + shared_context.ac_with_soc_timeout = _ac_with_soc_timeout; + shared_context.ac_with_soc_timer = 3600000; soft_over_current_tolerance_percent = _soft_over_current_tolerance_percent; soft_over_current_measurement_noise_A = _soft_over_current_measurement_noise_A; - if (charge_mode == ChargeMode::AC and ac_hlc_enabled) + if (config_context.charge_mode == ChargeMode::AC and config_context.ac_hlc_enabled) EVLOG_info << "AC HLC mode enabled."; } Charger::EvseState Charger::get_current_state() { - std::scoped_lock lock(state_machine_mutex); - return current_state; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_current_state"); + return shared_context.current_state; } bool Charger::get_authorized_pnc() { - std::scoped_lock lock(state_machine_mutex); - return (authorized and authorized_pnc); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_authorized_pnc"); + return (shared_context.authorized and shared_context.authorized_pnc); } bool Charger::get_authorized_eim() { - std::scoped_lock lock(state_machine_mutex); - return (authorized and not authorized_pnc); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_authorized_eim"); + return (shared_context.authorized and not shared_context.authorized_pnc); } bool Charger::get_authorized_pnc_ready_for_hlc() { bool auth = false, ready = false; - std::scoped_lock lock(state_machine_mutex); - auth = (authorized and authorized_pnc); - ready = (current_state == EvseState::ChargingPausedEV) or (current_state == EvseState::ChargingPausedEVSE) or - (current_state == EvseState::Charging) or (current_state == EvseState::WaitingForEnergy); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_authorized_pnc_ready_for_hlc"); + auth = (shared_context.authorized and shared_context.authorized_pnc); + ready = (shared_context.current_state == EvseState::ChargingPausedEV) or + (shared_context.current_state == EvseState::ChargingPausedEVSE) or + (shared_context.current_state == EvseState::Charging) or + (shared_context.current_state == EvseState::WaitingForEnergy); return (auth and ready); } bool Charger::get_authorized_eim_ready_for_hlc() { bool auth = false, ready = false; - std::scoped_lock lock(state_machine_mutex); - auth = (authorized and not authorized_pnc); - ready = (current_state == EvseState::ChargingPausedEV) or (current_state == EvseState::ChargingPausedEVSE) or - (current_state == EvseState::Charging) or (current_state == EvseState::WaitingForEnergy); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_authorized_eim_ready_for_hlc"); + auth = (shared_context.authorized and not shared_context.authorized_pnc); + ready = (shared_context.current_state == EvseState::ChargingPausedEV) or + (shared_context.current_state == EvseState::ChargingPausedEVSE) or + (shared_context.current_state == EvseState::Charging) or + (shared_context.current_state == EvseState::WaitingForEnergy); return (auth and ready); } void Charger::authorize(bool a, const types::authorization::ProvidedIdToken& token) { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: authorize"); if (a) { // First user interaction was auth? Then start session already here and not at plug in - if (not session_active) { + if (not shared_context.session_active) { start_session(true); } - authorized = true; - authorized_pnc = token.authorization_type == types::authorization::AuthorizationType::PlugAndCharge; - id_token = token; + shared_context.authorized = true; + shared_context.authorized_pnc = + token.authorization_type == types::authorization::AuthorizationType::PlugAndCharge; + shared_context.id_token = token; } else { - if (session_active) { + if (shared_context.session_active) { stop_session(); } - authorized = false; + shared_context.authorized = false; } } -types::authorization::ProvidedIdToken Charger::get_id_token() { - std::scoped_lock lock(state_machine_mutex); - return id_token; -} - bool Charger::deauthorize() { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: deauthorize"); return deauthorize_internal(); } bool Charger::deauthorize_internal() { - if (session_active) { - auto s = current_state; + if (shared_context.session_active) { + auto s = shared_context.current_state; if (s == EvseState::Disabled or s == EvseState::Idle or s == EvseState::WaitingForAuthentication) { // We can safely remove auth as it is not in use right now - if (not authorized) { - signal_event(types::evse_manager::SessionEventEnum::PluginTimeout); + if (not shared_context.authorized) { + signal_simple_event(types::evse_manager::SessionEventEnum::PluginTimeout); return false; } - authorized = false; + shared_context.authorized = false; stop_session(); return true; } @@ -1105,26 +1129,26 @@ bool Charger::deauthorize_internal() { } bool Charger::disable(int connector_id) { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: disable"); if (connector_id not_eq 0) { - connector_enabled = false; + shared_context.connector_enabled = false; } - current_state = EvseState::Disabled; - signal_event(types::evse_manager::SessionEventEnum::Disabled); + shared_context.current_state = EvseState::Disabled; + signal_simple_event(types::evse_manager::SessionEventEnum::Disabled); return true; } bool Charger::enable(int connector_id) { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: enable"); if (connector_id not_eq 0) { - connector_enabled = true; + shared_context.connector_enabled = true; } - signal_event(types::evse_manager::SessionEventEnum::Enabled); - if (current_state == EvseState::Disabled) { - if (connector_enabled) { - current_state = EvseState::Idle; + signal_simple_event(types::evse_manager::SessionEventEnum::Enabled); + if (shared_context.current_state == EvseState::Disabled) { + if (shared_context.connector_enabled) { + shared_context.current_state = EvseState::Idle; } return true; } @@ -1132,8 +1156,8 @@ bool Charger::enable(int connector_id) { } void Charger::set_faulted() { - std::scoped_lock lock(state_machine_mutex); - error_prevent_charging_flag = true; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_faulted"); + shared_context.error_prevent_charging_flag = true; } std::string Charger::evse_state_to_string(EvseState s) { @@ -1182,26 +1206,26 @@ std::string Charger::evse_state_to_string(EvseState s) { } float Charger::get_max_current() { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_max_current"); return get_max_current_internal(); } float Charger::get_max_current_internal() { - auto maxc = max_current; + auto maxc = shared_context.max_current; if (connector_type == types::evse_board_support::Connector_type::IEC62196Type2Socket and - max_current_cable < maxc and current_state not_eq EvseState::Idle) { - maxc = max_current_cable; + shared_context.max_current_cable < maxc and shared_context.current_state not_eq EvseState::Idle) { + maxc = shared_context.max_current_cable; } return maxc; } void Charger::set_current_drawn_by_vehicle(float l1, float l2, float l3) { - std::scoped_lock lock(state_machine_mutex); - current_drawn_by_vehicle[0] = l1; - current_drawn_by_vehicle[1] = l2; - current_drawn_by_vehicle[2] = l3; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_current_drawn_by_vehicle"); + shared_context.current_drawn_by_vehicle[0] = l1; + shared_context.current_drawn_by_vehicle[1] = l2; + shared_context.current_drawn_by_vehicle[2] = l3; } void Charger::check_soft_over_current() { @@ -1209,27 +1233,29 @@ void Charger::check_soft_over_current() { float limit = (get_max_current_internal() + soft_over_current_measurement_noise_A) * (1. + soft_over_current_tolerance_percent / 100.); - if (current_drawn_by_vehicle[0] > limit or current_drawn_by_vehicle[1] > limit or - current_drawn_by_vehicle[2] > limit) { - if (not over_current) { - over_current = true; + if (shared_context.current_drawn_by_vehicle[0] > limit or shared_context.current_drawn_by_vehicle[1] > limit or + shared_context.current_drawn_by_vehicle[2] > limit) { + if (not internal_context.over_current) { + internal_context.over_current = true; // timestamp when over current happend first - last_over_current_event = date::utc_clock::now(); + internal_context.last_over_current_event = std::chrono::steady_clock::now(); session_log.evse(false, fmt::format("Soft overcurrent event (L1:{}, L2:{}, L3:{}, limit {}), starting timer.", - current_drawn_by_vehicle[0], current_drawn_by_vehicle[1], - current_drawn_by_vehicle[2], limit)); + shared_context.current_drawn_by_vehicle[0], + shared_context.current_drawn_by_vehicle[1], + shared_context.current_drawn_by_vehicle[2], limit)); } } else { - over_current = false; + internal_context.over_current = false; } - auto now = date::utc_clock::now(); + auto now = std::chrono::steady_clock::now(); auto timeSinceOverCurrentStarted = - std::chrono::duration_cast(now - last_over_current_event).count(); - if (over_current and timeSinceOverCurrentStarted >= soft_over_current_timeout) { + std::chrono::duration_cast(now - internal_context.last_over_current_event).count(); + if (internal_context.over_current and timeSinceOverCurrentStarted >= SOFT_OVER_CURRENT_TIMEOUT) { auto errstr = - fmt::format("Soft overcurrent event (L1:{}, L2:{}, L3:{}, limit {}) triggered", current_drawn_by_vehicle[0], - current_drawn_by_vehicle[1], current_drawn_by_vehicle[2], limit); + fmt::format("Soft overcurrent event (L1:{}, L2:{}, L3:{}, limit {}) triggered", + shared_context.current_drawn_by_vehicle[0], shared_context.current_drawn_by_vehicle[1], + shared_context.current_drawn_by_vehicle[2], limit); session_log.evse(false, errstr); // raise the OC error error_handling->raise_overcurrent_error(errstr); @@ -1239,80 +1265,76 @@ void Charger::check_soft_over_current() { // returns whether power is actually available from EnergyManager // i.e. max_current is in valid range bool Charger::power_available() { - if (max_current_valid_until < date::utc_clock::now()) { + if (shared_context.max_current_valid_until < date::utc_clock::now()) { EVLOG_warning << "Power budget expired, falling back to 0."; - max_current = 0.; - signal_max_current(max_current); + shared_context.max_current = 0.; + signal_max_current(shared_context.max_current); } return (get_max_current_internal() > 5.9); } void Charger::request_error_sequence() { - std::scoped_lock lock(state_machine_mutex); - if (current_state == EvseState::WaitingForAuthentication or current_state == EvseState::PrepareCharging) { - t_step_EF_return_state = current_state; - current_state = EvseState::T_step_EF; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: request_error_sequence"); + if (shared_context.current_state == EvseState::WaitingForAuthentication or + shared_context.current_state == EvseState::PrepareCharging) { + internal_context.t_step_EF_return_state = shared_context.current_state; + shared_context.current_state = EvseState::T_step_EF; signal_slac_reset(); if (hlc_use_5percent_current_session) { - t_step_EF_return_pwm = PWM_5_PERCENT; + internal_context.t_step_EF_return_pwm = PWM_5_PERCENT; } else { - t_step_EF_return_pwm = 0.; + internal_context.t_step_EF_return_pwm = 0.; } } } void Charger::set_matching_started(bool m) { - std::scoped_lock lock(state_machine_mutex); - matching_started = m; -} - -bool Charger::get_matching_started() { - std::scoped_lock lock(state_machine_mutex); - return matching_started; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_matching_started"); + shared_context.matching_started = m; } void Charger::notify_currentdemand_started() { - std::scoped_lock lock(state_machine_mutex); - if (current_state == EvseState::PrepareCharging) { - signal_event(types::evse_manager::SessionEventEnum::ChargingStarted); - current_state = EvseState::Charging; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: notify_currentdemand_started"); + if (shared_context.current_state == EvseState::PrepareCharging) { + signal_simple_event(types::evse_manager::SessionEventEnum::ChargingStarted); + shared_context.current_state = EvseState::Charging; } } void Charger::inform_new_evse_max_hlc_limits( const types::iso15118_charger::DC_EVSEMaximumLimits& _currentEvseMaxLimits) { - std::scoped_lock lock(state_machine_mutex); - current_evse_max_limits = _currentEvseMaxLimits; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: inform_new_evse_max_hlc_limits"); + shared_context.current_evse_max_limits = _currentEvseMaxLimits; } types::iso15118_charger::DC_EVSEMaximumLimits Charger::get_evse_max_hlc_limits() { - std::scoped_lock lock(state_machine_mutex); - return current_evse_max_limits; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_evse_max_hlc_limits"); + return shared_context.current_evse_max_limits; } // HLC stack signalled a pause request for the lower layers. void Charger::dlink_pause() { - std::scoped_lock lock(state_machine_mutex); - hlc_allow_close_contactor = false; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: dlink_pause"); + shared_context.hlc_allow_close_contactor = false; pwm_off(); - hlc_charging_terminate_pause = HlcTerminatePause::Pause; + shared_context.hlc_charging_terminate_pause = HlcTerminatePause::Pause; } // HLC requested end of charging session, so we can stop the 5% PWM void Charger::dlink_terminate() { - std::scoped_lock lock(state_machine_mutex); - hlc_allow_close_contactor = false; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: dlink_terminate"); + shared_context.hlc_allow_close_contactor = false; pwm_off(); - hlc_charging_terminate_pause = HlcTerminatePause::Terminate; + shared_context.hlc_charging_terminate_pause = HlcTerminatePause::Terminate; } void Charger::dlink_error() { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: dlink_error"); - hlc_allow_close_contactor = false; + shared_context.hlc_allow_close_contactor = false; // Is PWM on at the moment? - if (not pwm_running) { + if (not shared_context.pwm_running) { // [V2G3-M07-04]: With receiving a D-LINK_ERROR.request from HLE in X1 state, the EVSE’s communication node // shall perform a state X1 to state E/F to state X1 or X2 transition. } else { @@ -1331,18 +1353,18 @@ void Charger::dlink_error() { // Do t_step_X1 with a t_step_EF afterwards // [V2G3-M07-08] The state E/F shall be applied at least T_step_EF: This is already handled in the // t_step_EF state. - t_step_X1_return_state = EvseState::T_step_EF; - t_step_X1_return_pwm = 0.; - current_state = EvseState::T_step_X1; + internal_context.t_step_X1_return_state = EvseState::T_step_EF; + internal_context.t_step_X1_return_pwm = 0.; + shared_context.current_state = EvseState::T_step_X1; // After returning from T_step_EF, go to Waiting for Auth (We are restarting the session) - t_step_EF_return_state = EvseState::WaitingForAuthentication; + internal_context.t_step_EF_return_state = EvseState::WaitingForAuthentication; // [V2G3-M07-09] After applying state E/F, the EVSE shall switch to contol pilot state X1 or X2 as soon // as the EVSE is ready control for pilot incoming duty matching cycle requests: This is already handled // in the Auth step. // [V2G3-M07-05] says we need to go through X1 at the end of the sequence - t_step_EF_return_pwm = 0.; + internal_context.t_step_EF_return_pwm = 0.; } // else { // [V2G3-M07-10] Gives us two options for nominal PWM mode and HLC in case of error: We choose [V2G3-M07-12] @@ -1353,59 +1375,59 @@ void Charger::dlink_error() { } void Charger::set_hlc_charging_active() { - std::scoped_lock lock(state_machine_mutex); - hlc_charging_active = true; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_hlc_charging_active"); + shared_context.hlc_charging_active = true; } void Charger::set_hlc_allow_close_contactor(bool on) { - std::scoped_lock lock(state_machine_mutex); - hlc_allow_close_contactor = on; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_hlc_allow_close_contactor"); + shared_context.hlc_allow_close_contactor = on; } void Charger::set_hlc_error() { - std::scoped_lock lock(state_machine_mutex); - error_prevent_charging_flag = true; + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_hlc_error"); + shared_context.error_prevent_charging_flag = true; } // this resets the BCB sequence (which may contain 1-3 toggle pulses) void Charger::bcb_toggle_reset() { - hlc_ev_pause_bcb_count = 0; - hlc_bcb_sequence_started = false; + internal_context.hlc_ev_pause_bcb_count = 0; + internal_context.hlc_bcb_sequence_started = false; } // call this B->C transitions void Charger::bcb_toggle_detect_start_pulse() { // For HLC charging, PWM already off: This is probably a BCB Toggle to wake us up from sleep mode. // Remember start of BCB toggle. - hlc_ev_pause_start_of_bcb = std::chrono::steady_clock::now(); - if (hlc_ev_pause_bcb_count == 0) { + internal_context.hlc_ev_pause_start_of_bcb = std::chrono::steady_clock::now(); + if (internal_context.hlc_ev_pause_bcb_count == 0) { // remember sequence start - hlc_ev_pause_start_of_bcb_sequence = std::chrono::steady_clock::now(); - hlc_bcb_sequence_started = true; + internal_context.hlc_ev_pause_start_of_bcb_sequence = std::chrono::steady_clock::now(); + internal_context.hlc_bcb_sequence_started = true; } } // call this on C->B transitions void Charger::bcb_toggle_detect_stop_pulse() { - if (not hlc_bcb_sequence_started) { + if (not internal_context.hlc_bcb_sequence_started) { return; } // This is probably and end of BCB toggle, verify it was not too long or too short - auto pulse_length = std::chrono::steady_clock::now() - hlc_ev_pause_start_of_bcb; + auto pulse_length = std::chrono::steady_clock::now() - internal_context.hlc_ev_pause_start_of_bcb; if (pulse_length > TP_EV_VALD_STATE_DURATION_MIN and pulse_length < TP_EV_VALD_STATE_DURATION_MAX) { // enable PWM again. ISO stack should have been ready for the whole time. // FIXME where do we go from here? Auth? - hlc_ev_pause_bcb_count++; + internal_context.hlc_ev_pause_bcb_count++; session_log.car(false, fmt::format("BCB toggle ({} ms), #{} in sequence", std::chrono::duration_cast(pulse_length).count(), - hlc_ev_pause_bcb_count)); + internal_context.hlc_ev_pause_bcb_count)); } else { - hlc_ev_pause_bcb_count = 0; + internal_context.hlc_ev_pause_bcb_count = 0; EVLOG_warning << "BCB toggle with invalid duration detected: " << std::chrono::duration_cast(pulse_length).count(); } @@ -1414,27 +1436,23 @@ void Charger::bcb_toggle_detect_stop_pulse() { // Query if a BCB sequence (of 1-3 pulses) was detected and is finished. If that is true, PWM can be enabled again // etc bool Charger::bcb_toggle_detected() { - auto sequence_length = std::chrono::steady_clock::now() - hlc_ev_pause_start_of_bcb_sequence; - if (hlc_bcb_sequence_started and (sequence_length > TT_EVSE_VALD_TOGGLE or hlc_ev_pause_bcb_count >= 3)) { + auto sequence_length = std::chrono::steady_clock::now() - internal_context.hlc_ev_pause_start_of_bcb_sequence; + if (internal_context.hlc_bcb_sequence_started and + (sequence_length > TT_EVSE_VALD_TOGGLE or internal_context.hlc_ev_pause_bcb_count >= 3)) { // no need to wait for further BCB toggles - hlc_ev_pause_bcb_count = 0; + internal_context.hlc_ev_pause_bcb_count = 0; return true; } return false; } -void Charger::set_rcd_error() { - std::scoped_lock lock(state_machine_mutex); - error_prevent_charging_flag = true; -} - bool Charger::errors_prevent_charging() { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: errors_prevent_charging"); return errors_prevent_charging_internal(); } bool Charger::errors_prevent_charging_internal() { - if (error_prevent_charging_flag) { + if (shared_context.error_prevent_charging_flag) { graceful_stop_charging(); return true; } @@ -1442,13 +1460,12 @@ bool Charger::errors_prevent_charging_internal() { } void Charger::graceful_stop_charging() { - - if (pwm_running) { + if (shared_context.pwm_running) { pwm_off(); } // Shutdown DC power supplies - if (charge_mode == ChargeMode::DC) { + if (config_context.charge_mode == ChargeMode::DC) { signal_dc_supply_off(); } diff --git a/modules/EvseManager/Charger.hpp b/modules/EvseManager/Charger.hpp index a2133bccb..b5c137586 100644 --- a/modules/EvseManager/Charger.hpp +++ b/modules/EvseManager/Charger.hpp @@ -38,6 +38,7 @@ #include "ErrorHandling.hpp" #include "IECStateMachine.hpp" +#include "scoped_lock_timeout.hpp" namespace module { @@ -97,7 +98,6 @@ class Charger { void set_faulted(); void set_hlc_error(); - void set_rcd_error(); // Public interface during charging // @@ -107,8 +107,6 @@ class Charger { // call when in state WaitingForAuthentication void authorize(bool a, const types::authorization::ProvidedIdToken& token); bool deauthorize(); - types::authorization::ProvidedIdToken get_id_token(); - std::optional get_stop_transaction_id_token(); bool get_authorized_pnc(); bool get_authorized_eim(); @@ -127,8 +125,6 @@ class Charger { bool cancel_transaction(const types::evse_manager::StopTransactionRequest& request); // cancel transaction ahead of time when car is still plugged - types::evse_manager::StopTransactionReason get_transaction_finished_reason(); // get reason for last finished event - types::evse_manager::StartSessionReason get_session_started_reason(); // get reason for last session start event // execute a virtual replug sequence. Does NOT generate a Car plugged in event etc, // since the session is not restarted. It can be used to e.g. restart the ISO session @@ -138,7 +134,11 @@ class Charger { void set_current_drawn_by_vehicle(float l1, float l2, float l3); // Signal for EvseEvents - sigslot::signal signal_event; + sigslot::signal signal_simple_event; + sigslot::signal signal_session_started_event; + sigslot::signal signal_transaction_started_event; + sigslot::signal> + signal_transaction_finished_event; sigslot::signal<> signal_ac_with_soc_timeout; @@ -155,7 +155,6 @@ class Charger { void request_error_sequence(); void set_matching_started(bool m); - bool get_matching_started(); void notify_currentdemand_started(); @@ -187,18 +186,25 @@ class Charger { void bcb_toggle_detect_stop_pulse(); bool bcb_toggle_detected(); - // main Charger thread - Everest::Thread main_thread_handle; + void clear_errors_on_unplug(); - const std::unique_ptr& bsp; - const std::unique_ptr& error_handling; - const types::evse_board_support::Connector_type& connector_type; + void update_pwm_now(float dc); + void update_pwm_now_if_changed(float dc); + void update_pwm_max_every_5seconds(float dc); + void pwm_off(); + void pwm_F(); + + void process_cp_events_independent(CPEvent cp_event); + void process_cp_events_state(CPEvent cp_event); + void run_state_machine(); void main_thread(); - float max_current; - std::chrono::time_point max_current_valid_until; - float max_current_cable{0.}; + void graceful_stop_charging(); + + float ampere_to_duty_cycle(float ampere); + + void check_soft_over_current(); bool power_available(); @@ -208,124 +214,112 @@ class Charger { void start_transaction(); void stop_transaction(); - bool transaction_active; - bool session_active; - types::evse_manager::StopTransactionReason last_stop_transaction_reason; - types::evse_manager::StartSessionReason last_start_session_reason; - // This mutex locks all variables related to the state machine - std::recursive_mutex state_machine_mutex; - - EvseState current_state; - EvseState last_state; - EvseState last_state_detect_state_change; - - std::chrono::system_clock::time_point current_state_started; - - bool connector_enabled; - - bool error_prevent_charging_flag{false}; - bool last_error_prevent_charging_flag{false}; - void graceful_stop_charging(); - - float ampere_to_duty_cycle(float ampere); - - void check_soft_over_current(); - float current_drawn_by_vehicle[3]; - bool over_current; - std::chrono::time_point last_over_current_event; - const int soft_over_current_timeout = 7000; - - // 4 seconds according to table 3 of ISO15118-3 - const int t_step_EF = 4000; - EvseState t_step_EF_return_state; - float t_step_EF_return_pwm; - - // 3 seconds according to IEC61851-1 - const int t_step_X1 = 3000; - EvseState t_step_X1_return_state; - float t_step_X1_return_pwm; - - static constexpr float PWM_5_PERCENT = 0.05; - static constexpr int T_REPLUG_MS = 4000; - - bool matching_started; - - void process_cp_events_independent(CPEvent cp_event); - void process_cp_events_state(CPEvent cp_event); - void run_state_machine(); - - bool authorized; - // set to true if auth is from PnC, otherwise to false (EIM) - bool authorized_pnc; - - types::authorization::ProvidedIdToken id_token; - std::optional - stop_transaction_id_token; // only set in case transaction was stopped locally - - // AC or DC - ChargeMode charge_mode{0}; - // Config option - bool ac_hlc_enabled; - // HLC enabled in current AC session. This can change during the session if e.g. HLC fails. - bool ac_hlc_enabled_current_session; - // Config option - bool ac_hlc_use_5percent; + Everest::timed_mutex_traceable state_machine_mutex; + + // used by different threads, complete main loop must be locked for write access + struct SharedContext { + // As per IEC61851-1 A.5.3 + bool legacy_wakeup_done{false}; + bool hlc_allow_close_contactor{false}; + bool iec_allow_close_contactor{false}; + bool hlc_charging_active{false}; + HlcTerminatePause hlc_charging_terminate_pause; + types::iso15118_charger::DC_EVSEMaximumLimits current_evse_max_limits; + bool pwm_running{false}; + std::optional + stop_transaction_id_token; // only set in case transaction was stopped locally + types::authorization::ProvidedIdToken id_token; + bool authorized; + // set to true if auth is from PnC, otherwise to false (EIM) + bool authorized_pnc; + bool matching_started; + float max_current; + std::chrono::time_point max_current_valid_until; + float max_current_cable{0.}; + bool transaction_active; + bool session_active; + bool connector_enabled; + EvseState current_state; + types::evse_manager::StopTransactionReason last_stop_transaction_reason; + types::evse_manager::StartSessionReason last_start_session_reason; + float current_drawn_by_vehicle[3]; + bool error_prevent_charging_flag{false}; + int ac_with_soc_timer; + // non standard compliant option: time out after a while and switch back to DC to get SoC update + bool ac_with_soc_timeout; + } shared_context; + + struct ConfigContext { + // non standard compliant option to enforce HLC in AC mode + bool ac_enforce_hlc; + // Config option to use 5 percent PWM in HLC AC mode + bool ac_hlc_use_5percent; + // Config option to enable HLC in AC mode + bool ac_hlc_enabled; + // AC or DC + ChargeMode charge_mode{0}; + } config_context; + + // Used by different threads, but requires no complete state machine locking + std::atomic_bool contactors_closed{false}; + std::atomic soft_over_current_tolerance_percent{10.}; + std::atomic soft_over_current_measurement_noise_A{0.5}; // HLC uses 5 percent signalling. Used both for AC and DC modes. - bool hlc_use_5percent_current_session; - // non standard compliant option to enforce HLC in AC mode - bool ac_enforce_hlc; - // non standard compliant option: time out after a while and switch back to DC to get SoC update - bool ac_with_soc_timeout; - int ac_with_soc_timer; - - std::chrono::time_point last_pwm_update; - - float update_pwm_last_dc; - void update_pwm_now(float dc); - void update_pwm_now_if_changed(float dc); - void update_pwm_max_every_5seconds(float dc); - void pwm_off(); - void pwm_F(); - bool pwm_running{false}; - - types::iso15118_charger::DC_EVSEMaximumLimits current_evse_max_limits; - - static constexpr auto SLEEP_BEFORE_ENABLING_PWM_HLC_MODE = std::chrono::seconds(1); - static constexpr auto MAINLOOP_UPDATE_RATE = std::chrono::milliseconds(100); + std::atomic_bool hlc_use_5percent_current_session; + // HLC enabled in current AC session. This can change during the session if e.g. HLC fails. + std::atomic_bool ac_hlc_enabled_current_session; + + // This struct is only used from main loop thread + struct InternalContext { + bool hlc_bcb_sequence_started{false}; + int hlc_ev_pause_bcb_count{0}; + std::chrono::time_point hlc_ev_pause_start_of_bcb; + std::chrono::time_point hlc_ev_pause_start_of_bcb_sequence; + float update_pwm_last_dc; + std::chrono::time_point last_pwm_update; + + EvseState t_step_EF_return_state; + float t_step_EF_return_pwm; + + EvseState t_step_X1_return_state; + float t_step_X1_return_pwm; + std::chrono::time_point last_over_current_event; + bool over_current{false}; + + bool last_error_prevent_charging_flag{false}; + std::chrono::system_clock::time_point current_state_started; + EvseState last_state_detect_state_change; + EvseState last_state; + } internal_context; - float soft_over_current_tolerance_percent{10.}; - float soft_over_current_measurement_noise_A{0.5}; + // main Charger thread + Everest::Thread main_thread_handle; - HlcTerminatePause hlc_charging_terminate_pause; - bool hlc_charging_active{false}; - bool hlc_allow_close_contactor{false}; - bool iec_allow_close_contactor{false}; + const std::unique_ptr& bsp; + const std::unique_ptr& error_handling; + const types::evse_board_support::Connector_type& connector_type; + // constants + static constexpr float CHARGER_ABSOLUTE_MAX_CURRENT{80.}; + constexpr static int LEGACY_WAKEUP_TIMEOUT{30000}; // valid Length of BCB toggles static constexpr auto TP_EV_VALD_STATE_DURATION_MIN = std::chrono::milliseconds(200 - 50); // We give 50 msecs tolerance to the norm values (table 3 ISO15118-3) static constexpr auto TP_EV_VALD_STATE_DURATION_MAX = std::chrono::milliseconds(400 + 50); // We give 50 msecs tolerance to the norm values (table 3 ISO15118-3) - // Maximum duration of a BCB toggle sequence of 1-3 BCB toggles static constexpr auto TT_EVSE_VALD_TOGGLE = std::chrono::milliseconds(3500 + 200); // We give 200 msecs tolerance to the norm values (table 3 ISO15118-3) - - std::chrono::time_point hlc_ev_pause_start_of_bcb; - std::chrono::time_point hlc_ev_pause_start_of_bcb_sequence; - int hlc_ev_pause_bcb_count{0}; - bool hlc_bcb_sequence_started{false}; - - bool contactors_closed{false}; - - // As per IEC61851-1 A.5.3 - bool legacy_wakeup_done{false}; - constexpr static int legacy_wakeup_timeout{30000}; - - void clear_errors_on_unplug(); - - static constexpr float CHARGER_ABSOLUTE_MAX_CURRENT{80.}; + static constexpr auto SLEEP_BEFORE_ENABLING_PWM_HLC_MODE = std::chrono::seconds(1); + static constexpr auto MAINLOOP_UPDATE_RATE = std::chrono::milliseconds(100); + static constexpr float PWM_5_PERCENT = 0.05; + static constexpr int T_REPLUG_MS = 4000; + // 3 seconds according to IEC61851-1 + static constexpr int T_STEP_X1 = 3000; + // 4 seconds according to table 3 of ISO15118-3 + static constexpr int T_STEP_EF = 4000; + static constexpr int SOFT_OVER_CURRENT_TIMEOUT = 7000; }; } // namespace module diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index 50c60f65b..21aa09ea9 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -8,6 +8,7 @@ #include "IECStateMachine.hpp" #include "SessionLog.hpp" #include "Timeout.hpp" +#include "scoped_lock_timeout.hpp" using namespace std::literals::chrono_literals; namespace module { @@ -251,7 +252,8 @@ void EvseManager::ready() { { // dont publish ev_info here, it will be published when other values change. // otherwise we will create too much traffic on mqtt - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, + "EvseManager.cpp: set ev_info present_voltage/current"); ev_info.present_voltage = present_values.EVSEPresentVoltage; ev_info.present_current = present_values.EVSEPresentCurrent; // p_evse->publish_ev_info(ev_info); @@ -281,7 +283,7 @@ void EvseManager::ready() { } { - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: publish_ev_info"); ev_info.target_voltage = latest_target_voltage; ev_info.target_current = latest_target_current; p_evse->publish_ev_info(ev_info); @@ -307,7 +309,7 @@ void EvseManager::ready() { r_hlc[0]->subscribe_currentDemand_Finished([this] { powersupply_DC_off(); }); r_hlc[0]->subscribe_DC_EVMaximumLimits([this](types::iso15118_charger::DC_EVMaximumLimits l) { - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_EVMaximumLimits"); ev_info.maximum_current_limit = l.DC_EVMaximumCurrentLimit; ev_info.maximum_power_limit = l.DC_EVMaximumPowerLimit; ev_info.maximum_voltage_limit = l.DC_EVMaximumVoltageLimit; @@ -315,70 +317,70 @@ void EvseManager::ready() { }); r_hlc[0]->subscribe_DepartureTime([this](const std::string& t) { - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DepartureTime"); ev_info.departure_time = t; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_AC_EAmount([this](double e) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_AC_EAmount"); ev_info.remaining_energy_needed = e; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_AC_EVMaxVoltage([this](double v) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_AC_EVMaxVoltage"); ev_info.maximum_voltage_limit = v; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_AC_EVMaxCurrent([this](double c) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_AC_EVMaxCurrent"); ev_info.maximum_current_limit = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_AC_EVMinCurrent([this](double c) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_AC_EVMinCurrent"); ev_info.minimum_current_limit = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_EVEnergyCapacity([this](double c) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_EVEnergyCapacity"); ev_info.battery_capacity = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_EVEnergyRequest([this](double c) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_EVEnergyCapacity"); ev_info.remaining_energy_needed = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_FullSOC([this](double c) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_FullSOC"); ev_info.battery_full_soc = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_BulkSOC([this](double c) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_BulkSOC"); ev_info.battery_bulk_soc = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_EVRemainingTime([this](types::iso15118_charger::DC_EVRemainingTime t) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_EVRemainingTime"); ev_info.estimated_time_full = t.EV_RemainingTimeToFullSoC; ev_info.estimated_time_bulk = t.EV_RemainingTimeToBulkSoC; p_evse->publish_ev_info(ev_info); @@ -386,7 +388,7 @@ void EvseManager::ready() { r_hlc[0]->subscribe_DC_EVStatus([this](types::iso15118_charger::DC_EVStatusType s) { // FIXME send only on change / throttle messages - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp subscribe_DC_EVStatus"); ev_info.soc = s.DC_EVRESSSOC; p_evse->publish_ev_info(ev_info); }); @@ -440,7 +442,7 @@ void EvseManager::ready() { if ((config.dbg_hlc_auth_after_tstep and charger->get_authorized_eim_ready_for_hlc()) or (not config.dbg_hlc_auth_after_tstep and charger->get_authorized_eim())) { { - std::scoped_lock lock(hlc_mutex); + Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: subscribe_Require_Auth_EIM"); hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = false; } @@ -448,7 +450,7 @@ void EvseManager::ready() { types::authorization::CertificateStatus::NoCertificateAvailable); } else { p_token_provider->publish_provided_token(autocharge_token); - std::scoped_lock lock(hlc_mutex); + Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: publish_provided_token"); hlc_waiting_for_auth_eim = true; hlc_waiting_for_auth_pnc = false; } @@ -461,7 +463,7 @@ void EvseManager::ready() { p_evse->publish_car_manufacturer(car_manufacturer); { - std::scoped_lock lock(ev_info_mutex); + Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_EVCCIDD"); ev_info.evcc_id = token; p_evse->publish_ev_info(ev_info); } @@ -476,12 +478,12 @@ void EvseManager::ready() { p_token_provider->publish_provided_token(_token); if (charger->get_authorized_pnc()) { { - std::scoped_lock lock(hlc_mutex); + Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: subscribe_Require_Auth_PnC 1"); hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = false; } } else { - std::scoped_lock lock(hlc_mutex); + Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: subscribe_Require_Auth_PnC 2"); hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = true; } @@ -584,7 +586,7 @@ void EvseManager::ready() { latest_target_voltage = 0; latest_target_current = 0; { - std::scoped_lock lock(hlc_mutex); + Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: bsp->signal_event.connect"); hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = false; } @@ -602,12 +604,6 @@ void EvseManager::ready() { r_hlc[0]->call_ac_contactor_closed(false); } } - - if (config.ac_with_soc) - charger->signal_ac_with_soc_timeout.connect([this]() { - EVLOG_info << "AC with SoC timeout"; - switch_DC_mode(); - }); }); r_bsp->subscribe_ac_nr_of_phases_available([this](int n) { signalNrOfPhasesAvailable(n); }); @@ -627,7 +623,7 @@ void EvseManager::ready() { // Store local cache { - std::scoped_lock lock(power_mutex); + Everest::scoped_lock_timeout lock(power_mutex, "EvseManager.cpp: subscribe_powermeter"); latest_powermeter_data_billing = p; } @@ -685,14 +681,13 @@ void EvseManager::ready() { } }); - charger->signal_event.connect([this](types::evse_manager::SessionEventEnum s) { + charger->signal_simple_event.connect([this](types::evse_manager::SessionEventEnum s) { // Cancel reservations if charger is disabled or faulted if (s == types::evse_manager::SessionEventEnum::Disabled or s == types::evse_manager::SessionEventEnum::PermanentFault) { cancel_reservation(true); } - if (s == types::evse_manager::SessionEventEnum::SessionStarted or - s == types::evse_manager::SessionEventEnum::SessionFinished) { + if (s == types::evse_manager::SessionEventEnum::SessionFinished) { // Reset EV information on Session start and end ev_info = types::evse_manager::EVInfo(); p_evse->publish_ev_info(ev_info); @@ -700,13 +695,7 @@ void EvseManager::ready() { std::vector payment_options; - if (get_hlc_enabled() and s == types::evse_manager::SessionEventEnum::SessionStarted and - charger->get_session_started_reason() == types::evse_manager::StartSessionReason::Authorized) { - - payment_options.push_back(types::iso15118_charger::PaymentOption::ExternalPayment); - r_hlc[0]->call_session_setup(payment_options, false); - - } else if (get_hlc_enabled() and s == types::evse_manager::SessionEventEnum::SessionFinished) { + if (get_hlc_enabled() and s == types::evse_manager::SessionEventEnum::SessionFinished) { if (config.payment_enable_eim) { payment_options.push_back(types::iso15118_charger::PaymentOption::ExternalPayment); } @@ -717,6 +706,19 @@ void EvseManager::ready() { } }); + charger->signal_session_started_event.connect([this](types::evse_manager::StartSessionReason start_reason) { + // Reset EV information on Session start and end + ev_info = types::evse_manager::EVInfo(); + p_evse->publish_ev_info(ev_info); + + std::vector payment_options; + + if (get_hlc_enabled() and start_reason == types::evse_manager::StartSessionReason::Authorized) { + payment_options.push_back(types::iso15118_charger::PaymentOption::ExternalPayment); + r_hlc[0]->call_session_setup(payment_options, false); + } + }); + invoke_ready(*p_evse); invoke_ready(*p_energy_grid); invoke_ready(*p_token_provider); @@ -852,7 +854,7 @@ void EvseManager::ready_to_start_charging() { } types::powermeter::Powermeter EvseManager::get_latest_powermeter_data_billing() { - std::scoped_lock lock(power_mutex); + Everest::scoped_lock_timeout lock(power_mutex, "EvseManager.cpp: get_latest_powermeter_data_billing"); return latest_powermeter_data_billing; } @@ -861,7 +863,7 @@ types::evse_board_support::HardwareCapabilities EvseManager::get_hw_capabilities } int32_t EvseManager::get_reservation_id() { - std::lock_guard lock(reservation_mutex); + Everest::scoped_lock_timeout lock(reservation_mutex, "EvseManager.cpp: get_reservation_id"); return reservation_id; } @@ -1071,7 +1073,7 @@ bool EvseManager::reserve(int32_t id) { return false; } - std::lock_guard lock(reservation_mutex); + Everest::scoped_lock_timeout lock(reservation_mutex, "EvseManager.cpp: reserve"); if (not reserved) { reserved = true; @@ -1090,7 +1092,7 @@ bool EvseManager::reserve(int32_t id) { void EvseManager::cancel_reservation(bool signal_event) { - std::lock_guard lock(reservation_mutex); + Everest::scoped_lock_timeout lock(reservation_mutex, "EvseManager.cpp: cancel_reservation"); if (reserved) { reserved = false; reservation_id = 0; @@ -1105,7 +1107,7 @@ void EvseManager::cancel_reservation(bool signal_event) { } bool EvseManager::is_reserved() { - std::lock_guard lock(reservation_mutex); + Everest::scoped_lock_timeout lock(reservation_mutex, "EvseManager.cpp: is_reserved"); return reserved; } @@ -1114,12 +1116,12 @@ bool EvseManager::getLocalThreePhases() { } bool EvseManager::get_hlc_enabled() { - std::lock_guard lock(hlc_mutex); + Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: get_hlc_enabled"); return hlc_enabled; } bool EvseManager::get_hlc_waiting_for_auth_pnc() { - std::lock_guard lock(hlc_mutex); + Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: get_hlc_waiting_for_auth_pnc"); return hlc_waiting_for_auth_pnc; } @@ -1146,7 +1148,7 @@ void EvseManager::log_v2g_message(Object m) { void EvseManager::charger_was_authorized() { - std::scoped_lock lock(hlc_mutex); + Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: charger_was_authorized"); if (hlc_waiting_for_auth_pnc and charger->get_authorized_pnc()) { r_hlc[0]->call_authorization_response(types::authorization::AuthorizationStatus::Accepted, types::authorization::CertificateStatus::Accepted); @@ -1472,7 +1474,7 @@ void EvseManager::fail_session() { } types::evse_manager::EVInfo EvseManager::get_ev_info() { - std::scoped_lock l(ev_info_mutex); + Everest::scoped_lock_timeout l(ev_info_mutex, "EvseManager.cpp: get_ev_info"); return ev_info; } diff --git a/modules/EvseManager/EvseManager.hpp b/modules/EvseManager/EvseManager.hpp index c847078c7..4ebecbdd8 100644 --- a/modules/EvseManager/EvseManager.hpp +++ b/modules/EvseManager/EvseManager.hpp @@ -42,6 +42,7 @@ #include "ErrorHandling.hpp" #include "SessionLog.hpp" #include "VarContainer.hpp" +#include "scoped_lock_timeout.hpp" // ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 namespace module { @@ -190,7 +191,7 @@ class EvseManager : public Everest::ModuleBase { // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 // insert your private definitions here - std::mutex power_mutex; + Everest::timed_mutex_traceable power_mutex; types::powermeter::Powermeter latest_powermeter_data_billing; Everest::Thread energyThreadHandle; @@ -202,7 +203,7 @@ class EvseManager : public Everest::ModuleBase { std::atomic_bool contactor_open{true}; - std::mutex hlc_mutex; + Everest::timed_mutex_traceable hlc_mutex; bool hlc_enabled; @@ -222,7 +223,7 @@ class EvseManager : public Everest::ModuleBase { // Reservations bool reserved; int32_t reservation_id; - std::mutex reservation_mutex; + Everest::timed_mutex_traceable reservation_mutex; void setup_AC_mode(); void setup_fake_DC_mode(); @@ -242,7 +243,7 @@ class EvseManager : public Everest::ModuleBase { bool wait_powersupply_DC_below_voltage(double target_voltage); // EV information - std::mutex ev_info_mutex; + Everest::timed_mutex_traceable ev_info_mutex; types::evse_manager::EVInfo ev_info; types::evse_manager::CarManufacturer car_manufacturer{types::evse_manager::CarManufacturer::Unknown}; diff --git a/modules/EvseManager/IECStateMachine.cpp b/modules/EvseManager/IECStateMachine.cpp index 24010f71e..7caca01e9 100644 --- a/modules/EvseManager/IECStateMachine.cpp +++ b/modules/EvseManager/IECStateMachine.cpp @@ -89,7 +89,8 @@ void IECStateMachine::process_bsp_event(const types::board_support_common::BspEv std::visit(overloaded{[this](RawCPState& raw_state) { // If it is a raw CP state, run it through the state machine { - std::lock_guard l(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, + "IECStateMachine::process_bsp_event"); cp_state = raw_state; } feed_state_machine(); @@ -110,13 +111,16 @@ void IECStateMachine::process_bsp_event(const types::board_support_common::BspEv } void IECStateMachine::feed_state_machine() { - auto events = state_machine(); + std::thread feed([this]() { + auto events = state_machine(); - // Process all events - while (not events.empty()) { - signal_event(events.front()); - events.pop(); - } + // Process all events + while (not events.empty()) { + signal_event(events.front()); + events.pop(); + } + }); + feed.detach(); } // Main IEC state machine. Needs to be called whenever: @@ -126,7 +130,7 @@ void IECStateMachine::feed_state_machine() { std::queue IECStateMachine::state_machine() { std::queue events; - std::lock_guard lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::state_machine"); switch (cp_state) { @@ -282,7 +286,7 @@ std::queue IECStateMachine::state_machine() { // High level state machine sets PWM duty cycle void IECStateMachine::set_pwm(double value) { { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::set_pwm"); if (value > 0 && value < 1) { pwm_running = true; } else { @@ -291,33 +295,36 @@ void IECStateMachine::set_pwm(double value) { } r_bsp->call_pwm_on(value * 100); + feed_state_machine(); } // High level state machine sets state X1 void IECStateMachine::set_pwm_off() { { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::set_pwm_off"); pwm_running = false; } r_bsp->call_pwm_off(); + // Don't run the state machine in the callers context feed_state_machine(); } // High level state machine sets state F void IECStateMachine::set_pwm_F() { { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::set_pwm_F"); pwm_running = false; } r_bsp->call_pwm_F(); + // Don't run the state machine in the callers context feed_state_machine(); } // The higher level state machine in Charger.cpp calls this to indicate it allows contactors to be switched on void IECStateMachine::allow_power_on(bool value, types::evse_board_support::Reason reason) { { - std::scoped_lock lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::allow_power_on"); // Only set the flags here in case of power on. power_on_allowed = value; power_on_reason = reason; @@ -327,6 +334,7 @@ void IECStateMachine::allow_power_on(bool value, types::evse_board_support::Reas } } // The actual power on will be handled in the state machine to verify it is in the correct CP state etc. + // Don't run the state machine in the callers context feed_state_machine(); } diff --git a/modules/EvseManager/IECStateMachine.hpp b/modules/EvseManager/IECStateMachine.hpp index f50a77159..b8c86dda8 100644 --- a/modules/EvseManager/IECStateMachine.hpp +++ b/modules/EvseManager/IECStateMachine.hpp @@ -32,6 +32,8 @@ #include "Timeout.hpp" #include "utils/thread.hpp" +#include "scoped_lock_timeout.hpp" + namespace module { // Abstract events that drive the higher level state machine in Charger.cpp @@ -111,7 +113,7 @@ class IECStateMachine { RawCPState cp_state{RawCPState::Disabled}, last_cp_state{RawCPState::Disabled}; AsyncTimeout timeout_state_c1; - std::mutex state_machine_mutex; + Everest::timed_mutex_traceable state_machine_mutex; void feed_state_machine(); std::queue state_machine(); diff --git a/modules/EvseManager/backtrace.cpp b/modules/EvseManager/backtrace.cpp new file mode 100644 index 000000000..0ce328eef --- /dev/null +++ b/modules/EvseManager/backtrace.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include +#include +#include + +#include "backtrace.hpp" + +/* + Simple backtrace signal handler +*/ +namespace Everest { + +void signal_handler(int signo) { + void* array[10]; + size_t size; + + // get void*'s for all entries on the stack + size = backtrace(array, 10); + + // print out all the frames to stderr + printf("\n---------------------------------------------------------------------------------------------------\n"); + backtrace_symbols_fd(array, size, STDOUT_FILENO); + printf("---------------------------------------------------------------------------------------------------\n\n"); +} + +void install_backtrace_handler() { + struct sigaction bt_handler; + memset(&bt_handler, 0, sizeof(bt_handler)); + + bt_handler.sa_handler = signal_handler; + + if (sigaction(SIGUSR1, &bt_handler, NULL) < 0) { + perror("sigaction"); + } +} + +void request_backtrace(pthread_t id) { + pthread_kill(id, SIGUSR1); +} + +} // namespace Everest \ No newline at end of file diff --git a/modules/EvseManager/backtrace.hpp b/modules/EvseManager/backtrace.hpp new file mode 100644 index 000000000..0cf7b7bef --- /dev/null +++ b/modules/EvseManager/backtrace.hpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef EVEREST_BACKTRACE +#define EVEREST_BACKTRACE + +#include + +/* + Simple backtrace signal handler +*/ +namespace Everest { +void signal_handler(int signo); +void install_backtrace_handler(); +void request_backtrace(pthread_t id); +} // namespace Everest +#endif \ No newline at end of file diff --git a/modules/EvseManager/energy_grid/energyImpl.cpp b/modules/EvseManager/energy_grid/energyImpl.cpp index 8713953e4..744252475 100644 --- a/modules/EvseManager/energy_grid/energyImpl.cpp +++ b/modules/EvseManager/energy_grid/energyImpl.cpp @@ -103,7 +103,8 @@ void energyImpl::ready() { // request energy at the start and end of a charging session mod->charger->signal_state.connect([this](Charger::EvseState s) { if (s == Charger::EvseState::WaitingForAuthentication || s == Charger::EvseState::Finished) { - request_energy_from_energy_manager(); + std::thread request_energy_thread([this]() { request_energy_from_energy_manager(); }); + request_energy_thread.detach(); } }); } diff --git a/modules/EvseManager/evse/evse_managerImpl.cpp b/modules/EvseManager/evse/evse_managerImpl.cpp index ca89fd1ae..8cf1b32de 100644 --- a/modules/EvseManager/evse/evse_managerImpl.cpp +++ b/modules/EvseManager/evse/evse_managerImpl.cpp @@ -154,91 +154,98 @@ void evse_managerImpl::ready() { publish_session_event(j); }); - mod->charger->signal_event.connect([this](const types::evse_manager::SessionEventEnum& e) { - types::evse_manager::SessionEvent se; - - se.event = e; - - if (e == types::evse_manager::SessionEventEnum::SessionStarted) { + mod->charger->signal_session_started_event.connect( + [this](const types::evse_manager::StartSessionReason& start_reason) { + types::evse_manager::SessionEvent se; + se.event = types::evse_manager::SessionEventEnum::SessionStarted; this->mod->selected_protocol = "IEC61851-1"; types::evse_manager::SessionStarted session_started; session_started.timestamp = date::format("%FT%TZ", std::chrono::time_point_cast(date::utc_clock::now())); - auto reason = mod->charger->get_session_started_reason(); - - if (mod->config.disable_authentication && reason == types::evse_manager::StartSessionReason::EVConnected) { - // Free service, authorize immediately - types::authorization::ProvidedIdToken provided_token; - provided_token.authorization_type = types::authorization::AuthorizationType::RFID; - provided_token.id_token = "FREESERVICE"; - provided_token.prevalidated = true; - mod->charger->authorize(true, provided_token); - mod->charger_was_authorized(); + if (mod->config.disable_authentication && + start_reason == types::evse_manager::StartSessionReason::EVConnected) { + + // Free service, authorize immediately in a separate thread (to avoid dead lock with charger state + // machine, this signal handler runs in state machine context) + std::thread authorize_thread([this]() { + types::authorization::ProvidedIdToken provided_token; + provided_token.authorization_type = types::authorization::AuthorizationType::RFID; + provided_token.id_token = "FREESERVICE"; + provided_token.prevalidated = true; + mod->charger->authorize(true, provided_token); + mod->charger_was_authorized(); + }); + authorize_thread.detach(); } - session_started.reason = reason; + session_started.reason = start_reason; set_session_uuid(); session_started.logging_path = session_log.startSession( mod->config.logfile_suffix == "session_uuid" ? session_uuid : mod->config.logfile_suffix); - session_log.evse( - false, fmt::format("Session Started: {}", types::evse_manager::start_session_reason_to_string(reason))); + session_log.evse(false, fmt::format("Session Started: {}", + types::evse_manager::start_session_reason_to_string(start_reason))); mod->telemetry.publish("session", "events", { {"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())}, {"type", "session_started"}, {"session_id", session_uuid}, - {"reason", types::evse_manager::start_session_reason_to_string(reason)}, + {"reason", types::evse_manager::start_session_reason_to_string(start_reason)}, }); se.session_started = session_started; - } else if (e == types::evse_manager::SessionEventEnum::SessionFinished) { - types::evse_manager::SessionFinished session_finished; - session_finished.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now()); - session_log.evse(false, fmt::format("Session Finished")); - session_log.stopSession(); - mod->telemetry.publish("session", "events", - {{"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())}, - {"type", "session_finished"}, - {"session_id", session_uuid}}); - se.session_finished = session_finished; - } else if (e == types::evse_manager::SessionEventEnum::TransactionStarted) { - types::evse_manager::TransactionStarted transaction_started; - transaction_started.timestamp = - date::format("%FT%TZ", std::chrono::time_point_cast(date::utc_clock::now())); + se.uuid = session_uuid; + publish_session_event(se); + }); - transaction_started.meter_value = mod->get_latest_powermeter_data_billing(); - if (mod->is_reserved()) { - transaction_started.reservation_id.emplace(mod->get_reservation_id()); - mod->cancel_reservation(false); - } + mod->charger->signal_transaction_started_event.connect([this]( + const types::authorization::ProvidedIdToken& id_token) { + types::evse_manager::SessionEvent se; + se.event = types::evse_manager::SessionEventEnum::TransactionStarted; + types::evse_manager::TransactionStarted transaction_started; + transaction_started.timestamp = + date::format("%FT%TZ", std::chrono::time_point_cast(date::utc_clock::now())); + + transaction_started.meter_value = mod->get_latest_powermeter_data_billing(); + if (mod->is_reserved()) { + transaction_started.reservation_id.emplace(mod->get_reservation_id()); + mod->cancel_reservation(false); + } - transaction_started.id_tag = mod->charger->get_id_token(); + transaction_started.id_tag = id_token; - double energy_import = transaction_started.meter_value.energy_Wh_import.total; + double energy_import = transaction_started.meter_value.energy_Wh_import.total; - session_log.evse(false, fmt::format("Transaction Started ({} kWh)", energy_import / 1000.)); + session_log.evse(false, fmt::format("Transaction Started ({} kWh)", energy_import / 1000.)); - Everest::TelemetryMap telemetry_data = { - {"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())}, - {"type", "transaction_started"}, - {"session_id", session_uuid}, - {"energy_counter_import_wh", transaction_started.meter_value.energy_Wh_import.total}, - {"id_tag", transaction_started.id_tag.id_token}}; + Everest::TelemetryMap telemetry_data = { + {"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())}, + {"type", "transaction_started"}, + {"session_id", session_uuid}, + {"energy_counter_import_wh", transaction_started.meter_value.energy_Wh_import.total}, + {"id_tag", transaction_started.id_tag.id_token}}; - if (transaction_started.meter_value.energy_Wh_export.has_value()) { - telemetry_data["energy_counter_export_wh"] = - transaction_started.meter_value.energy_Wh_export.value().total; - } - mod->telemetry.publish("session", "events", telemetry_data); + if (transaction_started.meter_value.energy_Wh_export.has_value()) { + telemetry_data["energy_counter_export_wh"] = transaction_started.meter_value.energy_Wh_export.value().total; + } + mod->telemetry.publish("session", "events", telemetry_data); + + se.transaction_started.emplace(transaction_started); + se.uuid = session_uuid; + publish_session_event(se); + }); + + mod->charger->signal_transaction_finished_event.connect( + [this](const types::evse_manager::StopTransactionReason& finished_reason, + std::optional finish_token) { + types::evse_manager::SessionEvent se; - se.transaction_started.emplace(transaction_started); - } else if (e == types::evse_manager::SessionEventEnum::TransactionFinished) { + se.event = types::evse_manager::SessionEventEnum::TransactionFinished; this->mod->selected_protocol = "Unknown"; types::evse_manager::TransactionFinished transaction_finished; @@ -247,14 +254,13 @@ void evse_managerImpl::ready() { transaction_finished.meter_value = mod->get_latest_powermeter_data_billing(); - auto reason = mod->charger->get_transaction_finished_reason(); - transaction_finished.reason.emplace(reason); - transaction_finished.id_tag = mod->charger->get_stop_transaction_id_token(); + transaction_finished.reason.emplace(finished_reason); + transaction_finished.id_tag = finish_token; double energy_import = transaction_finished.meter_value.energy_Wh_import.total; session_log.evse(false, fmt::format("Transaction Finished: {} ({} kWh)", - types::evse_manager::stop_transaction_reason_to_string(reason), + types::evse_manager::stop_transaction_reason_to_string(finished_reason), energy_import / 1000.)); Everest::TelemetryMap telemetry_data = { @@ -262,7 +268,7 @@ void evse_managerImpl::ready() { {"type", "transaction_finished"}, {"session_id", session_uuid}, {"energy_counter_import_wh", energy_import}, - {"reason", types::evse_manager::stop_transaction_reason_to_string(reason)}}; + {"reason", types::evse_manager::stop_transaction_reason_to_string(finished_reason)}}; if (transaction_finished.meter_value.energy_Wh_export.has_value()) { telemetry_data["energy_counter_export_wh"] = @@ -272,6 +278,26 @@ void evse_managerImpl::ready() { mod->telemetry.publish("session", "events", telemetry_data); se.transaction_finished.emplace(transaction_finished); + se.uuid = session_uuid; + + publish_session_event(se); + }); + + mod->charger->signal_simple_event.connect([this](const types::evse_manager::SessionEventEnum& e) { + types::evse_manager::SessionEvent se; + + se.event = e; + + if (e == types::evse_manager::SessionEventEnum::SessionFinished) { + types::evse_manager::SessionFinished session_finished; + session_finished.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now()); + session_log.evse(false, fmt::format("Session Finished")); + session_log.stopSession(); + mod->telemetry.publish("session", "events", + {{"timestamp", Everest::Date::to_rfc3339(date::utc_clock::now())}, + {"type", "session_finished"}, + {"session_id", session_uuid}}); + se.session_finished = session_finished; } else if (e == types::evse_manager::SessionEventEnum::Enabled or e == types::evse_manager::SessionEventEnum::Disabled) { if (connector_status_changed) { @@ -282,6 +308,8 @@ void evse_managerImpl::ready() { se.uuid = session_uuid; publish_session_event(se); + + // Clear UUID after publishing the SessionFinished event if (e == types::evse_manager::SessionEventEnum::SessionFinished) { session_uuid = ""; this->mod->selected_protocol = "Unknown"; diff --git a/modules/EvseManager/scoped_lock_timeout.hpp b/modules/EvseManager/scoped_lock_timeout.hpp new file mode 100644 index 000000000..c8ce62ee3 --- /dev/null +++ b/modules/EvseManager/scoped_lock_timeout.hpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef SCOPED_LOCK_TIMEOUT +#define SCOPED_LOCK_TIMEOUT + +#include "everest/exceptions.hpp" +#include "everest/logging.hpp" +#include +#include + +#include "backtrace.hpp" + +/* + Simple helper class for scoped lock with timeout +*/ +namespace Everest { + +class timed_mutex_traceable : public std::timed_mutex { +public: + std::string description; + pthread_t p_id; +}; + +template class scoped_lock_timeout { +public: + explicit scoped_lock_timeout(mutex_type& __m, const std::string& description) : mutex(__m) { + if (not mutex.try_lock_for(std::chrono::seconds(120))) { + request_backtrace(pthread_self()); + request_backtrace(mutex.p_id); + // Give some time for other timeouts to report their state and backtraces + std::this_thread::sleep_for(std::chrono::seconds(10)); + + std::string different_thread; + if (mutex.p_id not_eq pthread_self()) { + different_thread = " from a different thread."; + } else { + different_thread = " from the same thread"; + } + + EVLOG_AND_THROW(EverestTimeoutError("Mutex deadlock detected: Failed to lock " + description + + ", mutex held by " + mutex.description + different_thread)); + } else { + locked = true; + mutex.description = description; + mutex.p_id = pthread_self(); + } + } + + ~scoped_lock_timeout() { + if (locked) { + mutex.unlock(); + } + } + + scoped_lock_timeout(const scoped_lock_timeout&) = delete; + scoped_lock_timeout& operator=(const scoped_lock_timeout&) = delete; + +private: + bool locked{false}; + mutex_type& mutex; +}; +} // namespace Everest + +#endif From 685a825b53ece9f698215bdc92cb13734351c1e6 Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Mon, 5 Feb 2024 15:24:36 +0100 Subject: [PATCH 2/6] Replace str copies with Enums Signed-off-by: Cornelius Claussen --- modules/EvseManager/Charger.cpp | 74 ++++--- modules/EvseManager/EvseManager.cpp | 73 ++++--- modules/EvseManager/IECStateMachine.cpp | 12 +- modules/EvseManager/scoped_lock_timeout.hpp | 228 +++++++++++++++++++- 4 files changed, 312 insertions(+), 75 deletions(-) diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index c0cc66655..f69712600 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -55,7 +55,7 @@ Charger::Charger(const std::unique_ptr& bsp, const std::unique_ error_handling->signal_error.connect([this](const types::evse_manager::Error e, const bool prevent_charging) { if (prevent_charging) { std::thread error_thread([this]() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: error_handling->signal_error"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_signal_error); shared_context.error_prevent_charging_flag = true; }); error_thread.detach(); @@ -68,7 +68,7 @@ Charger::Charger(const std::unique_ptr& bsp, const std::unique_ { std::thread error_thread([this]() { Everest::scoped_lock_timeout lock(state_machine_mutex, - "Charger.cpp: error_handling->signal_all_errors_cleared"); + Everest::MutexDescription::Charger_signal_error_cleared); shared_context.error_prevent_charging_flag = false; }); error_thread.detach(); @@ -96,7 +96,7 @@ void Charger::main_thread() { std::this_thread::sleep_for(MAINLOOP_UPDATE_RATE); { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: mainloop"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_mainloop); // update power limits power_available(); // Run our own state machine update (i.e. run everything that needs @@ -703,7 +703,7 @@ void Charger::process_event(CPEvent cp_event) { contactors_closed = false; } - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: process_event"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_process_event); run_state_machine(); @@ -902,7 +902,7 @@ bool Charger::set_max_current(float c, std::chrono::time_point // pause if currently charging, else do nothing. bool Charger::pause_charging() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: pause_charging"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_pause_charging); if (shared_context.current_state == EvseState::Charging) { shared_context.legacy_wakeup_done = false; shared_context.current_state = EvseState::ChargingPausedEVSE; @@ -912,7 +912,7 @@ bool Charger::pause_charging() { } bool Charger::resume_charging() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: resume_charging"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_resume_charging); if (shared_context.hlc_charging_active and shared_context.transaction_active and shared_context.current_state == EvseState::ChargingPausedEVSE) { @@ -930,7 +930,7 @@ bool Charger::resume_charging() { // pause charging since no power is available at the moment bool Charger::pause_charging_wait_for_power() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: pause_charging_wait_for_power"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_waiting_for_power); return pause_charging_wait_for_power_internal(); } @@ -945,7 +945,7 @@ bool Charger::pause_charging_wait_for_power_internal() { // resume charging since power became available. Does not resume if user paused charging. bool Charger::resume_charging_power_available() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: resume_charging_power_available"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_resume_power_available); if (shared_context.transaction_active and shared_context.current_state == EvseState::WaitingForEnergy and power_available()) { @@ -968,7 +968,7 @@ bool Charger::evse_replug() { // Cancel transaction/charging from external EvseManager interface (e.g. via OCPP) bool Charger::cancel_transaction(const types::evse_manager::StopTransactionRequest& request) { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: cancel_transaction"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_cancel_transaction); if (shared_context.transaction_active) { if (shared_context.hlc_charging_active) { @@ -1034,7 +1034,7 @@ void Charger::setup(bool three_phases, bool has_ventilation, const std::string& // set up board support package bsp->setup(three_phases, has_ventilation, country_code); - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: setup"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_setup); // cache our config variables config_context.charge_mode = _charge_mode; ac_hlc_enabled_current_session = config_context.ac_hlc_enabled = _ac_hlc_enabled; @@ -1049,23 +1049,24 @@ void Charger::setup(bool three_phases, bool has_ventilation, const std::string& } Charger::EvseState Charger::get_current_state() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_current_state"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_get_current_state); return shared_context.current_state; } bool Charger::get_authorized_pnc() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_authorized_pnc"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_get_authorized_pnc); return (shared_context.authorized and shared_context.authorized_pnc); } bool Charger::get_authorized_eim() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_authorized_eim"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_get_authorized_eim); return (shared_context.authorized and not shared_context.authorized_pnc); } bool Charger::get_authorized_pnc_ready_for_hlc() { bool auth = false, ready = false; - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_authorized_pnc_ready_for_hlc"); + Everest::scoped_lock_timeout lock(state_machine_mutex, + Everest::MutexDescription::Charger_get_authorized_pnc_ready_for_hlc); auth = (shared_context.authorized and shared_context.authorized_pnc); ready = (shared_context.current_state == EvseState::ChargingPausedEV) or (shared_context.current_state == EvseState::ChargingPausedEVSE) or @@ -1076,7 +1077,8 @@ bool Charger::get_authorized_pnc_ready_for_hlc() { bool Charger::get_authorized_eim_ready_for_hlc() { bool auth = false, ready = false; - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_authorized_eim_ready_for_hlc"); + Everest::scoped_lock_timeout lock(state_machine_mutex, + Everest::MutexDescription::Charger_get_authorized_eim_ready_for_hlc); auth = (shared_context.authorized and not shared_context.authorized_pnc); ready = (shared_context.current_state == EvseState::ChargingPausedEV) or (shared_context.current_state == EvseState::ChargingPausedEVSE) or @@ -1086,7 +1088,7 @@ bool Charger::get_authorized_eim_ready_for_hlc() { } void Charger::authorize(bool a, const types::authorization::ProvidedIdToken& token) { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: authorize"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_authorize); if (a) { // First user interaction was auth? Then start session already here and not at plug in if (not shared_context.session_active) { @@ -1105,7 +1107,7 @@ void Charger::authorize(bool a, const types::authorization::ProvidedIdToken& tok } bool Charger::deauthorize() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: deauthorize"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_deauthorize); return deauthorize_internal(); } @@ -1129,7 +1131,7 @@ bool Charger::deauthorize_internal() { } bool Charger::disable(int connector_id) { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: disable"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_disable); if (connector_id not_eq 0) { shared_context.connector_enabled = false; } @@ -1139,7 +1141,7 @@ bool Charger::disable(int connector_id) { } bool Charger::enable(int connector_id) { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: enable"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_enable); if (connector_id not_eq 0) { shared_context.connector_enabled = true; @@ -1156,7 +1158,7 @@ bool Charger::enable(int connector_id) { } void Charger::set_faulted() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_faulted"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_set_faulted); shared_context.error_prevent_charging_flag = true; } @@ -1206,7 +1208,7 @@ std::string Charger::evse_state_to_string(EvseState s) { } float Charger::get_max_current() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_max_current"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_get_max_current); return get_max_current_internal(); } @@ -1222,7 +1224,8 @@ float Charger::get_max_current_internal() { } void Charger::set_current_drawn_by_vehicle(float l1, float l2, float l3) { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_current_drawn_by_vehicle"); + Everest::scoped_lock_timeout lock(state_machine_mutex, + Everest::MutexDescription::Charger_set_current_drawn_by_vehicle); shared_context.current_drawn_by_vehicle[0] = l1; shared_context.current_drawn_by_vehicle[1] = l2; shared_context.current_drawn_by_vehicle[2] = l3; @@ -1274,7 +1277,7 @@ bool Charger::power_available() { } void Charger::request_error_sequence() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: request_error_sequence"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_request_error_sequence); if (shared_context.current_state == EvseState::WaitingForAuthentication or shared_context.current_state == EvseState::PrepareCharging) { internal_context.t_step_EF_return_state = shared_context.current_state; @@ -1289,12 +1292,13 @@ void Charger::request_error_sequence() { } void Charger::set_matching_started(bool m) { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_matching_started"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_set_matching_started); shared_context.matching_started = m; } void Charger::notify_currentdemand_started() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: notify_currentdemand_started"); + Everest::scoped_lock_timeout lock(state_machine_mutex, + Everest::MutexDescription::Charger_notify_currentdemand_started); if (shared_context.current_state == EvseState::PrepareCharging) { signal_simple_event(types::evse_manager::SessionEventEnum::ChargingStarted); shared_context.current_state = EvseState::Charging; @@ -1303,18 +1307,19 @@ void Charger::notify_currentdemand_started() { void Charger::inform_new_evse_max_hlc_limits( const types::iso15118_charger::DC_EVSEMaximumLimits& _currentEvseMaxLimits) { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: inform_new_evse_max_hlc_limits"); + Everest::scoped_lock_timeout lock(state_machine_mutex, + Everest::MutexDescription::Charger_inform_new_evse_max_hlc_limits); shared_context.current_evse_max_limits = _currentEvseMaxLimits; } types::iso15118_charger::DC_EVSEMaximumLimits Charger::get_evse_max_hlc_limits() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: get_evse_max_hlc_limits"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_get_evse_max_hlc_limits); return shared_context.current_evse_max_limits; } // HLC stack signalled a pause request for the lower layers. void Charger::dlink_pause() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: dlink_pause"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_dlink_pause); shared_context.hlc_allow_close_contactor = false; pwm_off(); shared_context.hlc_charging_terminate_pause = HlcTerminatePause::Pause; @@ -1322,14 +1327,14 @@ void Charger::dlink_pause() { // HLC requested end of charging session, so we can stop the 5% PWM void Charger::dlink_terminate() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: dlink_terminate"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_dlink_terminate); shared_context.hlc_allow_close_contactor = false; pwm_off(); shared_context.hlc_charging_terminate_pause = HlcTerminatePause::Terminate; } void Charger::dlink_error() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: dlink_error"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_dlink_error); shared_context.hlc_allow_close_contactor = false; @@ -1375,17 +1380,18 @@ void Charger::dlink_error() { } void Charger::set_hlc_charging_active() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_hlc_charging_active"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_set_hlc_charging_active); shared_context.hlc_charging_active = true; } void Charger::set_hlc_allow_close_contactor(bool on) { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_hlc_allow_close_contactor"); + Everest::scoped_lock_timeout lock(state_machine_mutex, + Everest::MutexDescription::Charger_set_hlc_allow_close_contactor); shared_context.hlc_allow_close_contactor = on; } void Charger::set_hlc_error() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: set_hlc_error"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_set_hlc_error); shared_context.error_prevent_charging_flag = true; } @@ -1447,7 +1453,7 @@ bool Charger::bcb_toggle_detected() { } bool Charger::errors_prevent_charging() { - Everest::scoped_lock_timeout lock(state_machine_mutex, "Charger.cpp: errors_prevent_charging"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_errors_prevent_charging); return errors_prevent_charging_internal(); } diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index 21aa09ea9..fcfd12e3e 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -252,8 +252,7 @@ void EvseManager::ready() { { // dont publish ev_info here, it will be published when other values change. // otherwise we will create too much traffic on mqtt - Everest::scoped_lock_timeout lock(ev_info_mutex, - "EvseManager.cpp: set ev_info present_voltage/current"); + Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_set_ev_info); ev_info.present_voltage = present_values.EVSEPresentVoltage; ev_info.present_current = present_values.EVSEPresentCurrent; // p_evse->publish_ev_info(ev_info); @@ -283,7 +282,8 @@ void EvseManager::ready() { } { - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: publish_ev_info"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_publish_ev_info); ev_info.target_voltage = latest_target_voltage; ev_info.target_current = latest_target_current; p_evse->publish_ev_info(ev_info); @@ -309,7 +309,8 @@ void EvseManager::ready() { r_hlc[0]->subscribe_currentDemand_Finished([this] { powersupply_DC_off(); }); r_hlc[0]->subscribe_DC_EVMaximumLimits([this](types::iso15118_charger::DC_EVMaximumLimits l) { - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_EVMaximumLimits"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_subscribe_DC_EVMaximumLimits); ev_info.maximum_current_limit = l.DC_EVMaximumCurrentLimit; ev_info.maximum_power_limit = l.DC_EVMaximumPowerLimit; ev_info.maximum_voltage_limit = l.DC_EVMaximumVoltageLimit; @@ -317,70 +318,77 @@ void EvseManager::ready() { }); r_hlc[0]->subscribe_DepartureTime([this](const std::string& t) { - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DepartureTime"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_subscribe_DepartureTime); ev_info.departure_time = t; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_AC_EAmount([this](double e) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_AC_EAmount"); + Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_AC_EAmount); ev_info.remaining_energy_needed = e; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_AC_EVMaxVoltage([this](double v) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_AC_EVMaxVoltage"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_subscribe_AC_EVMaxVoltage); ev_info.maximum_voltage_limit = v; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_AC_EVMaxCurrent([this](double c) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_AC_EVMaxCurrent"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_subscribe_AC_EVMaxCurrent); ev_info.maximum_current_limit = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_AC_EVMinCurrent([this](double c) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_AC_EVMinCurrent"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_subscribe_AC_EVMinCurrent); ev_info.minimum_current_limit = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_EVEnergyCapacity([this](double c) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_EVEnergyCapacity"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_subscribe_DC_EVEnergyCapacity); ev_info.battery_capacity = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_EVEnergyRequest([this](double c) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_EVEnergyCapacity"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_subscribe_DC_EVEnergyRequest); ev_info.remaining_energy_needed = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_FullSOC([this](double c) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_FullSOC"); + Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_DC_FullSOC); ev_info.battery_full_soc = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_BulkSOC([this](double c) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_BulkSOC"); + Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_DC_BulkSOC); ev_info.battery_bulk_soc = c; p_evse->publish_ev_info(ev_info); }); r_hlc[0]->subscribe_DC_EVRemainingTime([this](types::iso15118_charger::DC_EVRemainingTime t) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_DC_EVRemainingTime"); + Everest::scoped_lock_timeout lock(ev_info_mutex, + Everest::MutexDescription::EVSE_subscribe_DC_EVRemainingTime); ev_info.estimated_time_full = t.EV_RemainingTimeToFullSoC; ev_info.estimated_time_bulk = t.EV_RemainingTimeToBulkSoC; p_evse->publish_ev_info(ev_info); @@ -388,7 +396,7 @@ void EvseManager::ready() { r_hlc[0]->subscribe_DC_EVStatus([this](types::iso15118_charger::DC_EVStatusType s) { // FIXME send only on change / throttle messages - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp subscribe_DC_EVStatus"); + Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_DC_EVStatus); ev_info.soc = s.DC_EVRESSSOC; p_evse->publish_ev_info(ev_info); }); @@ -442,7 +450,8 @@ void EvseManager::ready() { if ((config.dbg_hlc_auth_after_tstep and charger->get_authorized_eim_ready_for_hlc()) or (not config.dbg_hlc_auth_after_tstep and charger->get_authorized_eim())) { { - Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: subscribe_Require_Auth_EIM"); + Everest::scoped_lock_timeout lock(hlc_mutex, + Everest::MutexDescription::EVSE_subscribe_Require_Auth_EIM); hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = false; } @@ -450,7 +459,7 @@ void EvseManager::ready() { types::authorization::CertificateStatus::NoCertificateAvailable); } else { p_token_provider->publish_provided_token(autocharge_token); - Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: publish_provided_token"); + Everest::scoped_lock_timeout lock(hlc_mutex, Everest::MutexDescription::EVSE_publish_provided_token); hlc_waiting_for_auth_eim = true; hlc_waiting_for_auth_pnc = false; } @@ -463,7 +472,7 @@ void EvseManager::ready() { p_evse->publish_car_manufacturer(car_manufacturer); { - Everest::scoped_lock_timeout lock(ev_info_mutex, "EvseManager.cpp: subscribe_EVCCIDD"); + Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_EVCCIDD); ev_info.evcc_id = token; p_evse->publish_ev_info(ev_info); } @@ -478,12 +487,14 @@ void EvseManager::ready() { p_token_provider->publish_provided_token(_token); if (charger->get_authorized_pnc()) { { - Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: subscribe_Require_Auth_PnC 1"); + Everest::scoped_lock_timeout lock(hlc_mutex, + Everest::MutexDescription::EVSE_subscribe_Require_Auth_PnC); hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = false; } } else { - Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: subscribe_Require_Auth_PnC 2"); + Everest::scoped_lock_timeout lock(hlc_mutex, + Everest::MutexDescription::EVSE_subscribe_Require_Auth_PnC2); hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = true; } @@ -586,7 +597,7 @@ void EvseManager::ready() { latest_target_voltage = 0; latest_target_current = 0; { - Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: bsp->signal_event.connect"); + Everest::scoped_lock_timeout lock(hlc_mutex, Everest::MutexDescription::EVSE_signal_event); hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = false; } @@ -623,7 +634,7 @@ void EvseManager::ready() { // Store local cache { - Everest::scoped_lock_timeout lock(power_mutex, "EvseManager.cpp: subscribe_powermeter"); + Everest::scoped_lock_timeout lock(power_mutex, Everest::MutexDescription::EVSE_subscribe_powermeter); latest_powermeter_data_billing = p; } @@ -854,7 +865,7 @@ void EvseManager::ready_to_start_charging() { } types::powermeter::Powermeter EvseManager::get_latest_powermeter_data_billing() { - Everest::scoped_lock_timeout lock(power_mutex, "EvseManager.cpp: get_latest_powermeter_data_billing"); + Everest::scoped_lock_timeout lock(power_mutex, Everest::MutexDescription::EVSE_get_latest_powermeter_data_billing); return latest_powermeter_data_billing; } @@ -863,7 +874,7 @@ types::evse_board_support::HardwareCapabilities EvseManager::get_hw_capabilities } int32_t EvseManager::get_reservation_id() { - Everest::scoped_lock_timeout lock(reservation_mutex, "EvseManager.cpp: get_reservation_id"); + Everest::scoped_lock_timeout lock(reservation_mutex, Everest::MutexDescription::EVSE_get_reservation_id); return reservation_id; } @@ -1073,7 +1084,7 @@ bool EvseManager::reserve(int32_t id) { return false; } - Everest::scoped_lock_timeout lock(reservation_mutex, "EvseManager.cpp: reserve"); + Everest::scoped_lock_timeout lock(reservation_mutex, Everest::MutexDescription::EVSE_reserve); if (not reserved) { reserved = true; @@ -1092,7 +1103,7 @@ bool EvseManager::reserve(int32_t id) { void EvseManager::cancel_reservation(bool signal_event) { - Everest::scoped_lock_timeout lock(reservation_mutex, "EvseManager.cpp: cancel_reservation"); + Everest::scoped_lock_timeout lock(reservation_mutex, Everest::MutexDescription::EVSE_cancel_reservation); if (reserved) { reserved = false; reservation_id = 0; @@ -1107,7 +1118,7 @@ void EvseManager::cancel_reservation(bool signal_event) { } bool EvseManager::is_reserved() { - Everest::scoped_lock_timeout lock(reservation_mutex, "EvseManager.cpp: is_reserved"); + Everest::scoped_lock_timeout lock(reservation_mutex, Everest::MutexDescription::EVSE_is_reserved); return reserved; } @@ -1116,12 +1127,12 @@ bool EvseManager::getLocalThreePhases() { } bool EvseManager::get_hlc_enabled() { - Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: get_hlc_enabled"); + Everest::scoped_lock_timeout lock(hlc_mutex, Everest::MutexDescription::EVSE_get_hlc_enabled); return hlc_enabled; } bool EvseManager::get_hlc_waiting_for_auth_pnc() { - Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: get_hlc_waiting_for_auth_pnc"); + Everest::scoped_lock_timeout lock(hlc_mutex, Everest::MutexDescription::EVSE_get_hlc_waiting_for_auth_pnc); return hlc_waiting_for_auth_pnc; } @@ -1148,7 +1159,7 @@ void EvseManager::log_v2g_message(Object m) { void EvseManager::charger_was_authorized() { - Everest::scoped_lock_timeout lock(hlc_mutex, "EvseManager.cpp: charger_was_authorized"); + Everest::scoped_lock_timeout lock(hlc_mutex, Everest::MutexDescription::EVSE_charger_was_authorized); if (hlc_waiting_for_auth_pnc and charger->get_authorized_pnc()) { r_hlc[0]->call_authorization_response(types::authorization::AuthorizationStatus::Accepted, types::authorization::CertificateStatus::Accepted); @@ -1474,7 +1485,7 @@ void EvseManager::fail_session() { } types::evse_manager::EVInfo EvseManager::get_ev_info() { - Everest::scoped_lock_timeout l(ev_info_mutex, "EvseManager.cpp: get_ev_info"); + Everest::scoped_lock_timeout l(ev_info_mutex, Everest::MutexDescription::EVSE_get_ev_info); return ev_info; } diff --git a/modules/EvseManager/IECStateMachine.cpp b/modules/EvseManager/IECStateMachine.cpp index 7caca01e9..91fe7e3ee 100644 --- a/modules/EvseManager/IECStateMachine.cpp +++ b/modules/EvseManager/IECStateMachine.cpp @@ -90,7 +90,7 @@ void IECStateMachine::process_bsp_event(const types::board_support_common::BspEv // If it is a raw CP state, run it through the state machine { Everest::scoped_lock_timeout lock(state_machine_mutex, - "IECStateMachine::process_bsp_event"); + Everest::MutexDescription::IEC_process_bsp_event); cp_state = raw_state; } feed_state_machine(); @@ -130,7 +130,7 @@ void IECStateMachine::feed_state_machine() { std::queue IECStateMachine::state_machine() { std::queue events; - Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::state_machine"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_state_machine); switch (cp_state) { @@ -286,7 +286,7 @@ std::queue IECStateMachine::state_machine() { // High level state machine sets PWM duty cycle void IECStateMachine::set_pwm(double value) { { - Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::set_pwm"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_set_pwm); if (value > 0 && value < 1) { pwm_running = true; } else { @@ -302,7 +302,7 @@ void IECStateMachine::set_pwm(double value) { // High level state machine sets state X1 void IECStateMachine::set_pwm_off() { { - Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::set_pwm_off"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_set_pwm_off); pwm_running = false; } r_bsp->call_pwm_off(); @@ -313,7 +313,7 @@ void IECStateMachine::set_pwm_off() { // High level state machine sets state F void IECStateMachine::set_pwm_F() { { - Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::set_pwm_F"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_set_pwm_F); pwm_running = false; } r_bsp->call_pwm_F(); @@ -324,7 +324,7 @@ void IECStateMachine::set_pwm_F() { // The higher level state machine in Charger.cpp calls this to indicate it allows contactors to be switched on void IECStateMachine::allow_power_on(bool value, types::evse_board_support::Reason reason) { { - Everest::scoped_lock_timeout lock(state_machine_mutex, "IECStateMachine::allow_power_on"); + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::IEC_allow_power_on); // Only set the flags here in case of power on. power_on_allowed = value; power_on_reason = reason; diff --git a/modules/EvseManager/scoped_lock_timeout.hpp b/modules/EvseManager/scoped_lock_timeout.hpp index c8ce62ee3..e98c16230 100644 --- a/modules/EvseManager/scoped_lock_timeout.hpp +++ b/modules/EvseManager/scoped_lock_timeout.hpp @@ -15,15 +15,235 @@ */ namespace Everest { +enum class MutexDescription { + Undefined, + Charger_signal_error, + Charger_signal_error_cleared, + Charger_mainloop, + Charger_process_event, + Charger_pause_charging, + Charger_resume_charging, + Charger_waiting_for_power, + Charger_resume_power_available, + Charger_cancel_transaction, + Charger_setup, + Charger_get_current_state, + Charger_get_authorized_pnc, + Charger_get_authorized_eim, + Charger_get_authorized_pnc_ready_for_hlc, + Charger_get_authorized_eim_ready_for_hlc, + Charger_authorize, + Charger_deauthorize, + Charger_disable, + Charger_enable, + Charger_set_faulted, + Charger_get_max_current, + Charger_set_current_drawn_by_vehicle, + Charger_request_error_sequence, + Charger_set_matching_started, + Charger_notify_currentdemand_started, + Charger_inform_new_evse_max_hlc_limits, + Charger_get_evse_max_hlc_limits, + Charger_dlink_pause, + Charger_dlink_terminate, + Charger_dlink_error, + Charger_set_hlc_charging_active, + Charger_set_hlc_allow_close_contactor, + Charger_set_hlc_error, + Charger_errors_prevent_charging, + IEC_process_bsp_event, + IEC_state_machine, + IEC_set_pwm, + IEC_set_pwm_off, + IEC_set_pwm_F, + IEC_allow_power_on, + EVSE_set_ev_info, + EVSE_publish_ev_info, + EVSE_subscribe_DC_EVMaximumLimits, + EVSE_subscribe_DepartureTime, + EVSE_subscribe_AC_EAmount, + EVSE_subscribe_AC_EVMaxVoltage, + EVSE_subscribe_AC_EVMaxCurrent, + EVSE_subscribe_AC_EVMinCurrent, + EVSE_subscribe_DC_EVEnergyCapacity, + EVSE_subscribe_DC_EVEnergyRequest, + EVSE_subscribe_DC_FullSOC, + EVSE_subscribe_DC_BulkSOC, + EVSE_subscribe_DC_EVRemainingTime, + EVSE_subscribe_DC_EVStatus, + EVSE_subscribe_Require_Auth_EIM, + EVSE_publish_provided_token, + EVSE_subscribe_EVCCIDD, + EVSE_subscribe_Require_Auth_PnC, + EVSE_subscribe_Require_Auth_PnC2, + EVSE_signal_event, + EVSE_subscribe_powermeter, + EVSE_get_latest_powermeter_data_billing, + EVSE_get_reservation_id, + EVSE_reserve, + EVSE_cancel_reservation, + EVSE_is_reserved, + EVSE_get_hlc_enabled, + EVSE_get_hlc_waiting_for_auth_pnc, + EVSE_charger_was_authorized, + EVSE_get_ev_info +}; + +static std::string to_string(MutexDescription d) { + switch (d) { + case MutexDescription::Charger_signal_error: + return "Charger.cpp: error_handling->signal_error"; + case MutexDescription::Charger_signal_error_cleared: + return "Charger.cpp: error_handling->signal_all_errors_cleared"; + case MutexDescription::Charger_mainloop: + return "Charger.cpp: mainloop"; + case MutexDescription::Charger_process_event: + return "Charger.cpp: process_event"; + case MutexDescription::Charger_pause_charging: + return "Charger.cpp: pause_charging"; + case MutexDescription::Charger_resume_charging: + return "Charger.cpp: resume_charging"; + case MutexDescription::Charger_waiting_for_power: + return "Charger.cpp: pause_charging_wait_for_power"; + case MutexDescription::Charger_resume_power_available: + return "Charger.cpp: resume_charging_power_available"; + case MutexDescription::Charger_cancel_transaction: + return "Charger.cpp: cancel_transaction"; + case MutexDescription::Charger_setup: + return "Charger.cpp: setup"; + case MutexDescription::Charger_get_current_state: + return "Charger.cpp: get_current_state"; + case MutexDescription::Charger_get_authorized_pnc: + return "Charger.cpp: get_authorized_pnc"; + case MutexDescription::Charger_get_authorized_eim: + return "Charger.cpp: get_authorized_eim"; + case MutexDescription::Charger_get_authorized_pnc_ready_for_hlc: + return "Charger.cpp: get_authorized_pnc_ready_for_hlc"; + case MutexDescription::Charger_get_authorized_eim_ready_for_hlc: + return "Charger.cpp: get_authorized_eim_ready_for_hlc"; + case MutexDescription::Charger_authorize: + return "Charger.cpp: authorize"; + case MutexDescription::Charger_deauthorize: + return "Charger.cpp: deauthorize"; + case MutexDescription::Charger_disable: + return "Charger.cpp: disable"; + case MutexDescription::Charger_enable: + return "Charger.cpp: enable"; + case MutexDescription::Charger_set_faulted: + return "Charger.cpp: set_faulted"; + case MutexDescription::Charger_get_max_current: + return "Charger.cpp: get_max_current"; + case MutexDescription::Charger_set_current_drawn_by_vehicle: + return "Charger.cpp: set_current_drawn_by_vehicle"; + case MutexDescription::Charger_request_error_sequence: + return "Charger.cpp: request_error_sequence"; + case MutexDescription::Charger_set_matching_started: + return "Charger.cpp: set_matching_started"; + case MutexDescription::Charger_notify_currentdemand_started: + return "Charger.cpp: notify_currentdemand_started"; + case MutexDescription::Charger_inform_new_evse_max_hlc_limits: + return "Charger.cpp: inform_new_evse_max_hlc_limits"; + case MutexDescription::Charger_get_evse_max_hlc_limits: + return "Charger.cpp: get_evse_max_hlc_limits"; + case MutexDescription::Charger_dlink_pause: + return "Charger.cpp: dlink_pause"; + case MutexDescription::Charger_dlink_terminate: + return "Charger.cpp: dlink_error"; + case MutexDescription::Charger_dlink_error: + return "Charger.cpp: dlink_error"; + case MutexDescription::Charger_set_hlc_charging_active: + return "Charger.cpp: set_hlc_charging_active"; + case MutexDescription::Charger_set_hlc_allow_close_contactor: + return "Charger.cpp: set_hlc_allow_close_contactor"; + case MutexDescription::Charger_set_hlc_error: + return "Charger.cpp: set_hlc_error"; + case MutexDescription::Charger_errors_prevent_charging: + return "Charger.cpp: errors_prevent_charging"; + case MutexDescription::IEC_process_bsp_event: + return "IECStateMachine::process_bsp_event"; + case MutexDescription::IEC_state_machine: + return "IECStateMachine::state_machine"; + case MutexDescription::IEC_set_pwm: + return "IECStateMachine::set_pwm"; + case MutexDescription::IEC_set_pwm_off: + return "IECStateMachine::set_pwm_off"; + case MutexDescription::IEC_set_pwm_F: + return "IECStateMachine::set_pwm_F"; + case MutexDescription::IEC_allow_power_on: + return "IECStateMachine::allow_power_on"; + case MutexDescription::EVSE_set_ev_info: + return "EvseManager.cpp: set ev_info present_voltage/current"; + case MutexDescription::EVSE_publish_ev_info: + return "EvseManager.cpp: publish_ev_info"; + case MutexDescription::EVSE_subscribe_DC_EVMaximumLimits: + return "EvseManager.cpp: subscribe_DC_EVMaximumLimits"; + case MutexDescription::EVSE_subscribe_DepartureTime: + return "EvseManager.cpp: subscribe_DepartureTime"; + case MutexDescription::EVSE_subscribe_AC_EAmount: + return "EvseManager.cpp: subscribe_AC_EAmount"; + case MutexDescription::EVSE_subscribe_AC_EVMaxVoltage: + return "EvseManager.cpp: subscribe_AC_EVMaxVoltage"; + case MutexDescription::EVSE_subscribe_AC_EVMaxCurrent: + return "EvseManager.cpp: subscribe_AC_EVMaxCurrent"; + case MutexDescription::EVSE_subscribe_AC_EVMinCurrent: + return "EvseManager.cpp: subscribe_AC_EVMinCurrent"; + case MutexDescription::EVSE_subscribe_DC_EVEnergyCapacity: + return "EvseManager.cpp: subscribe_DC_EVEnergyCapacity"; + case MutexDescription::EVSE_subscribe_DC_EVEnergyRequest: + return "EvseManager.cpp: subscribe_DC_EVEnergyRequest"; + case MutexDescription::EVSE_subscribe_DC_FullSOC: + return "EvseManager.cpp: subscribe_DC_FullSOC"; + case MutexDescription::EVSE_subscribe_DC_BulkSOC: + return "EvseManager.cpp: subscribe_DC_BulkSOC"; + case MutexDescription::EVSE_subscribe_DC_EVRemainingTime: + return "EvseManager.cpp: subscribe_DC_EVRemainingTime"; + case MutexDescription::EVSE_subscribe_DC_EVStatus: + return "EvseManager.cpp subscribe_DC_EVStatus"; + case MutexDescription::EVSE_subscribe_Require_Auth_EIM: + return "EvseManager.cpp: subscribe_Require_Auth_EIM"; + case MutexDescription::EVSE_publish_provided_token: + return "EvseManager.cpp: publish_provided_token"; + case MutexDescription::EVSE_subscribe_EVCCIDD: + return "EvseManager.cpp: subscribe_EVCCIDD"; + case MutexDescription::EVSE_subscribe_Require_Auth_PnC: + return "EvseManager.cpp: subscribe_Require_Auth_PnC 1"; + case MutexDescription::EVSE_subscribe_Require_Auth_PnC2: + return "EvseManager.cpp: subscribe_Require_Auth_PnC 2"; + case MutexDescription::EVSE_signal_event: + return "EvseManager.cpp: bsp->signal_event.connect"; + case MutexDescription::EVSE_subscribe_powermeter: + return "EvseManager.cpp: subscribe_powermeter"; + case MutexDescription::EVSE_get_latest_powermeter_data_billing: + return "EvseManager.cpp: get_latest_powermeter_data_billing"; + case MutexDescription::EVSE_get_reservation_id: + return "EvseManager.cpp: get_reservation_id"; + case MutexDescription::EVSE_reserve: + return "EvseManager.cpp: reserve"; + case MutexDescription::EVSE_cancel_reservation: + return "EvseManager.cpp: cancel_reservation"; + case MutexDescription::EVSE_is_reserved: + return "EvseManager.cpp: is_reserved"; + case MutexDescription::EVSE_get_hlc_enabled: + return "EvseManager.cpp: get_hlc_enabled"; + case MutexDescription::EVSE_get_hlc_waiting_for_auth_pnc: + return "EvseManager.cpp: get_hlc_waiting_for_auth_pnc"; + case MutexDescription::EVSE_charger_was_authorized: + return "EvseManager.cpp: charger_was_authorized"; + case MutexDescription::EVSE_get_ev_info: + return "EvseManager.cpp: get_ev_info"; + } + return "Undefined"; +} + class timed_mutex_traceable : public std::timed_mutex { public: - std::string description; + MutexDescription description; pthread_t p_id; }; template class scoped_lock_timeout { public: - explicit scoped_lock_timeout(mutex_type& __m, const std::string& description) : mutex(__m) { + explicit scoped_lock_timeout(mutex_type& __m, MutexDescription description) : mutex(__m) { if (not mutex.try_lock_for(std::chrono::seconds(120))) { request_backtrace(pthread_self()); request_backtrace(mutex.p_id); @@ -37,8 +257,8 @@ template class scoped_lock_timeout { different_thread = " from the same thread"; } - EVLOG_AND_THROW(EverestTimeoutError("Mutex deadlock detected: Failed to lock " + description + - ", mutex held by " + mutex.description + different_thread)); + EVLOG_AND_THROW(EverestTimeoutError("Mutex deadlock detected: Failed to lock " + to_string(description) + + ", mutex held by " + to_string(mutex.description) + different_thread)); } else { locked = true; mutex.description = description; From b88863983be9bcbc9f36087afe7c3b0f93715d17 Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Mon, 5 Feb 2024 15:27:41 +0100 Subject: [PATCH 3/6] Move 120s to member constexpr Signed-off-by: Cornelius Claussen --- modules/EvseManager/scoped_lock_timeout.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/EvseManager/scoped_lock_timeout.hpp b/modules/EvseManager/scoped_lock_timeout.hpp index e98c16230..15e61a2f4 100644 --- a/modules/EvseManager/scoped_lock_timeout.hpp +++ b/modules/EvseManager/scoped_lock_timeout.hpp @@ -244,7 +244,7 @@ class timed_mutex_traceable : public std::timed_mutex { template class scoped_lock_timeout { public: explicit scoped_lock_timeout(mutex_type& __m, MutexDescription description) : mutex(__m) { - if (not mutex.try_lock_for(std::chrono::seconds(120))) { + if (not mutex.try_lock_for(deadlock_timeout)) { request_backtrace(pthread_self()); request_backtrace(mutex.p_id); // Give some time for other timeouts to report their state and backtraces @@ -278,6 +278,9 @@ template class scoped_lock_timeout { private: bool locked{false}; mutex_type& mutex; + + // This should be lower then command timeouts from framework (by default 300s) + static constexpr auto deadlock_timeout = std::chrono::seconds(120); }; } // namespace Everest From c43eb5e5a5b4c9612d4bb7f14b3546d64c96e72c Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Tue, 6 Feb 2024 08:36:52 +0100 Subject: [PATCH 4/6] Use pthreads etc only on linux Signed-off-by: Cornelius Claussen --- modules/EvseManager/Charger.cpp | 2 ++ modules/EvseManager/backtrace.cpp | 5 +++-- modules/EvseManager/backtrace.hpp | 3 ++- modules/EvseManager/scoped_lock_timeout.hpp | 6 ++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index f69712600..11270d46f 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -23,7 +23,9 @@ Charger::Charger(const std::unique_ptr& bsp, const std::unique_ const types::evse_board_support::Connector_type& connector_type) : bsp(bsp), error_handling(error_handling), connector_type(connector_type) { +#ifdef __linux__ Everest::install_backtrace_handler(); +#endif shared_context.connector_enabled = true; shared_context.max_current = 6.0; diff --git a/modules/EvseManager/backtrace.cpp b/modules/EvseManager/backtrace.cpp index 0ce328eef..1f70d36fc 100644 --- a/modules/EvseManager/backtrace.cpp +++ b/modules/EvseManager/backtrace.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest - +#ifdef __linux__ #include #include #include @@ -40,4 +40,5 @@ void request_backtrace(pthread_t id) { pthread_kill(id, SIGUSR1); } -} // namespace Everest \ No newline at end of file +} // namespace Everest +#endif \ No newline at end of file diff --git a/modules/EvseManager/backtrace.hpp b/modules/EvseManager/backtrace.hpp index 0cf7b7bef..72b41ca6a 100644 --- a/modules/EvseManager/backtrace.hpp +++ b/modules/EvseManager/backtrace.hpp @@ -2,7 +2,7 @@ // Copyright Pionix GmbH and Contributors to EVerest #ifndef EVEREST_BACKTRACE #define EVEREST_BACKTRACE - +#ifdef __linux__ #include /* @@ -13,4 +13,5 @@ void signal_handler(int signo); void install_backtrace_handler(); void request_backtrace(pthread_t id); } // namespace Everest +#endif #endif \ No newline at end of file diff --git a/modules/EvseManager/scoped_lock_timeout.hpp b/modules/EvseManager/scoped_lock_timeout.hpp index 15e61a2f4..25955b2fd 100644 --- a/modules/EvseManager/scoped_lock_timeout.hpp +++ b/modules/EvseManager/scoped_lock_timeout.hpp @@ -236,15 +236,18 @@ static std::string to_string(MutexDescription d) { } class timed_mutex_traceable : public std::timed_mutex { +#ifdef __linux__ public: MutexDescription description; pthread_t p_id; +#endif }; template class scoped_lock_timeout { public: explicit scoped_lock_timeout(mutex_type& __m, MutexDescription description) : mutex(__m) { if (not mutex.try_lock_for(deadlock_timeout)) { +#ifdef __linux__ request_backtrace(pthread_self()); request_backtrace(mutex.p_id); // Give some time for other timeouts to report their state and backtraces @@ -259,10 +262,13 @@ template class scoped_lock_timeout { EVLOG_AND_THROW(EverestTimeoutError("Mutex deadlock detected: Failed to lock " + to_string(description) + ", mutex held by " + to_string(mutex.description) + different_thread)); +#endif } else { locked = true; +#ifdef __linux__ mutex.description = description; mutex.p_id = pthread_self(); +#endif } } From 9a45a51c46c2796161f97f91041f74b838ddbf02 Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Tue, 6 Feb 2024 18:04:25 +0100 Subject: [PATCH 5/6] build backtraces only on GNU Signed-off-by: Cornelius Claussen --- modules/EvseManager/backtrace.cpp | 8 +++++--- modules/EvseManager/backtrace.hpp | 5 +++++ modules/EvseManager/scoped_lock_timeout.hpp | 6 +++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/EvseManager/backtrace.cpp b/modules/EvseManager/backtrace.cpp index 1f70d36fc..7b89cd0a7 100644 --- a/modules/EvseManager/backtrace.cpp +++ b/modules/EvseManager/backtrace.cpp @@ -1,12 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest -#ifdef __linux__ + +#include "backtrace.hpp" + +#ifdef EVEREST_USE_BACKTRACES + #include #include #include -#include "backtrace.hpp" - /* Simple backtrace signal handler */ diff --git a/modules/EvseManager/backtrace.hpp b/modules/EvseManager/backtrace.hpp index 72b41ca6a..a24f76c6d 100644 --- a/modules/EvseManager/backtrace.hpp +++ b/modules/EvseManager/backtrace.hpp @@ -5,6 +5,10 @@ #ifdef __linux__ #include +#include +#ifdef __USE_GNU +#include "backtrace.hpp" +#define EVEREST_USE_BACKTRACES /* Simple backtrace signal handler */ @@ -14,4 +18,5 @@ void install_backtrace_handler(); void request_backtrace(pthread_t id); } // namespace Everest #endif +#endif #endif \ No newline at end of file diff --git a/modules/EvseManager/scoped_lock_timeout.hpp b/modules/EvseManager/scoped_lock_timeout.hpp index 25955b2fd..65dc05dc2 100644 --- a/modules/EvseManager/scoped_lock_timeout.hpp +++ b/modules/EvseManager/scoped_lock_timeout.hpp @@ -236,7 +236,7 @@ static std::string to_string(MutexDescription d) { } class timed_mutex_traceable : public std::timed_mutex { -#ifdef __linux__ +#ifdef EVEREST_USE_BACKTRACES public: MutexDescription description; pthread_t p_id; @@ -247,7 +247,7 @@ template class scoped_lock_timeout { public: explicit scoped_lock_timeout(mutex_type& __m, MutexDescription description) : mutex(__m) { if (not mutex.try_lock_for(deadlock_timeout)) { -#ifdef __linux__ +#ifdef EVEREST_USE_BACKTRACES request_backtrace(pthread_self()); request_backtrace(mutex.p_id); // Give some time for other timeouts to report their state and backtraces @@ -265,7 +265,7 @@ template class scoped_lock_timeout { #endif } else { locked = true; -#ifdef __linux__ +#ifdef EVEREST_USE_BACKTRACES mutex.description = description; mutex.p_id = pthread_self(); #endif From 45a81c553408418ebc5daa31ae24eb615193a6eb Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Tue, 6 Feb 2024 18:07:54 +0100 Subject: [PATCH 6/6] compile fix Signed-off-by: Cornelius Claussen --- modules/EvseManager/Charger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index 11270d46f..9041dd6a3 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -23,7 +23,7 @@ Charger::Charger(const std::unique_ptr& bsp, const std::unique_ const types::evse_board_support::Connector_type& connector_type) : bsp(bsp), error_handling(error_handling), connector_type(connector_type) { -#ifdef __linux__ +#ifdef EVEREST_USE_BACKTRACES Everest::install_backtrace_handler(); #endif