// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "EvseManager.hpp"

#include <algorithm>
#include <chrono>
#include <fmt/color.h>
#include <fmt/core.h>

#include "IECStateMachine.hpp"
#include "SessionLog.hpp"
#include "Timeout.hpp"
#include "scoped_lock_timeout.hpp"
#include "utils.hpp"

using namespace std::literals::chrono_literals;

namespace module {

namespace {
const std::vector<std::unique_ptr<powermeterIntf>> EMPTY_POWERMETER_VECTOR;

types::power_supply_DC::Capabilities get_sane_default_power_supply_capabilities() {
    // Init power supply capabilities with safe defaults
    types::power_supply_DC::Capabilities psu_caps;
    psu_caps.bidirectional = false;
    psu_caps.peak_current_ripple_A = 0.5;
    psu_caps.current_regulation_tolerance_A = 0.5;
    psu_caps.max_export_current_A = 0;
    psu_caps.max_export_power_W = 0;
    psu_caps.max_export_voltage_V = 60;
    psu_caps.min_export_voltage_V = 0;
    psu_caps.min_export_current_A = 0;
    return psu_caps;
}

void trim_colons_from_string(std::string& text) {
    text.erase(remove(text.begin(), text.end(), ':'), text.end());
}

types::authorization::ProvidedIdToken create_autocharge_token(std::string token, int connector_id) {
    types::authorization::ProvidedIdToken autocharge_token;
    autocharge_token.authorization_type = types::authorization::AuthorizationType::Autocharge;
    trim_colons_from_string(token);
    autocharge_token.id_token = {"VID:" + token, types::authorization::IdTokenType::MacAddress};
    autocharge_token.connectors.emplace({connector_id});
    return autocharge_token;
}

bool almost_eq(double a, double b) {
    constexpr auto eps = 1e-6;
    return std::fabs(a - b) <= eps;
}

std::optional<float> min_optional(std::optional<float> const& a, std::optional<float> const& b) {
    // if both a and b have values, return the smaller one.
    if (a.has_value() and b.has_value()) {
        return (b.value() < a.value() ? b.value() : a.value());
    }
    // if a has a value, return that one.
    if (a.has_value()) {
        return a;
    }

    // else return b. It is either the only value or empty.
    return b;
}

float min_optional(float a, std::optional<float> b) {
    // if both a and b have values, return the smaller one.
    if (b.has_value()) {
        return (b.value() < a ? b.value() : a);
    }
    // else return a
    return a;
}

types::dc_external_derate::ExternalDerating
get_dc_external_derate(std::optional<float> present_voltage,
                       types::dc_external_derate::ExternalDerating dc_external_derate) {

    if (!present_voltage.has_value()) {
        return dc_external_derate;
    }

    const float voltage = present_voltage.value();

    if (voltage <= 0.0f) {
        return dc_external_derate;
    }

    types::dc_external_derate::ExternalDerating d = dc_external_derate;

    // Derive power limits from current limits (only if power limit is not set)
    if (!d.max_export_power_W.has_value() && d.max_export_current_A.has_value()) {
        d.max_export_power_W = d.max_export_current_A.value() * voltage;
    }
    if (!d.max_import_power_W.has_value() && d.max_import_current_A.has_value()) {
        d.max_import_power_W = d.max_import_current_A.value() * voltage;
    }

    // Derive current limits from power limits (only if current limit is not set)
    if (!d.max_export_current_A.has_value() && d.max_export_power_W.has_value()) {
        d.max_export_current_A = d.max_export_power_W.value() / voltage;
    }
    if (!d.max_import_current_A.has_value() && d.max_import_power_W.has_value()) {
        d.max_import_current_A = d.max_import_power_W.value() / voltage;
    }

    return d;
}

std::vector<types::iso15118::EnergyTransferMode>
get_supported_ac_energy_transfers(const Conf& config, const types::evse_board_support::HardwareCapabilities& caps) {
    std::vector<types::iso15118::EnergyTransferMode> energy_transfers;

    const auto min_phases = std::clamp(caps.min_phase_count_import, 1, 3);
    const auto max_phases = std::clamp(caps.max_phase_count_import, min_phases, 3);

    for (const auto& [count, mode] : {
             std::pair{1, types::iso15118::EnergyTransferMode::AC_single_phase_core},
             std::pair{2, types::iso15118::EnergyTransferMode::AC_two_phase},
             std::pair{3, types::iso15118::EnergyTransferMode::AC_three_phase_core},
         }) {
        if (count >= min_phases and count <= max_phases) {
            energy_transfers.push_back(mode);
        }
    }

    if (config.supported_iso_ac_bpt and caps.max_current_A_export > 0 and caps.max_phase_count_export >= 1) {
        energy_transfers.push_back(types::iso15118::EnergyTransferMode::AC_BPT);
    }

    return energy_transfers;
}

} // namespace

void EvseManager::init() {

    if (!config.connector_type.empty()) {
        try {
            connector_type = types::evse_manager::string_to_connector_type_enum(config.connector_type);
        } catch (const std::out_of_range& e) {
            EVLOG_warning << "Unknown/invalid connector type: " << config.connector_type;
        }
    }

    latest_target_current_low_pass_last_update = std::chrono::steady_clock::now();

    store = std::unique_ptr<PersistentStore>(new PersistentStore(r_store, info.id));

    random_delay_enabled = config.uk_smartcharging_random_delay_enable;
    random_delay_max_duration = std::chrono::seconds(config.uk_smartcharging_random_delay_max_duration);
    if (random_delay_enabled) {
        EVLOG_info << "UK Smart Charging regulations: enabled random delay with a default of "
                   << random_delay_max_duration.load().count() << "s.";
    }

    session_log.setPath(config.session_logging_path);
    session_log.setMqtt([this](json const& data) {
        std::string hlc_log_topic = "everest_api/" + this->info.id + "/var/hlc_log";
        mqtt.publish(hlc_log_topic, data.dump());
    });
    if (config.session_logging) {
        session_log.enable();
    }
    session_log.xmlOutput(config.session_logging_xml);

    // default initialize hardware capabilities with safe values until actual capabilities are received.
    // This is important to have a defined behavior of the charger even if the bsp does not provide
    // capabilities or is slow in doing so.
    {
        auto hw_capabilities_handle = hw_capabilities.handle();
        hw_capabilities_handle->max_current_A_import = 0;
        hw_capabilities_handle->min_current_A_import = 0;
        hw_capabilities_handle->max_phase_count_import = 1;
        hw_capabilities_handle->min_phase_count_import = 0;
        hw_capabilities_handle->max_current_A_export = 0;
        hw_capabilities_handle->min_current_A_export = 0;
        hw_capabilities_handle->max_phase_count_export = 0;
        hw_capabilities_handle->min_phase_count_export = 0;
        hw_capabilities_handle->supports_changing_phases_during_charging = false;
        hw_capabilities_handle->supports_cp_state_E = false;
        hw_capabilities_handle->connector_type = types::evse_board_support::Connector_type::IEC62196Type2Cable;
    }

    invoke_init(*p_evse);
    invoke_init(*p_energy_grid);
    invoke_init(*p_token_provider);
    invoke_init(*p_random_delay);

    // check if a slac module is connected to the optional requirement
    slac_enabled = not r_slac.empty();

    // if hlc is disabled in config, disable slac even if requirement is connected
    if (not(config.ac_hlc_enabled or config.ac_with_soc or config.charge_mode == "DC")) {
        slac_enabled = false;
    }

    // Use SLAC MAC address for Autocharge if configured.
    if (config.autocharge_use_slac_instead_of_hlc and slac_enabled and config.enable_autocharge) {
        r_slac[0]->subscribe_ev_mac_address([this](const std::string& token) {
            p_token_provider->publish_provided_token(create_autocharge_token(token, config.connector_id));
        });
    }

    hlc_enabled = not r_hlc.empty();
    if (not slac_enabled)
        hlc_enabled = false;

    if (config.charge_mode == "DC" and (not hlc_enabled or not slac_enabled or r_powersupply_DC.empty())) {
        EVLOG_error << "DC mode requires slac, HLC and powersupply DCDC to be connected";
        exit(255);
    }

    if (config.charge_mode == "DC" and r_imd.empty()) {
        EVLOG_warning << "DC mode without isolation monitoring configured, please check your national regulations.";
    }

    pnc_enabled = config.payment_enable_contract;
    central_contract_validation_allowed = config.central_contract_validation_allowed;
    contract_certificate_installation_enabled = config.contract_certificate_installation_enabled;

    reserved = false;
    reservation_id = -1;

    hlc_waiting_for_auth_eim = false;
    hlc_waiting_for_auth_pnc = false;

    latest_target_voltage = 0;
    latest_target_current = 0;

    // apply sane defaults capabilities settings once on boot
    powersupply_capabilities = get_sane_default_power_supply_capabilities();

    if (hlc_enabled) {
        if (config.charge_mode == "DC") {
            // subscribe to run time updates for real initial values (and changes e.g. due to de-rating)
            r_powersupply_DC[0]->subscribe_capabilities([this](const auto& caps) {
                update_powersupply_capabilities(caps);

                auto mode = types::iso15118::EnergyTransferMode::DC_extended;
                auto bpt_mode = types::iso15118::EnergyTransferMode::DC_BPT;

                if (connector_type.has_value() and
                    connector_type.value() == types::evse_manager::ConnectorTypeEnum::cMCS) {
                    mode = types::iso15118::EnergyTransferMode::MCS;
                    bpt_mode = types::iso15118::EnergyTransferMode::MCS_BPT;
                }

                std::vector<types::iso15118::EnergyTransferMode> energy_transfers{mode};
                if (caps.bidirectional) {
                    energy_transfers.push_back(bpt_mode);
                }

                const bool was_updated = update_supported_energy_transfers(energy_transfers);

                if (was_updated) {
                    this->publish_and_update_supported_energy_transfers();
                }
            });
        }
    }

    r_bsp->subscribe_request_stop_transaction(
        [this](types::evse_manager::StopTransactionRequest r) { charger->cancel_transaction(r); });

    r_bsp->subscribe_capabilities([this](types::evse_board_support::HardwareCapabilities const& c) {
        {
            auto hw_caps_handle = hw_capabilities.handle();
            hw_caps_handle.wait([this]() { return ready_for_capabilities.load(); });
            *hw_caps_handle = c;
        }

        if (ac_nr_phases_active == 0) {
            ac_nr_phases_active = c.max_phase_count_import;
        }

        if (ac_nr_phases_active > c.max_phase_count_import) {
            ac_nr_phases_active = c.min_phase_count_import;
        }

        signalNrOfPhasesAvailable(ac_nr_phases_active);

        const auto get_max_phases = [](int max_phase_count) -> AcPhases {
            switch (max_phase_count) {
            case 1:
                return AcPhases::SinglePhase;
            case 2:
                return AcPhases::TwoPhases;
            case 3:
                return AcPhases::ThreePhases;

            default:
                EVLOG_warning << "Invalid max_phase_count of " << max_phase_count << ". Falling back to single phase";
            }
            return AcPhases::SinglePhase;
        };

        bsp->set_max_phases(get_max_phases(c.max_phase_count_import));
        charger->set_connector_type(c.connector_type);
        p_evse->publish_hw_capabilities(c);
        if (config.charge_mode == "AC" and hlc_enabled) {
            EVLOG_debug << fmt::format("Max AC hardware capabilities: {}A/{}ph", c.max_current_A_import,
                                       c.max_phase_count_import);

            const auto energy_transfers = get_supported_ac_energy_transfers(config, c);

            if (update_supported_energy_transfers(energy_transfers)) {
                this->publish_and_update_supported_energy_transfers();
            }

            update_hlc_ac_parameters();
        }
    });
}

void EvseManager::ready() {
    bsp = std::make_unique<IECStateMachine>(r_bsp, config.lock_connector_in_state_b);

    if (config.hack_simplified_mode_limit_10A) {
        bsp->set_ev_simplified_mode_evse_limit(true);
    }

    // we provide the powermeter interface to the ErrorHandling only if we need to react to powermeter errors
    // otherwise we provide an empty vector of pointers to the powermeter interface
    error_handling = std::unique_ptr<ErrorHandling>(
        new ErrorHandling(r_bsp, r_hlc, r_connector_lock, r_ac_rcd, p_evse, r_imd, r_powersupply_DC,
                          config.fail_on_powermeter_errors ? r_powermeter_billing() : EMPTY_POWERMETER_VECTOR,
                          r_over_voltage_monitor, config.inoperative_error_use_vendor_id));

    internal_over_voltage_monitor = std::make_unique<OverVoltageMonitor>(
        [this](OverVoltageMonitor::FaultType type, const std::string& description) {
            if (this->error_handling) {
                const auto severity = type == OverVoltageMonitor::FaultType::Emergency
                                          ? Everest::error::Severity::High
                                          : Everest::error::Severity::Medium;
                this->error_handling->raise_over_voltage_error(severity, description);
            }
        },
        std::chrono::milliseconds(config.internal_over_voltage_duration_ms));

    if (not config.lock_connector_in_state_b) {
        EVLOG_warning << "Unlock connector in CP state B. This violates IEC61851-1:2019 D.6.5 Table D.9 line 4 and "
                         "should not be used in public environments!";
    }

    const auto hw_caps = *hw_capabilities.handle();
    charger = std::make_unique<Charger>(bsp, error_handling, r_powermeter_billing(), store, hw_caps.connector_type,
                                        config.evse_id);

    // Now incoming hardware capabilities can be processed
    ready_for_capabilities.store(true);
    hw_capabilities.notify_all();

    if (r_connector_lock.size() > 0) {
        bsp->signal_lock.connect([this]() { r_connector_lock[0]->call_lock(); });
        bsp->signal_unlock.connect([this]() { r_connector_lock[0]->call_unlock(); });
    }

    if (hlc_enabled) {

        // Set up EVSE ID
        types::iso15118::EVSEID evseid = {config.evse_id, config.evse_id_din};

        // Set up auth options for HLC
        std::vector<types::iso15118::PaymentOption> payment_options;
        // if pnc is disabled, disable contract installation and central contract validation
        bool _contract_certificate_installation_enabled =
            pnc_enabled ? contract_certificate_installation_enabled.load() : false;
        bool _central_contract_validation_allowed = pnc_enabled ? central_contract_validation_allowed.load() : false;

        if (config.payment_enable_eim) {
            payment_options.push_back(types::iso15118::PaymentOption::ExternalPayment);
        }
        if (pnc_enabled) {
            payment_options.push_back(types::iso15118::PaymentOption::Contract);
        }
        if (!config.payment_enable_eim and !pnc_enabled) {
            EVLOG_warning << "Both payment options are disabled! ExternalPayment is nevertheless enabled in this case.";
            payment_options.push_back(types::iso15118::PaymentOption::ExternalPayment);
        }
        r_hlc[0]->call_session_setup(payment_options, _contract_certificate_installation_enabled,
                                     _central_contract_validation_allowed);

        r_hlc[0]->subscribe_hlc_session_failed([this](types::evse_manager::HlcSessionFailedReasonEnum reason) {
            types::evse_manager::HlcSessionFailedEvent ev;
            ev.uuid = charger->get_session_id();
            ev.reason = reason;
            p_evse->publish_hlc_session_failed(ev);
        });

        r_hlc[0]->subscribe_dlink_error([this] {
            session_log.evse(true, "D-LINK_ERROR.req");
            // Inform charger
            charger->dlink_error();
            // Inform SLAC layer, it will leave the logical network
            r_slac[0]->call_dlink_error();
        });

        r_hlc[0]->subscribe_dlink_pause([this] {
            // tell charger (it will disable PWM)
            session_log.evse(true, "D-LINK_PAUSE.req");
            charger->dlink_pause();
            r_slac[0]->call_dlink_pause();
        });

        r_hlc[0]->subscribe_dlink_terminate([this] {
            selected_d20_energy_service.reset();
            session_log.evse(true, "D-LINK_TERMINATE.req");
            charger->dlink_terminate();
            r_slac[0]->call_dlink_terminate();
        });

        r_hlc[0]->subscribe_v2g_setup_finished([this] { charger->set_hlc_charging_active(); });

        r_hlc[0]->subscribe_ac_close_contactor([this] {
            session_log.car(true, "AC HLC Close contactor");
            charger->set_hlc_allow_close_contactor(true);
        });

        r_hlc[0]->subscribe_ac_open_contactor([this] {
            session_log.car(true, "AC HLC Open contactor");
            charger->set_hlc_allow_close_contactor(false);
        });

        // Trigger SLAC restart
        charger->signal_slac_start.connect([this] { r_slac[0]->call_enter_bcd(); });
        // Trigger SLAC reset
        charger->signal_slac_reset.connect([this] { r_slac[0]->call_reset(false); });

        // Ask HLC to stop charging session
        charger->signal_hlc_stop_charging.connect([this] { r_hlc[0]->call_stop_charging(true); });
        charger->signal_hlc_pause_charging.connect([this] { r_hlc[0]->call_pause_charging(true); });
        charger->signal_hlc_plug_in_timeout.connect([this] {
            r_hlc[0]->call_authorization_response(types::authorization::AuthorizationStatus::Unknown,
                                                  types::authorization::CertificateStatus::NoCertificateAvailable);
        });

        // Charger needs to inform ISO stack about emergency stop
        charger->signal_hlc_error.connect([this](types::iso15118::EvseError error) {
            if (r_hlc.empty()) {
                EVLOG_warning << "HLC module not connected, cannot send error!";
                return;
            }
            r_hlc[0]->call_send_error(error);
        });

        // Right now only no energy pause after cable check and pre charge is supported
        // For DIN it is always stop before cablecheck
        charger->signal_hlc_no_energy_available.connect([this] {
            if (config.zero_power_ignore_pause) {
                EVLOG_info << "HLC module wont perform a pause. Even when there is no energy available.";
                return;
            }

            const auto no_energy_pause_mode = config.zero_power_allow_ev_to_ignore_pause
                                                  ? types::iso15118::NoEnergyPauseMode::AllowEvToIgnorePause
                                                  : types::iso15118::NoEnergyPauseMode::PauseBeforeCableCheck;
            r_hlc[0]->call_no_energy_pause_charging(no_energy_pause_mode);
        });

        auto sae_mode = types::iso15118::SaeJ2847BidiMode::None;

        std::vector<types::iso15118::EnergyTransferMode> initial_energy_transfers;

        if (config.charge_mode == "AC") {
            types::iso15118::SetupPhysicalValues setup_physical_values;
            setup_physical_values.ac_nominal_voltage = config.ac_nominal_voltage;
            r_hlc[0]->call_set_charging_parameters(setup_physical_values);

            const auto hw_caps = *hw_capabilities.handle();
            initial_energy_transfers = get_supported_ac_energy_transfers(config, hw_caps);

            r_hlc[0]->subscribe_ac_eamount([this](double e) {
                // FIXME send only on change / throttle messages
                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_ev_max_voltage([this](double v) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_ac_ev_max_voltage);
                ev_info.maximum_voltage_limit = v;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_ac_ev_max_current([this](double c) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_ac_ev_max_current);
                ev_info.maximum_current_limit = c;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_ac_ev_min_current([this](double c) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_ac_ev_min_current);
                ev_info.minimum_current_limit = c;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_ac_ev_power_limits([this](types::iso15118::AcEvPowerLimits const& l) {
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_ac_ev_power_limits);
                if (l.max_charge_power.has_value()) {
                    ev_info.ac_max_charge_power = l.max_charge_power.value().total;
                }
                if (l.min_charge_power.has_value()) {
                    ev_info.ac_min_charge_power = l.min_charge_power.value().total;
                }
                if (l.max_discharge_power.has_value()) {
                    ev_info.ac_max_discharge_power = l.max_discharge_power.value().total;
                }
                if (l.min_discharge_power.has_value()) {
                    ev_info.ac_min_discharge_power = l.min_discharge_power.value().total;
                }
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_ac_ev_present_powers([this](types::iso15118::AcEvPresentPowerValues const& values) {
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_ac_ev_present_powers);
                ev_info.ac_present_active_power = values.present_active_power.total;
                if (values.present_reactive_power.has_value()) {
                    ev_info.ac_present_reactive_power = values.present_reactive_power.value().total;
                }
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_ac_ev_dynamic_control_mode(
                [this](types::iso15118::AcEvDynamicModeValues const& values) {
                    Everest::scoped_lock_timeout lock(
                        ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_ac_ev_dynamic_control_mode);
                    ev_info.target_energy_request = values.target_energy_request;
                    ev_info.max_energy_request = values.max_energy_request;
                    ev_info.min_energy_request = values.min_energy_request;
                    if (values.departure_time.has_value()) {
                        // TODO(SL): Convert departure_time to rfc3339
                        // ev_info.departure_time = values.departure_time.value();
                    }
                    // TODO(SL): Missing max_v2x_energy_request & min_v2x_energy_request
                    p_evse->publish_ev_info(ev_info);
                });

        } else if (config.charge_mode == "DC") {
            // Create voltage plausibility monitor for DC charging
            voltage_plausibility_monitor = std::make_unique<VoltagePlausibilityMonitor>(
                [this](const std::string& description) {
                    if (this->error_handling) {
                        this->error_handling->raise_voltage_plausibility_fault(description);
                    }
                },
                config.voltage_plausibility_max_spread_threshold_V,
                std::chrono::milliseconds(config.voltage_plausibility_fault_duration_ms));

            if (connector_type.has_value() and connector_type.value() == types::evse_manager::ConnectorTypeEnum::cMCS) {
                initial_energy_transfers.push_back(types::iso15118::EnergyTransferMode::MCS);
            } else {
                initial_energy_transfers.push_back(types::iso15118::EnergyTransferMode::DC_extended);
            }

            const auto caps = get_powersupply_capabilities();
            update_powersupply_capabilities(caps);

            if (caps.bidirectional) {
                if (connector_type.has_value() and
                    connector_type.value() == types::evse_manager::ConnectorTypeEnum::cMCS) {
                    initial_energy_transfers.push_back(types::iso15118::EnergyTransferMode::MCS_BPT);
                } else {
                    initial_energy_transfers.push_back(types::iso15118::EnergyTransferMode::DC_BPT);
                }
            }

            // Set present measurements on HLC to sane defaults
            types::iso15118::DcEvsePresentVoltageCurrent present_values;
            present_values.evse_present_voltage = 0;
            present_values.evse_present_current = 0;
            r_hlc[0]->call_update_dc_present_values(present_values);

            // Cable check for DC charging
            r_hlc[0]->subscribe_start_cable_check([this] {
                power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::CableCheck;
                cable_check();
            });

            // Cable check for DC charging
            r_hlc[0]->subscribe_start_pre_charge(
                [this] { power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::PreCharge; });

            // Notification that current demand has started
            r_hlc[0]->subscribe_current_demand_started([this] {
                power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::Charging;
                current_demand_active = true;
                apply_new_target_voltage_current();
                charger->notify_currentdemand_started();
                if (not r_over_voltage_monitor.empty()) {
                    r_over_voltage_monitor[0]->call_start();
                }
                if (internal_over_voltage_monitor) {
                    internal_over_voltage_monitor->reset();
                    internal_over_voltage_monitor->start_monitor();
                }
                if (voltage_plausibility_monitor) {
                    voltage_plausibility_monitor->reset();
                    voltage_plausibility_monitor->start_monitor();
                }
            });

            r_hlc[0]->subscribe_current_demand_finished([this] {
                current_demand_active = false;
                sae_bidi_active = false;
                if (not r_over_voltage_monitor.empty()) {
                    r_over_voltage_monitor[0]->call_stop();
                }
                if (voltage_plausibility_monitor) {
                    voltage_plausibility_monitor->stop_monitor();
                }
                if (internal_over_voltage_monitor) {
                    internal_over_voltage_monitor->stop_monitor();
                }
            });

            // Subscribe to voltage measurements from over_voltage_monitor interface
            // The internal monitor acts as a software watchdog following the hardware OVM values
            if (not r_over_voltage_monitor.empty()) {
                r_over_voltage_monitor[0]->subscribe_voltage_measurement_V([this](float voltage_V) {
                    if (internal_over_voltage_monitor) {
                        internal_over_voltage_monitor->update_voltage(voltage_V);
                    }
                });
            }

            // Isolation monitoring for DC charging handler
            if (not r_imd.empty()) {

                imd_stop();

                r_imd[0]->subscribe_isolation_measurement([this](
                                                              types::isolation_monitor::IsolationMeasurement const& m) {
                    // new DC isolation monitoring measurement received
                    if (voltage_plausibility_monitor && m.voltage_V.has_value()) {
                        voltage_plausibility_monitor->update_isolation_monitor_voltage(m.voltage_V.value());
                    }

                    // Check for isolation errors
                    if (charger->get_current_state() == Charger::EvseState::Charging and
                        not check_isolation_resistance_in_range(m.resistance_F_Ohm)) {
                        error_handling->raise_isolation_resistance_fault(
                            fmt::format("Isolation resistance too low during charging: {} Ohm", m.resistance_F_Ohm),
                            "Resistance");
                    }
                    // Check L1e and L2e to PE as defined by IEC 61851-23:2023, $6.3.1.112.2
                    if (charger->get_current_state() == Charger::EvseState::Charging) {
                        bool is_in_range = check_voltage_to_protective_earth_in_range(m);
                        auto now = std::chrono::steady_clock::now();

                        if (not is_in_range) {
                            // Voltage to earth is out of range
                            if (voltage_to_earth_failure_count == 0) {
                                // First failure - record the time
                                first_voltage_to_earth_failure_time = now;
                                voltage_to_earth_failure_count = 1;
                                EVLOG_debug << "Voltage to earth out of range (first failure)";
                            } else {
                                // Subsequent failure - increment count
                                voltage_to_earth_failure_count++;
                                auto time_since_first_failure = std::chrono::duration_cast<std::chrono::seconds>(
                                    now - first_voltage_to_earth_failure_time);

                                EVLOG_debug << "Voltage to earth out of range. Failure count: "
                                            << voltage_to_earth_failure_count
                                            << ", time since first: " << time_since_first_failure.count() << "s";

                                // Only raise error if we have at least 2 failures AND at least 2 seconds between first
                                // and last
                                if (voltage_to_earth_failure_count >= REQUIRED_CONSECUTIVE_FAILURES and
                                    time_since_first_failure >= MIN_TIME_BETWEEN_FIRST_AND_LAST_FAILURE) {
                                    error_handling->raise_isolation_resistance_fault(
                                        fmt::format("Voltage to earth too high during charging L1e: {} V, L2e: {} V",
                                                    m.voltage_to_earth_l1e_V.value_or(NAN),
                                                    m.voltage_to_earth_l2e_V.value_or(NAN)),
                                        "VoltageToEarth");
                                }
                            }
                        } else {
                            // Voltage to earth is in range - reset failure tracking
                            if (voltage_to_earth_failure_count > 0) {
                                EVLOG_debug << "Voltage to earth back in range, resetting failure counter";
                                voltage_to_earth_failure_count = 0;
                            }
                        }
                    }
                    isolation_measurement = m;
                });

                r_imd[0]->subscribe_self_test_result([this](bool result) {
                    session_log.evse(false, fmt::format("Isolation monitor self test result: {}", result));
                    selftest_result = result;
                });
            }

            // On start up, stop ovm in case it is still enabled from a previous run
            if (not r_over_voltage_monitor.empty()) {
                r_over_voltage_monitor[0]->call_stop();
            }

            // Get voltage/current from DC power supply
            if (not r_powersupply_DC.empty()) {
                r_powersupply_DC[0]->subscribe_voltage_current([this](types::power_supply_DC::VoltageCurrent const& m) {
                    powersupply_measurement = m;
                    if (voltage_plausibility_monitor) {
                        voltage_plausibility_monitor->update_power_supply_voltage(m.voltage_V);
                    }
                    types::iso15118::DcEvsePresentVoltageCurrent present_values;
                    present_values.evse_present_voltage = (m.voltage_V > 0 ? m.voltage_V : 0.0);
                    present_values.evse_present_current = m.current_A;

                    if (config.hack_present_current_offset > 0) {
                        const auto current_offset = std::fabs(present_values.evse_present_current.value()) +
                                                    static_cast<float>(config.hack_present_current_offset);
                        present_values.evse_present_current = (m.current_A >= 0) ? current_offset : -current_offset;
                    }

                    if (config.hack_pause_imd_during_precharge and m.voltage_V * std::fabs(m.current_A) > 1000) {
                        // Start IMD again as it was stopped after CableCheck
                        imd_start();
                        EVLOG_info << "Hack: Restarting Isolation Measurement at " << m.voltage_V << " " << m.current_A;
                    }

                    r_hlc[0]->call_update_dc_present_values(present_values);

                    {
                        // 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, Everest::MutexDescription::EVSE_set_ev_info);
                        ev_info.present_voltage = present_values.evse_present_voltage;
                        ev_info.present_current = present_values.evse_present_current;
                        // p_evse->publish_ev_info(ev_info);
                    }
                });
            }

            // Car requests a target voltage and current limit.
            // Store raw EV values and immediately clamp against EVSE limits.
            // Clamping is also re-applied by the Charger state machine
            // (signal_dc_enforce_target_limits) in case the EV doesnt respect the
            // limits or does not change the target values for some time.
            r_hlc[0]->subscribe_dc_ev_target_voltage_current([this](types::iso15118::DcEvTargetValues const& v) {
                raw_ev_target_voltage = v.dc_ev_target_voltage;
                raw_ev_target_current = v.dc_ev_target_current;
                process_dc_ev_target_voltage_current(charger->get_evse_max_hlc_limits());
            });

            r_hlc[0]->subscribe_d20_dc_dynamic_charge_mode(
                [this](types::iso15118::DcChargeDynamicModeValues const& values) {
                    static bool last_is_actually_exporting_to_grid{false};

                    const auto energy_flow_changed =
                        is_actually_exporting_to_grid != last_is_actually_exporting_to_grid;
                    last_is_actually_exporting_to_grid = is_actually_exporting_to_grid;

                    bool target_changed{false};

                    double min_charge_power{0.0};
                    double max_charge_power{0.0};
                    double max_charge_current{0.0};

                    double target_current{0.0};
                    double target_voltage{0.0};

                    const auto max_hlc_limits = charger->get_evse_max_hlc_limits();
                    const auto min_hlc_limits = charger->get_evse_min_hlc_limits();

                    // TODO(SL): How to handle 0kW step between switching from uni to bidi
                    if (is_actually_exporting_to_grid and current_demand_active) {
                        if (values.max_discharge_power.has_value() and
                            max_hlc_limits.evse_maximum_discharge_power_limit.has_value()) {
                            // fabs every discharge limit
                            const auto ev_max_discharge_power = std::fabs(values.max_discharge_power.value());
                            const auto evse_max_discharge_power =
                                std::fabs(max_hlc_limits.evse_maximum_discharge_power_limit.value());
                            max_charge_power = (ev_max_discharge_power > evse_max_discharge_power)
                                                   ? evse_max_discharge_power
                                                   : ev_max_discharge_power;
                        }

                        if (values.min_discharge_power.has_value() and
                            min_hlc_limits.evse_minimum_discharge_power_limit.has_value()) {
                            const auto ev_min_discharge_power = std::fabs(values.min_discharge_power.value());
                            const auto evse_min_discharge_power =
                                std::fabs(min_hlc_limits.evse_minimum_discharge_power_limit.value());
                            min_charge_power = (ev_min_discharge_power < evse_min_discharge_power)
                                                   ? evse_min_discharge_power
                                                   : ev_min_discharge_power;
                        }

                        if (values.max_discharge_current.has_value() and
                            max_hlc_limits.evse_maximum_discharge_current_limit.has_value()) {
                            const auto ev_max_discharge_current = std::fabs(values.max_discharge_current.value());
                            const auto evse_max_discharge_current =
                                std::fabs(max_hlc_limits.evse_maximum_discharge_current_limit.value());
                            max_charge_current = (ev_max_discharge_current > evse_max_discharge_current)
                                                     ? evse_max_discharge_current
                                                     : ev_max_discharge_current;
                        }
                    } else {
                        // Charging
                        max_charge_power = (values.max_charge_power > max_hlc_limits.evse_maximum_power_limit)
                                               ? max_hlc_limits.evse_maximum_power_limit
                                               : values.max_charge_power;
                        min_charge_power = (values.min_charge_power > min_hlc_limits.evse_minimum_power_limit)
                                               ? values.min_charge_power
                                               : min_hlc_limits.evse_minimum_power_limit;
                        max_charge_current = (values.max_charge_current > max_hlc_limits.evse_maximum_current_limit)
                                                 ? max_hlc_limits.evse_maximum_current_limit
                                                 : values.max_charge_current;
                    }

                    if (min_charge_power > max_charge_power) {
                        EVLOG_error << "Minimum charge power limit is greater then the maximum charge power limit";
                        return;
                    }

                    // Setting voltage. charging: EvMaxVoltage, discharging: EvMinVoltage
                    target_voltage = (is_actually_exporting_to_grid)
                                         ? std::max(values.min_voltage, min_hlc_limits.evse_minimum_voltage_limit)
                                         : values.max_voltage;

                    // Setting  power to actual voltage on the powermeter
                    const auto actual_voltage = ev_info.present_voltage.value_or(latest_target_voltage);

                    target_current = (actual_voltage <= 0.0)
                                         ? max_charge_current
                                         : std::min((max_charge_power / actual_voltage), max_charge_current);

                    raw_ev_target_voltage = target_voltage;
                    raw_ev_target_current = target_current;
                    process_dc_ev_target_voltage_current(charger->get_evse_max_hlc_limits());
                });

            // Car requests DC contactor open. We don't actually open but switch off DC supply.
            // opening will be done by Charger on C->B CP event.
            r_hlc[0]->subscribe_dc_open_contactor([this] {
                powersupply_DC_off();
                imd_stop();
            });

            // Back up switch off - charger signalled that it needs to switch off now.
            // During normal operation this should be done earlier before switching off relais by HLC protocol.
            charger->signal_dc_supply_off.connect([this] {
                powersupply_DC_off();
                imd_stop();
            });

            // Re-evaluate DC target limits so that updated EVSE
            // limits are enforced even when the EV does not send new target values
            charger->signal_dc_enforce_target_limits.connect(
                [this](types::iso15118::DcEvseMaximumLimits const& limits) {
                    process_dc_ev_target_voltage_current(limits);
                });

            // Current demand has finished - switch off DC supply
            r_hlc[0]->subscribe_current_demand_finished([this] { powersupply_DC_off(); });

            r_hlc[0]->subscribe_dc_ev_maximum_limits([this](types::iso15118::DcEvMaximumLimits const& l) {
                EVLOG_info << "Received EV maximum limits: " << l;
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_dc_ev_maximum_limits);

                ev_info.maximum_current_limit = l.dc_ev_maximum_current_limit;
                ev_info.maximum_power_limit = l.dc_ev_maximum_power_limit;
                ev_info.maximum_voltage_limit = l.dc_ev_maximum_voltage_limit;
                p_evse->publish_ev_info(ev_info);

                // Update limits for over voltage monitoring
                if (not r_over_voltage_monitor.empty()) {
                    r_over_voltage_monitor[0]->call_set_limits(get_emergency_over_voltage_threshold(),
                                                               get_error_over_voltage_threshold());
                    // Subscribe to voltage measurements from over_voltage_monitor for plausibility check
                    r_over_voltage_monitor[0]->subscribe_voltage_measurement_V([this](float voltage_V) {
                        if (voltage_plausibility_monitor) {
                            voltage_plausibility_monitor->update_over_voltage_monitor_voltage(voltage_V);
                        }
                    });
                }
                if (internal_over_voltage_monitor) {
                    internal_over_voltage_monitor->set_limits(get_emergency_over_voltage_threshold(),
                                                              get_error_over_voltage_threshold());
                }
            });

            r_hlc[0]->subscribe_departure_time([this](const std::string& t) {
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_departure_time);
                ev_info.departure_time = t;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_dc_ev_energy_capacity([this](double c) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_dc_ev_energy_capacity);
                ev_info.battery_capacity = c;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_dc_ev_energy_request([this](double c) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_dc_ev_energy_request);
                ev_info.remaining_energy_needed = c;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_dc_full_soc([this](double c) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_dc_full_soc);
                ev_info.battery_full_soc = c;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_dc_bulk_soc([this](double c) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_dc_bulk_soc);
                ev_info.battery_bulk_soc = c;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_dc_ev_remaining_time([this](types::iso15118::DcEvRemainingTime const& t) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_dc_ev_remaining_time);
                ev_info.estimated_time_full = t.ev_remaining_time_to_full_soc;
                ev_info.estimated_time_bulk = t.ev_remaining_time_to_full_bulk_soc;
                p_evse->publish_ev_info(ev_info);
            });

            r_hlc[0]->subscribe_dc_ev_status([this](types::iso15118::DcEvStatus const& s) {
                // FIXME send only on change / throttle messages
                Everest::scoped_lock_timeout lock(ev_info_mutex,
                                                  Everest::MutexDescription::EVSE_subscribe_dc_ev_status);
                ev_info.soc = s.dc_ev_ress_soc;
                p_evse->publish_ev_info(ev_info);
            });

            // SAE J2847/2 Bidi
            if (config.sae_j2847_2_bpt_enabled == true) {

                sae_mode = types::iso15118::string_to_sae_j2847bidi_mode(config.sae_j2847_2_bpt_mode);

                r_hlc[0]->subscribe_sae_bidi_mode_active([this] {
                    sae_bidi_active = true;

                    if (config.sae_j2847_2_bpt_mode == "V2H") {
                        setup_v2h_mode();
                        session_log.evse(true, "SAE J2847 V2H mode is active");
                    } else if (config.sae_j2847_2_bpt_mode == "V2G") {
                        session_log.evse(true, "SAE J2847 V2G mode is active");
                    } else {
                        EVLOG_error << "Unknown mode discovered. Please select V2G or V2H!";
                    }
                });
            }

            // unused vars of HLC for now:

            // ac_close_contactor
            // ac_open_contactor

            // selected_payment_option
            // requested_energy_transfer_mode

            // EV_ChargingSession
            // dc_bulk_charging_complete
            // dc_charging_complete
        } else {
            EVLOG_error << "Unsupported charging mode.";
            exit(255);
        }

        r_hlc[0]->subscribe_selected_service_parameters(
            [this](types::iso15118::SelectedServiceParameters const& parameters) {
                selected_d20_energy_service.emplace(parameters.energy_transfer);
                charger->set_hlc_d20_active();

                session_log.car(true,
                                fmt::format("EV selected service: {}",
                                            types::iso15118::service_category_to_string(parameters.energy_transfer)));
            });

        r_hlc[0]->call_receipt_is_required(config.ev_receipt_required);

        this->update_supported_energy_transfers(initial_energy_transfers);
        r_hlc[0]->call_setup(evseid, sae_mode, config.session_logging);
        this->publish_and_update_supported_energy_transfers();

        if ((config.bpt_channel == "Unified" or config.bpt_channel == "Separated") and
            (config.bpt_generator_mode == "GridFollowing" or config.bpt_generator_mode == "GridForming")) {
            types::iso15118::BptSetup bpt_setup_config{};

            bpt_setup_config.bpt_channel = config.bpt_channel == "Unified" ? types::iso15118::BptChannel::Unified
                                                                           : types::iso15118::BptChannel::Separated;
            bpt_setup_config.generator_mode = config.bpt_generator_mode == "GridFollowing"
                                                  ? types::iso15118::GeneratorMode::GridFollowing
                                                  : types::iso15118::GeneratorMode::GridForming;

            if (config.bpt_grid_code_island_method == "Active" or config.bpt_grid_code_island_method == "Passive") {
                bpt_setup_config.grid_code_detection = config.bpt_grid_code_island_method == "Active"
                                                           ? types::iso15118::GridCodeIslandingDetectionMethod::Active
                                                           : types::iso15118::GridCodeIslandingDetectionMethod::Passive;
            }
            r_hlc[0]->call_bpt_setup(bpt_setup_config);
        }
        // reset error flags
        r_hlc[0]->call_reset_error();

        // implement Auth handlers
        r_hlc[0]->subscribe_require_auth_eim([this]() {
            //  Do we have auth already (i.e. delayed HLC after charging already running)?
            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())) {
                hlc_waiting_for_auth_eim = false;
                hlc_waiting_for_auth_pnc = false;
                r_hlc[0]->call_authorization_response(types::authorization::AuthorizationStatus::Accepted,
                                                      types::authorization::CertificateStatus::NoCertificateAvailable);
                charger->get_stopwatch().mark("Auth EIM Done");
            } else {
                if (config.enable_autocharge) {
                    p_token_provider->publish_provided_token(autocharge_token);
                }
                hlc_waiting_for_auth_eim = true;
                hlc_waiting_for_auth_pnc = false;
            }
        });

        if (not config.autocharge_use_slac_instead_of_hlc) {
            r_hlc[0]->subscribe_evcc_id([this](const std::string& token) {
                autocharge_token = create_autocharge_token(token, config.connector_id);
                car_manufacturer = get_manufacturer_from_mac(token);
                p_evse->publish_car_manufacturer(car_manufacturer);

                {
                    Everest::scoped_lock_timeout lock(ev_info_mutex, Everest::MutexDescription::EVSE_subscribe_evcc_id);
                    ev_info.evcc_id = token;
                    p_evse->publish_ev_info(ev_info);
                }
            });
        }

        r_hlc[0]->subscribe_require_auth_pnc([this](types::authorization::ProvidedIdToken const& _token) {
            // Do we have auth already (i.e. delayed HLC after charging already running)?
            auto copied_token = _token;
            std::vector<int> referenced_connectors = {this->config.connector_id};
            copied_token.connectors.emplace(referenced_connectors);

            if (charger->get_authorized_pnc()) {
                hlc_waiting_for_auth_eim = false;
                hlc_waiting_for_auth_pnc = false;
            } else {
                // only publish token if we are not authorized yet. This enables pause/resume with PnC.
                p_token_provider->publish_provided_token(copied_token);
                hlc_waiting_for_auth_eim = false;
                hlc_waiting_for_auth_pnc = true;
            }
        });

        // Install debug V2G Messages handler if session logging is enabled
        if (config.session_logging) {
            r_hlc[0]->subscribe_v2g_messages(
                [this](types::iso15118::V2gMessages const& v2g_messages) { log_v2g_message(v2g_messages); });

            r_hlc[0]->subscribe_selected_protocol(
                [this](std::string const& selected_protocol) { this->selected_protocol = selected_protocol; });
        }
        // switch to DC mode for first session for AC with SoC
        if (config.ac_with_soc) {

            bsp->signal_event.connect([this](const CPEvent event) {
                if (event == CPEvent::CarUnplugged) {
                    // configure for DC again for next session. Will reset to AC when SoC is received
                    switch_DC_mode();
                }
            });

            charger->signal_ac_with_soc_timeout.connect([this]() { switch_DC_mode(); });

            r_hlc[0]->subscribe_dc_ev_status([this](types::iso15118::DcEvStatus const& status) {
                EVLOG_info << fmt::format("SoC received: {}.", status.dc_ev_ress_soc);
                switch_AC_mode();
            });
        }
    }

    bsp->signal_event.connect([this](const CPEvent event) {
        // Forward events from BSP to SLAC module before we process the events in the charger
        if (slac_enabled) {
            if (event == CPEvent::EFtoBCD) {
                // this means entering BCD from E|F
                r_slac[0]->call_enter_bcd();
            } else if (event == CPEvent::BCDtoEF) {
                r_slac[0]->call_leave_bcd();
            } else if (event == CPEvent::CarPluggedIn) {
                // CC: right now we dont support energy saving mode, so no need to reset slac here.
                // It is more important to start slac as early as possible to avoid unneccesary retries
                // e.g. by Tesla cars which send the first SLAC_PARM_REQ directly after plugin.
                // If we start slac too late, Tesla will do a B->C->DF->B sequence for each retry which
                // may confuse the PWM state machine in some implementations.
                // r_slac[0]->call_reset(true);
                // This is entering BCD from state A
                car_manufacturer = types::evse_manager::CarManufacturer::Unknown;
                r_slac[0]->call_enter_bcd();
            } else if (event == CPEvent::CarUnplugged) {
                // Make a local copy as leave_bcd() will overwrite the slac_unmatched flag
                bool unmatched_on_unplug = not slac_unmatched;
                r_slac[0]->call_leave_bcd();
                if (unmatched_on_unplug) {
                    r_slac[0]->call_reset(false);
                }
                hlc_waiting_for_auth_pnc = false;
                hlc_waiting_for_auth_eim = false;
            }
        }

        if (not r_over_voltage_monitor.empty() and event == CPEvent::CarUnplugged) {
            r_over_voltage_monitor[0]->call_reset_over_voltage_error();
        }
        if (not r_over_voltage_monitor.empty() and (event == CPEvent::CarUnplugged or event == CPEvent::PowerOff)) {
            r_over_voltage_monitor[0]->call_stop();
        }
        if (internal_over_voltage_monitor and (event == CPEvent::CarUnplugged or event == CPEvent::PowerOff)) {
            internal_over_voltage_monitor->stop_monitor();
            internal_over_voltage_monitor->reset();
        }
        if (voltage_plausibility_monitor and (event == CPEvent::CarUnplugged or event == CPEvent::PowerOff)) {
            voltage_plausibility_monitor->stop_monitor();
        }

        charger->bsp_event_queue.push(event);

        // Forward some events to HLC
        if (hlc_enabled) {
            // Reset HLC auth waiting flags on new session
            if (event == CPEvent::CarPluggedIn) {
                r_hlc[0]->call_reset_error();
                r_hlc[0]->call_ac_contactor_closed(false);
                r_hlc[0]->call_stop_charging(false);
                latest_target_voltage = 0;
                latest_target_current = 0;
                hlc_waiting_for_auth_eim = false;
                hlc_waiting_for_auth_pnc = false;
            }

            if (event == CPEvent::PowerOn) {
                contactor_open = false;
                r_hlc[0]->call_ac_contactor_closed(true);
            }

            if (event == CPEvent::PowerOff) {
                contactor_open = true;
                latest_target_voltage = 0;
                latest_target_current = 0;
                r_hlc[0]->call_ac_contactor_closed(false);
            }
        }
    });

    r_bsp->subscribe_ac_nr_of_phases_available([this](int n) { signalNrOfPhasesAvailable(n); });
    r_bsp->subscribe_ac_pp_ampacity(
        [this](types::board_support_common::ProximityPilot const& pp) { bsp->set_pp_ampacity(pp); });

    if (r_powermeter_billing().size() > 0) {
        r_powermeter_billing()[0]->subscribe_powermeter([this](types::powermeter::Powermeter const& p) {
            // Update voltage plausibility monitor with powermeter voltage
            if (voltage_plausibility_monitor && p.voltage_V.has_value() && p.voltage_V.value().DC.has_value()) {
                voltage_plausibility_monitor->update_powermeter_voltage(p.voltage_V.value().DC.value());
            }
            // Inform charger about current charging current. This is used for slow OC detection.
            if (p.current_A and p.current_A.value().L1 and p.current_A.value().L2 and p.current_A.value().L3) {
                charger->set_current_drawn_by_vehicle(p.current_A.value().L1.value(), p.current_A.value().L2.value(),
                                                      p.current_A.value().L3.value());
            }

            // Inform HLC about the power meter data
            if (hlc_enabled) {
                r_hlc[0]->call_update_meter_info(p);

                if (p.power_W and selected_d20_energy_service.has_value()) {
                    r_hlc[0]->call_update_ac_present_power(p.power_W.value());
                }
            }

            // Store local cache
            {
                Everest::scoped_lock_timeout lock(power_mutex, Everest::MutexDescription::EVSE_subscribe_powermeter);
                latest_powermeter_data_billing = p;
            }

            {
                std::scoped_lock<std::mutex> lk(powermeter_mutex);
                initial_powermeter_value_received = true;
            }
            powermeter_cv.notify_one();

            // External Nodered interface
            if (p.phase_seq_error) {
                mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/phaseSeqError", config.connector_id),
                             p.phase_seq_error.value());
            }

            mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/time_stamp", config.connector_id),
                         p.timestamp);

            if (p.power_W) {
                mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/totalKw", config.connector_id),
                             p.power_W.value().total / 1000., 1);
            }

            mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/totalKWattHr", config.connector_id),
                         p.energy_Wh_import.total / 1000.);

            if (p.energy_Wh_export.has_value()) {
                mqtt.publish(
                    fmt::format("everest_external/nodered/{}/powermeter/totalExportKWattHr", config.connector_id),
                    p.energy_Wh_export.value().total / 1000.);
            }

            json j;
            to_json(j, p);
            mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter_json", config.connector_id), j.dump());
            // /External Nodered interface
        });
    }

    if (slac_enabled) {
        r_slac[0]->subscribe_state([this](const types::slac::State s) {
            session_log.evse(true, fmt::format("SLAC {}", types::slac::state_to_string(s)));
            // Notify charger whether matching was started (or is done) or not
            if (s == types::slac::State::UNMATCHED) {
                charger->set_matching_started(false);
                slac_unmatched = true;
            } else {
                charger->set_matching_started(true);
                slac_unmatched = false;
            }
        });

        r_slac[0]->subscribe_request_error_routine([this]() {
            EVLOG_info << "Received request error routine from SLAC in evsemanager\n";
            charger->request_error_sequence();
        });

        r_slac[0]->subscribe_dlink_ready([this](const bool value) {
            session_log.evse(true, fmt::format("D-LINK_READY ({})", value));
            if (hlc_enabled) {
                r_hlc[0]->call_dlink_ready(value);
                charger->get_stopwatch().mark("D-LINK_READY");
            }
        });
    }

    charger->signal_max_current.connect([this](float ampere) {
        // The charger changed the max current setting. Forward to HLC
        if (hlc_enabled) {
            if (not selected_d20_energy_service.has_value() and ampere >= 0.0) {
                r_hlc[0]->call_update_ac_max_current(ampere); // ISO-2
                return;
            }

            if (selected_d20_energy_service.value() == types::iso15118::ServiceCategory::AC or
                selected_d20_energy_service.value() == types::iso15118::ServiceCategory::AC_BPT) {

                types::units::Power target_power = {ampere * static_cast<float>(config.ac_nominal_voltage) *
                                                    hw_capabilities.handle()->max_phase_count_import};

                // TODO(SL): Adding target frequency
                // TODO(SL): Adding reactive power
                r_hlc[0]->call_update_ac_target_values({target_power});
            }
        }
    });

    // Cancel reservations if charger is faulted
    error_handling->signal_error.connect([this](ErrorHandlingEvents event) {
        if (event == ErrorHandlingEvents::ForceEmergencyShutdown or event == ErrorHandlingEvents::ForceErrorShutdown) {
            cancel_reservation(true);
        }
    });

    charger->signal_simple_event.connect([this](types::evse_manager::SessionEventEnum s) {
        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);
        }

        if (not hlc_enabled) {
            return;
        }

        if (s != types::evse_manager::SessionEventEnum::Authorized and
            s != types::evse_manager::SessionEventEnum::SessionFinished) {
            return;
        }

        std::vector<types::iso15118::PaymentOption> payment_options;
        // if pnc is disabled, disable contract installation and central contract validation
        bool _contract_certificate_installation_enabled =
            pnc_enabled ? contract_certificate_installation_enabled.load() : false;
        bool _central_contract_validation_allowed = pnc_enabled ? central_contract_validation_allowed.load() : false;

        if (config.payment_enable_eim) {
            payment_options.push_back(types::iso15118::PaymentOption::ExternalPayment);
        }
        if (pnc_enabled and s == types::evse_manager::SessionEventEnum::SessionFinished) {
            // PnC is enabled and this is a SessionFinished event -> enable Contract payment option
            payment_options.push_back(types::iso15118::PaymentOption::Contract);
        } else {
            // We dont add contract if this is an Authorized event, as in this case the ISO15118 stack
            // should not offer the contract option and certifiate installation service.
            _contract_certificate_installation_enabled = false;
        }

        if (config.payment_enable_eim == false and pnc_enabled == false) {
            EVLOG_warning << "Both payment options are disabled! ExternalPayment is nevertheless enabled in this case.";
            payment_options.push_back(types::iso15118::PaymentOption::ExternalPayment);
        }
        r_hlc[0]->call_session_setup(payment_options, _contract_certificate_installation_enabled,
                                     _central_contract_validation_allowed);
    });

    charger->signal_session_started_event.connect(
        [this](types::evse_manager::StartSessionReason start_reason,
               const std::optional<types::authorization::ProvidedIdToken>& provided_id_token) {
            // Reset EV information on Session start and end
            ev_info = types::evse_manager::EVInfo();
            p_evse->publish_ev_info(ev_info);

            if (not hlc_enabled) {
                return;
            }

            std::vector<types::iso15118::PaymentOption> payment_options;
            // if pnc is disabled, disable contract installation and central contract validation
            bool _contract_certificate_installation_enabled =
                pnc_enabled ? contract_certificate_installation_enabled.load() : false;
            bool _central_contract_validation_allowed =
                pnc_enabled ? central_contract_validation_allowed.load() : false;

            if (start_reason == types::evse_manager::StartSessionReason::Authorized) {
                // Session is already authorized, only use ExternalPayment in PaymentOptions
                payment_options.push_back(types::iso15118::PaymentOption::ExternalPayment);
                _contract_certificate_installation_enabled = false;
                _central_contract_validation_allowed = false;
            } else {
                // Set payment options according to configuration
                if (config.payment_enable_eim) {
                    payment_options.push_back(types::iso15118::PaymentOption::ExternalPayment);
                }
                if (pnc_enabled) {
                    payment_options.push_back(types::iso15118::PaymentOption::Contract);
                }
            }
            r_hlc[0]->call_session_setup(payment_options, _contract_certificate_installation_enabled,
                                         _central_contract_validation_allowed);
        });

    invoke_ready(*p_evse);
    invoke_ready(*p_energy_grid);
    invoke_ready(*p_token_provider);
    invoke_ready(*p_random_delay);

    if (config.ac_with_soc) {
        setup_fake_DC_mode();
    } else {
        charger->setup(config.has_ventilation,
                       (config.charge_mode == "DC" ? Charger::ChargeMode::DC : Charger::ChargeMode::AC), hlc_enabled,
                       config.ac_hlc_use_5percent, config.ac_enforce_hlc, false,
                       config.soft_over_current_tolerance_percent, config.soft_over_current_measurement_noise_A,
                       config.switch_3ph1ph_delay_s, config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms,
                       config.state_F_after_fault_ms, config.fail_on_powermeter_errors, config.raise_mrec9,
                       config.sleep_before_enabling_pwm_hlc_mode_ms,
                       utils::get_session_id_type_from_string(config.session_id_type),
                       config.hlc_charge_loop_without_energy_timeout_s);
    }

    telemetryThreadHandle = std::thread([this]() {
        while (not telemetryThreadHandle.shouldExit()) {
            sleep(10);
            auto p = get_latest_powermeter_data_billing();
            Everest::TelemetryMap telemetry_data{{"timestamp", p.timestamp},
                                                 {"type", "power_meter"},
                                                 {"meter_id", p.meter_id.value_or("N/A")},
                                                 {"energy_import_total_Wh", p.energy_Wh_import.total}};

            if (p.energy_Wh_import.L1) {
                telemetry_data["energy_import_L1_Wh"] = p.energy_Wh_import.L1.value();
            }
            if (p.energy_Wh_import.L2) {
                telemetry_data["energy_import_L2_Wh"] = p.energy_Wh_import.L2.value();
            }
            if (p.energy_Wh_import.L3) {
                telemetry_data["energy_import_L3_Wh"] = p.energy_Wh_import.L3.value();
            }

            if (p.energy_Wh_export) {
                telemetry_data["energy_export_total_Wh"] = p.energy_Wh_export.value().total;
            }
            if (p.energy_Wh_export and p.energy_Wh_export.value().L1) {
                telemetry_data["energy_export_L1_Wh"] = p.energy_Wh_export.value().L1.value();
            }
            if (p.energy_Wh_export and p.energy_Wh_export.value().L2) {
                telemetry_data["energy_export_L2_Wh"] = p.energy_Wh_export.value().L2.value();
            }
            if (p.energy_Wh_export and p.energy_Wh_export.value().L3) {
                telemetry_data["energy_export_L3_Wh"] = p.energy_Wh_export.value().L3.value();
            }

            if (p.power_W) {
                telemetry_data["power_total_W"] = p.power_W.value().total;
            }
            if (p.power_W and p.power_W.value().L1) {
                telemetry_data["power_L1_W"] = p.power_W.value().L1.value();
            }
            if (p.power_W and p.power_W.value().L2) {
                telemetry_data["power_L3_W"] = p.power_W.value().L2.value();
            }
            if (p.power_W and p.power_W.value().L3) {
                telemetry_data["power_L3_W"] = p.power_W.value().L3.value();
            }

            if (p.VAR) {
                telemetry_data["var_total"] = p.VAR.value().total;
            }
            if (p.VAR and p.VAR.value().L1) {
                telemetry_data["var_L1"] = p.VAR.value().L1.value();
            }
            if (p.VAR and p.VAR.value().L2) {
                telemetry_data["var_L1"] = p.VAR.value().L2.value();
            }
            if (p.VAR and p.VAR.value().L3) {
                telemetry_data["var_L1"] = p.VAR.value().L3.value();
            }

            if (p.voltage_V and p.voltage_V.value().L1) {
                telemetry_data["voltage_L1_V"] = p.voltage_V.value().L1.value();
            }
            if (p.voltage_V and p.voltage_V.value().L2) {
                telemetry_data["voltage_L2_V"] = p.voltage_V.value().L2.value();
            }
            if (p.voltage_V and p.voltage_V.value().L3) {
                telemetry_data["voltage_L3_V"] = p.voltage_V.value().L3.value();
            }
            if (p.voltage_V and p.voltage_V.value().DC) {
                telemetry_data["voltage_DC_V"] = p.voltage_V.value().DC.value();
            }

            if (p.current_A and p.current_A.value().L1) {
                telemetry_data["current_L1_A"] = p.current_A.value().L1.value();
            }
            if (p.current_A and p.current_A.value().L2) {
                telemetry_data["current_L2_A"] = p.current_A.value().L2.value();
            }
            if (p.current_A and p.current_A.value().L3) {
                telemetry_data["current_L3_A"] = p.current_A.value().L3.value();
            }
            if (p.current_A and p.current_A.value().DC) {
                telemetry_data["current_DC_A"] = p.current_A.value().DC.value();
            }

            if (p.frequency_Hz) {
                telemetry_data["frequency_L1_Hz"] = p.frequency_Hz.value().L1;
            }
            if (p.frequency_Hz and p.frequency_Hz.value().L2) {
                telemetry_data["frequency_L2_Hz"] = p.frequency_Hz.value().L2.value();
            }
            if (p.frequency_Hz and p.frequency_Hz.value().L3) {
                telemetry_data["frequency_L3_Hz"] = p.frequency_Hz.value().L3.value();
            }

            if (p.phase_seq_error) {
                telemetry_data["phase_seq_error"] = p.phase_seq_error.value();
            }

            // Publish as external telemetry data
            telemetry.publish("livedata", "power_meter", telemetry_data);
        }
    });

    {
        // wait for first powermeter value
        std::unique_lock<std::mutex> lk(powermeter_mutex);
        this->powermeter_cv.wait_for(lk, std::chrono::milliseconds(this->config.initial_meter_value_timeout_ms),
                                     [this] { return initial_powermeter_value_received; });
    }

    // Resuming left-over transaction from e.g. powerloss. This information allows other modules like to OCPP to be
    // informed that the EvseManager is aware of previous sessions so that no individual cleanup is required
    const auto session_id = store->get_session();
    if (!session_id.empty()) {
        charger->signal_session_resumed_event(session_id);
    }

    // By default cleanup left-over transaction from e.g. power loss
    // TOOD: Add resume handling
    charger->cleanup_transactions_on_startup();

    //  start with a limit of 0 amps. We will get a budget from EnergyManager that is locally limited by hw
    //  caps.
    charger->set_max_current(0.0F, steady_clock::now() + std::chrono::seconds(120));
    this->p_evse->publish_waiting_for_external_ready(config.external_ready_to_start_charging);
    if (not config.external_ready_to_start_charging) {
        // immediately ready, otherwise delay until we get the external signal
        this->ready_to_start_charging();
    }
}

void EvseManager::ready_to_start_charging() {
    Everest::scoped_lock_timeout lock(charger_ready_mutex, Everest::MutexDescription::EVSE_charger_ready);
    if (charger_ready) {
        EVLOG_warning << "Already ready to start charging!";
        return;
    }
    charger_ready = true;

    p_evse->publish_supported_energy_transfer_modes(*this->supported_energy_transfers.handle());

    timepoint_ready_for_charging = std::chrono::steady_clock::now();
    charger->run();

    // this will publish a session event Enabled or Disabled that allows other modules the retrieve this state on
    // startup
    charger->enable_disable_initial_state_publish();

    this->p_evse->publish_ready(true);
    EVLOG_info << fmt::format(fmt::emphasis::bold | fg(fmt::terminal_color::green), "🌀🌀🌀 Ready to start charging 🌀🌀🌀");
    if (!initial_powermeter_value_received) {
        EVLOG_warning << "No powermeter value received yet!";
    }
}

types::powermeter::Powermeter EvseManager::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;
}

types::evse_board_support::HardwareCapabilities EvseManager::get_hw_capabilities() {
    return *hw_capabilities.handle();
}

int32_t EvseManager::get_reservation_id() {
    Everest::scoped_lock_timeout lock(reservation_mutex, Everest::MutexDescription::EVSE_get_reservation_id);
    return reservation_id;
}

void EvseManager::switch_DC_mode() {
    setup_fake_DC_mode();
}

void EvseManager::switch_AC_mode() {
    setup_AC_mode();
}

// This sets up a fake DC mode that is just supposed to work until we get the SoC.
// It is only used for AC<>DC<>AC<>DC mode to get AC charging with SoC.
void EvseManager::setup_fake_DC_mode() {
    charger->setup(config.has_ventilation, Charger::ChargeMode::DC, hlc_enabled, config.ac_hlc_use_5percent,
                   config.ac_enforce_hlc, false, config.soft_over_current_tolerance_percent,
                   config.soft_over_current_measurement_noise_A, config.switch_3ph1ph_delay_s,
                   config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms, config.state_F_after_fault_ms,
                   config.fail_on_powermeter_errors, config.raise_mrec9, config.sleep_before_enabling_pwm_hlc_mode_ms,
                   utils::get_session_id_type_from_string(config.session_id_type),
                   config.hlc_charge_loop_without_energy_timeout_s);

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

    // Set up energy transfer modes for HLC. For now we only support either DC or AC, not both at the same time.
    std::vector<types::iso15118::EnergyTransferMode> transfer_modes;

    transfer_modes.push_back(types::iso15118::EnergyTransferMode::DC_core);
    transfer_modes.push_back(types::iso15118::EnergyTransferMode::DC_extended);
    transfer_modes.push_back(types::iso15118::EnergyTransferMode::DC_combo_core);
    transfer_modes.push_back(types::iso15118::EnergyTransferMode::DC_unique);

    types::iso15118::DcEvsePresentVoltageCurrent present_values;
    present_values.evse_present_voltage = 400; // FIXME: set a correct values
    present_values.evse_present_current = 0;

    r_hlc[0]->call_update_dc_present_values(present_values);

    types::iso15118::DcEvseMaximumLimits evse_max_limits;
    evse_max_limits.evse_maximum_current_limit = 400;
    evse_max_limits.evse_maximum_power_limit = 200000;
    evse_max_limits.evse_maximum_voltage_limit = 1000;
    r_hlc[0]->call_update_dc_maximum_limits(evse_max_limits);

    types::iso15118::DcEvseMinimumLimits evse_min_limits;
    evse_min_limits.evse_minimum_current_limit = 0;
    evse_min_limits.evse_minimum_voltage_limit = 0;
    r_hlc[0]->call_update_dc_minimum_limits(evse_min_limits);

    constexpr auto sae_mode = types::iso15118::SaeJ2847BidiMode::None;

    r_hlc[0]->call_setup(evseid, sae_mode, config.session_logging);
    this->update_supported_energy_transfers(transfer_modes);
    this->publish_and_update_supported_energy_transfers();
}

void EvseManager::setup_AC_mode() {
    charger->setup(config.has_ventilation, Charger::ChargeMode::AC, hlc_enabled, config.ac_hlc_use_5percent,
                   config.ac_enforce_hlc, true, config.soft_over_current_tolerance_percent,
                   config.soft_over_current_measurement_noise_A, config.switch_3ph1ph_delay_s,
                   config.switch_3ph1ph_cp_state, config.soft_over_current_timeout_ms, config.state_F_after_fault_ms,
                   config.fail_on_powermeter_errors, config.raise_mrec9, config.sleep_before_enabling_pwm_hlc_mode_ms,
                   utils::get_session_id_type_from_string(config.session_id_type),
                   config.hlc_charge_loop_without_energy_timeout_s);

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

    // Set up energy transfer modes for HLC. For now we only support either DC or AC, not both at the same time.
    std::vector<types::iso15118::EnergyTransferMode> transfer_modes;

    transfer_modes.push_back(types::iso15118::EnergyTransferMode::AC_single_phase_core);

    if (hw_capabilities.handle()->max_phase_count_import == 3) {
        transfer_modes.push_back(types::iso15118::EnergyTransferMode::AC_three_phase_core);
    }

    types::iso15118::SetupPhysicalValues setup_physical_values;

    constexpr auto sae_mode = types::iso15118::SaeJ2847BidiMode::None;

    if (hlc_enabled) {
        r_hlc[0]->call_setup(evseid, sae_mode, config.session_logging);
        this->update_supported_energy_transfers(transfer_modes);
        this->publish_and_update_supported_energy_transfers();
    }
}

void EvseManager::setup_v2h_mode() {

    const auto timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
    types::energy::ExternalLimits external_limits;
    types::energy::ScheduleReqEntry target_entry;
    target_entry.timestamp = timestamp;
    target_entry.limits_to_leaves.total_power_W = {powersupply_capabilities.max_import_power_W.value_or(0.),
                                                   info.id + "/setup_v2h_mode"};

    types::energy::ScheduleReqEntry zero_entry;
    zero_entry.timestamp = timestamp;
    zero_entry.limits_to_leaves.total_power_W = {0};

    external_limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, target_entry);
    external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, zero_entry);

    update_local_energy_limit(external_limits);
}

bool EvseManager::update_local_energy_limit(types::energy::ExternalLimits l) {
    std::scoped_lock lock(external_local_limits_mutex);
    external_local_energy_limits = l;
    // wait for EnergyManager to assign optimized current on next opimizer run
    return true;
}

// Helper function to set a watt limit in an ExternalLimits type
bool EvseManager::update_max_watt_limit(types::energy::ExternalLimits& limits, float max_watt_export,
                                        std::optional<float> max_watt_import) {
    types::energy::ScheduleReqEntry e;
    e.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());

    e.limits_to_leaves.total_power_W = {max_watt_export, info.id + " update_max_watt_limit"};
    limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, e);

    e.limits_to_leaves.total_power_W = {max_watt_import.value_or(0.0f), info.id + " update_max_watt_limit"};
    limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, e);

    return true;
}

void EvseManager::update_to_zero_discharge_limit(types::energy::ExternalLimits& limits) {
    types::energy::ScheduleReqEntry e;
    e.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());

    e.limits_to_leaves.ac_max_current_A = {0., info.id + " set_zero_discharge_limit"};
    e.limits_to_leaves.total_power_W = {0., info.id + " set_zero_discharge_limit"};
    limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, e);
}

// Helper function to set a current limit in an ExternalLimits type
bool EvseManager::update_max_current_limit(types::energy::ExternalLimits& limits, float max_current_import,
                                           float max_current_export) {
    if (config.charge_mode == "DC") {
        return false;
    }

    types::energy::ScheduleReqEntry e;
    e.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());

    e.limits_to_leaves.ac_max_current_A = {max_current_import, info.id + " update_max_current_limit"};
    limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, e);

    e.limits_to_leaves.ac_max_current_A = {max_current_export, info.id + " update_max_current_limit"};
    limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, e);

    return true;
}

bool EvseManager::reserve(int32_t id, const bool signal_reservation_event) {
    EVLOG_debug << "Reserve called for reservation id " << id
                << ", signal reservation event: " << signal_reservation_event;

    // is the evse Unavailable?
    if (charger->get_current_state() == Charger::EvseState::Disabled) {
        EVLOG_info << "Rejecting reservation because charger is disabled.";
        return false;
    }

    // is the evse faulted?
    if (charger->stop_charging_on_fatal_error()) {
        EVLOG_info << "Rejecting reservation because of a fatal error.";
        return false;
    }

    // is the connector currently ready to accept a new car?
    if (charger->get_current_state() not_eq Charger::EvseState::Idle) {
        EVLOG_info << "Rejecting reservation because evse is not idle";
        return false;
    }

    Everest::scoped_lock_timeout lock(reservation_mutex, Everest::MutexDescription::EVSE_reserve);

    const bool overwrite_reservation = (this->reservation_id == id);

    if (reserved && this->reservation_id != -1) {
        EVLOG_info << "Rejecting reservation because evse is already reserved for reservation id "
                   << this->reservation_id;
    }

    // Check if this evse is not already reserved, or overwrite reservation if it is for the same reservation id.
    if (not reserved || this->reservation_id == -1 || overwrite_reservation) {
        EVLOG_debug << "Make the reservation with id " << id;
        reserved = true;
        if (id >= 0) {
            this->reservation_id = id;
        }

        // When overwriting the reservation, don't signal.
        if ((not overwrite_reservation || this->reservation_id == -1) && signal_reservation_event) {
            // publish event to other modules
            types::evse_manager::SessionEvent se;
            se.event = types::evse_manager::SessionEventEnum::ReservationStart;

            // Normally we should signal for each connector when an evse is reserved, but since in this implementation
            // each evse only has one connector, this is sufficient for now.
            signalReservationEvent(se);
        }

        return true;
    }

    return false;
}

void EvseManager::cancel_reservation(bool signal_event) {

    Everest::scoped_lock_timeout lock(reservation_mutex, Everest::MutexDescription::EVSE_cancel_reservation);
    if (reserved) {
        EVLOG_debug << "Reservation cancelled";
        reserved = false;
        this->reservation_id = -1;

        // publish event to other modules
        if (signal_event) {
            types::evse_manager::SessionEvent se;
            se.event = types::evse_manager::SessionEventEnum::ReservationEnd;
            signalReservationEvent(se);
        }
    }
}

bool EvseManager::is_reserved() {
    Everest::scoped_lock_timeout lock(reservation_mutex, Everest::MutexDescription::EVSE_is_reserved);
    return reserved;
}

bool EvseManager::get_hlc_waiting_for_auth_pnc() {
    return hlc_waiting_for_auth_pnc;
}

void EvseManager::set_pnc_enabled(const bool value) {
    pnc_enabled = value;
}

void EvseManager::set_central_contract_validation_allowed(const bool value) {
    central_contract_validation_allowed = value;
}

void EvseManager::set_contract_certificate_installation_enabled(const bool value) {
    contract_certificate_installation_enabled = value;
}

void EvseManager::publish_and_update_supported_energy_transfers() {
    std::vector<types::iso15118::EnergyTransferMode> transfer_modes;
    {
        auto transfer_modes_handle = this->supported_energy_transfers.handle();
        transfer_modes = *transfer_modes_handle;
    }

    p_evse->publish_supported_energy_transfer_modes(transfer_modes);

    if (hlc_enabled and not r_hlc.empty()) {
        r_hlc[0]->call_update_energy_transfer_modes(transfer_modes);
    }
}

bool EvseManager::update_supported_energy_transfers(
    const std::vector<types::iso15118::EnergyTransferMode>& energy_transfers) {
    auto handle = this->supported_energy_transfers.handle();

    if (*handle == energy_transfers) {
        return false;
    }

    *handle = energy_transfers;
    return true;
}

bool EvseManager::update_supported_energy_transfers(const types::iso15118::EnergyTransferMode& energy_transfer) {
    return update_supported_energy_transfers(std::vector<types::iso15118::EnergyTransferMode>{energy_transfer});
}

void EvseManager::update_hlc_ac_parameters() {
    // Copy hw_caps before acquiring hlc_ac_parameters_mutex to avoid holding two locks simultaneously
    const auto hw_caps = *hw_capabilities.handle();
    std::scoped_lock lock(hlc_ac_parameters_mutex);

    types::iso15118::AcEvseMaximumPower ac_maximum_power;
    const float max_charge_power_per_phase = config.ac_nominal_voltage * hw_caps.max_current_A_import;
    const float max_discharge_power_per_phase = config.ac_nominal_voltage * hw_caps.max_current_A_export;

    ac_maximum_power.charge_power = {
        max_charge_power_per_phase * hw_caps.max_phase_count_import,
        max_charge_power_per_phase,
        hw_caps.max_phase_count_import >= 2 ? std::make_optional(max_charge_power_per_phase) : std::nullopt,
        hw_caps.max_phase_count_import == 3 ? std::make_optional(max_charge_power_per_phase) : std::nullopt,
    };
    ac_maximum_power.discharge_power.emplace(types::units::Power{
        max_discharge_power_per_phase * hw_caps.max_phase_count_export,
        max_discharge_power_per_phase,
        hw_caps.max_phase_count_export >= 2 ? std::make_optional(max_discharge_power_per_phase) : std::nullopt,
        hw_caps.max_phase_count_export == 3 ? std::make_optional(max_discharge_power_per_phase) : std::nullopt,
    });
    r_hlc[0]->call_update_ac_maximum_limits(ac_maximum_power);

    types::iso15118::AcEvseMinimumPower ac_minimum_power;
    const float min_charge_power_per_phase = config.ac_nominal_voltage * hw_caps.min_current_A_import;
    const float min_discharge_power_per_phase = config.ac_nominal_voltage * hw_caps.min_current_A_export;

    ac_minimum_power.charge_power = {
        min_charge_power_per_phase * hw_caps.max_phase_count_import,
        min_charge_power_per_phase,
        hw_caps.max_phase_count_import >= 2 ? std::make_optional(min_charge_power_per_phase) : std::nullopt,
        hw_caps.max_phase_count_import == 3 ? std::make_optional(min_charge_power_per_phase) : std::nullopt,
    };

    ac_minimum_power.discharge_power.emplace(types::units::Power{
        min_discharge_power_per_phase * hw_caps.max_phase_count_export,
        min_discharge_power_per_phase,
        hw_caps.max_phase_count_export >= 2 ? std::make_optional(min_discharge_power_per_phase) : std::nullopt,
        hw_caps.max_phase_count_export == 3 ? std::make_optional(min_discharge_power_per_phase) : std::nullopt,
    });
    r_hlc[0]->call_update_ac_minimum_limits(ac_minimum_power);

    std::vector<types::iso15118::Connector> ac_connectors{types::iso15118::Connector::SinglePhase};
    if (hw_caps.max_phase_count_import == 3) {
        ac_connectors.push_back(types::iso15118::Connector::ThreePhase);
    }
    r_hlc[0]->call_update_ac_parameters({50, static_cast<float>(config.ac_nominal_voltage), ac_connectors, std::nullopt,
                                         std::nullopt}); // TODO(sl): Getting nominal frequency
}

void EvseManager::log_v2g_message(types::iso15118::V2gMessages const& v2g_messages) {
    if (not config.session_logging) {
        return;
    }
    const std::string msg = types::iso15118::v2g_message_id_to_string(v2g_messages.id);
    const std::string xml = v2g_messages.xml.value_or("");
    const std::string json_str = v2g_messages.v2g_json.value_or("");
    const std::string exi_hex = v2g_messages.exi.value_or("");
    const std::string exi_base64 = v2g_messages.exi_base64.value_or("");

    // All messages from EVSE contain Req and all originating from Car contain Res
    if (msg.find("Res") == std::string::npos) {
        session_log.car(true, fmt::format("V2G {}", msg), xml, exi_hex, exi_base64, json_str);
    } else {
        session_log.evse(true, fmt::format("V2G {}", msg), xml, exi_hex, exi_base64, json_str);
    }
}

void EvseManager::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);
        hlc_waiting_for_auth_eim = false;
        hlc_waiting_for_auth_pnc = false;
        charger->get_stopwatch().mark("Auth PnC Done");
    }

    if (hlc_waiting_for_auth_eim and charger->get_authorized_eim()) {
        r_hlc[0]->call_authorization_response(types::authorization::AuthorizationStatus::Accepted,
                                              types::authorization::CertificateStatus::NoCertificateAvailable);
        hlc_waiting_for_auth_eim = false;
        hlc_waiting_for_auth_pnc = false;
        charger->get_stopwatch().mark("Auth EIM Done");
    }
}

static double get_cable_check_voltage(double ev_max_cpd, double evse_max_cpd) {
    double cable_check_voltage = 500;
    // IEC 61851-23 (2023) CC.4.1.2 / Formular CC.1
    if (ev_max_cpd <= 500) {
        if ((ev_max_cpd + 50) < cable_check_voltage) {
            cable_check_voltage = (ev_max_cpd + 50);
        }
        if (evse_max_cpd < cable_check_voltage) {
            cable_check_voltage = evse_max_cpd;
        }
    } else {
        cable_check_voltage = evse_max_cpd;
        if (1.1 * ev_max_cpd < cable_check_voltage) {
            cable_check_voltage = 1.1 * ev_max_cpd;
        }
    }

    return cable_check_voltage;
}

double EvseManager::get_emergency_over_voltage_threshold() {

    float ev_max_voltage = 500.;

    if (ev_info.maximum_voltage_limit.has_value()) {
        ev_max_voltage = ev_info.maximum_voltage_limit.value();
    } else {
        EVLOG_error << "OverVoltageThreshold: Did not receive EV maximum voltage, falling back to 500V";
    }

    auto evse_caps = get_powersupply_capabilities();

    double negotiated_max_voltage = std::min(ev_max_voltage, evse_caps.max_export_voltage_V);

    double ovp = 550.;

    if (negotiated_max_voltage > 1000) {
        // IEC 61851-23-3 (DRAFT 2025) Table 202
        ovp = 1375.;
    } else if (negotiated_max_voltage > 850) {
        // IEC 61851-23 (2023) 6.3.1.106.2 Table 103
        ovp = 1100.;
    } else if (negotiated_max_voltage > 750) {
        // IEC 61851-23 (2023) 6.3.1.106.2 Table 103
        ovp = 935.;
    } else if (negotiated_max_voltage > 500) {
        // IEC 61851-23 (2023) 6.3.1.106.2 Table 103
        ovp = 825.;
    }

    return ovp;
}

double EvseManager::get_error_over_voltage_threshold() {
    double ev_max_voltage = 500;

    if (ev_info.maximum_voltage_limit.has_value()) {
        ev_max_voltage = ev_info.maximum_voltage_limit.value();
    } else {
        EVLOG_error << "OverVoltageThreshold: Did not receive EV maximum voltage, falling back to 500V";
    }

    return ev_max_voltage;
}

bool EvseManager::cable_check_should_exit() {
    return charger->get_current_state() not_eq Charger::EvseState::PrepareCharging;
}

bool EvseManager::check_voltage_to_protective_earth_in_range(types::isolation_monitor::IsolationMeasurement m) {
    static constexpr double MAX_VOLTAGE_STATIC = 550.0; // defined by IEC 61851-23:2023, $6.3.1.112.2
    if (m.voltage_V.has_value() and m.voltage_to_earth_l1e_V.has_value() and m.voltage_to_earth_l2e_V.has_value()) {
        // This may trigger while ramping down voltage so we should try to check against the minium valid voltage the
        // the power supply can deliver. If the UL1e/2e is poorly synchronized to the actual voltage measurement it may
        // trigger false positives thus limiting the checks to min_export_voltage
        if (m.voltage_V.value() > powersupply_capabilities.min_export_voltage_V) {
            // implemented according to the standard see IEC 61851-23:2023, $6.3.1.112.2
            // if the charger rating is above 500, use as maximum value 110% of actual voltage
            // if the charger rating is below 500, use as maxiumum value 550V
            if (powersupply_capabilities.max_export_voltage_V > 500) {
                double max_allowed_value = m.voltage_V.value() * 1.1;
                return (std::fabs(m.voltage_to_earth_l1e_V.value()) < max_allowed_value and
                        std::fabs(m.voltage_to_earth_l2e_V.value()) < max_allowed_value);
            } else {
                return (std::fabs(m.voltage_to_earth_l1e_V.value()) < MAX_VOLTAGE_STATIC and
                        std::fabs(m.voltage_to_earth_l2e_V.value()) < MAX_VOLTAGE_STATIC);
            }
        }
    }
    // if we can't check, assume all good
    return true;
}

bool EvseManager::check_isolation_resistance_in_range(double resistance) {
    if (resistance < CABLECHECK_INSULATION_FAULT_RESISTANCE_OHM) {
        session_log.evse(false, fmt::format("Isolation measurement FAULT R_F {}.", resistance));
        r_hlc[0]->call_update_isolation_status(types::iso15118::IsolationStatus::Fault);
        return false;
    } else {
        session_log.evse(false, fmt::format("Isolation measurement Ok R_F {}.", resistance));
        r_hlc[0]->call_update_isolation_status(types::iso15118::IsolationStatus::Valid);
    }
    return true;
}

void EvseManager::cable_check() {

    if (r_imd.empty()) {
        // If no IMD is connected, we skip isolation checking.
        EVLOG_info << "No IMD: skipping cable check.";
        r_hlc[0]->call_update_isolation_status(types::iso15118::IsolationStatus::No_IMD);
        r_hlc[0]->call_cable_check_finished(true);
        return;
    }

    // start cable check in a seperate thread.
    std::thread t([this]() {
        session_log.evse(true, "Start cable check...");
        charger->get_stopwatch().report_phase();
        charger->get_stopwatch().mark_phase("CableCheck");

        // Verify output is below 60V initially
        if (not wait_powersupply_DC_below_voltage(CABLECHECK_SAFE_VOLTAGE)) {
            std::ostringstream oss;
            oss << "Voltage did not drop below " << CABLECHECK_SAFE_VOLTAGE << "V within timeout.";
            fail_cable_check(oss.str());
            return;
        }
        charger->get_stopwatch().mark("<60V");

        // Allow closing from HLC perspective, it will wait for CP state C in Charger IEC state machine as well.
        session_log.car(true, "DC HLC Close contactor (in CableCheck)");
        charger->set_hlc_allow_close_contactor(true);

        // Some HW platforms require the self test to be performed at the beginning of cablecheck phase.
        // They will close the relays only after the self test was successful.
        // This is done at a configurable voltage. As only the self test is done and no isolation resistance
        // measurement, this voltage may be different from the voltage used later to measure the resistance
        // (which is derived from the ev_max_voltage instead)
        if (config.cable_check_enable_imd_self_test_relays_open) {
            session_log.evse(true, "IMD Early self test in cablecheck");
            // Set power supply to configured voltage for the self test
            if (not powersupply_DC_set(config.cable_check_relays_open_voltage_V, CABLECHECK_CURRENT_LIMIT)) {
                fail_cable_check(
                    "CableCheck: Could not set DC power supply voltage and current for early IMD self test.");
                return;
            }
            EVLOG_info << "CableCheck early IMD self test: Using " << config.cable_check_relays_open_voltage_V << " V";

            powersupply_DC_on();

            if (not wait_powersupply_DC_voltage_reached(config.cable_check_relays_open_voltage_V)) {
                std::ostringstream oss;
                oss << "CableCheck: Voltage did not rise to " << config.cable_check_relays_open_voltage_V
                    << " V within timeout";
                fail_cable_check(oss.str());
                return;
            }

            selftest_result.clear();
            r_imd[0]->call_start_self_test(config.cable_check_relays_open_voltage_V);
            EVLOG_info << "CableCheck: Early IMD self test started.";

            // Wait for the result of the self test
            bool result{false};
            bool result_received{false};

            for (int wait_seconds = 0; wait_seconds < CABLECHECK_SELFTEST_TIMEOUT; wait_seconds++) {
                if (cable_check_should_exit()) {
                    fail_cable_check("Cancel cable check");
                    return;
                }
                if (selftest_result.wait_for(result, 1s)) {
                    result_received = true;
                    break;
                }
            }

            if (not result_received) {
                fail_cable_check("CableCheck: Did not get a early self test result from IMD within timeout");
                return;
            }

            if (not result) {
                EVLOG_error << "CableCheck: Early IMD Self test failed";
                fail_cable_check("Early IMD self test failed during cable check");
                return;
            }

            powersupply_DC_off();
            charger->get_stopwatch().mark("Early IMD self test");
        }

        // normally contactors should be closed before entering cable check routine.
        // On some hardware implementation it may take some time until the confirmation arrives though,
        // so we wait with a timeout here until the contactors are confirmed to be closed.

        Timeout timeout;
        timeout.start(std::chrono::seconds(config.cable_check_relays_closed_timeout_s));

        while (not timeout.reached() and not cable_check_should_exit()) {
            if (not contactor_open) {
                break;
            }
            std::this_thread::sleep_for(100ms);
        }

        // If relais are still open after timeout, give up
        if (contactor_open) {
            fail_cable_check("CableCheck: Contactors are still open after timeout, giving up.");
            return;
        }

        charger->get_stopwatch().mark("Relay On");

        // Get correct voltage used to test the isolation
        for (int retry_ev_info = 0; retry_ev_info < 10; retry_ev_info++) {
            auto ev_info = get_ev_info();
            if (ev_info.maximum_voltage_limit.has_value()) {
                break;
            }
            std::this_thread::sleep_for(100ms);
        }

        float ev_max_voltage = 500.;

        if (ev_info.maximum_voltage_limit.has_value()) {
            EVLOG_info << "EV reports " << ev_info.maximum_voltage_limit.value() << " V as maximum voltage";
            ev_max_voltage = ev_info.maximum_voltage_limit.value();
        } else {
            EVLOG_error << "CableCheck: Did not receive EV maximum voltage, falling back to 500V";
        }

        auto evse_caps = get_powersupply_capabilities();

        double cable_check_voltage = get_cable_check_voltage(ev_max_voltage, evse_caps.max_export_voltage_V);

        // Allow overriding the cable check voltage from a configuration value
        if (config.dc_isolation_voltage_V > 0) {
            cable_check_voltage = config.dc_isolation_voltage_V;
        }

        charger->get_stopwatch().mark("EVInfo");

        // Set the DC ouput voltage for testing
        if (not powersupply_DC_set(cable_check_voltage, CABLECHECK_CURRENT_LIMIT)) {
            fail_cable_check("CableCheck: Could not set DC power supply voltage and current.");
            return;
        } else {
            EVLOG_info << "CableCheck: Using " << cable_check_voltage << " V";
        }

        // Switch on output voltage
        powersupply_DC_on();

        // Wait until the voltage has rised to the target value.
        // This also handles the short circuit test according to IEC 61851-23 (2023) 6.3.1.109:
        // CC.7.6.20.3: the maximum R for the short circuit test is 110 Ohms.
        // CC.7.6.20.7: maximum current should be reduced to <5A within 1s. We set a current limit below 5A, so the
        // power supply should always achieve that.
        // Within 2.5s present voltage at side B must be below 60V. As the power supply ramp up speed varies greatly,
        // we can only achieve this by limiting the current to I < cable_check_voltage/110 Ohm. The hard coded limit
        // above fulfills that for all voltage ranges.
        if (not wait_powersupply_DC_voltage_reached(cable_check_voltage)) {
            std::ostringstream oss;
            oss << "CableCheck: Voltage did not rise to " << cable_check_voltage << " V within timeout";
            fail_cable_check(oss.str());
            return;
        }

        charger->get_stopwatch().mark("VRampUp");

        // CC 4.1.3: Now relais are closed, voltage is up. We need to perform a self test of the IMD device
        if (config.cable_check_enable_imd_self_test) {
            selftest_result.clear();
            r_imd[0]->call_start_self_test(cable_check_voltage);
            EVLOG_info << "CableCheck: IMD self test started.";

            // Wait for the result of the self test
            bool result{false};
            bool result_received{false};

            for (int wait_seconds = 0; wait_seconds < CABLECHECK_SELFTEST_TIMEOUT; wait_seconds++) {
                if (cable_check_should_exit()) {
                    fail_cable_check("Cancel cable check");
                    return;
                }
                if (selftest_result.wait_for(result, 1s)) {
                    result_received = true;
                    break;
                }
            }

            if (not result_received) {
                fail_cable_check("CableCheck: Did not get a self test result from IMD within timeout");
                return;
            }

            if (not result) {
                EVLOG_error << "CableCheck: IMD Self test failed";
                fail_cable_check("IMD self test failed during cable check");
                return;
            }
        }
        charger->get_stopwatch().mark("Self test");

        // CC.4.1.4: Perform the insulation resistance check
        imd_start();

        if (config.cable_check_wait_number_of_imd_measurements > 0) {
            // read out new isolation resistance value
            isolation_measurement.clear();
            types::isolation_monitor::IsolationMeasurement m;

            EVLOG_info << "CableCheck: Waiting for " << config.cable_check_wait_number_of_imd_measurements
                       << " isolation measurement sample(s)";
            // Wait for N isolation measurement values
            for (int i = 0; i < config.cable_check_wait_number_of_imd_measurements; i++) {
                if (not isolation_measurement.wait_for(m, 5s) or cable_check_should_exit()) {
                    imd_stop();
                    fail_cable_check("CableCheck: Did not receive isolation measurement from IMD within 5 seconds.");
                    return;
                }
            }

            charger->get_stopwatch().mark("Measure");

            // Now the value is valid and can be trusted.
            // Verify it is within ranges. Fault is <100 kOhm
            // Note that 2023 edition removed the warning level which was included in the 2014 edition.
            // Refer to IEC 61851-23 (2023) 6.3.1.105 and CC.4.1.2 / CC.4.1.4
            if (not check_isolation_resistance_in_range(m.resistance_F_Ohm)) {
                imd_stop();
                std::ostringstream oss;
                oss << "Isolation resistance too low: " << m.resistance_F_Ohm << " Ohm";
                error_handling->raise_isolation_resistance_fault(oss.str(), "Resistance");
                fail_cable_check(oss.str());
                return;
            }
        } else {
            // If no measurements are needed after self test, report valid isolation status to ISO stack
            r_hlc[0]->call_update_isolation_status(types::iso15118::IsolationStatus::Valid);
        }

        // We are done with the isolation measurement and can now report success to the EV,
        // but before we do so we need to do a few things for cleanup

        if (config.hack_pause_imd_during_precharge) {
            imd_stop();
        }

        if (config.cable_check_wait_below_60V_before_finish) {
            // CC.4.1.2: We need to wait until voltage is below 60V before sending a CableCheck Finished to the EV
            powersupply_DC_off();

            if (not wait_powersupply_DC_below_voltage(CABLECHECK_SAFE_VOLTAGE)) {
                std::ostringstream oss;
                oss << "Voltage did not drop below " << CABLECHECK_SAFE_VOLTAGE << "V within timeout.";
                imd_stop();
                fail_cable_check(oss.str());
                return;
            }
            charger->get_stopwatch().mark("VRampDown");

            EVLOG_info << "CableCheck done, output is below " << CABLECHECK_SAFE_VOLTAGE << "V";
        }

        // Report CableCheck Finished with success to EV
        r_hlc[0]->call_cable_check_finished(true);
        charger->get_stopwatch().report_phase();
        charger->get_stopwatch().mark_phase("PrepareCharging");
    });
    // Detach thread and exit command handler right away
    t.detach();
}

void EvseManager::powersupply_DC_on() {
    if (not powersupply_dc_is_on) {
        session_log.evse(false, "DC power supply: switch ON called, ChargingPhase: " +
                                    types::power_supply_DC::charging_phase_to_string(power_supply_DC_charging_phase));
        r_powersupply_DC[0]->call_setMode(types::power_supply_DC::Mode::Export, power_supply_DC_charging_phase);
        powersupply_dc_is_on = true;
    }
}

// input voltage/current is what the evse/car would like to set.
// if it is more then what the energymanager gave us, we can limit it here.
bool EvseManager::powersupply_DC_set(double _voltage, double _current) {
    if (last_power_supply_voltage == _voltage and last_power_supply_current == _current) {
        return true;
    }

    double voltage = _voltage;
    double current = _current;
    static bool last_is_actually_exporting_to_grid{false};

    const bool charging_phase_changed = last_power_supply_DC_charging_phase not_eq power_supply_DC_charging_phase;
    last_power_supply_DC_charging_phase = power_supply_DC_charging_phase;

    // Some cars always request integer ampere values, so if we offer 14.34A they will request 14.0A.
    // On low power DC charging this makes quite a difference
    // this option will deliver the offered ampere value in those cases

    if (config.hack_fix_hlc_integer_current_requests) {
        auto hlc_limits = charger->get_evse_max_hlc_limits();
        if (hlc_limits.evse_maximum_current_limit - (int)current < 1.)
            current = hlc_limits.evse_maximum_current_limit;
    }

    if (config.sae_j2847_2_bpt_enabled) {
        current = std::abs(current);
    }

    auto caps = get_powersupply_capabilities();

    if (((config.hack_allow_bpt_with_iso2 or sae_bidi_active or session_is_iso_d20_dc_bpt()) and
         current_demand_active) and
        is_actually_exporting_to_grid) {
        if (not last_is_actually_exporting_to_grid and powersupply_dc_is_on) {
            // switching from import from grid to export to grid
            session_log.evse(false, "DC power supply: switch ON in import mode");
            r_powersupply_DC[0]->call_setMode(types::power_supply_DC::Mode::Import, power_supply_DC_charging_phase);
        }
        last_is_actually_exporting_to_grid = is_actually_exporting_to_grid;
        // Hack: we are exporting to grid but are in ISO-2 mode
        // check limits of supply
        if (caps.min_import_voltage_V.has_value() and caps.max_import_voltage_V.has_value() and
            voltage >= caps.min_import_voltage_V.value() and voltage <= caps.max_import_voltage_V.value()) {

            if (caps.max_import_current_A.has_value() and current > caps.max_import_current_A.value()) {
                current = caps.max_import_current_A.value();
            }

            if (caps.min_import_current_A.has_value() and current < caps.min_import_current_A.value()) {
                current = caps.min_import_current_A.value();
            }

            // Now it is within limits of DC power supply.
            // now also limit with the limits given by the energymanager.
            // FIXME: dont do this for now, see if the car reduces if we supply new limits.

            session_log.evse(false, fmt::format("DC power supply set: {:.2f}V/{:.2f}A, requested was {:.2f}V/{:.2f}A.",
                                                voltage, current, _voltage, _current));

            // set the new limits for the DC output
            r_powersupply_DC[0]->call_setImportVoltageCurrent(voltage, current);
            last_power_supply_voltage = voltage;
            last_power_supply_current = current;
            return true;
        }
        EVLOG_critical << fmt::format("DC voltage/current out of limits requested: Voltage {:.2f} Current {:.2f}.",
                                      voltage, current);
        return false;
    }
    if (powersupply_dc_is_on and (charging_phase_changed or (((config.hack_allow_bpt_with_iso2 or sae_bidi_active or
                                                               session_is_iso_d20_dc_bpt()) and
                                                              last_is_actually_exporting_to_grid) and
                                                             current_demand_active))) {
        // switching from export to grid to import from grid
        session_log.evse(false, "DC power supply: switch ON in export mode");
        r_powersupply_DC[0]->call_setMode(types::power_supply_DC::Mode::Export, power_supply_DC_charging_phase);
        last_is_actually_exporting_to_grid = is_actually_exporting_to_grid;
    }

    // check limits of supply
    if (voltage >= caps.min_export_voltage_V and voltage <= caps.max_export_voltage_V) {

        if (current > caps.max_export_current_A)
            current = caps.max_export_current_A;

        if (current < caps.min_export_current_A)
            current = caps.min_export_current_A;

        // Now it is within limits of DC power supply.
        // now also limit with the limits given by the energymanager.
        // FIXME: dont do this for now, see if the car reduces if we supply new limits.

        session_log.evse(false, fmt::format("DC power supply set: {:.2f}V/{:.2f}A, requested was {:.2f}V/{:.2f}A.",
                                            voltage, current, _voltage, _current));

        // set the new limits for the DC output
        r_powersupply_DC[0]->call_setExportVoltageCurrent(voltage, current);
        last_power_supply_voltage = voltage;
        last_power_supply_current = current;
        return true;
    }
    EVLOG_critical << fmt::format("DC voltage/current out of limits requested: Voltage {:.2f} Current {:.2f}.", voltage,
                                  current);
    return false;
}

void EvseManager::powersupply_DC_off() {
    if (powersupply_dc_is_on) {
        session_log.evse(false, "DC power supply OFF");
        r_powersupply_DC[0]->call_setMode(types::power_supply_DC::Mode::Off, power_supply_DC_charging_phase);
        powersupply_dc_is_on = false;
    }
    power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::Other;
}

bool EvseManager::wait_powersupply_DC_voltage_reached(double target_voltage) {
    // wait until the voltage has rised to the target value
    Timeout timeout;
    timeout.start(10s);
    bool voltage_ok = false;
    while (not timeout.reached()) {
        if (cable_check_should_exit()) {
            EVLOG_warning << "Cancel cable check wait voltage reached";
            power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::Other;
            powersupply_DC_off();
            r_hlc[0]->call_cable_check_finished(false);
            break;
        }
        types::power_supply_DC::VoltageCurrent m;
        if (powersupply_measurement.wait_for(m, 2000ms)) {
            if (fabs(m.voltage_V - target_voltage) < 10) {
                voltage_ok = true;
                break;
            }
        } else {
            EVLOG_info << "Did not receive voltage measurement from power supply within 2 seconds.";
            power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::Other;
            powersupply_DC_off();
            break;
        }
    }
    return voltage_ok;
}

bool EvseManager::wait_powersupply_DC_below_voltage(double target_voltage) {
    // wait until the voltage is below the target voltage
    Timeout timeout;
    timeout.start(10s);
    bool voltage_ok = false;
    while (not timeout.reached()) {
        if (cable_check_should_exit()) {
            EVLOG_warning << "Cancel cable check wait below voltage";
            power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::Other;
            powersupply_DC_off();
            r_hlc[0]->call_cable_check_finished(false);
            break;
        }
        types::power_supply_DC::VoltageCurrent m;
        if (powersupply_measurement.wait_for(m, 2000ms)) {
            if (m.voltage_V < target_voltage) {
                voltage_ok = true;
                break;
            }
        } else {
            EVLOG_info << "Did not receive voltage measurement from power supply within 2 seconds.";
            power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::Other;
            powersupply_DC_off();
            break;
        }
    }
    return voltage_ok;
}

const std::vector<std::unique_ptr<powermeterIntf>>& EvseManager::r_powermeter_billing() {
    if (r_powermeter_car_side.size() > 0) {
        return r_powermeter_car_side;
    } else {
        return r_powermeter_grid_side;
    }
}

void EvseManager::imd_stop() {
    if (not r_imd.empty()) {
        r_imd[0]->call_stop();
    }
}

void EvseManager::imd_start() {
    if (not r_imd.empty()) {
        r_imd[0]->call_start();
    }
}

// This returns our active local limits, which is either externally set limits
// or hardware capabilties
types::energy::ExternalLimits EvseManager::get_local_energy_limits() {
    types::energy::ExternalLimits active_local_limits;

    std::scoped_lock lock(external_local_limits_mutex);

    // external limits are empty
    if (external_local_energy_limits.schedule_import.empty() and external_local_energy_limits.schedule_export.empty()) {
        if (config.charge_mode == "AC") {
            auto handle = hw_capabilities.handle();
            update_max_current_limit(active_local_limits, handle->max_current_A_import, handle->max_current_A_export);
        } else {
            update_max_watt_limit(active_local_limits, get_powersupply_capabilities().max_export_power_W,
                                  get_powersupply_capabilities().max_import_power_W);
        }
    } else {
        // apply external limits if they are lower
        active_local_limits = external_local_energy_limits;
    }

    // Bidi: set local limits to 0A/0W for exporting to grid, except if bidi is actually active at the moment
    if (not(config.hack_allow_bpt_with_iso2 or sae_bidi_active or session_is_iso_d20_dc_bpt() or
            session_is_iso_d20_ac_bpt())) {
        update_to_zero_discharge_limit(active_local_limits);
    }

    return active_local_limits;
}

void EvseManager::fail_cable_check(const std::string& reason) {
    if (config.charge_mode == "DC") {
        power_supply_DC_charging_phase = types::power_supply_DC::ChargingPhase::Other;
        powersupply_DC_off();
        // CC.4.1.2: We need to wait until voltage is below 60V before sending a CableCheck Finished to the EV
        if (not wait_powersupply_DC_below_voltage(CABLECHECK_SAFE_VOLTAGE)) {
            EVLOG_error << "Voltage did not drop below 60V within timeout, sending CableCheck Finished(false) anyway";
        }
        r_hlc[0]->call_cable_check_finished(false);
    }
    // Raising a cable check fault should not happen if:
    // - a cancel_transaction (DeAuthorized) is triggered during cable check
    // - the car has already been unplugged (Idle/Finished), which prevents a race condition
    //   where the detached cable check thread raises an error after clear_errors_on_unplug()
    //   has already run, leaving the charger permanently inoperative (see GitHub issue #1392)
    const auto current_state = charger->get_current_state();
    const auto last_stop_transaction_reason = charger->get_last_stop_transaction_reason();
    if (current_state == Charger::EvseState::Idle || current_state == Charger::EvseState::Finished) {
        EVLOG_info << "Cable check failed due to: " << reason
                   << ", but session already ended (car unplugged). Not raising cable check fault error.";
    } else if (not last_stop_transaction_reason.has_value() or
               last_stop_transaction_reason.value() != types::evse_manager::StopTransactionReason::DeAuthorized) {
        // Raising the cable check error also causes the HLC stack to get notified
        this->error_handling->raise_cable_check_fault(reason);
    }
}

types::evse_manager::EVInfo EvseManager::get_ev_info() {
    Everest::scoped_lock_timeout l(ev_info_mutex, Everest::MutexDescription::EVSE_get_ev_info);
    return ev_info;
}

void EvseManager::process_dc_ev_target_voltage_current(const types::iso15118::DcEvseMaximumLimits& hlc_limits) {
    double clamped_voltage = raw_ev_target_voltage.load();
    double clamped_current = raw_ev_target_current.load();

    // Hack for Skoda Enyaq that should be fixed in a different way
    if (config.hack_skoda_enyaq and (clamped_voltage < 300 or clamped_current < 0)) {
        return;
    }

    // Limit voltage/current for broken EV implementations
    const auto ev_info_snapshot = get_ev_info();
    if (ev_info_snapshot.maximum_current_limit.has_value() and
        clamped_current > ev_info_snapshot.maximum_current_limit.value()) {
        clamped_current = ev_info_snapshot.maximum_current_limit.value();
    }
    if (ev_info_snapshot.maximum_voltage_limit.has_value() and
        clamped_voltage > ev_info_snapshot.maximum_voltage_limit.value()) {
        clamped_voltage = ev_info_snapshot.maximum_voltage_limit.value();
    }

    bool car_breaks_limit{false};
    if (clamped_current > hlc_limits.evse_maximum_current_limit) {
        clamped_current = hlc_limits.evse_maximum_current_limit;
        car_breaks_limit = true;
    }

    // ISO15118-20 [V2G20-2183] allows EVs to only provide voltage or current,
    // i.e. some EV implementations send target voltage as zero later.
    // While this requirement is going to be dropped from the standard, we
    // have to support older implementations by using a "cached" latest non-zero
    // voltage the EV told us.
    // EVs sending a current of zero has not yet been seen, so we don't care
    // about this particular case here at the moment.
    bool car_sent_zero_voltage{false};
    if (almost_eq(clamped_voltage, 0.0) and ev_info.target_voltage.value_or(0.f) > 0.f) {
        clamped_voltage = ev_info.target_voltage.value();
        car_sent_zero_voltage = true;
    }

    const auto actual_voltage =
        ev_info_snapshot.present_voltage.has_value() ? ev_info_snapshot.present_voltage.value() : clamped_voltage;

    const auto target_power = clamped_current * actual_voltage;
    if (target_power > hlc_limits.evse_maximum_power_limit) {
        clamped_current = hlc_limits.evse_maximum_power_limit / actual_voltage;
        car_breaks_limit = true;
    }

    bool target_changed = false;
    if (clamped_voltage not_eq latest_target_voltage or clamped_current not_eq latest_target_current) {
        latest_target_voltage = clamped_voltage;
        latest_target_current = clamped_current;
        target_changed = true;
    }

    apply_new_target_voltage_current();

    if (target_changed) {
        if (not contactor_open) {
            powersupply_DC_on();
        }
        if (car_breaks_limit) {
            EVLOG_warning << "EV ignores new EVSE max limits. Setting target current to new EVSE max limits";
        }
        if (car_sent_zero_voltage) {
            EVLOG_warning << "EV did not provide a recent voltage. Re-using the previously communicated value again.";
        }

        {
            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);
        }
    }
}

void EvseManager::apply_new_target_voltage_current() {

    if (latest_target_voltage > 0) {
        const double time_since_last_update =
            std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
                                                                  latest_target_current_low_pass_last_update.load())
                .count();
        if (time_since_last_update > 0) {
            double diff_amp = (latest_target_current - latest_target_current_low_pass);
            if (diff_amp > time_since_last_update / 1000. * config.dc_ramp_ampere_per_second) {
                diff_amp = time_since_last_update / 1000. * config.dc_ramp_ampere_per_second;
                latest_target_current_low_pass = latest_target_current_low_pass + diff_amp;
            } else {
                latest_target_current_low_pass.store(latest_target_current);
            }
        } else {
            latest_target_current_low_pass.store(latest_target_current);
        }

        latest_target_current_low_pass_last_update.store(std::chrono::steady_clock::now());

        powersupply_DC_set(latest_target_voltage, latest_target_current_low_pass);
    }

    // We allow the EV to adjust the voltage/current for a few seconds
    // so we reset the timer on every new target value received.
    charger->reset_dc_enforce_target_limits_timer();
}

bool EvseManager::session_is_iso_d20_ac_bpt() {
    return selected_d20_energy_service.has_value() &&
           selected_d20_energy_service.value() == types::iso15118::ServiceCategory::AC_BPT;
}

bool EvseManager::session_is_iso_d20_dc_bpt() {
    return selected_d20_energy_service.has_value() and
           selected_d20_energy_service.value() == types::iso15118::ServiceCategory::DC_BPT;
}

types::power_supply_DC::Capabilities EvseManager::get_powersupply_capabilities() {
    types::power_supply_DC::Capabilities caps;
    types::dc_external_derate::ExternalDerating derate;

    {
        std::scoped_lock lock(powersupply_capabilities_mutex);
        caps = powersupply_capabilities;
    }
    {
        std::scoped_lock lock(dc_external_derate_mutex);
        derate = get_dc_external_derate(this->ev_info.present_voltage, dc_external_derate);
    }

    // Apply external derating if set
    caps.max_export_current_A = min_optional(caps.max_export_current_A, derate.max_export_current_A);
    caps.max_import_current_A = min_optional(caps.max_import_current_A, derate.max_import_current_A);
    caps.max_export_power_W = min_optional(caps.max_export_power_W, derate.max_export_power_W);
    caps.max_import_power_W = min_optional(caps.max_import_power_W, derate.max_import_power_W);

    return caps;
}

void EvseManager::set_external_derating(types::dc_external_derate::ExternalDerating d) {
    std::scoped_lock lock(dc_external_derate_mutex);
    dc_external_derate = d;
}

} // namespace module
