// SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest /* * Charger.h * * Created on: 08.03.2021 * Author: cornelius * * IEC 61851-1 compliant AC/DC high level charging logic * * This class provides: * 1) Hi level state machine that is controlled by a) events from evse_board_support interface * and b) by external commands from higher levels * * The state machine runs in its own (big) thread. After plugin, * The charger waits in state WaitingForAuthentication forever. Send * Authenticate() * from hi level to start charging. After car is unplugged, it waits in * ChargingFinished forever (or in an error state if an error happens during * charging). */ #ifndef SRC_EVDRIVERS_CHARGER_H_ #define SRC_EVDRIVERS_CHARGER_H_ #include "SessionLog.hpp" #include "ld-ev.hpp" #include "utils/thread.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ErrorHandling.hpp" #include "EventQueue.hpp" #include "IECStateMachine.hpp" #include "PersistentStore.hpp" #include "scoped_lock_timeout.hpp" #include "utils.hpp" namespace module { const std::string IEC62196Type2Cable = "IEC62196Type2Cable"; const std::string IEC62196Type2Socket = "IEC62196Type2Socket"; class Charger { public: Charger(const std::unique_ptr& bsp, const std::unique_ptr& error_handling, const std::vector>& r_powermeter_billing, const std::unique_ptr& store, const types::evse_board_support::Connector_type& connector_type, const std::string& evse_id); ~Charger(); enum class ChargeMode { AC, DC }; enum class EvseState { Disabled, Idle, WaitingForAuthentication, PrepareCharging, Charging, ChargingPausedEV, ChargingPausedEVSE, StoppingCharging, Finished, T_step_EF, T_step_X1, SwitchPhases }; enum class HlcTerminatePause { Unknown, Terminate, Pause }; // Public interface to configure Charger // // Call anytime also during charging, but call setters in this block at // least initially once. // // external input to charger: update max_current and new validUntil bool set_max_current(float ampere, std::chrono::time_point validUntil); float get_max_current(); sigslot::signal signal_max_current; void setup(bool has_ventilation, const ChargeMode charge_mode, bool ac_hlc_enabled, bool ac_hlc_use_5percent, bool ac_enforce_hlc, bool ac_with_soc_timeout, float soft_over_current_tolerance_percent, float soft_over_current_measurement_noise_A, const int switch_3ph1ph_delay_s, const std::string switch_3ph1ph_cp_state, const int soft_over_current_timeout_ms, const int _state_F_after_fault_ms, const bool fail_on_powermeter_errors, const bool raise_mrec9, const int sleep_before_enabling_pwm_hlc_mode_ms, const utils::SessionIdType session_id_type, const int hlc_charge_loop_without_energy_timeout_s); void enable_disable_initial_state_publish(); bool enable_disable(int connector_id, const types::evse_manager::EnableDisableSource& source); // Public interface during charging // // Call anytime, but mostly used during charging session // // Returns active session_uuid. Returns empty string if not session is active std::string get_session_id() const; // call when in state WaitingForAuthentication void authorize(bool a, const types::authorization::ProvidedIdToken& token, const types::authorization::ValidationResult& result); bool deauthorize(); bool get_authorized_pnc(); bool get_authorized_eim(); // this indicates the charger is done with all of its t_step_XX routines and HLC can now also start charging bool get_authorized_eim_ready_for_hlc(); bool get_authorized_pnc_ready_for_hlc(); // trigger replug sequence while charging to switch number of phases bool switch_three_phases_while_charging(bool n); bool pause_charging(); bool resume_charging(); bool cancel_transaction(const types::evse_manager::StopTransactionRequest& request); // cancel transaction ahead of time when car is still plugged void set_current_drawn_by_vehicle(float l1, float l2, float l3); // Signal for EvseEvents sigslot::signal signal_simple_event; sigslot::signal signal_session_resumed_event; sigslot::signal> signal_session_started_event; sigslot::signal signal_transaction_started_event; sigslot::signal> signal_transaction_finished_event; sigslot::signal signal_charging_paused_evse_event; sigslot::signal<> signal_ac_with_soc_timeout; sigslot::signal<> signal_dc_supply_off; sigslot::signal signal_dc_enforce_target_limits; sigslot::signal<> signal_slac_reset; sigslot::signal<> signal_slac_start; sigslot::signal<> signal_hlc_stop_charging; sigslot::signal<> signal_hlc_pause_charging; sigslot::signal signal_hlc_error; sigslot::signal<> signal_hlc_plug_in_timeout; sigslot::signal<> signal_hlc_no_energy_available; void run(); void request_error_sequence(); void set_matching_started(bool m); void notify_currentdemand_started(); void reset_dc_enforce_target_limits_timer(); std::string evse_state_to_string(EvseState s); EvseState get_current_state(); sigslot::signal signal_state; void inform_new_evse_max_hlc_limits(const types::iso15118::DcEvseMaximumLimits& l); types::iso15118::DcEvseMaximumLimits get_evse_max_hlc_limits(); void inform_new_evse_min_hlc_limits(const types::iso15118::DcEvseMinimumLimits& limits); types::iso15118::DcEvseMinimumLimits get_evse_min_hlc_limits(); void dlink_pause(); void dlink_error(); void dlink_terminate(); void set_hlc_charging_active(); void set_hlc_allow_close_contactor(bool on); void set_hlc_d20_active(); bool stop_charging_on_fatal_error(); bool entered_fatal_error_state(); int time_in_fatal_error_state_ms(); /// @brief Returns the OCMF start data. /// /// The data is generated when starting the transaction. The call resets the /// internal variable and is thus not idempotent. std::optional get_start_signed_meter_value(); /// @brief Returns the OCMF stop data. /// /// The data is generated when stopping the transaction. The call resets the /// internal variable and is thus not idempotent. std::optional get_stop_signed_meter_value(); types::evse_manager::EnableDisableSource get_last_enable_disable_source(); utils::Stopwatch& get_stopwatch() { return stopwatch; } void set_connector_type(types::evse_board_support::Connector_type t) { connector_type = t; } std::optional get_last_stop_transaction_reason(); void cleanup_transactions_on_startup(); EventQueue bsp_event_queue; private: utils::Stopwatch stopwatch; std::optional take_signed_meter_data(std::optional& data); bool stop_charging_on_fatal_error_internal(); float get_max_current_internal(); float get_max_current_signalled_to_ev_internal(); bool deauthorize_internal(); bool pause_charging_wait_for_power_internal(); void bcb_toggle_reset(); void bcb_toggle_detect_start_pulse(); void bcb_toggle_detect_stop_pulse(); bool bcb_toggle_detected(); void clear_errors_on_unplug(); void update_pwm_now(float duty_cycle); void update_pwm_now_if_changed(float duty_cycle); void update_pwm_now_if_changed_ampere(float duty_cycle); void update_pwm_max_every_5seconds_ampere(float duty_cycle); void cp_state_X1(); void cp_state_F(); void process_cp_events_independent(CPEvent cp_event); void process_cp_events_state(CPEvent cp_event); void main_thread(); void error_thread(); void emergency_shutdown(); void error_shutdown(); float ampere_to_duty_cycle(float ampere); void check_soft_over_current(); bool power_available(); void start_session(bool authfirst); void stop_session(); bool start_transaction(); void stop_transaction(); void process_event(CPEvent event); void set_state(EvseState s); // This mutex locks all variables related to the state machine 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 contactor_open{true}; bool hlc_charging_active{false}; HlcTerminatePause hlc_charging_terminate_pause; types::iso15118::DcEvseMaximumLimits current_evse_max_limits; types::iso15118::DcEvseMinimumLimits current_evse_min_limits; bool pwm_running{false}; std::optional stop_transaction_id_token; // only set in case transaction was stopped locally types::authorization::ProvidedIdToken id_token; types::authorization::ValidationResult validation_result; std::atomic_bool flag_authorized{false}; std::atomic_bool flag_externally_cancelled{false}; std::atomic_bool flag_paused_by_evse{false}; std::atomic_bool flag_ev_plugged_in{false}; // 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; std::optional max_current_cable; std::atomic_bool flag_transaction_active; bool session_active; std::string session_uuid; bool connector_enabled; // Set when disable is requested while a session/transaction is active. // Tells the state machine to transition to Disabled once the session is // properly terminated instead of returning to Idle. bool flag_disable_requested{false}; EvseState current_state; std::optional last_stop_transaction_reason; types::evse_manager::StartSessionReason last_start_session_reason; float current_drawn_by_vehicle[3]; ShutdownType shutdown_type{ShutdownType::None}; ShutdownType last_shutdown_type{ShutdownType::None}; 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; bool contactor_welded{false}; bool switch_3ph1ph_threephase{false}; bool switch_3ph1ph_threephase_ongoing{false}; std::optional stop_signed_meter_value; std::optional start_signed_meter_value; std::atomic_bool hlc_d20_active{false}; } 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}; // Delay when switching from 1ph to 3ph or 3ph to 1ph int switch_3ph1ph_delay_s{10}; // Use state F if true, otherwise use X1 bool switch_3ph1ph_cp_state_F{false}; // Tolerate soft over current for given time int soft_over_current_timeout_ms{7000}; // Switch to F for configured ms after a fatal error int state_F_after_fault_ms{300}; // Fail on powermeter errors bool fail_on_powermeter_errors; // Raise MREC9 authorization timeout error bool raise_mrec9; // sleep before enabling pwm in hlc mode int sleep_before_enabling_pwm_hlc_mode_ms{1000}; // type used to generate session ids utils::SessionIdType session_id_type{utils::SessionIdType::UUID}; // Timeout in seconds that defines for how long the EVSE allows the ISO charge loop (AC: ChargingStatus, DC: // CurrentDemand) int hlc_charge_loop_without_energy_timeout_s{300}; } config_context; // Used by different threads, but requires no complete state machine locking 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. 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_duty_cycle; std::chrono::time_point last_pwm_update; EvseState t_step_EF_return_state; float t_step_EF_return_pwm; float t_step_EF_return_ampere; EvseState switching_phases_return_state; EvseState t_step_X1_return_state; float t_step_X1_return_pwm; std::chrono::time_point last_over_current_event; bool over_current{false}; ShutdownType last_shutdown_type{ShutdownType::None}; std::chrono::system_clock::time_point current_state_started; EvseState last_state_detect_state_change; EvseState last_state; bool pp_warning_printed{false}; bool no_energy_warning_printed{false}; float pwm_set_last_ampere{0}; bool t_step_ef_x1_pause{false}; bool cp_state_F_active{false}; bool ac_x1_fallback_nominal_timeout_running{false}; std::chrono::time_point ac_x1_fallback_nominal_timeout_started; bool auth_received_printed{false}; bool hlc_charge_loop_no_energy_timeout_running{false}; std::chrono::time_point iso_charge_loop_no_energy_timeout_started; std::chrono::time_point fatal_error_became_active; bool fatal_error_timer_running{false}; bool dc_statistics_printed{false}; types::evse_manager::ChargingPausedEVSEReasons last_charging_paused_evse_reasons; } internal_context; // main Charger thread Everest::Thread main_thread_handle; Everest::Thread error_thread_handle; std::atomic last_dc_enforce_target_limits{}; const std::unique_ptr& bsp; const std::unique_ptr& error_handling; const std::vector>& r_powermeter_billing; const std::unique_ptr& store; std::atomic connector_type{ types::evse_board_support::Connector_type::IEC62196Type2Cable}; const std::string evse_id; EventQueue error_handling_event_queue; // constants constexpr static int LEGACY_WAKEUP_TIMEOUT{30000}; constexpr static int PREPARING_TIMEOUT_PAUSED_BY_EV{10000}; // 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) 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 IEC_PWM_MAX_UPDATE_INTERVAL = 5000; // EV READY certification requires a small pause of 500-1000 ms in X1 after a t_step_EF sequence before going to X2- // This is not required by IEC61851-1, but it is allowed by the IEC. It helps some older EVs to start charging // after the wake-up sequence. static constexpr int STAY_IN_X1_AFTER_TSTEP_EF_MS = 750; static constexpr int WAIT_FOR_ENERGY_IN_AUTHLOOP_TIMEOUT_MS = 5000; static constexpr int AC_X1_FALLBACK_TO_NOMINAL_TIMEOUT_MS = 10000; static constexpr int STOPPING_CHARGING_TIMEOUT_MS = 20000; // Ensures apply_new_target_voltage_current() is called at least every DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS // during DC charging. This re-applies EVSE limits to the power supply even when the EV does not send // new target values or ignores updated limits from energy management. // The timer is reset whenever apply_new_target_voltage_current() is called from any source // (subscribe callback, energy manager, or this periodic signal). static constexpr int DC_ENFORCE_TARGET_LIMITS_INTERVAL_MS = 5000; types::evse_manager::EnableDisableSource active_enable_disable_source{ types::evse_manager::Enable_source::Unspecified, types::evse_manager::Enable_state::Unassigned, 10000}; std::vector enable_disable_source_table; bool parse_enable_disable_source_table(); void enable_disable_source_table_update(const types::evse_manager::EnableDisableSource& source); protected: // provide access for unit tests void run_state_machine(); constexpr auto& get_shared_context() { return shared_context; } constexpr const auto& get_enable_disable_source_table() const { return enable_disable_source_table; } }; } // namespace module #endif // SRC_EVDRIVERS_CHARGER_H_