diff --git a/dependencies.yaml b/dependencies.yaml index 665263b4a..d9a78a5dc 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -46,7 +46,7 @@ libfsm: # libiso15118 libiso15118: git: https://github.com/EVerest/libiso15118.git - git_tag: v0.2.0 + git_tag: v0.3.0 cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBISO15118" # LEM DCBM 400/600 module @@ -71,7 +71,7 @@ libocpp: # Josev Josev: git: https://github.com/EVerest/ext-switchev-iso15118.git - git_tag: 2024.9.0 + git_tag: 2024.10.0 cmake_condition: "EVEREST_ENABLE_PY_SUPPORT AND EVEREST_DEPENDENCY_ENABLED_JOSEV" # libcbv2g libcbv2g: diff --git a/interfaces/ISO15118_charger.yaml b/interfaces/ISO15118_charger.yaml index 0a8789a51..a322aa6e2 100644 --- a/interfaces/ISO15118_charger.yaml +++ b/interfaces/ISO15118_charger.yaml @@ -351,3 +351,14 @@ vars: Parameters that may be displayed on the EVSE (Soc, battery capacity) type: object $ref: /iso15118_charger#/DisplayParameters + d20_dc_dynamic_charge_mode: + description: >- + The parameters the EVCC offers and sets for dynamic control mode + type: object + $ref: /iso15118_charger#/DcChargeDynamicModeValues + dc_ev_present_voltage: + description: Present Voltage measured from the EV + type: number + meter_info_requested: + description: The EV requested meter infos from the EVSE + type: "null" diff --git a/modules/Evse15118D20/Evse15118D20.hpp b/modules/Evse15118D20/Evse15118D20.hpp index c8ea4ce55..7d9bd1360 100644 --- a/modules/Evse15118D20/Evse15118D20.hpp +++ b/modules/Evse15118D20/Evse15118D20.hpp @@ -28,6 +28,9 @@ struct Conf { bool enable_ssl_logging; bool enable_tls_key_logging; bool enable_sdp_server; + bool supported_dynamic_mode; + bool supported_mobility_needs_mode_provided_by_secc; + bool supported_scheduled_mode; }; class Evse15118D20 : public Everest::ModuleBase { diff --git a/modules/Evse15118D20/charger/ISO15118_chargerImpl.cpp b/modules/Evse15118D20/charger/ISO15118_chargerImpl.cpp index f1f400b68..dbdfcee84 100644 --- a/modules/Evse15118D20/charger/ISO15118_chargerImpl.cpp +++ b/modules/Evse15118D20/charger/ISO15118_chargerImpl.cpp @@ -15,6 +15,8 @@ static constexpr auto WAIT_FOR_SETUP_DONE_MS{std::chrono::milliseconds(200)}; std::mutex GEL; // Global EVerest Lock +namespace dt = iso15118::message_20::datatypes; + namespace { std::filesystem::path construct_cert_path(const std::filesystem::path& initial_path, const std::string& config_path) { @@ -43,19 +45,85 @@ iso15118::config::TlsNegotiationStrategy convert_tls_negotiation_strategy(const } } -types::iso15118_charger::DisplayParameters -convert_display_parameters(const iso15118::session::feedback::DisplayParameters& in) { +template std::optional convert_from_optional(const std::optional& in) { + return (in) ? std::make_optional(static_cast(*in)) : std::nullopt; +} + +template <> std::optional convert_from_optional(const std::optional& in) { + return (in) ? std::make_optional(dt::from_RationalNumber(*in)) : std::nullopt; +} + +types::iso15118_charger::DisplayParameters convert_display_parameters(const dt::DisplayParameters& in) { return {in.present_soc, - in.minimum_soc, + in.min_soc, in.target_soc, - in.maximum_soc, - in.remaining_time_to_minimum_soc, + in.max_soc, + in.remaining_time_to_min_soc, in.remaining_time_to_target_soc, - in.remaining_time_to_maximum_soc, - in.battery_energy_capacity, + in.remaining_time_to_max_soc, + in.charging_complete, + convert_from_optional(in.battery_energy_capacity), in.inlet_hot}; } +types::iso15118_charger::DcChargeDynamicModeValues convert_dynamic_values(const dt::Dynamic_DC_CLReqControlMode& in) { + return {dt::from_RationalNumber(in.target_energy_request), + dt::from_RationalNumber(in.max_energy_request), + dt::from_RationalNumber(in.min_energy_request), + dt::from_RationalNumber(in.max_charge_power), + dt::from_RationalNumber(in.min_charge_power), + dt::from_RationalNumber(in.max_charge_current), + dt::from_RationalNumber(in.max_voltage), + dt::from_RationalNumber(in.min_voltage), + convert_from_optional(in.departure_time), + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt}; +} + +types::iso15118_charger::DcChargeDynamicModeValues +convert_dynamic_values(const iso15118::message_20::datatypes::BPT_Dynamic_DC_CLReqControlMode& in) { + return {dt::from_RationalNumber(in.target_energy_request), + dt::from_RationalNumber(in.max_energy_request), + dt::from_RationalNumber(in.min_energy_request), + dt::from_RationalNumber(in.max_charge_power), + dt::from_RationalNumber(in.min_charge_power), + dt::from_RationalNumber(in.max_charge_current), + dt::from_RationalNumber(in.max_voltage), + dt::from_RationalNumber(in.min_voltage), + convert_from_optional(in.departure_time), + dt::from_RationalNumber(in.max_discharge_power), + dt::from_RationalNumber(in.min_discharge_power), + dt::from_RationalNumber(in.max_discharge_current), + convert_from_optional(in.max_v2x_energy_request), + convert_from_optional(in.min_v2x_energy_request)}; +} + +auto fill_mobility_needs_modes_from_config(const module::Conf& module_config) { + + std::vector mobility_needs_modes{}; + + if (module_config.supported_dynamic_mode) { + mobility_needs_modes.push_back({dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedByEvcc}); + if (module_config.supported_mobility_needs_mode_provided_by_secc) { + mobility_needs_modes.push_back({dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedBySecc}); + } + } + + if (module_config.supported_scheduled_mode) { + mobility_needs_modes.push_back({dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc}); + } + + if (mobility_needs_modes.empty()) { + EVLOG_warning << "Control mobility modes are empty! Setting dynamic mode as default!"; + mobility_needs_modes.push_back({dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedByEvcc}); + } + + return mobility_needs_modes; +} + } // namespace void ISO15118_chargerImpl::init() { @@ -112,23 +180,85 @@ void ISO15118_chargerImpl::ready() { }; const auto callbacks = create_callbacks(); + setup_config.control_mobility_modes = fill_mobility_needs_modes_from_config(mod->config); + controller = std::make_unique(tbd_config, callbacks, setup_config); - controller->loop(); + + try { + controller->loop(); + } catch (const std::exception& e) { + EVLOG_error << e.what(); + } } iso15118::session::feedback::Callbacks ISO15118_chargerImpl::create_callbacks() { - iso15118::session::feedback::Callbacks callbacks; - callbacks.dc_charge_target = [this](const iso15118::session::feedback::DcChargeTarget& charge_target) { - publish_dc_ev_target_voltage_current({charge_target.voltage, charge_target.current}); + using ScheduleControlMode = dt::Scheduled_DC_CLReqControlMode; + using BPT_ScheduleReqControlMode = dt::BPT_Scheduled_DC_CLReqControlMode; + using DynamicReqControlMode = dt::Dynamic_DC_CLReqControlMode; + using BPT_DynamicReqControlMode = dt::BPT_Dynamic_DC_CLReqControlMode; + + namespace feedback = iso15118::session::feedback; + + feedback::Callbacks callbacks; + + callbacks.dc_pre_charge_target_voltage = [this](float target_voltage) { + publish_dc_ev_target_voltage_current({target_voltage, 0}); + }; + + callbacks.dc_charge_loop_req = [this](const feedback::DcChargeLoopReq& dc_charge_loop_req) { + if (const auto* dc_control_mode = std::get_if(&dc_charge_loop_req)) { + if (const auto* scheduled_mode = std::get_if(dc_control_mode)) { + const auto target_voltage = dt::from_RationalNumber(scheduled_mode->target_voltage); + const auto target_current = dt::from_RationalNumber(scheduled_mode->target_current); + + publish_dc_ev_target_voltage_current({target_voltage, target_current}); + + if (scheduled_mode->max_charge_current and scheduled_mode->max_voltage and + scheduled_mode->max_charge_power) { + const auto max_current = dt::from_RationalNumber(scheduled_mode->max_charge_current.value()); + const auto max_voltage = dt::from_RationalNumber(scheduled_mode->max_voltage.value()); + const auto max_power = dt::from_RationalNumber(scheduled_mode->max_charge_power.value()); + publish_dc_ev_maximum_limits({max_current, max_power, max_voltage}); + } + + } else if (const auto* bpt_scheduled_mode = std::get_if(dc_control_mode)) { + const auto target_voltage = dt::from_RationalNumber(bpt_scheduled_mode->target_voltage); + const auto target_current = dt::from_RationalNumber(bpt_scheduled_mode->target_current); + publish_dc_ev_target_voltage_current({target_voltage, target_current}); + + if (bpt_scheduled_mode->max_charge_current and bpt_scheduled_mode->max_voltage and + bpt_scheduled_mode->max_charge_power) { + const auto max_current = dt::from_RationalNumber(bpt_scheduled_mode->max_charge_current.value()); + const auto max_voltage = dt::from_RationalNumber(bpt_scheduled_mode->max_voltage.value()); + const auto max_power = dt::from_RationalNumber(bpt_scheduled_mode->max_charge_power.value()); + publish_dc_ev_maximum_limits({max_current, max_power, max_voltage}); + } + + // publish_dc_ev_maximum_limits({max_limits.current, max_limits.power, max_limits.voltage}); + } else if (const auto* dynamic_mode = std::get_if(dc_control_mode)) { + publish_d20_dc_dynamic_charge_mode(convert_dynamic_values(*dynamic_mode)); + } else if (const auto* bpt_dynamic_mode = std::get_if(dc_control_mode)) { + publish_d20_dc_dynamic_charge_mode(convert_dynamic_values(*bpt_dynamic_mode)); + } + } else if (const auto* display_parameters = std::get_if(&dc_charge_loop_req)) { + publish_display_parameters(convert_display_parameters(*display_parameters)); + } else if (const auto* present_voltage = std::get_if(&dc_charge_loop_req)) { + publish_dc_ev_present_voltage(dt::from_RationalNumber(*present_voltage)); + } else if (const auto* meter_info_requested = std::get_if(&dc_charge_loop_req)) { + if (*meter_info_requested) { + EVLOG_info << "Meter info is requested from EV"; + publish_meter_info_requested(nullptr); + } + } }; - callbacks.dc_max_limits = [this](const iso15118::session::feedback::DcMaximumLimits& max_limits) { + callbacks.dc_max_limits = [this](const feedback::DcMaximumLimits& max_limits) { publish_dc_ev_maximum_limits({max_limits.current, max_limits.power, max_limits.voltage}); }; - callbacks.signal = [this](iso15118::session::feedback::Signal signal) { - using Signal = iso15118::session::feedback::Signal; + callbacks.signal = [this](feedback::Signal signal) { + using Signal = feedback::Signal; switch (signal) { case Signal::CHARGE_LOOP_STARTED: publish_current_demand_started(nullptr); @@ -169,10 +299,6 @@ iso15118::session::feedback::Callbacks ISO15118_chargerImpl::create_callbacks() callbacks.selected_protocol = [this](const std::string& protocol) { publish_selected_protocol(protocol); }; - callbacks.display_parameters = [this](const iso15118::session::feedback::DisplayParameters parameters) { - publish_display_parameters(convert_display_parameters(parameters)); - }; - return callbacks; } @@ -184,24 +310,24 @@ void ISO15118_chargerImpl::handle_setup( std::scoped_lock lock(GEL); setup_config.evse_id = evse_id.evse_id; // TODO(SL): Check format for d20 - std::vector services; + std::vector services; for (const auto& mode : supported_energy_transfer_modes) { if (mode.energy_transfer_mode == types::iso15118_charger::EnergyTransferMode::AC_single_phase_core || mode.energy_transfer_mode == types::iso15118_charger::EnergyTransferMode::AC_three_phase_core) { if (mode.bidirectional) { - services.push_back(iso15118::message_20::ServiceCategory::AC_BPT); + services.push_back(dt::ServiceCategory::AC_BPT); } else { - services.push_back(iso15118::message_20::ServiceCategory::AC); + services.push_back(dt::ServiceCategory::AC); } } else if (mode.energy_transfer_mode == types::iso15118_charger::EnergyTransferMode::DC_core || mode.energy_transfer_mode == types::iso15118_charger::EnergyTransferMode::DC_extended || mode.energy_transfer_mode == types::iso15118_charger::EnergyTransferMode::DC_combo_core || mode.energy_transfer_mode == types::iso15118_charger::EnergyTransferMode::DC_unique) { if (mode.bidirectional) { - services.push_back(iso15118::message_20::ServiceCategory::DC_BPT); + services.push_back(dt::ServiceCategory::DC_BPT); } else { - services.push_back(iso15118::message_20::ServiceCategory::DC); + services.push_back(dt::ServiceCategory::DC); } } } @@ -220,11 +346,11 @@ void ISO15118_chargerImpl::handle_session_setup(std::vector auth_services; + std::vector auth_services; for (auto& option : payment_options) { if (option == types::iso15118_charger::PaymentOption::ExternalPayment) { - auth_services.push_back(iso15118::message_20::Authorization::EIM); + auth_services.push_back(dt::Authorization::EIM); } else if (option == types::iso15118_charger::PaymentOption::Contract) { // auth_services.push_back(iso15118::message_20::Authorization::PnC); EVLOG_warning << "Currently Plug&Charge is not supported and ignored"; @@ -295,24 +421,20 @@ void ISO15118_chargerImpl::handle_update_dc_maximum_limits( types::iso15118_charger::DcEvseMaximumLimits& maximum_limits) { std::scoped_lock lock(GEL); - setup_config.dc_limits.charge_limits.current.max = - iso15118::message_20::from_float(maximum_limits.evse_maximum_current_limit); - setup_config.dc_limits.charge_limits.power.max = - iso15118::message_20::from_float(maximum_limits.evse_maximum_power_limit); - setup_config.dc_limits.voltage.max = iso15118::message_20::from_float(maximum_limits.evse_maximum_voltage_limit); + setup_config.dc_limits.charge_limits.current.max = dt::from_float(maximum_limits.evse_maximum_current_limit); + setup_config.dc_limits.charge_limits.power.max = dt::from_float(maximum_limits.evse_maximum_power_limit); + setup_config.dc_limits.voltage.max = dt::from_float(maximum_limits.evse_maximum_voltage_limit); if (maximum_limits.evse_maximum_discharge_current_limit.has_value() or maximum_limits.evse_maximum_discharge_power_limit.has_value()) { auto& discharge_limits = setup_config.dc_limits.discharge_limits.emplace(); if (maximum_limits.evse_maximum_discharge_current_limit.has_value()) { - discharge_limits.current.max = - iso15118::message_20::from_float(*maximum_limits.evse_maximum_discharge_current_limit); + discharge_limits.current.max = dt::from_float(*maximum_limits.evse_maximum_discharge_current_limit); } if (maximum_limits.evse_maximum_discharge_power_limit.has_value()) { - discharge_limits.power.max = - iso15118::message_20::from_float(*maximum_limits.evse_maximum_discharge_power_limit); + discharge_limits.power.max = dt::from_float(*maximum_limits.evse_maximum_discharge_power_limit); } } @@ -327,25 +449,21 @@ void ISO15118_chargerImpl::handle_update_dc_minimum_limits( types::iso15118_charger::DcEvseMinimumLimits& minimum_limits) { std::scoped_lock lock(GEL); - setup_config.dc_limits.charge_limits.current.min = - iso15118::message_20::from_float(minimum_limits.evse_minimum_current_limit); + setup_config.dc_limits.charge_limits.current.min = dt::from_float(minimum_limits.evse_minimum_current_limit); - setup_config.dc_limits.charge_limits.power.min = - iso15118::message_20::from_float(minimum_limits.evse_minimum_power_limit); - setup_config.dc_limits.voltage.min = iso15118::message_20::from_float(minimum_limits.evse_minimum_voltage_limit); + setup_config.dc_limits.charge_limits.power.min = dt::from_float(minimum_limits.evse_minimum_power_limit); + setup_config.dc_limits.voltage.min = dt::from_float(minimum_limits.evse_minimum_voltage_limit); if (minimum_limits.evse_minimum_discharge_current_limit.has_value() or minimum_limits.evse_minimum_discharge_power_limit.has_value()) { auto& discharge_limits = setup_config.dc_limits.discharge_limits.emplace(); if (minimum_limits.evse_minimum_discharge_current_limit.has_value()) { - discharge_limits.current.min = - iso15118::message_20::from_float(*minimum_limits.evse_minimum_discharge_current_limit); + discharge_limits.current.min = dt::from_float(*minimum_limits.evse_minimum_discharge_current_limit); } if (minimum_limits.evse_minimum_discharge_power_limit.has_value()) { - discharge_limits.power.min = - iso15118::message_20::from_float(*minimum_limits.evse_minimum_discharge_power_limit); + discharge_limits.power.min = dt::from_float(*minimum_limits.evse_minimum_discharge_power_limit); } } diff --git a/modules/Evse15118D20/manifest.yaml b/modules/Evse15118D20/manifest.yaml index e13f8203a..18834a3e2 100644 --- a/modules/Evse15118D20/manifest.yaml +++ b/modules/Evse15118D20/manifest.yaml @@ -43,6 +43,20 @@ config: Enable the built-in SDP server type: boolean default: true + supported_dynamic_mode: + description: The EVSE should support dynamic mode + type: boolean + default: true + supported_mobility_needs_mode_provided_by_secc: + description: >- + The EVSE should support the mobility needs mode provided by the SECC. + Mobility needs mode provided by the EVCC is always provided. + type: boolean + default: false + supported_scheduled_mode: + description: The EVSE should support scheduled mode + type: boolean + default: false provides: charger: interface: ISO15118_charger diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index 33d03439f..0782f8729 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -397,6 +397,48 @@ void EvseManager::ready() { } }); + r_hlc[0]->subscribe_d20_dc_dynamic_charge_mode( + [this](types::iso15118_charger::DcChargeDynamicModeValues values) { + constexpr auto PRE_CHARGE_MAX_POWER = 800.0f; + + bool target_changed{false}; + + if (values.min_voltage > latest_target_voltage) { + latest_target_voltage = values.min_voltage + 10; // TODO(sl): Check if okay + target_changed = true; + } + if (values.max_voltage < latest_target_voltage) { + latest_target_voltage = values.max_voltage - 10; // TODO(sl): Check if okay + target_changed = true; + } + + const double latest_target_power = latest_target_voltage * latest_target_current; + + if (latest_target_power <= PRE_CHARGE_MAX_POWER or values.min_charge_power > latest_target_power or + values.max_charge_power < latest_target_power) { + latest_target_current = static_cast(values.max_charge_power) / latest_target_voltage; + if (values.max_charge_current < latest_target_current) { + latest_target_current = values.max_charge_current; + } + target_changed = true; + } + + if (target_changed) { + apply_new_target_voltage_current(); + if (not contactor_open) { + powersupply_DC_on(); + } + + { + 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); + } + } + }); + // 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] { diff --git a/modules/IsoMux/charger/ISO15118_chargerImpl.cpp b/modules/IsoMux/charger/ISO15118_chargerImpl.cpp index cbd1a2a3e..53a045b7c 100644 --- a/modules/IsoMux/charger/ISO15118_chargerImpl.cpp +++ b/modules/IsoMux/charger/ISO15118_chargerImpl.cpp @@ -435,6 +435,36 @@ void ISO15118_chargerImpl::init() { publish_display_parameters(o); } }); + + mod->r_iso20->subscribe_d20_dc_dynamic_charge_mode([this](const auto o) { + if (mod->selected_iso20()) { + publish_d20_dc_dynamic_charge_mode(o); + } + }); + + mod->r_iso2->subscribe_dc_ev_present_voltage([this](const auto o) { + if (not mod->selected_iso20()) { + publish_dc_ev_present_voltage(o); + } + }); + + mod->r_iso20->subscribe_dc_ev_present_voltage([this](const auto o) { + if (mod->selected_iso20()) { + publish_dc_ev_present_voltage(o); + } + }); + + mod->r_iso2->subscribe_meter_info_requested([this]() { + if (not mod->selected_iso20()) { + publish_meter_info_requested(nullptr); + } + }); + + mod->r_iso20->subscribe_meter_info_requested([this]() { + if (mod->selected_iso20()) { + publish_meter_info_requested(nullptr); + } + }); } void ISO15118_chargerImpl::ready() { diff --git a/types/iso15118_charger.yaml b/types/iso15118_charger.yaml index 89bbc1df3..d8a2893b4 100644 --- a/types/iso15118_charger.yaml +++ b/types/iso15118_charger.yaml @@ -579,6 +579,9 @@ types: description: Remaining time it takes to reach maximum SoC type: number minimum: 0 + charging_complete: + description: Indication if the charging is complete + type: boolean battery_energy_capacity: description: Energy capacity in Wh of the EV battery type: number @@ -586,3 +589,77 @@ types: inlet_hot: description: Inlet temperature is too high type: boolean + DcChargeDynamicModeValues: + description: Parameters for dynamic control mode + type: object + additionalProperties: false + required: + - target_energy_request + - max_energy_request + - min_energy_request + - max_charge_power + - min_charge_power + - max_charge_current + - max_voltage + - min_voltage + properties: + departure_time: + description: The time when the EV wants to finish charging + type: number + minimum: 0 + target_energy_request: + description: Energy request to fulfil the target SoC + type: number + minimum: 0 + max_energy_request: + description: Maximum acceptable energy level of the EV + type: number + minimum: 0 + min_energy_request: + description: Energy request to fulfil the minimum SoC + type: number + minimum: 0 + max_charge_power: + description: Maximum charge power allowed by the EV + type: number + minimum: 0 + min_charge_power: + description: Minimum charge power allowed by the EV + type: number + minimum: 0 + max_charge_current: + description: Maximum charge current allowed by the EV + type: number + minimum: 0 + max_voltage: + description: Maximum voltage allowed by the EV + type: number + minimum: 0 + min_voltage: + description: Minimum voltage allowd by the EV + type: number + minimum: 0 + max_discharge_power: + description: Maximum discharge current allowed by the EV + type: number + minimum: 0 + min_discharge_power: + description: Minimum discharge current allowed by the EV + type: number + minimum: 0 + max_discharge_current: + description: Maximum discharge current allowed by the EV + type: number + minimum: 0 + max_v2x_energy_request: + description: >- + Energy which may be charged until the PresentSOC has left the range + dedicated for cycling activity. + type: number + minimum: 0 + min_v2x_energy_request: + description: >- + Energy which needs to be charged until the PresentSOC has left the + range dedicated for cycling activity. + type: number + minimum: 0