diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 696a567f5..6c1af3f60 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,6 +22,7 @@ /modules/EnergyNode/ @corneliusclaussen @hikinggrass @pietfried /modules/EvseManager/ @corneliusclaussen @SebaLukas @hikinggrass @pietfried /modules/EvManager/ @SebaLukas @pietfried @MarzellT +/modules/Evse15118D20/ @SebaLukas @a-w50 @corneliusclaussen /modules/EvseSecurity/ @AssemblyJohn @pietfried @hikinggrass /modules/EvseV2G/ @corneliusclaussen @SebaLukas @james-ctc /modules/EvseSlac/ @a-w50 @corneliusclaussen @SebaLukas diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 499a2d2dd..fba4f9f8e 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -3,6 +3,7 @@ generate_config_run_script(CONFIG sil-two-evse) generate_config_run_script(CONFIG sil-ocpp) generate_config_run_script(CONFIG sil-ocpp201) generate_config_run_script(CONFIG sil-dc) +generate_config_run_script(CONFIG sil-dc-d20) generate_config_run_script(CONFIG sil-dc-tls) generate_config_run_script(CONFIG sil-dc-sae-v2g) generate_config_run_script(CONFIG sil-dc-sae-v2h) diff --git a/config/config-sil-dc-d20.yaml b/config/config-sil-dc-d20.yaml new file mode 100644 index 000000000..7195d547f --- /dev/null +++ b/config/config-sil-dc-d20.yaml @@ -0,0 +1,148 @@ +active_modules: + iso15118_charger: + module: Evse15118D20 + config_module: + device: auto + iso15118_car: + module: PyEvJosev + config_module: + device: auto + supported_DIN70121: false + supported_ISO15118_2: false + supported_ISO15118_20_DC: true + tls_active: true + evse_manager: + module: EvseManager + config_module: + connector_id: 1 + evse_id: DE*PNX*E12345*1 + evse_id_din: 49A80737A45678 + session_logging: true + session_logging_xml: false + session_logging_path: /tmp/everest-logs + charge_mode: DC + payment_enable_contract: false + connections: + bsp: + - module_id: yeti_driver + implementation_id: board_support + powermeter_car_side: + - module_id: powersupply_dc + implementation_id: powermeter + slac: + - module_id: slac + implementation_id: evse + hlc: + - module_id: iso15118_charger + implementation_id: charger + powersupply_DC: + - module_id: powersupply_dc + implementation_id: main + imd: + - module_id: imd + implementation_id: main + powersupply_dc: + module: DCSupplySimulator + yeti_driver: + module: JsYetiSimulator + config_module: + connector_id: 1 + slac: + module: SlacSimulator + imd: + config_implementation: + main: + selftest_success: true + module: IMDSimulator + ev_manager: + module: EvManager + config_module: + connector_id: 1 + auto_enable: true + auto_exec: false + auto_exec_commands: sleep 3;iso_wait_slac_matched;iso_start_v2g_session DC;iso_wait_pwr_ready;iso_wait_for_stop 15;iso_wait_v2g_session_stopped;unplug; + dc_target_current: 20 + dc_target_voltage: 400 + connections: + ev_board_support: + - module_id: yeti_driver + implementation_id: ev_board_support + ev: + - module_id: iso15118_car + implementation_id: ev + slac: + - module_id: slac + implementation_id: ev + auth: + module: Auth + config_module: + connection_timeout: 10 + selection_algorithm: FindFirst + connections: + token_provider: + - module_id: token_provider + implementation_id: main + token_validator: + - module_id: token_validator + implementation_id: main + evse_manager: + - module_id: evse_manager + implementation_id: evse + token_provider: + module: DummyTokenProvider + config_implementation: + main: + token: TOKEN1 + connections: + evse: + - module_id: evse_manager + implementation_id: evse + token_validator: + module: DummyTokenValidator + config_implementation: + main: + validation_result: Accepted + validation_reason: Token seems valid + sleep: 0.25 + evse_security: + module: EvseSecurity + config_module: + private_key_password: "123456" + energy_manager: + module: EnergyManager + config_module: + schedule_total_duration: 1 + schedule_interval_duration: 60 + debug: false + connections: + energy_trunk: + - module_id: grid_connection_point + implementation_id: energy_grid + grid_connection_point: + module: EnergyNode + config_module: + fuse_limit_A: 40.0 + phase_count: 3 + connections: + price_information: [] + energy_consumer: + - module_id: evse_manager + implementation_id: energy_grid + powermeter: + - module_id: yeti_driver + implementation_id: powermeter + api: + module: API + connections: + evse_manager: + - module_id: evse_manager + implementation_id: evse + error_history: + - module_id: error_history + implementation_id: error_history + error_history: + module: ErrorHistory + config_implementation: + error_history: + database_path: /tmp/error_history.db +x-module-layout: {} diff --git a/dependencies.yaml b/dependencies.yaml index 7ea8c13da..b7d4cc1ea 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -43,6 +43,12 @@ libfsm: git_tag: v0.2.0 cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBFSM" +# libiso15118 +libiso15118: + git: https://github.com/EVerest/libiso15118.git + git_tag: v0.2.0 + cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBISO15118" + # LEM DCBM 400/600 module libcurl: git: https://github.com/curl/curl.git diff --git a/interfaces/ISO15118_charger.yaml b/interfaces/ISO15118_charger.yaml index f8a17d875..0a8789a51 100644 --- a/interfaces/ISO15118_charger.yaml +++ b/interfaces/ISO15118_charger.yaml @@ -14,9 +14,9 @@ cmds: description: Available energy transfer modes supported by the EVSE type: array items: - description: The different energy modes supported by the SECC - type: string - $ref: /iso15118_charger#/EnergyTransferMode + description: The different energy modes supported by the SECC + type: object + $ref: /iso15118_charger#/SupportedEnergyMode minItems: 1 maxItems: 6 sae_j2847_mode: @@ -346,3 +346,8 @@ vars: description: >- Debug - Contains the selected protocol type: string + display_parameters: + description: >- + Parameters that may be displayed on the EVSE (Soc, battery capacity) + type: object + $ref: /iso15118_charger#/DisplayParameters diff --git a/module-dependencies.cmake b/module-dependencies.cmake index a252a2857..4d425af15 100644 --- a/module-dependencies.cmake +++ b/module-dependencies.cmake @@ -57,6 +57,11 @@ ev_define_dependency( DEPENDENCY_NAME sqlite_cpp DEPENDENT_MODULES_LIST ErrorHistory) +ev_define_dependency( + DEPENDENCY_NAME libiso15118 + OUTPUT_VARIABLE_SUFFIX LIBISO15118 + DEPENDENT_MODULES_LIST Evse15118D20) + if(NOT everest-gpio IN_LIST EVEREST_EXCLUDE_DEPENDENCIES) set(EVEREST_DEPENDENCY_ENABLED_EVEREST_GPIO ON) else() diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 98a06d1a3..fea8c4923 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -4,6 +4,7 @@ ev_add_module(EnergyManager) ev_add_module(EnergyNode) ev_add_module(EvManager) ev_add_module(ErrorHistory) +ev_add_module(Evse15118D20) ev_add_module(EvseManager) ev_add_module(EvseSecurity) ev_add_module(EvseSlac) diff --git a/modules/DummyV2G/main/ISO15118_chargerImpl.cpp b/modules/DummyV2G/main/ISO15118_chargerImpl.cpp index d522dbdaa..64216fe05 100644 --- a/modules/DummyV2G/main/ISO15118_chargerImpl.cpp +++ b/modules/DummyV2G/main/ISO15118_chargerImpl.cpp @@ -14,11 +14,16 @@ void ISO15118_chargerImpl::ready() { void ISO15118_chargerImpl::handle_setup( types::iso15118_charger::EVSEID& evse_id, - std::vector& supported_energy_transfer_modes, + std::vector& supported_energy_transfer_modes, types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) { // your code for cmd setup goes here } +void ISO15118_chargerImpl::handle_set_charging_parameters( + types::iso15118_charger::SetupPhysicalValues& physical_values) { + // your code for cmd set_charging_parameters goes here +} + void ISO15118_chargerImpl::handle_session_setup(std::vector& payment_options, bool& supported_certificate_service) { // your code for cmd session_setup goes here @@ -55,10 +60,6 @@ void ISO15118_chargerImpl::handle_stop_charging(bool& stop) { // your code for cmd stop_charging goes here } -void ISO15118_chargerImpl::handle_set_charging_parameters( - types::iso15118_charger::SetupPhysicalValues& physical_values) { -} - void ISO15118_chargerImpl::handle_update_ac_max_current(double& max_current) { // your code for cmd update_ac_max_current goes here } diff --git a/modules/DummyV2G/main/ISO15118_chargerImpl.hpp b/modules/DummyV2G/main/ISO15118_chargerImpl.hpp index df2c08c71..329c05ed1 100644 --- a/modules/DummyV2G/main/ISO15118_chargerImpl.hpp +++ b/modules/DummyV2G/main/ISO15118_chargerImpl.hpp @@ -33,9 +33,10 @@ class ISO15118_chargerImpl : public ISO15118_chargerImplBase { protected: // command handler functions (virtual) - virtual void handle_setup(types::iso15118_charger::EVSEID& evse_id, - std::vector& supported_energy_transfer_modes, - types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) override; + virtual void + handle_setup(types::iso15118_charger::EVSEID& evse_id, + std::vector& supported_energy_transfer_modes, + types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) override; virtual void handle_set_charging_parameters(types::iso15118_charger::SetupPhysicalValues& physical_values) override; virtual void handle_session_setup(std::vector& payment_options, bool& supported_certificate_service) override; diff --git a/modules/Evse15118D20/CMakeLists.txt b/modules/Evse15118D20/CMakeLists.txt new file mode 100644 index 000000000..e0a6fc3a7 --- /dev/null +++ b/modules/Evse15118D20/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# AUTO GENERATED - MARKED REGIONS WILL BE KEPT +# template version 3 +# + +# module setup: +# - ${MODULE_NAME}: module name +ev_setup_cpp_module() + +# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 +target_sources(${MODULE_NAME} + PRIVATE + charger/session_logger.cpp +) + +target_link_libraries(${MODULE_NAME} + PRIVATE + iso15118::iso15118 +) +# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 + +target_sources(${MODULE_NAME} + PRIVATE + "charger/ISO15118_chargerImpl.cpp" +) + +# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 +# insert other things like install cmds etc here +# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/Evse15118D20/Evse15118D20.cpp b/modules/Evse15118D20/Evse15118D20.cpp new file mode 100644 index 000000000..c139fdbc5 --- /dev/null +++ b/modules/Evse15118D20/Evse15118D20.cpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include "Evse15118D20.hpp" + +namespace module { + +void Evse15118D20::init() { + invoke_init(*p_charger); +} + +void Evse15118D20::ready() { + invoke_ready(*p_charger); +} + +} // namespace module diff --git a/modules/Evse15118D20/Evse15118D20.hpp b/modules/Evse15118D20/Evse15118D20.hpp new file mode 100644 index 000000000..091ef8563 --- /dev/null +++ b/modules/Evse15118D20/Evse15118D20.hpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef EVSE15118D20_HPP +#define EVSE15118D20_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 2 +// + +#include "ld-ev.hpp" + +// headers for provided interface implementations +#include + +// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 +// insert your custom include headers here +// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 + +namespace module { + +struct Conf { + std::string device; + std::string certificate_path; + std::string logging_path; + std::string tls_negotiation_strategy; + std::string private_key_password; + bool enable_ssl_logging; + bool enable_tls_key_logging; +}; + +class Evse15118D20 : public Everest::ModuleBase { +public: + Evse15118D20() = delete; + Evse15118D20(const ModuleInfo& info, std::unique_ptr p_charger, Conf& config) : + ModuleBase(info), p_charger(std::move(p_charger)), config(config){}; + + const std::unique_ptr p_charger; + const Conf& config; + + // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 + // insert your public definitions here + // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 + +protected: + // ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1 + // insert your protected definitions here + // ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1 + +private: + friend class LdEverest; + void init(); + void ready(); + + // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 + // insert your private definitions here + // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 +}; + +// ev@087e516b-124c-48df-94fb-109508c7cda9:v1 +// insert other definitions here +// ev@087e516b-124c-48df-94fb-109508c7cda9:v1 + +} // namespace module + +#endif // EVSE15118D20_HPP diff --git a/modules/Evse15118D20/charger/ISO15118_chargerImpl.cpp b/modules/Evse15118D20/charger/ISO15118_chargerImpl.cpp new file mode 100644 index 000000000..d4f220f34 --- /dev/null +++ b/modules/Evse15118D20/charger/ISO15118_chargerImpl.cpp @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include "ISO15118_chargerImpl.hpp" + +#include "session_logger.hpp" +#include "utils.hpp" + +#include +#include + +namespace module { +namespace charger { + +static constexpr auto WAIT_FOR_SETUP_DONE_MS{std::chrono::milliseconds(200)}; + +std::mutex GEL; // Global EVerest Lock + +namespace { + +std::filesystem::path construct_cert_path(const std::filesystem::path& initial_path, const std::string& config_path) { + if (config_path.empty()) { + return initial_path; + } + + if (config_path.front() == '/') { + return config_path; + } else { + return initial_path / config_path; + } +} + +iso15118::config::TlsNegotiationStrategy convert_tls_negotiation_strategy(const std::string& strategy) { + using Strategy = iso15118::config::TlsNegotiationStrategy; + if (strategy == "ACCEPT_CLIENT_OFFER") { + return Strategy::ACCEPT_CLIENT_OFFER; + } else if (strategy == "ENFORCE_TLS") { + return Strategy::ENFORCE_TLS; + } else if (strategy == "ENFORCE_NO_TLS") { + return Strategy::ENFORCE_NO_TLS; + } else { + EVLOG_AND_THROW(Everest::EverestConfigError("Invalid choice for tls_negotiation_strategy: " + strategy)); + // better safe than sorry + } +} + +types::iso15118_charger::DisplayParameters +convert_display_parameters(const iso15118::session::feedback::DisplayParameters& in) { + return {in.present_soc, + in.minimum_soc, + in.target_soc, + in.maximum_soc, + in.remaining_time_to_minimum_soc, + in.remaining_time_to_target_soc, + in.remaining_time_to_maximum_soc, + in.battery_energy_capacity, + in.inlet_hot}; +} + +} // namespace + +void ISO15118_chargerImpl::init() { + + // setup logging routine + iso15118::io::set_logging_callback([](const iso15118::LogLevel& level, const std::string& msg) { + switch (level) { + case iso15118::LogLevel::Error: + EVLOG_error << msg; + break; + case iso15118::LogLevel::Warning: + EVLOG_warning << msg; + break; + case iso15118::LogLevel::Info: + EVLOG_info << msg; + break; + case iso15118::LogLevel::Debug: + EVLOG_debug << msg; + break; + case iso15118::LogLevel::Trace: + EVLOG_verbose << msg; + break; + default: + EVLOG_critical << "(Loglevel not defined) - " << msg; + break; + } + }); +} + +void ISO15118_chargerImpl::ready() { + + while (true) { + if (setup_steps_done.all()) { + break; + } + std::this_thread::sleep_for(WAIT_FOR_SETUP_DONE_MS); + } + + const auto session_logger = std::make_unique(mod->config.logging_path); + + const auto default_cert_path = mod->info.paths.etc / "certs"; + const auto cert_path = construct_cert_path(default_cert_path, mod->config.certificate_path); + const iso15118::TbdConfig tbd_config = { + { + iso15118::config::CertificateBackend::EVEREST_LAYOUT, + cert_path.string(), + mod->config.private_key_password, + mod->config.enable_ssl_logging, + mod->config.enable_tls_key_logging, + }, + mod->config.device, + convert_tls_negotiation_strategy(mod->config.tls_negotiation_strategy), + }; + const auto callbacks = create_callbacks(); + + controller = std::make_unique(tbd_config, callbacks, setup_config); + controller->loop(); +} + +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}); + }; + + callbacks.dc_max_limits = [this](const iso15118::session::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; + switch (signal) { + case Signal::CHARGE_LOOP_STARTED: + publish_current_demand_started(nullptr); + break; + case Signal::SETUP_FINISHED: + publish_v2g_setup_finished(nullptr); + break; + case Signal::START_CABLE_CHECK: + publish_start_cable_check(nullptr); + break; + case Signal::REQUIRE_AUTH_EIM: + publish_require_auth_eim(nullptr); + break; + case Signal::CHARGE_LOOP_FINISHED: + publish_current_demand_finished(nullptr); + break; + case Signal::DC_OPEN_CONTACTOR: + publish_dc_open_contactor(nullptr); + break; + case Signal::DLINK_TERMINATE: + publish_dlink_terminate(nullptr); + break; + case Signal::DLINK_PAUSE: + publish_dlink_pause(nullptr); + break; + case Signal::DLINK_ERROR: + publish_dlink_error(nullptr); + break; + } + }; + + callbacks.v2g_message = [this](iso15118::message_20::Type id) { + const auto v2g_message_id = convert_v2g_message_type(id); + publish_v2g_messages({v2g_message_id}); + }; + + callbacks.evccid = [this](const std::string& evccid) { publish_evcc_id(evccid); }; + + 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; +} + +void ISO15118_chargerImpl::handle_setup( + types::iso15118_charger::EVSEID& evse_id, + std::vector& supported_energy_transfer_modes, + types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) { + + std::scoped_lock lock(GEL); + setup_config.evse_id = evse_id.evse_id; // TODO(SL): Check format for d20 + + 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); + } else { + services.push_back(iso15118::message_20::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); + } else { + services.push_back(iso15118::message_20::ServiceCategory::DC); + } + } + } + + setup_config.supported_energy_services = services; + + setup_steps_done.set(to_underlying_value(SetupStep::ENERGY_SERVICE)); +} + +void ISO15118_chargerImpl::handle_set_charging_parameters( + types::iso15118_charger::SetupPhysicalValues& physical_values) { + // your code for cmd set_charging_parameters goes here +} + +void ISO15118_chargerImpl::handle_session_setup(std::vector& payment_options, + bool& supported_certificate_service) { + std::scoped_lock lock(GEL); + + 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); + } 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"; + } + } + + setup_config.authorization_services = auth_services; + setup_config.enable_certificate_install_service = supported_certificate_service; + + setup_steps_done.set(to_underlying_value(SetupStep::AUTH_SETUP)); +} + +void ISO15118_chargerImpl::handle_certificate_response( + types::iso15118_charger::ResponseExiStreamStatus& exi_stream_status) { + // your code for cmd certificate_response goes here +} + +void ISO15118_chargerImpl::handle_authorization_response( + types::authorization::AuthorizationStatus& authorization_status, + types::authorization::CertificateStatus& certificate_status) { + + std::scoped_lock lock(GEL); + // Todo(sl): Currently PnC is not supported + bool authorized = false; + + if (authorization_status == types::authorization::AuthorizationStatus::Accepted) { + authorized = true; + } + + if (controller) { + controller->send_control_event(iso15118::d20::AuthorizationResponse{authorized}); + } +} + +void ISO15118_chargerImpl::handle_ac_contactor_closed(bool& status) { + // your code for cmd ac_contactor_closed goes here +} + +void ISO15118_chargerImpl::handle_dlink_ready(bool& value) { + // your code for cmd dlink_ready goes here +} + +void ISO15118_chargerImpl::handle_cable_check_finished(bool& status) { + + std::scoped_lock lock(GEL); + if (controller) { + controller->send_control_event(iso15118::d20::CableCheckFinished{status}); + } +} + +void ISO15118_chargerImpl::handle_receipt_is_required(bool& receipt_required) { + // your code for cmd receipt_is_required goes here +} + +void ISO15118_chargerImpl::handle_stop_charging(bool& stop) { + + std::scoped_lock lock(GEL); + if (controller) { + controller->send_control_event(iso15118::d20::StopCharging{stop}); + } +} + +void ISO15118_chargerImpl::handle_update_ac_max_current(double& max_current) { + // your code for cmd update_ac_max_current goes here +} + +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); + + 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); + } + + 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); + } + } + + if (controller) { + controller->update_dc_limits(setup_config.dc_limits); + } + + setup_steps_done.set(to_underlying_value(SetupStep::MAX_LIMITS)); +} + +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.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); + + 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); + } + + 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); + } + } + + if (controller) { + controller->update_dc_limits(setup_config.dc_limits); + } + + setup_steps_done.set(to_underlying_value(SetupStep::MIN_LIMITS)); +} + +void ISO15118_chargerImpl::handle_update_isolation_status(types::iso15118_charger::IsolationStatus& isolation_status) { + // your code for cmd update_isolation_status goes here +} + +void ISO15118_chargerImpl::handle_update_dc_present_values( + types::iso15118_charger::DcEvsePresentVoltageCurrent& present_voltage_current) { + + float voltage = present_voltage_current.evse_present_voltage; + float current = present_voltage_current.evse_present_current.value_or(0); + + std::scoped_lock lock(GEL); + if (controller) { + controller->send_control_event(iso15118::d20::PresentVoltageCurrent{voltage, current}); + } +} + +void ISO15118_chargerImpl::handle_update_meter_info(types::powermeter::Powermeter& powermeter) { + // your code for cmd update_meter_info goes here +} + +void ISO15118_chargerImpl::handle_send_error(types::iso15118_charger::EvseError& error) { + // your code for cmd send_error goes here +} + +void ISO15118_chargerImpl::handle_reset_error() { + // your code for cmd reset_error goes here +} + +} // namespace charger +} // namespace module diff --git a/modules/Evse15118D20/charger/ISO15118_chargerImpl.hpp b/modules/Evse15118D20/charger/ISO15118_chargerImpl.hpp new file mode 100644 index 000000000..34ca91853 --- /dev/null +++ b/modules/Evse15118D20/charger/ISO15118_chargerImpl.hpp @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef CHARGER_ISO15118_CHARGER_IMPL_HPP +#define CHARGER_ISO15118_CHARGER_IMPL_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 3 +// + +#include + +#include "../Evse15118D20.hpp" + +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 +#include + +#include "utils.hpp" + +#include +#include +#include +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 + +namespace module { +namespace charger { + +struct Conf {}; + +class ISO15118_chargerImpl : public ISO15118_chargerImplBase { +public: + ISO15118_chargerImpl() = delete; + ISO15118_chargerImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : + ISO15118_chargerImplBase(ev, "charger"), mod(mod), config(config){}; + + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + // insert your public definitions here + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + +protected: + // command handler functions (virtual) + virtual void + handle_setup(types::iso15118_charger::EVSEID& evse_id, + std::vector& supported_energy_transfer_modes, + types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) override; + virtual void handle_set_charging_parameters(types::iso15118_charger::SetupPhysicalValues& physical_values) override; + virtual void handle_session_setup(std::vector& payment_options, + bool& supported_certificate_service) override; + virtual void + handle_certificate_response(types::iso15118_charger::ResponseExiStreamStatus& exi_stream_status) override; + virtual void handle_authorization_response(types::authorization::AuthorizationStatus& authorization_status, + types::authorization::CertificateStatus& certificate_status) override; + virtual void handle_ac_contactor_closed(bool& status) override; + virtual void handle_dlink_ready(bool& value) override; + virtual void handle_cable_check_finished(bool& status) override; + virtual void handle_receipt_is_required(bool& receipt_required) override; + virtual void handle_stop_charging(bool& stop) override; + virtual void handle_update_ac_max_current(double& max_current) override; + virtual void handle_update_dc_maximum_limits(types::iso15118_charger::DcEvseMaximumLimits& maximum_limits) override; + virtual void handle_update_dc_minimum_limits(types::iso15118_charger::DcEvseMinimumLimits& minimum_limits) override; + virtual void handle_update_isolation_status(types::iso15118_charger::IsolationStatus& isolation_status) override; + virtual void handle_update_dc_present_values( + types::iso15118_charger::DcEvsePresentVoltageCurrent& present_voltage_current) override; + virtual void handle_update_meter_info(types::powermeter::Powermeter& powermeter) override; + virtual void handle_send_error(types::iso15118_charger::EvseError& error) override; + virtual void handle_reset_error() override; + + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + // insert your protected definitions here + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + +private: + const Everest::PtrContainer& mod; + const Conf& config; + + virtual void init() override; + virtual void ready() override; + + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 + iso15118::session::feedback::Callbacks create_callbacks(); + + std::unique_ptr controller; + + iso15118::d20::EvseSetupConfig setup_config; + std::bitset setup_steps_done{0}; + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 +}; + +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 +// insert other definitions here +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 + +} // namespace charger +} // namespace module + +#endif // CHARGER_ISO15118_CHARGER_IMPL_HPP diff --git a/modules/Evse15118D20/charger/session_logger.cpp b/modules/Evse15118D20/charger/session_logger.cpp new file mode 100644 index 000000000..5f7860572 --- /dev/null +++ b/modules/Evse15118D20/charger/session_logger.cpp @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include "session_logger.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +using LogEvent = iso15118::session::logging::Event; + +std::string get_filename_for_current_time() { + const auto now = std::chrono::system_clock::now(); + const auto now_t = std::chrono::system_clock::to_time_t(now); + + std::tm now_tm; + gmtime_r(&now_t, &now_tm); + + char buffer[64]; + strftime(buffer, sizeof(buffer), "%y%m%d_%H-%M-%S.yaml", &now_tm); + return buffer; +} + +// static auto timepoint_to_string(const iso15118::session::logging::TimePoint& timepoint) { +// using namespace date; +// return static_cast(timepoint); +// } + +std::ostream& operator<<(std::ostream& os, const iso15118::session::logging::ExiMessageDirection& direction) { + using Direction = iso15118::session::logging::ExiMessageDirection; + switch (direction) { + case Direction::FROM_EV: + return os << "FROM_EV"; + case Direction::TO_EV: + return os << "TO_EV"; + } + + return os; +} + +class SessionLog { +public: + SessionLog(const std::string& file_name) : file(file_name.c_str(), std::ios::out) { + if (not file.good()) { + throw std::runtime_error("Failed to open file " + file_name + " for writing iso15118 session log"); + } + + EVLOG_info << "Created logfile at: " << file_name; + } + void operator()(const iso15118::session::logging::SimpleEvent& event) { + file << "- type: INFO\n"; + add_timestamp(event.time_point); + file << " info: \"" << event.info << "\"\n"; + } + + void operator()(const iso15118::session::logging::ExiMessageEvent& event) { + file << "- type: EXI\n"; + add_timestamp(event.time_point); + file << " direction: " << event.direction << "\n"; + file << " sdp_payload_type: " << event.payload_type << "\n"; + add_hex_encoded_data(event.data, event.len); + } + + void flush() { + file.flush(); + } + +private: + std::fstream file; + + void add_timestamp(const iso15118::session::logging::TimePoint& timestamp) { + if (not timestamp_initialized) { + last_timestamp = timestamp; + timestamp_initialized = true; + } + + const auto offset_ms = std::chrono::duration_cast(timestamp - last_timestamp); + file << " timestamp_offset: " << offset_ms.count() << "\n"; + + const auto dp = date::floor(timestamp); + const auto time = date::make_time(timestamp - dp); + const auto milliseconds = std::chrono::duration_cast(time.subseconds()); + file << " timestamp: \""; + file << std::setfill('0') << std::setw(2) << time.hours().count() << ":"; + file << std::setfill('0') << std::setw(2) << time.minutes().count() << ":"; + file << std::setfill('0') << std::setw(2) << time.seconds().count() << "."; + file << std::setfill('0') << std::setw(4) << milliseconds.count(); + file << "\"\n"; + + last_timestamp = timestamp; + } + + void add_hex_encoded_data(const uint8_t* data, size_t len) { + file << " data: \""; + + const auto flags = file.flags(); + + file << std::hex; + + for (int i = 0; i < len; ++i) { + file << std::setfill('0') << std::setw(2) << static_cast(data[i]); + } + + file.flags(flags); + file << "\"\n"; + } + + iso15118::session::logging::TimePoint last_timestamp; + bool timestamp_initialized{false}; +}; + +SessionLogger::SessionLogger(std::filesystem::path output_dir_) : output_dir(std::filesystem::absolute(output_dir_)) { + // FIXME (aw): this is quite brute force ... + if (not std::filesystem::exists(output_dir)) { + std::filesystem::create_directory(output_dir); + } + + iso15118::session::logging::set_session_log_callback([this](std::uintptr_t id, const LogEvent& event) { + auto log_it = logs.find(id); + if (log_it == logs.end()) { + const auto log_file_name = output_dir / get_filename_for_current_time(); + const auto emplaced = logs.emplace(id, std::make_unique(log_file_name.string())); + + log_it = emplaced.first; + } + + auto& log = *log_it->second; + + std::visit(log, event); + log.flush(); + }); +} + +SessionLogger::~SessionLogger() = default; diff --git a/modules/Evse15118D20/charger/session_logger.hpp b/modules/Evse15118D20/charger/session_logger.hpp new file mode 100644 index 000000000..6bdc5982a --- /dev/null +++ b/modules/Evse15118D20/charger/session_logger.hpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#pragma once + +#include +#include +#include +#include + +// forward declare +class SessionLog; + +class SessionLogger { +public: + SessionLogger(std::filesystem::path output_dir); + ~SessionLogger(); + +private: + std::filesystem::path output_dir; + std::map> logs; +}; diff --git a/modules/Evse15118D20/charger/utils.hpp b/modules/Evse15118D20/charger/utils.hpp new file mode 100644 index 000000000..b2155c3f5 --- /dev/null +++ b/modules/Evse15118D20/charger/utils.hpp @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#pragma once + +#include +#include + +static constexpr auto NUMBER_OF_SETUP_STEPS = 4; + +enum class SetupStep { + ENERGY_SERVICE, + AUTH_SETUP, + MAX_LIMITS, + MIN_LIMITS, +}; + +template constexpr auto to_underlying_value(T t) { + return static_cast>(t); +} + +static_assert(NUMBER_OF_SETUP_STEPS == to_underlying_value(SetupStep::MIN_LIMITS) + 1, + "NUMBER_OF_SETUP_STEPS should be in sync with the SetupStep enum definition"); + +constexpr types::iso15118_charger::V2gMessageId convert_v2g_message_type(iso15118::message_20::Type type) { + + using Type = iso15118::message_20::Type; + using Id = types::iso15118_charger::V2gMessageId; + + switch (type) { + case Type::None: + return Id::UnknownMessage; + case Type::SupportedAppProtocolReq: + return Id::SupportedAppProtocolReq; + case Type::SupportedAppProtocolRes: + return Id::SupportedAppProtocolRes; + case Type::SessionSetupReq: + return Id::SessionSetupReq; + case Type::SessionSetupRes: + return Id::SessionSetupRes; + case Type::AuthorizationSetupReq: + return Id::AuthorizationSetupReq; + case Type::AuthorizationSetupRes: + return Id::AuthorizationSetupRes; + case Type::AuthorizationReq: + return Id::AuthorizationReq; + case Type::AuthorizationRes: + return Id::AuthorizationRes; + case Type::ServiceDiscoveryReq: + return Id::ServiceDiscoveryReq; + case Type::ServiceDiscoveryRes: + return Id::ServiceDiscoveryRes; + case Type::ServiceDetailReq: + return Id::ServiceDetailReq; + case Type::ServiceDetailRes: + return Id::ServiceDetailRes; + case Type::ServiceSelectionReq: + return Id::ServiceSelectionReq; + case Type::ServiceSelectionRes: + return Id::ServiceSelectionRes; + case Type::DC_ChargeParameterDiscoveryReq: + return Id::DcChargeParameterDiscoveryReq; + case Type::DC_ChargeParameterDiscoveryRes: + return Id::DcChargeParameterDiscoveryRes; + case Type::ScheduleExchangeReq: + return Id::ScheduleExchangeReq; + case Type::ScheduleExchangeRes: + return Id::ScheduleExchangeRes; + case Type::DC_CableCheckReq: + return Id::DcCableCheckReq; + case Type::DC_CableCheckRes: + return Id::DcCableCheckRes; + case Type::DC_PreChargeReq: + return Id::DcPreChargeReq; + case Type::DC_PreChargeRes: + return Id::DcPreChargeRes; + case Type::PowerDeliveryReq: + return Id::PowerDeliveryReq; + case Type::PowerDeliveryRes: + return Id::PowerDeliveryRes; + case Type::DC_ChargeLoopReq: + return Id::DcChargeLoopReq; + case Type::DC_ChargeLoopRes: + return Id::DcChargeLoopRes; + case Type::DC_WeldingDetectionReq: + return Id::DcWeldingDetectionReq; + case Type::DC_WeldingDetectionRes: + return Id::DcWeldingDetectionRes; + case Type::SessionStopReq: + return Id::SessionStopReq; + case Type::SessionStopRes: + return Id::SessionStopRes; + case Type::AC_ChargeParameterDiscoveryReq: + return Id::AcChargeParameterDiscoveryReq; + case Type::AC_ChargeParameterDiscoveryRes: + return Id::AcChargeParameterDiscoveryRes; + case Type::AC_ChargeLoopReq: + return Id::AcChargeLoopReq; + case Type::AC_ChargeLoopRes: + return Id::AcChargeLoopRes; + } + + return Id::UnknownMessage; +} diff --git a/modules/Evse15118D20/manifest.yaml b/modules/Evse15118D20/manifest.yaml new file mode 100644 index 000000000..f6e66fea2 --- /dev/null +++ b/modules/Evse15118D20/manifest.yaml @@ -0,0 +1,51 @@ +description: >- + This module is a draft implementation of iso15118-20 for the EVSE side +config: + device: + description: >- + Ethernet device used for HLC. Any local interface that has an ipv6 + link-local and a MAC addr will work + type: string + default: eth0 + certificate_path: + description: Path to certificate directories + type: string + default: "" + logging_path: + description: Path to logging directory (will be created if non existent) + type: string + default: "." + tls_negotiation_strategy: + description: Select strategy on how to negotiate connection encryption + type: string + enum: + - ACCEPT_CLIENT_OFFER + - ENFORCE_TLS + - ENFORCE_NO_TLS + default: ACCEPT_CLIENT_OFFER + private_key_password: + description: Password for private key files (USE ONLY FOR TESTING) + type: string + default: "123456" + enable_ssl_logging: + description: Verbosely log the ssl/tls connection + type: boolean + default: false + enable_tls_key_logging: + description: >- + Enable/Disable the export of TLS session keys (pre-master-secret) + during a TLS handshake. Note that this option is for testing and + simulation purpose only + type: boolean + default: false +provides: + charger: + interface: ISO15118_charger + description: >- + This interface provides limited access to iso15118-20 +enable_external_mqtt: false +metadata: + license: https://opensource.org/licenses/Apache-2.0 + authors: + - aw@pionix.de + - Sebastian Lukas diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index 13730d5bf..7dd8b0676 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -243,21 +243,28 @@ void EvseManager::ready() { auto sae_mode = types::iso15118_charger::SaeJ2847BidiMode::None; // Set up energy transfer modes for HLC. For now we only support either DC or AC, not both at the same time. - std::vector transfer_modes; + std::vector transfer_modes; if (config.charge_mode == "AC") { types::iso15118_charger::SetupPhysicalValues setup_physical_values; setup_physical_values.ac_nominal_voltage = config.ac_nominal_voltage; r_hlc[0]->call_set_charging_parameters(setup_physical_values); + constexpr auto support_bidi = false; + // FIXME: we cannot change this during run time at the moment. Refactor ISO interface to exclude transfer // modes from setup // transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::AC_single_phase_core); - transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::AC_three_phase_core); + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::AC_three_phase_core, support_bidi}); } else if (config.charge_mode == "DC") { - transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::DC_extended); + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::DC_extended, false}); + + const auto caps = get_powersupply_capabilities(); + update_powersupply_capabilities(caps); - update_powersupply_capabilities(get_powersupply_capabilities()); + if (caps.bidirectional) { + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::DC_extended, true}); + } // Set present measurements on HLC to sane defaults types::iso15118_charger::DcEvsePresentVoltageCurrent present_values; @@ -594,10 +601,8 @@ void EvseManager::ready() { // Install debug V2G Messages handler if session logging is enabled if (config.session_logging) { - r_hlc[0]->subscribe_v2g_messages([this](types::iso15118_charger::V2gMessages v2g_messages) { - json v2g = v2g_messages; - log_v2g_message(v2g); - }); + r_hlc[0]->subscribe_v2g_messages( + [this](types::iso15118_charger::V2gMessages v2g_messages) { log_v2g_message(v2g_messages); }); r_hlc[0]->subscribe_selected_protocol( [this](std::string selected_protocol) { this->selected_protocol = selected_protocol; }); @@ -1013,12 +1018,13 @@ void EvseManager::setup_fake_DC_mode() { types::iso15118_charger::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 transfer_modes; + std::vector transfer_modes; + constexpr auto support_bidi = false; - transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::DC_core); - transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::DC_extended); - transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::DC_combo_core); - transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::DC_unique); + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::DC_core, support_bidi}); + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::DC_extended, support_bidi}); + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::DC_combo_core, support_bidi}); + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::DC_unique, support_bidi}); types::iso15118_charger::DcEvsePresentVoltageCurrent present_values; present_values.evse_present_voltage = 400; // FIXME: set a correct values @@ -1037,7 +1043,7 @@ void EvseManager::setup_fake_DC_mode() { evse_min_limits.evse_minimum_voltage_limit = 0; r_hlc[0]->call_update_dc_minimum_limits(evse_min_limits); - const auto sae_mode = types::iso15118_charger::SaeJ2847BidiMode::None; + constexpr auto sae_mode = types::iso15118_charger::SaeJ2847BidiMode::None; r_hlc[0]->call_setup(evseid, transfer_modes, sae_mode, config.session_logging); } @@ -1051,17 +1057,18 @@ void EvseManager::setup_AC_mode() { types::iso15118_charger::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 transfer_modes; + std::vector transfer_modes; + constexpr auto support_bidi = false; - transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::AC_single_phase_core); + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::AC_single_phase_core, support_bidi}); if (get_hw_capabilities().max_phase_count_import == 3) { - transfer_modes.push_back(types::iso15118_charger::EnergyTransferMode::AC_three_phase_core); + transfer_modes.push_back({types::iso15118_charger::EnergyTransferMode::AC_three_phase_core, support_bidi}); } types::iso15118_charger::SetupPhysicalValues setup_physical_values; - const auto sae_mode = types::iso15118_charger::SaeJ2847BidiMode::None; + constexpr auto sae_mode = types::iso15118_charger::SaeJ2847BidiMode::None; if (get_hlc_enabled()) { r_hlc[0]->call_setup(evseid, transfer_modes, sae_mode, config.session_logging); @@ -1089,6 +1096,8 @@ void EvseManager::setup_v2h_mode() { powersupply_capabilities.min_import_voltage_V.has_value()) { evse_min_limits.evse_minimum_current_limit = -powersupply_capabilities.min_import_current_A.value(); evse_min_limits.evse_minimum_voltage_limit = powersupply_capabilities.min_import_voltage_V.value(); + evse_min_limits.evse_minimum_power_limit = + evse_min_limits.evse_minimum_current_limit * evse_min_limits.evse_minimum_voltage_limit; r_hlc[0]->call_update_dc_minimum_limits(evse_min_limits); } else { EVLOG_error << "No Import Current, Power or Voltage is available!!!"; @@ -1239,24 +1248,19 @@ bool EvseManager::get_hlc_waiting_for_auth_pnc() { return hlc_waiting_for_auth_pnc; } -void EvseManager::log_v2g_message(Object m) { - std::string msg = m["v2g_message_id"]; +void EvseManager::log_v2g_message(types::iso15118_charger::V2gMessages v2g_messages) { - std::string xml = ""; - std::string json_str = ""; - if (m["v2g_message_xml"].is_null() and m["v2g_message_json"].is_string()) { - json_str = m["v2g_message_json"]; - } else if (m["v2g_message_xml"].is_string()) { - xml = m["v2g_message_xml"]; - } + const std::string msg = types::iso15118_charger::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, m["v2g_message_exi_hex"], m["v2g_message_exi_base64"], - json_str); + 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, m["v2g_message_exi_hex"], m["v2g_message_exi_base64"], - json_str); + session_log.evse(true, fmt::format("V2G {}", msg), xml, exi_hex, exi_base64, json_str); } } diff --git a/modules/EvseManager/EvseManager.hpp b/modules/EvseManager/EvseManager.hpp index adc42f8aa..3127fb75f 100644 --- a/modules/EvseManager/EvseManager.hpp +++ b/modules/EvseManager/EvseManager.hpp @@ -226,6 +226,8 @@ class EvseManager : public Everest::ModuleBase { types::iso15118_charger::DcEvseMinimumLimits evse_min_limits; evse_min_limits.evse_minimum_current_limit = powersupply_capabilities.min_export_current_A; evse_min_limits.evse_minimum_voltage_limit = powersupply_capabilities.min_export_voltage_V; + evse_min_limits.evse_minimum_power_limit = + evse_min_limits.evse_minimum_current_limit * evse_min_limits.evse_minimum_voltage_limit; r_hlc[0]->call_update_dc_minimum_limits(evse_min_limits); // HLC layer will also get new maximum current/voltage/watt limits etc, but those will need to run through @@ -289,7 +291,7 @@ class EvseManager : public Everest::ModuleBase { types::authorization::ProvidedIdToken autocharge_token; - void log_v2g_message(Object m); + void log_v2g_message(types::iso15118_charger::V2gMessages v2g_messages); // Reservations bool reserved; diff --git a/modules/EvseV2G/charger/ISO15118_chargerImpl.cpp b/modules/EvseV2G/charger/ISO15118_chargerImpl.cpp index c7b8376ee..55d585a85 100644 --- a/modules/EvseV2G/charger/ISO15118_chargerImpl.cpp +++ b/modules/EvseV2G/charger/ISO15118_chargerImpl.cpp @@ -68,7 +68,7 @@ void ISO15118_chargerImpl::ready() { void ISO15118_chargerImpl::handle_setup( types::iso15118_charger::EVSEID& evse_id, - std::vector& supported_energy_transfer_modes, + std::vector& supported_energy_transfer_modes, types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) { uint8_t len = evse_id.evse_id.length(); @@ -89,9 +89,14 @@ void ISO15118_chargerImpl::handle_setup( v2g_ctx->is_dc_charger = true; - for (auto& energy_transfer_mode : supported_energy_transfer_modes) { + for (const auto& mode : supported_energy_transfer_modes) { - switch (energy_transfer_mode) { + if (mode.bidirectional) { + dlog(DLOG_LEVEL_INFO, "Ignoring bidirectional SupportedEnergyTransferMode"); + continue; + } + + switch (mode.energy_transfer_mode) { case types::iso15118_charger::EnergyTransferMode::AC_single_phase_core: energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_AC_single_phase_core; v2g_ctx->is_dc_charger = false; @@ -116,7 +121,7 @@ void ISO15118_chargerImpl::handle_setup( if (energyArrayLen == 0) { dlog(DLOG_LEVEL_WARNING, "Unable to configure SupportedEnergyTransferMode %s", - types::iso15118_charger::energy_transfer_mode_to_string(energy_transfer_mode).c_str()); + types::iso15118_charger::energy_transfer_mode_to_string(mode.energy_transfer_mode).c_str()); } break; } diff --git a/modules/EvseV2G/charger/ISO15118_chargerImpl.hpp b/modules/EvseV2G/charger/ISO15118_chargerImpl.hpp index df011e544..f08233d73 100644 --- a/modules/EvseV2G/charger/ISO15118_chargerImpl.hpp +++ b/modules/EvseV2G/charger/ISO15118_chargerImpl.hpp @@ -34,9 +34,10 @@ class ISO15118_chargerImpl : public ISO15118_chargerImplBase { protected: // command handler functions (virtual) - virtual void handle_setup(types::iso15118_charger::EVSEID& evse_id, - std::vector& supported_energy_transfer_modes, - types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) override; + virtual void + handle_setup(types::iso15118_charger::EVSEID& evse_id, + std::vector& supported_energy_transfer_modes, + types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) override; virtual void handle_set_charging_parameters(types::iso15118_charger::SetupPhysicalValues& physical_values) override; virtual void handle_session_setup(std::vector& payment_options, bool& supported_certificate_service) override; diff --git a/modules/EvseV2G/tests/ISO15118_chargerImplStub.hpp b/modules/EvseV2G/tests/ISO15118_chargerImplStub.hpp index a6779623f..d3a33f315 100644 --- a/modules/EvseV2G/tests/ISO15118_chargerImplStub.hpp +++ b/modules/EvseV2G/tests/ISO15118_chargerImplStub.hpp @@ -20,9 +20,10 @@ struct ISO15118_chargerImplStub : public ISO15118_chargerImplBase { virtual void ready() { } - virtual void handle_setup(types::iso15118_charger::EVSEID& evse_id, - std::vector& supported_energy_transfer_modes, - types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) { + virtual void + handle_setup(types::iso15118_charger::EVSEID& evse_id, + std::vector& supported_energy_transfer_modes, + types::iso15118_charger::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) { std::cout << "ISO15118_chargerImplBase::handle_setup called" << std::endl; } virtual void handle_set_charging_parameters(types::iso15118_charger::SetupPhysicalValues& physical_values) { diff --git a/modules/EvseV2G/v2g_server.cpp b/modules/EvseV2G/v2g_server.cpp index 4cb886327..891d147dc 100644 --- a/modules/EvseV2G/v2g_server.cpp +++ b/modules/EvseV2G/v2g_server.cpp @@ -144,9 +144,9 @@ static void publish_var_V2G_Message(v2g_connection* conn, bool is_req) { } #endif // EVEREST_MBED_TLS - v2g_message.v2g_message_exi_base64 = EXI_Base64; - v2g_message.v2g_message_id = get_v2g_message_id(conn->ctx->current_v2g_msg, conn->ctx->selected_protocol, is_req); - v2g_message.v2g_message_exi_hex = msg_as_hex_string; + v2g_message.exi_base64 = EXI_Base64; + v2g_message.id = get_v2g_message_id(conn->ctx->current_v2g_msg, conn->ctx->selected_protocol, is_req); + v2g_message.exi = msg_as_hex_string; conn->ctx->p_charger->publish_v2g_messages(v2g_message); } diff --git a/modules/PyEvJosev/utilities.py b/modules/PyEvJosev/utilities.py index e0c57d30f..1a88720e9 100644 --- a/modules/PyEvJosev/utilities.py +++ b/modules/PyEvJosev/utilities.py @@ -27,6 +27,7 @@ def emit(self, record): else: log.debug(msg) + def setup_everest_logging(): # remove all logging handler so that we'll have only our custom one # FIXME (aw): this is probably bad practice because if everyone does that, only the last one might survive @@ -40,6 +41,7 @@ def setup_everest_logging(): logging.getLogger().addHandler(handler) + def choose_first_ipv6_local() -> str: for iface in netifaces.interfaces(): if netifaces.AF_INET6 in netifaces.ifaddresses(iface): @@ -50,14 +52,17 @@ def choose_first_ipv6_local() -> str: log.warning('No necessary IPv6 link-local address was found!') return 'eth0' + def determine_network_interface(preferred_interface: str) -> str: if preferred_interface == "auto": return choose_first_ipv6_local() elif preferred_interface not in netifaces.interfaces(): - log.warning(f"The network interface {preferred_interface} was not found!") + log.warning( + f"The network interface {preferred_interface} was not found!") return preferred_interface + def patch_josev_config(josev_config: EVCCConfig, everest_config: dict) -> None: josev_config.use_tls = everest_config['tls_active'] @@ -93,5 +98,5 @@ def patch_josev_config(josev_config: EVCCConfig, everest_config: dict) -> None: josev_config.supported_protocols = load_requested_protocols(protocols) josev_config.supported_energy_services = load_requested_energy_services( - ['DC'] + ['DC','DC_BPT'] ) diff --git a/types/iso15118_charger.yaml b/types/iso15118_charger.yaml index 9ae161a12..995f124c7 100644 --- a/types/iso15118_charger.yaml +++ b/types/iso15118_charger.yaml @@ -100,6 +100,26 @@ types: - CertificateInstallationRes - CertificateUpdateReq - CertificateUpdateRes + - AuthorizationSetupReq + - AuthorizationSetupRes + - ScheduleExchangeReq + - ScheduleExchangeRes + - ServiceSelectionReq + - ServiceSelectionRes + - AcChargeLoopReq + - AcChargeLoopRes + - AcChargeParameterDiscoveryReq + - AcChargeParameterDiscoveryRes + - DcCableCheckReq + - DcCableCheckRes + - DcChargeLoopReq + - DcChargeLoopRes + - DcChargeParameterDiscoveryReq + - DcChargeParameterDiscoveryRes + - DcPreChargeReq + - DcPreChargeRes + - DcWeldingDetectionReq + - DcWeldingDetectionRes - UnknownMessage SaeJ2847BidiMode: description: Bidi mode for sae j2847_2 @@ -170,13 +190,24 @@ types: type: number minimum: 0 maximum: 1500 + evse_maximum_discharge_current_limit: + description: Maximum discharge current the EVSE can deliver in A + type: number + minimum: 0 + maximum: 10000 + evse_maximum_discharge_power_limit: + description: Maximum discharge power the EVSE can deliver in W + type: number + minimum: 0 + maximum: 1000000 DcEvseMinimumLimits: - description: Minimum Values (current and voltage) the EVSE can deliver + description: Minimum Values the EVSE can deliver type: object additionalProperties: false required: - evse_minimum_current_limit - evse_minimum_voltage_limit + - evse_minimum_power_limit properties: evse_minimum_current_limit: description: Minimum current the EVSE can deliver with the expected accuracy in A @@ -188,6 +219,21 @@ types: type: number minimum: 0 maximum: 1500 + evse_minimum_power_limit: + description: Minimum power the EVSE can deliver with the expected accuracy in W + type: number + minimum: 0 + maximum: 1000000 + evse_minimum_discharge_current_limit: + description: Minimum discharge current the EVSE can deliver in A + type: number + minimum: 0 + maximum: 10000 + evse_minimum_discharge_power_limit: + description: Minimum discharge power the EVSE can deliver in W + type: number + minimum: 0 + maximum: 1000000 SetupPhysicalValues: description: >- Initial physical values for setup a AC or DC charging session @@ -375,25 +421,25 @@ types: type: object additionalProperties: false required: - - v2g_message_id + - id properties: - v2g_message_id: + id: description: This element contains the id of the v2g message body type: string $ref: /iso15118_charger#/V2gMessageId - v2g_message_xml: + xml: description: Contains the decoded EXI stream as V2G message XML file type: string minLength: 0 - v2g_message_json: + v2g_json: description: Contains the decoded EXI stream as V2G message JSON file type: string minLength: 0 - v2g_message_exi_hex: + exi: description: Contains the EXI stream as hex string type: string minLength: 0 - v2g_message_exi_base64: + exi_base64: description: Contains the EXI stream as base64 string type: string minLength: 0 @@ -481,3 +527,62 @@ types: description: This contains the responder URL type: string maxLength: 512 + SupportedEnergyMode: + description: Supported energy mode & if the mode supports bidirectional + type: object + additionalProperties: false + required: + - energy_transfer_mode + - bidirectional + properties: + energy_transfer_mode: + description: The energy mode supported by the SECC + ype: string + $ref: /iso15118_charger#/EnergyTransferMode + bidirectional: + description: Set true if the powersupply (AC or DC) supports bidi mode + type: boolean + DisplayParameters: + description: Parameters that may be displayed on the EVSE + type: object + additionalProperties: false + properties: + present_soc: + description: Current SoC of the EV battery + type: number + minimum: 0 + maximum: 100 + minimum_soc: + description: Minimum SoC EV needs after charging + type: number + minimum: 0 + maximum: 100 + target_soc: + description: Target SoC EV needs after charging + type: number + minimum: 0 + maximum: 100 + maximum_soc: + description: The SoC at which the EV will prohibit + type: number + minimum: 0 + maximum: 100 + remaining_time_to_minimum_soc: + description: Remaining time it takes to reach minimum SoC + type: number + minimum: 0 + remaining_time_to_target_soc: + description: Remaining time it takes to reach target SoC + type: number + minimum: 0 + remaining_time_to_maximum_soc: + description: Remaining time it takes to reach maximum SoC + type: number + minimum: 0 + battery_energy_capacity: + description: Energy capacity in Wh of the EV battery + type: number + minimum: 0 + inlet_hot: + description: Inlet temperature is too high + type: boolean