diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 583f88282..f6349c26c 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -11,6 +11,7 @@ generate_config_run_script(CONFIG sil-gen-pm) generate_config_run_script(CONFIG sil-ocpp) generate_config_run_script(CONFIG sil-ocpp-custom-extension) generate_config_run_script(CONFIG sil-ocpp-pnc) +generate_config_run_script(CONFIG sil-ocpp201-pnc) generate_config_run_script(CONFIG example) # install configs diff --git a/config/config-sil-ocpp-pnc.yaml b/config/config-sil-ocpp-pnc.yaml index bfc4eb956..5c117a6d4 100644 --- a/config/config-sil-ocpp-pnc.yaml +++ b/config/config-sil-ocpp-pnc.yaml @@ -162,7 +162,7 @@ active_modules: evse_security: module: EvseSecurity config_module: - private_key_password: "V2GCaPass2023Valencia" + private_key_password: "123456" token_provider_1: module: DummyTokenProviderManual energy_manager: diff --git a/config/config-sil-ocpp201-pnc.yaml b/config/config-sil-ocpp201-pnc.yaml new file mode 100644 index 000000000..9fd745110 --- /dev/null +++ b/config/config-sil-ocpp201-pnc.yaml @@ -0,0 +1,192 @@ +active_modules: + iso15118_charger: + module: EvseV2G + config_module: + device: auto + tls_security: allow + verify_contract_cert_chain: false + connections: + security: + - module_id: evse_security + implementation_id: main + iso15118_car: + module: PyEvJosev + config_module: + device: auto + supported_ISO15118_2: true + tls_active: true + is_cert_install_needed: true + evse_manager_1: + module: EvseManager + config_module: + connector_id: 1 + three_phases: true + has_ventilation: true + country_code: DE + evse_id: "DE*PNX*00001" + session_logging: true + session_logging_xml: false + session_logging_path: /tmp/everest-logs + ac_hlc_enabled: true + ac_hlc_use_5percent: false + ac_enforce_hlc: false + connections: + bsp: + - module_id: yeti_driver_1 + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver_1 + implementation_id: powermeter + slac: + - module_id: slac + implementation_id: evse + hlc: + - module_id: iso15118_charger + implementation_id: charger + evse_manager_2: + module: EvseManager + config_module: + connector_id: 2 + three_phases: true + has_ventilation: true + country_code: DE + evse_id: "2" + session_logging: true + session_logging_xml: false + session_logging_path: /tmp + ac_hlc_enabled: false + ac_hlc_use_5percent: false + ac_enforce_hlc: false + connections: + bsp: + - module_id: yeti_driver_2 + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver_2 + implementation_id: powermeter + slac: + - module_id: slac + implementation_id: evse + hlc: + - module_id: iso15118_charger + implementation_id: charger + yeti_driver_1: + module: JsYetiSimulator + config_module: + connector_id: 1 + yeti_driver_2: + module: JsYetiSimulator + config_module: + connector_id: 2 + slac: + module: JsSlacSimulator + car_simulator_1: + module: JsCarSimulator + config_module: + connector_id: 1 + auto_enable: true + auto_exec: false + auto_exec_commands: sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug + connections: + simulation_control: + - module_id: yeti_driver_1 + implementation_id: yeti_simulation_control + ev: + - module_id: iso15118_car + implementation_id: ev + slac: + - module_id: slac + implementation_id: ev + car_simulator_2: + module: JsCarSimulator + config_module: + connector_id: 2 + auto_enable: true + auto_exec: false + connections: + simulation_control: + - module_id: yeti_driver_2 + implementation_id: yeti_simulation_control + ev: + - module_id: iso15118_car + implementation_id: ev + slac: + - module_id: slac + implementation_id: ev + ocpp: + module: OCPP201 + connections: + evse_manager: + - module_id: evse_manager_1 + implementation_id: evse + - module_id: evse_manager_2 + implementation_id: evse + auth: + - module_id: auth + implementation_id: main + system: + - module_id: system + implementation_id: main + security: + - module_id: evse_security + implementation_id: main + evse_security: + module: EvseSecurity + config_module: + private_key_password: "123456" + token_provider_1: + module: DummyTokenProviderManual + auth: + module: Auth + config_module: + connection_timeout: 120 + selection_algorithm: PlugEvents + connections: + token_provider: + - module_id: token_provider_1 + implementation_id: main + - module_id: ocpp + implementation_id: auth_provider + - module_id: evse_manager_1 + implementation_id: token_provider + - module_id: evse_manager_2 + implementation_id: token_provider + token_validator: + - module_id: ocpp + implementation_id: auth_validator + evse_manager: + - module_id: evse_manager_1 + implementation_id: evse + - module_id: evse_manager_2 + implementation_id: evse + energy_manager: + module: EnergyManager + 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_1 + implementation_id: energy_grid + - module_id: evse_manager_2 + implementation_id: energy_grid + powermeter: + - module_id: yeti_driver_1 + implementation_id: powermeter + api: + module: API + connections: + evse_manager: + - module_id: evse_manager_1 + implementation_id: evse + system: + module: System + +x-module-layout: {} diff --git a/dependencies.yaml b/dependencies.yaml index 54aa66164..1aa2f85ff 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -1,7 +1,7 @@ --- everest-framework: git: https://github.com/EVerest/everest-framework.git - git_tag: v0.10.1 + git_tag: v0.11.0 options: ["BUILD_TESTING OFF"] sigslot: git: https://github.com/palacaze/sigslot @@ -46,10 +46,18 @@ libcurl: git_tag: curl-8_4_0 cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBCURL" +# EvseSecurity +# This has to appear before libocpp in this file since it is also a direct dependency of libocpp +# and would otherwise be overwritten by the version used there +libevse-security: + git: https://github.com/EVerest/libevse-security.git + git_tag: bce1ba4 + cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBEVSE_SECURITY" + # OCPP libocpp: git: https://github.com/EVerest/libocpp.git - git_tag: 86adda6 + git_tag: 778e080 cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBOCPP" # Josev Josev: @@ -75,12 +83,7 @@ everest-utils: git: https://github.com/EVerest/everest-utils.git git_tag: v0.2.1 cmake_condition: "EVEREST_CORE_BUILD_TESTING" -# evse-security, since this is a direct dependency of libocpp it will get overwritten by the version set there -# setting it here can be misleading since it does not affect the version being used -libevse-security: - git: https://github.com/EVerest/libevse-security.git - git_tag: v0.4.3 - cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBEVSE_SECURITY" + # unit testing gtest: # GoogleTest now follows the Abseil Live at Head philosophy. We recommend updating to the latest commit in the main branch as often as possible. diff --git a/interfaces/evse_security.yaml b/interfaces/evse_security.yaml index 40feba778..353d2142e 100644 --- a/interfaces/evse_security.yaml +++ b/interfaces/evse_security.yaml @@ -71,10 +71,26 @@ cmds: description: Indicates the result of the command and optional certificate hash data type: object $ref: /evse_security#/GetInstalledCertificatesResult - get_ocsp_request_data: - description: Command to retrieve the OCSP request data of the V2G certificates + get_v2g_ocsp_request_data: + description: >- + Command to retrieve the OCSP request data of the V2G certificates. Contains OCSP data for each + certificate that is present in the chain (excluding the root). + result: + description: The OCSP request data of all V2G CA certificates including the Sub CAs (exluding the root) + type: object + $ref: /evse_security#/OCSPRequestDataList + get_mo_ocsp_request_data: + description: >- + Command to retrieve the OCSP request data of the given MO certificate chain. Contains OCSP data + for each certificate that is present in the chain (excluding the root) + arguments: + certificate_chain: + description: Certificate chain for which the OCSP data is retrieved + type: string result: - description: The OCSP request data of all V2G CA certificates including Sub CAs + description: >- + The OCSP request data of the given certificate chain. Contains OCSP data for each + certificate in the given chain. type: object $ref: /evse_security#/OCSPRequestDataList update_ocsp_cache: diff --git a/lib/staging/ocpp/evse_security_ocpp.cpp b/lib/staging/ocpp/evse_security_ocpp.cpp index 27fcf5409..e8b942d2c 100644 --- a/lib/staging/ocpp/evse_security_ocpp.cpp +++ b/lib/staging/ocpp/evse_security_ocpp.cpp @@ -28,9 +28,8 @@ EvseSecurity::update_leaf_certificate(const std::string& certificate_chain, this->r_security.call_update_leaf_certificate(certificate_chain, conversions::from_ocpp(certificate_type))); } -ocpp::CertificateValidationResult -EvseSecurity::verify_certificate(const std::string& certificate_chain, - const ocpp::CertificateSigningUseEnum& certificate_type) { +ocpp::CertificateValidationResult EvseSecurity::verify_certificate(const std::string& certificate_chain, + const ocpp::LeafCertificateType& certificate_type) { return conversions::to_ocpp( this->r_security.call_verify_certificate(certificate_chain, conversions::from_ocpp(certificate_type))); } @@ -53,16 +52,28 @@ EvseSecurity::get_installed_certificates(const std::vector EvseSecurity::get_ocsp_request_data() { +std::vector EvseSecurity::get_v2g_ocsp_request_data() { std::vector result; - const auto ocsp_request_data = this->r_security.call_get_ocsp_request_data(); + const auto ocsp_request_data = this->r_security.call_get_v2g_ocsp_request_data(); for (const auto& ocsp_request_entry : ocsp_request_data.ocsp_request_data_list) { result.push_back(conversions::to_ocpp(ocsp_request_entry)); } return result; } + +std::vector EvseSecurity::get_mo_ocsp_request_data(const std::string& certificate_chain) { + std::vector result; + + const auto ocsp_request_data = this->r_security.call_get_mo_ocsp_request_data(certificate_chain); + for (const auto& ocsp_request_entry : ocsp_request_data.ocsp_request_data_list) { + result.push_back(conversions::to_ocpp(ocsp_request_entry)); + } + + return result; +} + void EvseSecurity::update_ocsp_cache(const ocpp::CertificateHashDataType& certificate_hash_data, const std::string& ocsp_response) { this->r_security.call_update_ocsp_cache(conversions::from_ocpp(certificate_hash_data), ocsp_response); @@ -123,14 +134,16 @@ ocpp::CaCertificateType to_ocpp(types::evse_security::CaCertificateType other) { } } -ocpp::CertificateSigningUseEnum to_ocpp(types::evse_security::LeafCertificateType other) { +ocpp::LeafCertificateType to_ocpp(types::evse_security::LeafCertificateType other) { switch (other) { case types::evse_security::LeafCertificateType::CSMS: - return ocpp::CertificateSigningUseEnum::ChargingStationCertificate; + return ocpp::LeafCertificateType::CSMS; case types::evse_security::LeafCertificateType::V2G: - return ocpp::CertificateSigningUseEnum::V2GCertificate; + return ocpp::LeafCertificateType::V2G; case types::evse_security::LeafCertificateType::MF: - return ocpp::CertificateSigningUseEnum::ManufacturerCertificate; + return ocpp::LeafCertificateType::MF; + case types::evse_security::LeafCertificateType::MO: + return ocpp::LeafCertificateType::MO; default: throw std::runtime_error( "Could not convert types::evse_security::LeafCertificateType to ocpp::CertificateSigningUseEnum"); @@ -305,6 +318,22 @@ types::evse_security::LeafCertificateType from_ocpp(ocpp::CertificateSigningUseE } } +types::evse_security::LeafCertificateType from_ocpp(ocpp::LeafCertificateType other) { + switch (other) { + case ocpp::LeafCertificateType::CSMS: + return types::evse_security::LeafCertificateType::CSMS; + case ocpp::LeafCertificateType::V2G: + return types::evse_security::LeafCertificateType::V2G; + case ocpp::LeafCertificateType::MF: + return types::evse_security::LeafCertificateType::MF; + case ocpp::LeafCertificateType::MO: + return types::evse_security::LeafCertificateType::MO; + default: + throw std::runtime_error( + "Could not convert ocpp::CertificateSigningUseEnum to types::evse_security::LeafCertificateType"); + } +} + types::evse_security::CertificateType from_ocpp(ocpp::CertificateType other) { switch (other) { case ocpp::CertificateType::V2GRootCertificate: diff --git a/lib/staging/ocpp/evse_security_ocpp.hpp b/lib/staging/ocpp/evse_security_ocpp.hpp index b1882e6b0..2c4d9d150 100644 --- a/lib/staging/ocpp/evse_security_ocpp.hpp +++ b/lib/staging/ocpp/evse_security_ocpp.hpp @@ -20,15 +20,15 @@ class EvseSecurity : public ocpp::EvseSecurity { const ocpp::CaCertificateType& certificate_type) override; ocpp::DeleteCertificateResult delete_certificate(const ocpp::CertificateHashDataType& certificate_hash_data) override; - ocpp::CertificateValidationResult - verify_certificate(const std::string& certificate_chain, - const ocpp::CertificateSigningUseEnum& certificate_type) override; + ocpp::CertificateValidationResult verify_certificate(const std::string& certificate_chain, + const ocpp::LeafCertificateType& certificate_type) override; ocpp::InstallCertificateResult update_leaf_certificate(const std::string& certificate_chain, const ocpp::CertificateSigningUseEnum& certificate_type) override; std::vector get_installed_certificates(const std::vector& certificate_types) override; - std::vector get_ocsp_request_data() override; + std::vector get_v2g_ocsp_request_data() override; + std::vector get_mo_ocsp_request_data(const std::string& certificate_chain) override; void update_ocsp_cache(const ocpp::CertificateHashDataType& certificate_hash_data, const std::string& ocsp_response) override; bool is_ca_certificate_installed(const ocpp::CaCertificateType& certificate_type) override; @@ -44,7 +44,7 @@ class EvseSecurity : public ocpp::EvseSecurity { namespace conversions { ocpp::CaCertificateType to_ocpp(types::evse_security::CaCertificateType other); -ocpp::CertificateSigningUseEnum to_ocpp(types::evse_security::LeafCertificateType other); +ocpp::LeafCertificateType to_ocpp(types::evse_security::LeafCertificateType other); ocpp::CertificateType to_ocpp(types::evse_security::CertificateType other); ocpp::HashAlgorithmEnumType to_ocpp(types::evse_security::HashAlgorithm other); ocpp::InstallCertificateResult to_ocpp(types::evse_security::InstallCertificateResult other); @@ -58,6 +58,7 @@ ocpp::KeyPair to_ocpp(types::evse_security::KeyPair other); types::evse_security::CaCertificateType from_ocpp(ocpp::CaCertificateType other); types::evse_security::LeafCertificateType from_ocpp(ocpp::CertificateSigningUseEnum other); +types::evse_security::LeafCertificateType from_ocpp(ocpp::LeafCertificateType other); types::evse_security::CertificateType from_ocpp(ocpp::CertificateType other); types::evse_security::HashAlgorithm from_ocpp(ocpp::HashAlgorithmEnumType other); types::evse_security::InstallCertificateResult from_ocpp(ocpp::InstallCertificateResult other); @@ -70,4 +71,4 @@ types::evse_security::KeyPair from_ocpp(ocpp::KeyPair other); }; // namespace conversions -#endif // EVEREST_SECURITY_OCPP_HPP \ No newline at end of file +#endif // EVEREST_SECURITY_OCPP_HPP diff --git a/modules/EvseManager/CMakeLists.txt b/modules/EvseManager/CMakeLists.txt index 8441b3d55..d7409f670 100644 --- a/modules/EvseManager/CMakeLists.txt +++ b/modules/EvseManager/CMakeLists.txt @@ -57,4 +57,7 @@ target_sources(${MODULE_NAME} # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 # insert other things like install cmds etc here +if(EVEREST_CORE_BUILD_TESTING) + add_subdirectory(tests) +endif() # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index 5e83a11f7..d568da160 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -12,9 +12,12 @@ #include #include +#include +#include #include +#include "everest/logging.hpp" #include "scoped_lock_timeout.hpp" namespace module { @@ -53,32 +56,51 @@ Charger::Charger(const std::unique_ptr& bsp, const std::unique_ hlc_use_5percent_current_session = false; + // create thread for processing errors/error clearings + std::thread error_thread([this]() { + for (;;) { + auto events = this->error_handling_event_queue.wait(); + if (!events.empty()) { + Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_signal_loop); + for (auto& event : events) { + switch (event) { + case ErrorHandlingEvents::prevent_charging: + shared_context.error_prevent_charging_flag = true; + break; + case ErrorHandlingEvents::prevent_charging_welded: + shared_context.error_prevent_charging_flag = true; + shared_context.contactor_welded = true; + break; + case ErrorHandlingEvents::all_errors_cleared: + shared_context.error_prevent_charging_flag = false; + shared_context.contactor_welded = false; + break; + default: + EVLOG_error << "ErrorHandlingEvents invalid value: " + << static_cast>(event); + break; + } + } + } + } + }); + error_thread.detach(); + // Register callbacks for errors/error clearings error_handling->signal_error.connect([this](const types::evse_manager::Error e, const bool prevent_charging) { if (prevent_charging) { - std::thread error_thread([this, e]() { - Everest::scoped_lock_timeout lock(state_machine_mutex, Everest::MutexDescription::Charger_signal_error); - shared_context.error_prevent_charging_flag = true; - if (e.error_code == types::evse_manager::ErrorEnum::MREC17EVSEContactorFault) { - shared_context.contactor_welded = true; - } - }); - error_thread.detach(); + if (e.error_code == types::evse_manager::ErrorEnum::MREC17EVSEContactorFault) { + error_handling_event_queue.push(ErrorHandlingEvents::prevent_charging_welded); + } else { + error_handling_event_queue.push(ErrorHandlingEvents::prevent_charging); + } } }); error_handling->signal_all_errors_cleared.connect([this]() { EVLOG_info << "All errors cleared"; signal_simple_event(types::evse_manager::SessionEventEnum::AllErrorsCleared); - { - std::thread error_thread([this]() { - Everest::scoped_lock_timeout lock(state_machine_mutex, - Everest::MutexDescription::Charger_signal_error_cleared); - shared_context.error_prevent_charging_flag = false; - shared_context.contactor_welded = false; - }); - error_thread.detach(); - } + error_handling_event_queue.push(ErrorHandlingEvents::all_errors_cleared); }); } diff --git a/modules/EvseManager/Charger.hpp b/modules/EvseManager/Charger.hpp index 3aaaecb8f..0969881f8 100644 --- a/modules/EvseManager/Charger.hpp +++ b/modules/EvseManager/Charger.hpp @@ -37,6 +37,7 @@ #include #include "ErrorHandling.hpp" +#include "EventQueue.hpp" #include "IECStateMachine.hpp" #include "scoped_lock_timeout.hpp" @@ -301,6 +302,15 @@ class Charger { const std::unique_ptr& error_handling; const types::evse_board_support::Connector_type& connector_type; + // ErrorHandling events + enum class ErrorHandlingEvents : std::uint8_t { + prevent_charging, + prevent_charging_welded, + all_errors_cleared + }; + + EventQueue error_handling_event_queue; + // constants static constexpr float CHARGER_ABSOLUTE_MAX_CURRENT{80.}; constexpr static int LEGACY_WAKEUP_TIMEOUT{30000}; diff --git a/modules/EvseManager/EnumFlags.hpp b/modules/EvseManager/EnumFlags.hpp new file mode 100644 index 000000000..b2691fb2e --- /dev/null +++ b/modules/EvseManager/EnumFlags.hpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef ENUMFLAGS_HPP +#define ENUMFLAGS_HPP + +#include +#include +#include + +namespace module { + +template struct AtomicEnumFlags { + static_assert(std::is_enum() == true, "Not enum"); + static_assert(std::is_integral() == true, "Not integer"); + static_assert((sizeof(B) * 8) >= static_cast(T::last) + 1, "Underlying flag type too small"); + std::atomic _value{0ULL}; + + constexpr std::size_t bit(const T& flag) const { + return 1ULL << static_cast>(flag); + } + + constexpr void set(const T& flag, bool value) { + if (value) { + set(flag); + } else { + reset(flag); + } + } + + constexpr void set(const T& flag) { + _value |= bit(flag); + } + + constexpr void reset(const T& flag) { + _value &= ~bit(flag); + } + + constexpr void reset() { + _value = 0ULL; + } + + constexpr bool all_reset() const { + return _value == 0ULL; + } + + constexpr bool is_set(const T& flag) const { + return (_value & bit(flag)) != 0; + } + + constexpr bool is_reset(const T& flag) const { + return (_value & bit(flag)) == 0; + } +}; + +} // namespace module +#endif diff --git a/modules/EvseManager/ErrorHandling.cpp b/modules/EvseManager/ErrorHandling.cpp index ab2e8f3ae..c09922fd8 100644 --- a/modules/EvseManager/ErrorHandling.cpp +++ b/modules/EvseManager/ErrorHandling.cpp @@ -182,29 +182,32 @@ void ErrorHandling::raise_overcurrent_error(const std::string& description) { void ErrorHandling::clear_overcurrent_error() { // clear externally - p_evse->request_clear_all_evse_manager_MREC4OverCurrentFailure(); - types::evse_manager::ErrorEnum evse_error{types::evse_manager::ErrorEnum::VendorWarning}; - types::evse_manager::Error output_error; - output_error.error_description = ""; - output_error.error_severity = types::evse_manager::Error_severity::High; - output_error.error_code = types::evse_manager::ErrorEnum::MREC4OverCurrentFailure; - - if (modify_error_evse_manager("evse_manager/MREC4OverCurrentFailure", false, evse_error)) { - // signal to charger an error has been cleared that prevents charging - output_error.error_code = evse_error; - signal_error_cleared(output_error, true); - } else { - // signal an error cleared that does not prevent charging - output_error.error_code = evse_error; - signal_error_cleared(output_error, false); - } + if (active_errors.bsp.is_set(BspErrors::MREC4OverCurrentFailure)) { + p_evse->request_clear_all_evse_manager_MREC4OverCurrentFailure(); + + types::evse_manager::ErrorEnum evse_error{types::evse_manager::ErrorEnum::VendorWarning}; + types::evse_manager::Error output_error; + output_error.error_description = ""; + output_error.error_severity = types::evse_manager::Error_severity::High; + output_error.error_code = types::evse_manager::ErrorEnum::MREC4OverCurrentFailure; - if (active_errors.all_cleared()) { - // signal to charger that all errors are cleared now - signal_all_errors_cleared(); - // clear errors with HLC stack - if (hlc) { - r_hlc[0]->call_reset_error(); + if (modify_error_evse_manager("evse_manager/MREC4OverCurrentFailure", false, evse_error)) { + // signal to charger an error has been cleared that prevents charging + output_error.error_code = evse_error; + signal_error_cleared(output_error, true); + } else { + // signal an error cleared that does not prevent charging + output_error.error_code = evse_error; + signal_error_cleared(output_error, false); + } + + if (active_errors.all_cleared()) { + // signal to charger that all errors are cleared now + signal_all_errors_cleared(); + // clear errors with HLC stack + if (hlc) { + r_hlc[0]->call_reset_error(); + } } } } @@ -226,29 +229,32 @@ void ErrorHandling::raise_internal_error(const std::string& description) { void ErrorHandling::clear_internal_error() { // clear externally - p_evse->request_clear_all_evse_manager_Internal(); - types::evse_manager::ErrorEnum evse_error{types::evse_manager::ErrorEnum::VendorWarning}; - types::evse_manager::Error output_error; - output_error.error_description = ""; - output_error.error_severity = types::evse_manager::Error_severity::High; - output_error.error_code = types::evse_manager::ErrorEnum::VendorError; - - if (modify_error_evse_manager("evse_manager/Internal", false, evse_error)) { - // signal to charger an error has been cleared that prevents charging - output_error.error_code = evse_error; - signal_error_cleared(output_error, true); - } else { - // signal an error cleared that does not prevent charging - output_error.error_code = evse_error; - signal_error_cleared(output_error, false); - } + if (active_errors.evse_manager.is_set(EvseManagerErrors::Internal)) { + p_evse->request_clear_all_evse_manager_Internal(); - if (active_errors.all_cleared()) { - // signal to charger that all errors are cleared now - signal_all_errors_cleared(); - // clear errors with HLC stack - if (hlc) { - r_hlc[0]->call_reset_error(); + types::evse_manager::ErrorEnum evse_error{types::evse_manager::ErrorEnum::VendorWarning}; + types::evse_manager::Error output_error; + output_error.error_description = ""; + output_error.error_severity = types::evse_manager::Error_severity::High; + output_error.error_code = types::evse_manager::ErrorEnum::VendorError; + + if (modify_error_evse_manager("evse_manager/Internal", false, evse_error)) { + // signal to charger an error has been cleared that prevents charging + output_error.error_code = evse_error; + signal_error_cleared(output_error, true); + } else { + // signal an error cleared that does not prevent charging + output_error.error_code = evse_error; + signal_error_cleared(output_error, false); + } + + if (active_errors.all_cleared()) { + // signal to charger that all errors are cleared now + signal_all_errors_cleared(); + // clear errors with HLC stack + if (hlc) { + r_hlc[0]->call_reset_error(); + } } } } @@ -270,35 +276,38 @@ void ErrorHandling::raise_powermeter_transaction_start_failed_error(const std::s void ErrorHandling::clear_powermeter_transaction_start_failed_error() { // clear externally - p_evse->request_clear_all_evse_manager_PowermeterTransactionStartFailed(); - types::evse_manager::ErrorEnum evse_error{types::evse_manager::ErrorEnum::VendorWarning}; - types::evse_manager::Error output_error; - output_error.error_description = ""; - output_error.error_severity = types::evse_manager::Error_severity::High; - output_error.error_code = types::evse_manager::ErrorEnum::VendorError; - - if (modify_error_evse_manager("evse_manager/PowermeterTransactionStartFailed", false, evse_error)) { - // signal to charger an error has been cleared that prevents charging - output_error.error_code = evse_error; - signal_error_cleared(output_error, true); - } else { - // signal an error cleared that does not prevent charging - output_error.error_code = evse_error; - signal_error_cleared(output_error, false); - } + if (active_errors.evse_manager.is_set(EvseManagerErrors::PowermeterTransactionStartFailed)) { + p_evse->request_clear_all_evse_manager_PowermeterTransactionStartFailed(); - if (active_errors.all_cleared()) { - // signal to charger that all errors are cleared now - signal_all_errors_cleared(); - // clear errors with HLC stack - if (hlc) { - r_hlc[0]->call_reset_error(); + types::evse_manager::ErrorEnum evse_error{types::evse_manager::ErrorEnum::VendorWarning}; + types::evse_manager::Error output_error; + output_error.error_description = ""; + output_error.error_severity = types::evse_manager::Error_severity::High; + output_error.error_code = types::evse_manager::ErrorEnum::VendorError; + + if (modify_error_evse_manager("evse_manager/PowermeterTransactionStartFailed", false, evse_error)) { + // signal to charger an error has been cleared that prevents charging + output_error.error_code = evse_error; + signal_error_cleared(output_error, true); + } else { + // signal an error cleared that does not prevent charging + output_error.error_code = evse_error; + signal_error_cleared(output_error, false); + } + + if (active_errors.all_cleared()) { + // signal to charger that all errors are cleared now + signal_all_errors_cleared(); + // clear errors with HLC stack + if (hlc) { + r_hlc[0]->call_reset_error(); + } } } } bool ErrorHandling::modify_error_bsp(const Everest::error::Error& error, bool active, - types::evse_manager::ErrorEnum& evse_error) { + types::evse_manager::ErrorEnum evse_error) { const std::string& error_type = error.type; if (active) { @@ -308,121 +317,121 @@ bool ErrorHandling::modify_error_bsp(const Everest::error::Error& error, bool ac } if (error_type == "evse_board_support/DiodeFault") { - active_errors.bsp.DiodeFault = active; + active_errors.bsp.set(BspErrors::DiodeFault, active); evse_error = types::evse_manager::ErrorEnum::DiodeFault; } else if (error_type == "evse_board_support/VentilationNotAvailable") { - active_errors.bsp.VentilationNotAvailable = active; + active_errors.bsp.set(BspErrors::VentilationNotAvailable, active); evse_error = types::evse_manager::ErrorEnum::VentilationNotAvailable; } else if (error_type == "evse_board_support/BrownOut") { - active_errors.bsp.BrownOut = active; + active_errors.bsp.set(BspErrors::BrownOut, active); evse_error = types::evse_manager::ErrorEnum::BrownOut; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/EnergyManagement") { - active_errors.bsp.EnergyManagement = active; + active_errors.bsp.set(BspErrors::EnergyManagement, active); evse_error = types::evse_manager::ErrorEnum::EnergyManagement; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/PermanentFault") { - active_errors.bsp.PermanentFault = active; + active_errors.bsp.set(BspErrors::PermanentFault, active); evse_error = types::evse_manager::ErrorEnum::PermanentFault; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC2GroundFailure") { - active_errors.bsp.MREC2GroundFailure = active; + active_errors.bsp.set(BspErrors::MREC2GroundFailure, active); evse_error = types::evse_manager::ErrorEnum::MREC2GroundFailure; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC4OverCurrentFailure") { - active_errors.bsp.MREC4OverCurrentFailure = active; + active_errors.bsp.set(BspErrors::MREC4OverCurrentFailure, active); evse_error = types::evse_manager::ErrorEnum::MREC4OverCurrentFailure; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC5OverVoltage") { - active_errors.bsp.MREC5OverVoltage = active; + active_errors.bsp.set(BspErrors::MREC5OverVoltage, active); evse_error = types::evse_manager::ErrorEnum::MREC5OverVoltage; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC6UnderVoltage") { - active_errors.bsp.MREC6UnderVoltage = active; + active_errors.bsp.set(BspErrors::MREC6UnderVoltage, active); evse_error = types::evse_manager::ErrorEnum::MREC6UnderVoltage; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC8EmergencyStop") { - active_errors.bsp.MREC8EmergencyStop = active; + active_errors.bsp.set(BspErrors::MREC8EmergencyStop, active); evse_error = types::evse_manager::ErrorEnum::MREC8EmergencyStop; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_EmergencyShutdown); } } else if (error_type == "evse_board_support/MREC10InvalidVehicleMode") { - active_errors.bsp.MREC10InvalidVehicleMode = active; + active_errors.bsp.set(BspErrors::MREC10InvalidVehicleMode, active); evse_error = types::evse_manager::ErrorEnum::MREC10InvalidVehicleMode; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC14PilotFault") { - active_errors.bsp.MREC14PilotFault = active; + active_errors.bsp.set(BspErrors::MREC14PilotFault, active); evse_error = types::evse_manager::ErrorEnum::MREC14PilotFault; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC15PowerLoss") { - active_errors.bsp.MREC15PowerLoss = active; + active_errors.bsp.set(BspErrors::MREC15PowerLoss, active); evse_error = types::evse_manager::ErrorEnum::MREC15PowerLoss; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC17EVSEContactorFault") { - active_errors.bsp.MREC17EVSEContactorFault = active; + active_errors.bsp.set(BspErrors::MREC17EVSEContactorFault, active); evse_error = types::evse_manager::ErrorEnum::MREC17EVSEContactorFault; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Contactor); } } else if (error_type == "evse_board_support/MREC19CableOverTempStop") { - active_errors.bsp.MREC19CableOverTempStop = active; + active_errors.bsp.set(BspErrors::MREC19CableOverTempStop, active); evse_error = types::evse_manager::ErrorEnum::MREC19CableOverTempStop; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC20PartialInsertion") { - active_errors.bsp.MREC20PartialInsertion = active; + active_errors.bsp.set(BspErrors::MREC20PartialInsertion, active); evse_error = types::evse_manager::ErrorEnum::MREC20PartialInsertion; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC23ProximityFault") { - active_errors.bsp.MREC23ProximityFault = active; + active_errors.bsp.set(BspErrors::MREC23ProximityFault, active); evse_error = types::evse_manager::ErrorEnum::MREC23ProximityFault; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC24ConnectorVoltageHigh") { - active_errors.bsp.MREC24ConnectorVoltageHigh = active; + active_errors.bsp.set(BspErrors::MREC24ConnectorVoltageHigh, active); evse_error = types::evse_manager::ErrorEnum::MREC24ConnectorVoltageHigh; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC25BrokenLatch") { - active_errors.bsp.MREC25BrokenLatch = active; + active_errors.bsp.set(BspErrors::MREC25BrokenLatch, active); evse_error = types::evse_manager::ErrorEnum::MREC25BrokenLatch; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/MREC26CutCable") { - active_errors.bsp.MREC26CutCable = active; + active_errors.bsp.set(BspErrors::MREC26CutCable, active); evse_error = types::evse_manager::ErrorEnum::MREC26CutCable; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_board_support/VendorError") { - active_errors.bsp.VendorError = active; + active_errors.bsp.set(BspErrors::VendorError, active); evse_error = types::evse_manager::ErrorEnum::VendorError; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); @@ -438,11 +447,12 @@ bool ErrorHandling::modify_error_bsp(const Everest::error::Error& error, bool ac } return false; } - return true; // Error stops charging + // Error stops charging + return true; }; bool ErrorHandling::modify_error_connector_lock(const Everest::error::Error& error, bool active, - types::evse_manager::ErrorEnum& evse_error) { + types::evse_manager::ErrorEnum evse_error) { const std::string& error_type = error.type; if (active) { @@ -452,25 +462,25 @@ bool ErrorHandling::modify_error_connector_lock(const Everest::error::Error& err } if (error_type == "connector_lock/ConnectorLockCapNotCharged") { - active_errors.connector_lock.ConnectorLockCapNotCharged = active; + active_errors.connector_lock.set(ConnectorLockErrors::ConnectorLockCapNotCharged, active); evse_error = types::evse_manager::ErrorEnum::ConnectorLockCapNotCharged; } else if (error_type == "connector_lock/ConnectorLockUnexpectedClose") { - active_errors.connector_lock.ConnectorLockUnexpectedClose = active; + active_errors.connector_lock.set(ConnectorLockErrors::ConnectorLockUnexpectedClose, active); evse_error = types::evse_manager::ErrorEnum::ConnectorLockUnexpectedClose; } else if (error_type == "connector_lock/ConnectorLockUnexpectedOpen") { - active_errors.connector_lock.ConnectorLockUnexpectedOpen = active; + active_errors.connector_lock.set(ConnectorLockErrors::ConnectorLockUnexpectedOpen, active); evse_error = types::evse_manager::ErrorEnum::ConnectorLockUnexpectedOpen; } else if (error_type == "connector_lock/ConnectorLockFailedLock") { - active_errors.connector_lock.ConnectorLockFailedLock = active; + active_errors.connector_lock.set(ConnectorLockErrors::ConnectorLockFailedLock, active); evse_error = types::evse_manager::ErrorEnum::ConnectorLockFailedLock; } else if (error_type == "connector_lock/ConnectorLockFailedUnlock") { - active_errors.connector_lock.ConnectorLockFailedUnlock = active; + active_errors.connector_lock.set(ConnectorLockErrors::ConnectorLockFailedUnlock, active); evse_error = types::evse_manager::ErrorEnum::ConnectorLockFailedUnlock; } else if (error_type == "connector_lock/MREC1ConnectorLockFailure") { - active_errors.connector_lock.MREC1ConnectorLockFailure = active; + active_errors.connector_lock.set(ConnectorLockErrors::MREC1ConnectorLockFailure, active); evse_error = types::evse_manager::ErrorEnum::MREC1ConnectorLockFailure; } else if (error_type == "connector_lock/VendorError") { - active_errors.connector_lock.VendorError = active; + active_errors.connector_lock.set(ConnectorLockErrors::VendorError, active); evse_error = types::evse_manager::ErrorEnum::VendorError; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); @@ -482,11 +492,12 @@ bool ErrorHandling::modify_error_connector_lock(const Everest::error::Error& err } return false; } - return true; // Error stops charging + // Error stops charging + return true; }; bool ErrorHandling::modify_error_ac_rcd(const Everest::error::Error& error, bool active, - types::evse_manager::ErrorEnum& evse_error) { + types::evse_manager::ErrorEnum evse_error) { const std::string& error_type = error.type; if (active) { @@ -496,31 +507,31 @@ bool ErrorHandling::modify_error_ac_rcd(const Everest::error::Error& error, bool } if (error_type == "ac_rcd/MREC2GroundFailure") { - active_errors.ac_rcd.MREC2GroundFailure = active; + active_errors.ac_rcd.set(AcRcdErrors::MREC2GroundFailure, active); evse_error = types::evse_manager::ErrorEnum::MREC2GroundFailure; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_RCD); } } else if (error_type == "ac_rcd/VendorError") { - active_errors.ac_rcd.VendorError = active; + active_errors.ac_rcd.set(AcRcdErrors::VendorError, active); evse_error = types::evse_manager::ErrorEnum::VendorError; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "ac_rcd/Selftest") { - active_errors.ac_rcd.Selftest = active; + active_errors.ac_rcd.set(AcRcdErrors::Selftest, active); evse_error = types::evse_manager::ErrorEnum::RCD_Selftest; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "ac_rcd/AC") { - active_errors.ac_rcd.AC = active; + active_errors.ac_rcd.set(AcRcdErrors::AC, active); evse_error = types::evse_manager::ErrorEnum::RCD_AC; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_RCD); } } else if (error_type == "ac_rcd/DC") { - active_errors.ac_rcd.DC = active; + active_errors.ac_rcd.set(AcRcdErrors::DC, active); evse_error = types::evse_manager::ErrorEnum::RCD_DC; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_RCD); @@ -532,29 +543,32 @@ bool ErrorHandling::modify_error_ac_rcd(const Everest::error::Error& error, bool } return false; } - return true; // Error stops charging + // Error stops charging + return true; }; bool ErrorHandling::modify_error_evse_manager(const std::string& error_type, bool active, - types::evse_manager::ErrorEnum& evse_error) { + types::evse_manager::ErrorEnum evse_error) { if (error_type == "evse_manager/MREC4OverCurrentFailure") { - active_errors.bsp.MREC4OverCurrentFailure = active; + active_errors.bsp.set(BspErrors::MREC4OverCurrentFailure, active); evse_error = types::evse_manager::ErrorEnum::MREC4OverCurrentFailure; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else if (error_type == "evse_manager/PowermeterTransactionStartFailed") { - active_errors.evse_manager.PowermeterTransactionStartFailed = active; + active_errors.evse_manager.set(EvseManagerErrors::PowermeterTransactionStartFailed, active); evse_error = types::evse_manager::ErrorEnum::PowermeterTransactionStartFailed; if (hlc && active) { r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); } } else { - return false; // Error does not stop charging, ignored here + // Error does not stop charging, ignored here + return false; } - return true; // Error stops charging + // Error stops charging + return true; }; } // namespace module diff --git a/modules/EvseManager/ErrorHandling.hpp b/modules/EvseManager/ErrorHandling.hpp index ce2ff5927..406e8b8ce 100644 --- a/modules/EvseManager/ErrorHandling.hpp +++ b/modules/EvseManager/ErrorHandling.hpp @@ -17,6 +17,10 @@ #include "ld-ev.hpp" +#ifdef BUILD_TESTING_MODULE_EVSE_MANAGER +#include +#endif + #include #include #include @@ -29,76 +33,75 @@ #include #include +#include "EnumFlags.hpp" #include "Timeout.hpp" #include "utils/thread.hpp" namespace module { // bit field to track which errors are active at the moment. Only tracks errors that stop charging. -struct ActiveErrors { - struct bsp_errors { - std::atomic_bool DiodeFault{false}; - std::atomic_bool VentilationNotAvailable{false}; - std::atomic_bool BrownOut{false}; - std::atomic_bool EnergyManagement{false}; - std::atomic_bool PermanentFault{false}; - std::atomic_bool MREC2GroundFailure{false}; - std::atomic_bool MREC4OverCurrentFailure{false}; - std::atomic_bool MREC5OverVoltage{false}; - std::atomic_bool MREC6UnderVoltage{false}; - std::atomic_bool MREC8EmergencyStop{false}; - std::atomic_bool MREC10InvalidVehicleMode{false}; - std::atomic_bool MREC14PilotFault{false}; - std::atomic_bool MREC15PowerLoss{false}; - std::atomic_bool MREC17EVSEContactorFault{false}; - std::atomic_bool MREC19CableOverTempStop{false}; - std::atomic_bool MREC20PartialInsertion{false}; - std::atomic_bool MREC23ProximityFault{false}; - std::atomic_bool MREC24ConnectorVoltageHigh{false}; - std::atomic_bool MREC25BrokenLatch{false}; - std::atomic_bool MREC26CutCable{false}; - std::atomic_bool VendorError{false}; - } bsp; - - struct evse_manager_errors { - std::atomic_bool MREC4OverCurrentFailure{false}; - std::atomic_bool Internal{false}; - std::atomic_bool PowermeterTransactionStartFailed{false}; - } evse_manager; - - struct ac_rcd_errors { - std::atomic_bool MREC2GroundFailure{false}; - std::atomic_bool VendorError{false}; - std::atomic_bool Selftest{false}; - std::atomic_bool AC{false}; - std::atomic_bool DC{false}; - } ac_rcd; - - struct connector_lock_errors { - std::atomic_bool ConnectorLockCapNotCharged{false}; - std::atomic_bool ConnectorLockUnexpectedOpen{false}; - std::atomic_bool ConnectorLockUnexpectedClose{false}; - std::atomic_bool ConnectorLockFailedLock{false}; - std::atomic_bool ConnectorLockFailedUnlock{false}; - std::atomic_bool MREC1ConnectorLockFailure{false}; - std::atomic_bool VendorError{false}; - } connector_lock; - - bool all_cleared() { - return not(bsp.DiodeFault or bsp.VentilationNotAvailable or bsp.BrownOut or bsp.EnergyManagement or - bsp.PermanentFault or bsp.MREC2GroundFailure or bsp.MREC4OverCurrentFailure or - bsp.MREC5OverVoltage or bsp.MREC6UnderVoltage or bsp.MREC8EmergencyStop or - bsp.MREC10InvalidVehicleMode or bsp.MREC14PilotFault or bsp.MREC15PowerLoss or - bsp.MREC17EVSEContactorFault or bsp.MREC19CableOverTempStop or bsp.MREC20PartialInsertion or - bsp.MREC23ProximityFault or bsp.MREC24ConnectorVoltageHigh or bsp.MREC25BrokenLatch or - bsp.MREC26CutCable or evse_manager.MREC4OverCurrentFailure or evse_manager.Internal or - evse_manager.PowermeterTransactionStartFailed or ac_rcd.MREC2GroundFailure or ac_rcd.VendorError or - ac_rcd.Selftest or ac_rcd.AC or ac_rcd.DC or connector_lock.ConnectorLockCapNotCharged or - connector_lock.ConnectorLockUnexpectedOpen or connector_lock.ConnectorLockUnexpectedClose or - connector_lock.ConnectorLockFailedLock or connector_lock.ConnectorLockFailedUnlock or - connector_lock.MREC1ConnectorLockFailure or connector_lock.VendorError); - } +enum class BspErrors : std::uint8_t { + DiodeFault, + VentilationNotAvailable, + BrownOut, + EnergyManagement, + PermanentFault, + MREC2GroundFailure, + MREC4OverCurrentFailure, + MREC5OverVoltage, + MREC6UnderVoltage, + MREC8EmergencyStop, + MREC10InvalidVehicleMode, + MREC14PilotFault, + MREC15PowerLoss, + MREC17EVSEContactorFault, + MREC19CableOverTempStop, + MREC20PartialInsertion, + MREC23ProximityFault, + MREC24ConnectorVoltageHigh, + MREC25BrokenLatch, + MREC26CutCable, + VendorError, + last = VendorError +}; + +enum class EvseManagerErrors : std::uint8_t { + MREC4OverCurrentFailure, + Internal, + PowermeterTransactionStartFailed, + last = PowermeterTransactionStartFailed +}; + +enum class AcRcdErrors : std::uint8_t { + MREC2GroundFailure, + VendorError, + Selftest, + AC, + DC, + last = DC +}; + +enum class ConnectorLockErrors : std::uint8_t { + ConnectorLockCapNotCharged, + ConnectorLockUnexpectedOpen, + ConnectorLockUnexpectedClose, + ConnectorLockFailedLock, + ConnectorLockFailedUnlock, + MREC1ConnectorLockFailure, + VendorError, + last = VendorError +}; + +struct ActiveErrors { + AtomicEnumFlags bsp; + AtomicEnumFlags evse_manager; + AtomicEnumFlags ac_rcd; + AtomicEnumFlags connector_lock; + + inline bool all_cleared() { + return bsp.all_reset() && evse_manager.all_reset() && ac_rcd.all_reset() && connector_lock.all_reset(); + }; }; class ErrorHandling { @@ -133,17 +136,21 @@ class ErrorHandling { const std::vector>& r_ac_rcd; const std::unique_ptr& p_evse; - bool modify_error_bsp(const Everest::error::Error& error, bool active, types::evse_manager::ErrorEnum& evse_error); + bool modify_error_bsp(const Everest::error::Error& error, bool active, types::evse_manager::ErrorEnum evse_error); bool modify_error_connector_lock(const Everest::error::Error& error, bool active, - types::evse_manager::ErrorEnum& evse_error); + types::evse_manager::ErrorEnum evse_error); bool modify_error_ac_rcd(const Everest::error::Error& error, bool active, - types::evse_manager::ErrorEnum& evse_error); + types::evse_manager::ErrorEnum evse_error); bool modify_error_evse_manager(const std::string& error_type, bool active, - types::evse_manager::ErrorEnum& evse_error); + types::evse_manager::ErrorEnum evse_error); bool hlc{false}; ActiveErrors active_errors; + +#ifdef BUILD_TESTING_MODULE_EVSE_MANAGER + FRIEND_TEST(ErrorHandlingTest, vendor); +#endif }; } // namespace module diff --git a/modules/EvseManager/EventQueue.hpp b/modules/EvseManager/EventQueue.hpp new file mode 100644 index 000000000..8938ac64c --- /dev/null +++ b/modules/EvseManager/EventQueue.hpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef EVENTQUEUE_HPP +#define EVENTQUEUE_HPP + +#include +#include +#include +#include + +namespace module { + +template class EventQueue { +public: + using events_t = std::vector; + +private: + events_t pending; + std::mutex mux; + std::condition_variable cv; + +public: + void push(const E& event) { + { + std::lock_guard lock(mux); + pending.push_back(event); + } + cv.notify_all(); + } + + events_t get_events() { + std::lock_guard lock(mux); + events_t active; + pending.swap(active); + return active; + } + + events_t wait() { + std::unique_lock ul(mux); + cv.wait(ul); + events_t active; + pending.swap(active); + ul.unlock(); + return active; + } +}; + +} // namespace module +#endif diff --git a/modules/EvseManager/README.md b/modules/EvseManager/README.md new file mode 100644 index 000000000..5a18b1bfd --- /dev/null +++ b/modules/EvseManager/README.md @@ -0,0 +1,14 @@ +# EvseManager documentation + +see `doc.rst` + +## Tests + +```bash +cd everest-core +mkdir build && cd build +cmake -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=./dist .. +make -j$(nproc) install +``` + +Tests are installed in `./modules/EvseManager/tests/` diff --git a/modules/EvseManager/scoped_lock_timeout.hpp b/modules/EvseManager/scoped_lock_timeout.hpp index 78ca04a88..185620710 100644 --- a/modules/EvseManager/scoped_lock_timeout.hpp +++ b/modules/EvseManager/scoped_lock_timeout.hpp @@ -17,6 +17,7 @@ namespace Everest { enum class MutexDescription { Undefined, + Charger_signal_loop, Charger_signal_error, Charger_signal_error_cleared, Charger_mainloop, @@ -92,6 +93,8 @@ enum class MutexDescription { static std::string to_string(MutexDescription d) { switch (d) { + case MutexDescription::Charger_signal_loop: + return "Charger.cpp: error_handling->signal_loop"; case MutexDescription::Charger_signal_error: return "Charger.cpp: error_handling->signal_error"; case MutexDescription::Charger_signal_error_cleared: diff --git a/modules/EvseManager/tests/CMakeLists.txt b/modules/EvseManager/tests/CMakeLists.txt new file mode 100644 index 000000000..0fa93ca8d --- /dev/null +++ b/modules/EvseManager/tests/CMakeLists.txt @@ -0,0 +1,33 @@ +set(TEST_TARGET_NAME ${PROJECT_NAME}_EvseManager_tests) +add_executable(${TEST_TARGET_NAME}) + +add_dependencies(${TEST_TARGET_NAME} ${MODULE_NAME}) + +get_target_property(GENERATED_INCLUDE_DIR generate_cpp_files EVEREST_GENERATED_INCLUDE_DIR) + +target_include_directories(${TEST_TARGET_NAME} PRIVATE + . .. + ${GENERATED_INCLUDE_DIR} + ${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME} +) + +target_sources(${TEST_TARGET_NAME} PRIVATE + EnumFlagsTest.cpp + ErrorHandlingTest.cpp + EventQueueTest.cpp + ../ErrorHandling.cpp +) + +target_compile_definitions(${TEST_TARGET_NAME} PRIVATE + BUILD_TESTING_MODULE_EVSE_MANAGER +) + +target_link_libraries(${TEST_TARGET_NAME} PRIVATE + GTest::gmock + GTest::gtest_main + everest::log + everest::framework + sigslot +) + +add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME}) diff --git a/modules/EvseManager/tests/EnumFlagsTest.cpp b/modules/EvseManager/tests/EnumFlagsTest.cpp new file mode 100644 index 000000000..6e640f97e --- /dev/null +++ b/modules/EvseManager/tests/EnumFlagsTest.cpp @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include +#include + +#include + +namespace { + +enum class ErrorHandlingFlags : std::uint8_t { + prevent_charging, + prevent_charging_welded, + all_errors_cleared, + last = all_errors_cleared +}; + +enum class BspErrors : std::uint8_t { + DiodeFault, + VentilationNotAvailable, + BrownOut, + EnergyManagement, + PermanentFault, + MREC2GroundFailure, + MREC4OverCurrentFailure, + MREC5OverVoltage, + MREC6UnderVoltage, + MREC8EmergencyStop, + MREC10InvalidVehicleMode, + MREC14PilotFault, + MREC15PowerLoss, + MREC17EVSEContactorFault, + MREC19CableOverTempStop, + MREC20PartialInsertion, + MREC23ProximityFault, + MREC24ConnectorVoltageHigh, + MREC25BrokenLatch, + MREC26CutCable, + VendorError, + last = VendorError +}; + +TEST(AtomicEnumFlagsTest, init) { + module::AtomicEnumFlags flags; + EXPECT_TRUE(flags.all_reset()); +} + +TEST(AtomicEnumFlagsTest, init_large) { + module::AtomicEnumFlags flags; + EXPECT_TRUE(flags.all_reset()); +} + +TEST(AtomicEnumFlagsTest, set_reset_one) { + module::AtomicEnumFlags flags; + EXPECT_TRUE(flags.all_reset()); + + flags.set(ErrorHandlingFlags::all_errors_cleared); + EXPECT_FALSE(flags.all_reset()); + flags.reset(ErrorHandlingFlags::all_errors_cleared); + EXPECT_TRUE(flags.all_reset()); +} + +TEST(AtomicEnumFlagsTest, set_reset_two) { + module::AtomicEnumFlags flags; + EXPECT_TRUE(flags.all_reset()); + + flags.set(ErrorHandlingFlags::all_errors_cleared); + EXPECT_FALSE(flags.all_reset()); + flags.set(ErrorHandlingFlags::prevent_charging); + EXPECT_FALSE(flags.all_reset()); + flags.reset(ErrorHandlingFlags::all_errors_cleared); + EXPECT_FALSE(flags.all_reset()); + flags.reset(ErrorHandlingFlags::prevent_charging); + EXPECT_TRUE(flags.all_reset()); +} + +TEST(AtomicEnumFlagsTest, set_reset_three) { + module::AtomicEnumFlags flags; + EXPECT_TRUE(flags.all_reset()); + + flags.set(ErrorHandlingFlags::all_errors_cleared); + EXPECT_FALSE(flags.all_reset()); + flags.set(ErrorHandlingFlags::prevent_charging); + EXPECT_FALSE(flags.all_reset()); + flags.set(ErrorHandlingFlags::prevent_charging_welded); + EXPECT_FALSE(flags.all_reset()); + flags.reset(); + EXPECT_TRUE(flags.all_reset()); +} + +} // namespace diff --git a/modules/EvseManager/tests/ErrorHandlingTest.cpp b/modules/EvseManager/tests/ErrorHandlingTest.cpp new file mode 100644 index 000000000..c0d976858 --- /dev/null +++ b/modules/EvseManager/tests/ErrorHandlingTest.cpp @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include +#include + +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Tests that don't need access to private members/methods + +namespace { + +using BspErrors = module::BspErrors; +using EvseManagerErrors = module::EvseManagerErrors; +using AcRcdErrors = module::AcRcdErrors; +using ConnectorLockErrors = module::ConnectorLockErrors; + +class ActiveErrorsTest : public testing::Test { +public: + module::ActiveErrors errors; + + std::vector bsp_errors = { + BspErrors::DiodeFault, + BspErrors::VentilationNotAvailable, + BspErrors::BrownOut, + BspErrors::EnergyManagement, + BspErrors::PermanentFault, + BspErrors::MREC2GroundFailure, + BspErrors::MREC4OverCurrentFailure, + BspErrors::MREC5OverVoltage, + BspErrors::MREC6UnderVoltage, + BspErrors::MREC8EmergencyStop, + BspErrors::MREC10InvalidVehicleMode, + BspErrors::MREC14PilotFault, + BspErrors::MREC15PowerLoss, + BspErrors::MREC17EVSEContactorFault, + BspErrors::MREC19CableOverTempStop, + BspErrors::MREC20PartialInsertion, + BspErrors::MREC23ProximityFault, + BspErrors::MREC24ConnectorVoltageHigh, + BspErrors::MREC25BrokenLatch, + BspErrors::MREC26CutCable, + BspErrors::VendorError, + }; + + std::vector evse_manager_errors = { + EvseManagerErrors::MREC4OverCurrentFailure, + EvseManagerErrors::Internal, + EvseManagerErrors::PowermeterTransactionStartFailed, + }; + + std::vector ac_rcd_errors = { + AcRcdErrors::MREC2GroundFailure, + AcRcdErrors::VendorError, + AcRcdErrors::Selftest, + AcRcdErrors::AC, + AcRcdErrors::DC, + }; + + std::vector connector_lock_errors = { + ConnectorLockErrors::ConnectorLockCapNotCharged, + ConnectorLockErrors::ConnectorLockUnexpectedOpen, + ConnectorLockErrors::ConnectorLockUnexpectedClose, + ConnectorLockErrors::ConnectorLockFailedLock, + ConnectorLockErrors::ConnectorLockFailedUnlock, + ConnectorLockErrors::MREC1ConnectorLockFailure, + ConnectorLockErrors::VendorError, + }; + + void SetUp() override { + errors.bsp.reset(); + errors.evse_manager.reset(); + errors.ac_rcd.reset(); + errors.connector_lock.reset(); + } +}; + +//----------------------------------------------------------------------------- +TEST_F(ActiveErrorsTest, bsp_errors) { + ASSERT_TRUE(errors.all_cleared()); + for (auto& p : bsp_errors) { + SCOPED_TRACE(std::to_string(static_cast(p))); + EXPECT_FALSE(errors.bsp.is_set(p)); + errors.bsp.set(p); + EXPECT_TRUE(errors.bsp.is_set(p)); + EXPECT_FALSE(errors.all_cleared()); + errors.bsp.reset(p); + EXPECT_TRUE(errors.all_cleared()); + } +} + +TEST_F(ActiveErrorsTest, evse_manager_errors) { + ASSERT_TRUE(errors.all_cleared()); + for (auto& p : evse_manager_errors) { + SCOPED_TRACE(std::to_string(static_cast(p))); + EXPECT_FALSE(errors.evse_manager.is_set(p)); + errors.evse_manager.set(p); + EXPECT_TRUE(errors.evse_manager.is_set(p)); + EXPECT_FALSE(errors.all_cleared()); + errors.evse_manager.reset(p); + EXPECT_TRUE(errors.all_cleared()); + } +} + +TEST_F(ActiveErrorsTest, ac_rcd_errors) { + ASSERT_TRUE(errors.all_cleared()); + for (auto& p : ac_rcd_errors) { + SCOPED_TRACE(std::to_string(static_cast(p))); + EXPECT_FALSE(errors.ac_rcd.is_set(p)); + errors.ac_rcd.set(p); + EXPECT_TRUE(errors.ac_rcd.is_set(p)); + EXPECT_FALSE(errors.all_cleared()); + errors.ac_rcd.reset(p); + EXPECT_TRUE(errors.all_cleared()); + } +} + +TEST_F(ActiveErrorsTest, connector_lock_errors) { + ASSERT_TRUE(errors.all_cleared()); + for (auto& p : connector_lock_errors) { + SCOPED_TRACE(std::to_string(static_cast(p))); + EXPECT_FALSE(errors.connector_lock.is_set(p)); + errors.connector_lock.set(p); + EXPECT_TRUE(errors.connector_lock.is_set(p)); + EXPECT_FALSE(errors.all_cleared()); + errors.connector_lock.reset(p); + EXPECT_TRUE(errors.all_cleared()); + } +} + +} // namespace + +// ---------------------------------------------------------------------------- +// Tests that need access to private members/methods + +namespace module { + +struct ErrorHandlingSignals { + std::atomic signal_error = false; + std::atomic signal_error_cleared = false; + std::atomic signal_all_errors_cleared = false; + std::atomic called_signal_error = false; + std::atomic called_signal_error_cleared = false; + std::atomic called_signal_all_errors_cleared = false; + + ErrorHandlingSignals(module::ErrorHandling& eh) { + register_callbacks(eh); + } + + void register_callbacks(module::ErrorHandling& eh) { + eh.signal_error.connect([this](types::evse_manager::Error err, bool stop_charging) { + this->called_signal_error = true; + this->signal_error = true; + }); + eh.signal_error_cleared.connect([this](types::evse_manager::Error err, bool stop_charging) { + this->called_signal_error_cleared = true; + this->signal_error_cleared = true; + }); + eh.signal_all_errors_cleared.connect([this]() { + this->called_signal_all_errors_cleared = true; + this->signal_all_errors_cleared = true; + }); + } + + void reset() { + signal_error = false; + signal_error_cleared = false; + signal_all_errors_cleared = false; + called_signal_error = false; + called_signal_error_cleared = false; + called_signal_all_errors_cleared = false; + } +}; + +TEST(ErrorHandlingTest, vendor) { + stub::EvseManagerModuleAdapter manager; + Requirement req("", 0); + const std::unique_ptr evse_board_support = + std::make_unique(&manager, req, "manager"); + const std::vector> ISO15118_charger; + const std::vector> connector_lock; + const std::vector> ac_rcd; + const std::unique_ptr evse_managerImpl = std::make_unique(); + + module::ErrorHandling error_handling(evse_board_support, ISO15118_charger, connector_lock, ac_rcd, + evse_managerImpl); + + // signals are "raised" via raise_error() and clear_error() + // but not via modify_error_bsp() + ErrorHandlingSignals ehs(error_handling); + + EXPECT_FALSE(error_handling.hlc); + ImplementationIdentifier id("evse_manager", "main"); + Everest::error::Error error("evse_board_support/VendorError", "K2Faults::FAULT_CT_CLAMP", + "Vendor specific error code. Will stop charging session.", id); + + bool bResult; + bResult = error_handling.modify_error_bsp(error, true, types::evse_manager::ErrorEnum::VendorError); + EXPECT_TRUE(bResult); + EXPECT_FALSE(error_handling.active_errors.all_cleared()); + EXPECT_FALSE(ehs.called_signal_error); + EXPECT_FALSE(ehs.called_signal_error_cleared); + EXPECT_FALSE(ehs.called_signal_all_errors_cleared); + + ehs.reset(); + bResult = error_handling.modify_error_bsp(error, false, types::evse_manager::ErrorEnum::VendorError); + EXPECT_TRUE(bResult); + EXPECT_TRUE(error_handling.active_errors.all_cleared()); + EXPECT_FALSE(ehs.called_signal_error); + EXPECT_FALSE(ehs.called_signal_error_cleared); + EXPECT_FALSE(ehs.called_signal_all_errors_cleared); + + // VendorWarning not treated as an active error + ehs.reset(); + Everest::error::Error warning("evse_board_support/VendorWarning", "K2Faults::FAULT_CT_CLAMP", + "Vendor specific error code. Will not stop charging session.", id); + bResult = error_handling.modify_error_bsp(warning, true, types::evse_manager::ErrorEnum::VendorWarning); + EXPECT_FALSE(bResult); + EXPECT_TRUE(error_handling.active_errors.all_cleared()); + EXPECT_FALSE(ehs.called_signal_error); + EXPECT_FALSE(ehs.called_signal_error_cleared); + EXPECT_FALSE(ehs.called_signal_all_errors_cleared); + + ehs.reset(); + bResult = error_handling.modify_error_bsp(warning, false, types::evse_manager::ErrorEnum::VendorWarning); + EXPECT_FALSE(bResult); + EXPECT_TRUE(error_handling.active_errors.all_cleared()); + EXPECT_FALSE(ehs.called_signal_error); + EXPECT_FALSE(ehs.called_signal_error_cleared); + EXPECT_FALSE(ehs.called_signal_all_errors_cleared); + + ehs.reset(); + manager.raise_error("evse_board_support/VendorError", "K2Faults::FAULT_CT_CLAMP", + "Vendor specific error code. Will stop charging session."); + EXPECT_FALSE(error_handling.active_errors.all_cleared()); + EXPECT_TRUE(ehs.signal_error); + EXPECT_FALSE(ehs.signal_error_cleared); + EXPECT_FALSE(ehs.signal_all_errors_cleared); + EXPECT_TRUE(ehs.called_signal_error); + EXPECT_FALSE(ehs.called_signal_error_cleared); + EXPECT_FALSE(ehs.called_signal_all_errors_cleared); + + ehs.reset(); + manager.clear_error("evse_board_support/VendorError", "K2Faults::FAULT_CT_CLAMP", + "Vendor specific error code. Will stop charging session."); + EXPECT_TRUE(error_handling.active_errors.all_cleared()); + EXPECT_FALSE(ehs.signal_error); + EXPECT_TRUE(ehs.signal_error_cleared); + EXPECT_TRUE(ehs.signal_all_errors_cleared); + EXPECT_FALSE(ehs.called_signal_error); + EXPECT_TRUE(ehs.called_signal_error_cleared); + EXPECT_TRUE(ehs.called_signal_all_errors_cleared); +} + +} // namespace module diff --git a/modules/EvseManager/tests/EventQueueTest.cpp b/modules/EvseManager/tests/EventQueueTest.cpp new file mode 100644 index 000000000..6ec9bc39c --- /dev/null +++ b/modules/EvseManager/tests/EventQueueTest.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include +#include + +#include +#include + +namespace { + +enum class ErrorHandlingEvents : std::uint8_t { + prevent_charging, + prevent_charging_welded, + all_errors_cleared +}; + +TEST(EventQueue, init) { + module::EventQueue queue; + auto events = queue.get_events(); + EXPECT_EQ(events.size(), 0); +} + +TEST(EventQueue, one) { + module::EventQueue queue; + auto events = queue.get_events(); + EXPECT_EQ(events.size(), 0); + + queue.push(ErrorHandlingEvents::prevent_charging); + events = queue.get_events(); + ASSERT_EQ(events.size(), 1); + EXPECT_EQ(events[0], ErrorHandlingEvents::prevent_charging); + + events = queue.get_events(); + EXPECT_EQ(events.size(), 0); +} + +TEST(EventQueue, two) { + module::EventQueue queue; + auto events = queue.get_events(); + EXPECT_EQ(events.size(), 0); + + queue.push(ErrorHandlingEvents::prevent_charging); + queue.push(ErrorHandlingEvents::prevent_charging_welded); + events = queue.get_events(); + ASSERT_EQ(events.size(), 2); + EXPECT_EQ(events[0], ErrorHandlingEvents::prevent_charging); + EXPECT_EQ(events[1], ErrorHandlingEvents::prevent_charging_welded); + + events = queue.get_events(); + EXPECT_EQ(events.size(), 0); +} + +TEST(EventQueue, wait) { + module::EventQueue queue; + auto events = queue.get_events(); + EXPECT_EQ(events.size(), 0); + + std::size_t count = 0; + std::condition_variable cv; + std::mutex mux; + + std::thread wait_thread([&cv, &count, &queue]() { + cv.notify_one(); + auto events = queue.wait(); + count = events.size(); + cv.notify_one(); + }); + + std::unique_lock ul(mux); + cv.wait(ul); + ASSERT_EQ(count, 0U); + + queue.push(ErrorHandlingEvents::prevent_charging); + cv.wait(ul); + ASSERT_EQ(count, 1U); + + events = queue.get_events(); + EXPECT_EQ(events.size(), 0); + + wait_thread.join(); +} + +} // namespace diff --git a/modules/EvseManager/tests/EvseManagerStub.hpp b/modules/EvseManager/tests/EvseManagerStub.hpp new file mode 100644 index 000000000..630d65dee --- /dev/null +++ b/modules/EvseManager/tests/EvseManagerStub.hpp @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#ifndef EVSEMANAGERSTUB_H_ +#define EVSEMANAGERSTUB_H_ + +#include + +//----------------------------------------------------------------------------- +namespace module::stub { + +struct evse_managerImplStub : public evse_managerImplBase { + evse_managerImplStub() : evse_managerImplBase(nullptr, "manager") { + } + virtual void init() { + } + virtual void ready() { + } + virtual types::evse_manager::Evse handle_get_evse() { + return types::evse_manager::Evse(); + } + virtual bool handle_enable(int& connector_id) { + return true; + } + virtual bool handle_disable(int& connector_id) { + return true; + } + virtual void handle_authorize_response(types::authorization::ProvidedIdToken& provided_token, + types::authorization::ValidationResult& validation_result) { + } + virtual void handle_withdraw_authorization() { + } + virtual bool handle_reserve(int& reservation_id) { + return true; + } + virtual void handle_cancel_reservation() { + } + virtual void handle_set_faulted() { + } + virtual bool handle_pause_charging() { + return true; + } + virtual bool handle_resume_charging() { + return true; + } + virtual bool handle_stop_transaction(types::evse_manager::StopTransactionRequest& request) { + return true; + } + virtual bool handle_force_unlock(int& connector_id) { + return true; + } + virtual void handle_set_external_limits(types::energy::ExternalLimits& value) { + } + virtual types::evse_manager::SwitchThreePhasesWhileChargingResult + handle_switch_three_phases_while_charging(bool& three_phases) { + return types::evse_manager::SwitchThreePhasesWhileChargingResult::Success; + } + virtual void + handle_set_get_certificate_response(types::iso15118_charger::Response_Exi_Stream_Status& certificate_response) { + } + virtual bool handle_external_ready_to_start_charging() { + return true; + } +}; + +struct ModuleAdapterStub : public Everest::ModuleAdapter { + + ModuleAdapterStub() : Everest::ModuleAdapter() { + call = [this](const Requirement& req, const std::string& str, Parameters p) { + return this->call_fn(req, str, p); + }; + publish = [this](const std::string& s1, const std::string& s2, Value v) { this->publish_fn(s1, s2, v); }; + subscribe = [this](const Requirement& req, const std::string& str, ValueCallback cb) { + this->subscribe_fn(req, str, cb); + }; + subscribe_error = [this](const Requirement& req, const std::string& str, Everest::error::ErrorCallback cb) { + this->subscribe_error_fn(req, str, cb); + }; + subscribe_all_errors = [this](Everest::error::ErrorCallback cb) { this->subscribe_all_errors_fn(cb); }; + subscribe_error_cleared = [this](const Requirement& req, const std::string& str, + Everest::error::ErrorCallback cb) { + this->subscribe_error_cleared_fn(req, str, cb); + }; + subscribe_all_errors_cleared = [this](Everest::error::ErrorCallback cb) { + subscribe_all_errors_cleared_fn(cb); + }; + raise_error = [this](const std::string& s1, const std::string& s2, const std::string& s3, + const Everest::error::Severity& sev) { return this->raise_error_fn(s1, s2, s3, sev); }; + request_clear_error_uuid = [this](const std::string& str, const Everest::error::ErrorHandle& eh) { + return this->request_clear_error_uuid_fn(str, eh); + }; + request_clear_all_errors_of_module = [this](const std::string& str) { + return this->request_clear_all_errors_of_module_fn(str); + }; + request_clear_all_errors_of_type_of_module = [this](const std::string& s1, const std::string& s2) { + return this->request_clear_all_errors_of_type_of_module_fn(s1, s2); + }; + ext_mqtt_publish = [this](const std::string& s1, const std::string& s2) { this->ext_mqtt_publish_fn(s1, s2); }; + ext_mqtt_subscribe = [this](const std::string& str, StringHandler sh) { + return this->ext_mqtt_subscribe_fn(str, sh); + }; + telemetry_publish = [this](const std::string& s1, const std::string& s2, const std::string& s3, + const Everest::TelemetryMap& tm) { this->telemetry_publish_fn(s1, s2, s3, tm); }; + } + + virtual Result call_fn(const Requirement&, const std::string&, Parameters) { + std::printf("call_fn\n"); + return std::nullopt; + } + virtual void publish_fn(const std::string&, const std::string&, Value) { + std::printf("publish_fn\n"); + } + virtual void subscribe_fn(const Requirement&, const std::string&, ValueCallback) { + std::printf("subscribe_fn\n"); + } + virtual void subscribe_error_fn(const Requirement&, const std::string&, Everest::error::ErrorCallback) { + std::printf("subscribe_error_fn\n"); + } + virtual void subscribe_all_errors_fn(Everest::error::ErrorCallback) { + std::printf("subscribe_all_errors_fn\n"); + } + virtual void subscribe_error_cleared_fn(const Requirement&, const std::string&, Everest::error::ErrorCallback) { + std::printf("subscribe_error_cleared_fn\n"); + } + virtual void subscribe_all_errors_cleared_fn(Everest::error::ErrorCallback) { + std::printf("subscribe_all_errors_cleared_fn\n"); + } + virtual Everest::error::ErrorHandle raise_error_fn(const std::string&, const std::string&, const std::string&, + const Everest::error::Severity&) { + std::printf("raise_error_fn\n"); + return Everest::error::ErrorHandle(); + } + virtual Result request_clear_error_uuid_fn(const std::string&, const Everest::error::ErrorHandle&) { + std::printf("request_clear_error_uuid_fn\n"); + return std::nullopt; + } + virtual Result request_clear_all_errors_of_module_fn(const std::string&) { + std::printf("request_clear_all_errors_of_module_fn\n"); + return std::nullopt; + } + virtual Result request_clear_all_errors_of_type_of_module_fn(const std::string&, const std::string&) { + std::printf("request_clear_all_errors_of_type_of_module_fn\n"); + return std::nullopt; + } + virtual void ext_mqtt_publish_fn(const std::string&, const std::string&) { + std::printf("ext_mqtt_publish_fn\n"); + } + virtual std::function ext_mqtt_subscribe_fn(const std::string&, StringHandler) { + std::printf("ext_mqtt_subscribe_fn\n"); + return nullptr; + } + virtual void telemetry_publish_fn(const std::string&, const std::string&, const std::string&, + const Everest::TelemetryMap&) { + std::printf("telemetry_publish_fn\n"); + } +}; + +struct EvseManagerModuleAdapter : public ModuleAdapterStub { + EvseManagerModuleAdapter() : id("evse_manager", "main") { + } + + ImplementationIdentifier id; + std::map error_raise; + std::map error_clear; + + virtual void subscribe_error_fn(const Requirement&, const std::string& str, Everest::error::ErrorCallback cb) { + // std::printf("subscribe_error_fn %s\n", str.c_str()); + error_raise[str] = cb; + } + + virtual void subscribe_error_cleared_fn(const Requirement&, const std::string& str, + Everest::error::ErrorCallback cb) { + // std::printf("subscribe_error_cleared_fn %s\n", str.c_str()); + error_clear[str] = cb; + } + + void raise_error(const std::string& error_type, const std::string& error_message, const std::string& error_desc) { + Everest::error::Error error(error_type, error_message, error_desc, id); + error_raise[error_type](error); + } + + void clear_error(const std::string& error_type, const std::string& error_message, const std::string& error_desc) { + Everest::error::Error error(error_type, error_message, error_desc, id); + error_clear[error_type](error); + } +}; + +} // namespace module::stub + +#endif diff --git a/modules/EvseSecurity/conversions.cpp b/modules/EvseSecurity/conversions.cpp index f2417ad7f..eba1adaaf 100644 --- a/modules/EvseSecurity/conversions.cpp +++ b/modules/EvseSecurity/conversions.cpp @@ -45,6 +45,8 @@ evse_security::LeafCertificateType from_everest(types::evse_security::LeafCertif return evse_security::LeafCertificateType::V2G; case types::evse_security::LeafCertificateType::MF: return evse_security::LeafCertificateType::MF; + case types::evse_security::LeafCertificateType::MO: + return evse_security::LeafCertificateType::MO; default: throw std::runtime_error( "Could not convert types::evse_security::LeafCertificateType to evse_security::LeafCertificateType"); @@ -231,6 +233,8 @@ types::evse_security::LeafCertificateType to_everest(evse_security::LeafCertific return types::evse_security::LeafCertificateType::V2G; case evse_security::LeafCertificateType::MF: return types::evse_security::LeafCertificateType::MF; + case evse_security::LeafCertificateType::MO: + return types::evse_security::LeafCertificateType::MO; default: throw std::runtime_error( "Could not convert evse_security::LeafCertificateType to types::evse_security::LeafCertificateType"); @@ -422,4 +426,4 @@ types::evse_security::KeyPair to_everest(evse_security::KeyPair other) { } // namespace conversions -} // namespace module \ No newline at end of file +} // namespace module diff --git a/modules/EvseSecurity/main/evse_securityImpl.cpp b/modules/EvseSecurity/main/evse_securityImpl.cpp index 579a5d98b..cd6572464 100644 --- a/modules/EvseSecurity/main/evse_securityImpl.cpp +++ b/modules/EvseSecurity/main/evse_securityImpl.cpp @@ -63,8 +63,13 @@ types::evse_security::GetInstalledCertificatesResult evse_securityImpl::handle_g return conversions::to_everest(this->evse_security->get_installed_certificates(_certificate_types)); } -types::evse_security::OCSPRequestDataList evse_securityImpl::handle_get_ocsp_request_data() { - return conversions::to_everest(this->evse_security->get_ocsp_request_data()); +types::evse_security::OCSPRequestDataList evse_securityImpl::handle_get_v2g_ocsp_request_data() { + return conversions::to_everest(this->evse_security->get_v2g_ocsp_request_data()); +} + +types::evse_security::OCSPRequestDataList +evse_securityImpl::handle_get_mo_ocsp_request_data(std::string& certificate_chain) { + return conversions::to_everest(this->evse_security->get_mo_ocsp_request_data(certificate_chain)); } void evse_securityImpl::handle_update_ocsp_cache(types::evse_security::CertificateHashData& certificate_hash_data, diff --git a/modules/EvseSecurity/main/evse_securityImpl.hpp b/modules/EvseSecurity/main/evse_securityImpl.hpp index cccfb15be..139bd26a2 100644 --- a/modules/EvseSecurity/main/evse_securityImpl.hpp +++ b/modules/EvseSecurity/main/evse_securityImpl.hpp @@ -47,7 +47,9 @@ class evse_securityImpl : public evse_securityImplBase { types::evse_security::LeafCertificateType& certificate_type) override; virtual types::evse_security::GetInstalledCertificatesResult handle_get_installed_certificates(std::vector& certificate_types) override; - virtual types::evse_security::OCSPRequestDataList handle_get_ocsp_request_data() override; + virtual types::evse_security::OCSPRequestDataList handle_get_v2g_ocsp_request_data() override; + virtual types::evse_security::OCSPRequestDataList + handle_get_mo_ocsp_request_data(std::string& certificate_chain) override; virtual void handle_update_ocsp_cache(types::evse_security::CertificateHashData& certificate_hash_data, std::string& ocsp_response) override; virtual bool handle_is_ca_certificate_installed(types::evse_security::CaCertificateType& certificate_type) override; diff --git a/modules/EvseV2G/iso_server.cpp b/modules/EvseV2G/iso_server.cpp index 9850754e7..af4e2ca7b 100644 --- a/modules/EvseV2G/iso_server.cpp +++ b/modules/EvseV2G/iso_server.cpp @@ -1220,11 +1220,37 @@ static enum v2g_event handle_iso_payment_details(struct v2g_connection* conn) { } } + // initialize contract cert chain to retrieve ocsp request data std::string contract_cert_chain_pem = ""; + // Save the certificate chain in a variable in PEM format to publish it + mbedtls_x509_crt* crt = &conn->ctx->session.contract.crt; + unsigned char* base64Buffer = NULL; + size_t olen; + + while (crt != nullptr && crt->version != 0) { + mbedtls_base64_encode(NULL, 0, &olen, crt->raw.p, crt->raw.len); + base64Buffer = static_cast(malloc(olen)); + if ((base64Buffer == NULL) || + ((mbedtls_base64_encode(base64Buffer, olen, &olen, crt->raw.p, crt->raw.len)) != 0)) { + dlog(DLOG_LEVEL_ERROR, "Unable to encode certificate chain"); + break; + } + contract_cert_chain_pem.append("-----BEGIN CERTIFICATE-----\n"); + contract_cert_chain_pem.append(std::string(reinterpret_cast(base64Buffer), olen)); + contract_cert_chain_pem.append("\n-----END CERTIFICATE-----\n"); + + free(base64Buffer); + crt = crt->next; + } + + std::optional> iso15118_certificate_hash_data; + /* Only if certificate chain verification should be done locally by the EVSE */ if (conn->ctx->session.verify_contract_cert_chain == true) { - std::string v2g_root_cert_path = conn->ctx->certs_path + "/ca/v2g/V2G_ROOT_CA.pem"; - std::string mo_root_cert_path = conn->ctx->certs_path + "/ca/mo/MO_ROOT_CA.pem"; + std::string v2g_root_cert_path = + conn->ctx->r_security->call_get_verify_file(types::evse_security::CaCertificateType::V2G); + std::string mo_root_cert_path = + conn->ctx->r_security->call_get_verify_file(types::evse_security::CaCertificateType::MO); mbedtls_x509_crt contract_root_crt; mbedtls_x509_crt_init(&contract_root_crt); uint32_t flags; @@ -1259,27 +1285,10 @@ static enum v2g_event handle_iso_payment_details(struct v2g_connection* conn) { } dlog(DLOG_LEVEL_INFO, "Validation of the contract certificate was successful!"); - } else { - // Save the certificate chain in a variable in PEM format to publish it - mbedtls_x509_crt* crt = &conn->ctx->session.contract.crt; - unsigned char* base64Buffer = NULL; - size_t olen; - - while (crt != nullptr && crt->version != 0) { - mbedtls_base64_encode(NULL, 0, &olen, crt->raw.p, crt->raw.len); - base64Buffer = static_cast(malloc(olen)); - if ((base64Buffer == NULL) || - ((mbedtls_base64_encode(base64Buffer, olen, &olen, crt->raw.p, crt->raw.len)) != 0)) { - dlog(DLOG_LEVEL_ERROR, "Unable to encode certificate chain"); - break; - } - contract_cert_chain_pem.append("-----BEGIN CERTIFICATE-----\n"); - contract_cert_chain_pem.append(std::string(reinterpret_cast(base64Buffer), olen)); - contract_cert_chain_pem.append("\n-----END CERTIFICATE-----\n"); - free(base64Buffer); - crt = crt->next; - } + // contract chain ocsp data can only be retrieved if the MO root is present and the chain could be verified + const auto ocsp_response = conn->ctx->r_security->call_get_mo_ocsp_request_data(contract_cert_chain_pem); + iso15118_certificate_hash_data = convert_to_certificate_hash_data_info_vector(ocsp_response); } generate_random_data(&conn->ctx->session.gen_challenge, GEN_CHALLENGE_SIZE); @@ -1292,9 +1301,8 @@ static enum v2g_event handle_iso_payment_details(struct v2g_connection* conn) { types::authorization::ProvidedIdToken ProvidedIdToken; ProvidedIdToken.id_token = {std::string(cert_emaid), types::authorization::IdTokenType::eMAID}; ProvidedIdToken.authorization_type = types::authorization::AuthorizationType::PlugAndCharge; - if (contract_cert_chain_pem.empty() == false) { - ProvidedIdToken.certificate = contract_cert_chain_pem; - } + ProvidedIdToken.iso15118CertificateHashData = iso15118_certificate_hash_data; + ProvidedIdToken.certificate = contract_cert_chain_pem; conn->ctx->p_charger->publish_Require_Auth_PnC(ProvidedIdToken); } else { diff --git a/modules/EvseV2G/tools.cpp b/modules/EvseV2G/tools.cpp index 6d8f4691f..f94db476d 100644 --- a/modules/EvseV2G/tools.cpp +++ b/modules/EvseV2G/tools.cpp @@ -341,3 +341,36 @@ std::string convert_to_hex_str(const uint8_t* data, int len) { return string_stream.str(); } + +types::iso15118_charger::HashAlgorithm +convert_to_hash_algorithm(const types::evse_security::HashAlgorithm hash_algorithm) { + switch (hash_algorithm) { + case types::evse_security::HashAlgorithm::SHA256: + return types::iso15118_charger::HashAlgorithm::SHA256; + case types::evse_security::HashAlgorithm::SHA384: + return types::iso15118_charger::HashAlgorithm::SHA384; + case types::evse_security::HashAlgorithm::SHA512: + return types::iso15118_charger::HashAlgorithm::SHA512; + default: + throw std::runtime_error( + "Could not convert types::evse_security::HashAlgorithm to types::iso15118_charger::HashAlgorithm"); + } +} + +std::vector +convert_to_certificate_hash_data_info_vector(const types::evse_security::OCSPRequestDataList& ocsp_request_data_list) { + std::vector certificate_hash_data_info_vec; + for (const auto& ocsp_request_data : ocsp_request_data_list.ocsp_request_data_list) { + if (ocsp_request_data.responder_url.has_value() and ocsp_request_data.certificate_hash_data.has_value()) { + types::iso15118_charger::CertificateHashDataInfo certificate_hash_data; + certificate_hash_data.hashAlgorithm = + convert_to_hash_algorithm(ocsp_request_data.certificate_hash_data.value().hash_algorithm); + certificate_hash_data.issuerNameHash = ocsp_request_data.certificate_hash_data.value().issuer_name_hash; + certificate_hash_data.issuerKeyHash = ocsp_request_data.certificate_hash_data.value().issuer_key_hash; + certificate_hash_data.serialNumber = ocsp_request_data.certificate_hash_data.value().serial_number; + certificate_hash_data.responderURL = ocsp_request_data.responder_url.value(); + certificate_hash_data_info_vec.push_back(certificate_hash_data); + } + } + return certificate_hash_data_info_vec; +} diff --git a/modules/EvseV2G/tools.hpp b/modules/EvseV2G/tools.hpp index 5e74d90b6..b9365e233 100644 --- a/modules/EvseV2G/tools.hpp +++ b/modules/EvseV2G/tools.hpp @@ -4,6 +4,8 @@ #ifndef TOOLS_H #define TOOLS_H +#include +#include #include #include #include @@ -12,6 +14,7 @@ #include #include #include +#include #define MAX_FILE_NAME_LENGTH 100 #define MAX_PKI_CA_LENGTH 4 /* leaf up to root certificate */ @@ -117,4 +120,20 @@ uint8_t get_dir_numbered_file_names(char file_names[MAX_PKI_CA_LENGTH][MAX_FILE_ */ std::string convert_to_hex_str(const uint8_t* data, int len); +/** + * \brief convert the given \p hash_algorithm to type types::iso15118_charger::HashAlgorithm + * \param hash_algorithm + * \return types::iso15118_charger::HashAlgorithm + */ +types::iso15118_charger::HashAlgorithm +convert_to_hash_algorithm(const types::evse_security::HashAlgorithm hash_algorithm); + +/** + * \brief convert the given \p ocsp_request_data_list to std::vector + * \param ocsp_request_data_list + * \return std::vector + */ +std::vector +convert_to_certificate_hash_data_info_vector(const types::evse_security::OCSPRequestDataList& ocsp_request_data_list); + #endif /* TOOLS_H */ diff --git a/modules/OCPP201/OCPP201.cpp b/modules/OCPP201/OCPP201.cpp index 391d9a7c4..c610a337b 100644 --- a/modules/OCPP201/OCPP201.cpp +++ b/modules/OCPP201/OCPP201.cpp @@ -491,14 +491,8 @@ void OCPP201::ready() { evse->subscribe_iso15118_certificate_request( [this, evse_id](const types::iso15118_charger::Request_Exi_Stream_Schema& certificate_request) { - // transform request forward to libocpp - ocpp::v201::Get15118EVCertificateRequest ocpp_request; - ocpp_request.exiRequest = certificate_request.exiRequest; - ocpp_request.iso15118SchemaVersion = certificate_request.iso15118SchemaVersion; - ocpp_request.action = - conversions::to_ocpp_certificate_action_enum(certificate_request.certificateAction); - - auto ocpp_response = this->charge_point->on_get_15118_ev_certificate_request(ocpp_request); + auto ocpp_response = this->charge_point->on_get_15118_ev_certificate_request( + conversions::to_ocpp_get_15118_certificate_request(certificate_request)); EVLOG_debug << "Received response from get_15118_ev_certificate_request: " << ocpp_response; // transform response, inject action, send to associated EvseManager const auto everest_response_status = diff --git a/modules/OCPP201/conversions.cpp b/modules/OCPP201/conversions.cpp index df5b9d959..f9c89f917 100644 --- a/modules/OCPP201/conversions.cpp +++ b/modules/OCPP201/conversions.cpp @@ -753,6 +753,15 @@ ocpp::v201::AttributeEnum to_ocpp_attribute_enum(const types::ocpp::AttributeEnu } } +ocpp::v201::Get15118EVCertificateRequest +to_ocpp_get_15118_certificate_request(const types::iso15118_charger::Request_Exi_Stream_Schema& request) { + ocpp::v201::Get15118EVCertificateRequest _request; + _request.iso15118SchemaVersion = request.iso15118SchemaVersion; + _request.exiRequest = request.exiRequest; + _request.action = conversions::to_ocpp_certificate_action_enum(request.certificateAction); + return _request; +} + types::system::UploadLogsRequest to_everest_upload_logs_request(const ocpp::v201::GetLogRequest& request) { types::system::UploadLogsRequest _request; _request.location = request.log.remoteLocation.get(); diff --git a/modules/OCPP201/conversions.hpp b/modules/OCPP201/conversions.hpp index e6ba21e2d..2e3646c73 100644 --- a/modules/OCPP201/conversions.hpp +++ b/modules/OCPP201/conversions.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,11 @@ ocpp::v201::EVSE to_ocpp_evse(const types::ocpp::EVSE& evse); /// \brief Converts a given types::ocpp::AttributeEnum to ocpp::v201::AttributeEnum ocpp::v201::AttributeEnum to_ocpp_attribute_enum(const types::ocpp::AttributeEnum attribute_enum); +/// \brief Converts a given types::types::iso15118_charger::Request_Exi_Stream_Schema to +/// ocpp::v201::Get15118EVCertificateRequest +ocpp::v201::Get15118EVCertificateRequest +to_ocpp_get_15118_certificate_request(const types::iso15118_charger::Request_Exi_Stream_Schema& request); + /// \brief Converts a given ocpp::v201::ReasonEnum \p stop_reason to a types::evse_manager::StopTransactionReason. types::evse_manager::StopTransactionReason to_everest_stop_transaction_reason(const ocpp::v201::ReasonEnum& stop_reason); diff --git a/modules/YetiDriver/CMakeLists.txt b/modules/YetiDriver/CMakeLists.txt index f7a826a2f..394856075 100644 --- a/modules/YetiDriver/CMakeLists.txt +++ b/modules/YetiDriver/CMakeLists.txt @@ -36,4 +36,6 @@ target_sources(${MODULE_NAME} # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 # insert other things like install cmds etc here +install(FILES yetiR1_2.0-1_firmware.bin DESTINATION ${CMAKE_INSTALL_DATADIR}/everest/modules/YetiDriver/firmware) + # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/YetiDriver/board_support/evse_board_supportImpl.cpp b/modules/YetiDriver/board_support/evse_board_supportImpl.cpp index bcf12ed44..0d1dfb4c7 100644 --- a/modules/YetiDriver/board_support/evse_board_supportImpl.cpp +++ b/modules/YetiDriver/board_support/evse_board_supportImpl.cpp @@ -71,28 +71,39 @@ void evse_board_supportImpl::init() { std::lock_guard lock(capsMutex); caps.min_current_A_import = mod->config.caps_min_current_A; - caps.max_current_A_import = 6; + caps.max_current_A_import = 16; caps.min_phase_count_import = 1; caps.max_phase_count_import = 3; caps.supports_changing_phases_during_charging = false; + caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Cable; caps.min_current_A_export = mod->config.caps_min_current_A; - caps.max_current_A_export = 6; + caps.max_current_A_export = 16; caps.min_phase_count_export = 1; caps.max_phase_count_export = 3; caps.supports_changing_phases_during_charging = false; } mod->serial.signalCPState.connect([this](CpState cp_state) { - publish_event(cast_event_type(cp_state)); - - if (cp_state == CpState_STATE_A) { - mod->clear_errors_on_unplug(); + if (cp_state not_eq last_cp_state) { + auto event_cp_state = cast_event_type(cp_state); + EVLOG_info << "CP state changed: " << types::board_support_common::event_to_string(event_cp_state.event); + publish_event(event_cp_state); + + if (cp_state == CpState_STATE_A) { + mod->clear_errors_on_unplug(); + } + last_cp_state = cp_state; + } + }); + mod->serial.signalRelaisState.connect([this](bool relais_state) { + if (last_relais_state not_eq relais_state) { + publish_event(cast_event_type(relais_state)); + last_relais_state = relais_state; } }); - mod->serial.signalRelaisState.connect([this](bool relais_state) { publish_event(cast_event_type(relais_state)); }); + mod->serial.signalPPState.connect([this](PpState pp_state) { - std::lock_guard lock(capsMutex); last_pp = cast_pp_type(pp_state); publish_ac_pp_ampacity(last_pp); }); @@ -113,20 +124,26 @@ void evse_board_supportImpl::init() { caps.max_phase_count_export = l.hwcap_max_phase_count; caps.supports_changing_phases_during_charging = l.supports_changing_phases_during_charging; + if (not caps_received) { + EVLOG_info << "Yeti Controller Configuration:"; + EVLOG_info << " Hardware revision: " << l.hw_revision; + EVLOG_info << " Firmware version: " << l.sw_version_string; + EVLOG_info << " Current Limit: " << l.hwcap_max_current; + } caps_received = true; }); } void evse_board_supportImpl::ready() { + wait_for_caps(); +} + +void evse_board_supportImpl::wait_for_caps() { // Wait for caps to be received at least once int i; for (i = 0; i < 50; i++) { - { - std::lock_guard lock(capsMutex); - if (caps_received) - break; - } - + if (caps_received) + break; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (i == 49) { @@ -136,6 +153,7 @@ void evse_board_supportImpl::ready() { } types::evse_board_support::HardwareCapabilities evse_board_supportImpl::handle_get_hw_capabilities() { + wait_for_caps(); std::lock_guard lock(capsMutex); return caps; } diff --git a/modules/YetiDriver/board_support/evse_board_supportImpl.hpp b/modules/YetiDriver/board_support/evse_board_supportImpl.hpp index 636a4bd1d..259cbdca1 100644 --- a/modules/YetiDriver/board_support/evse_board_supportImpl.hpp +++ b/modules/YetiDriver/board_support/evse_board_supportImpl.hpp @@ -59,9 +59,12 @@ class evse_board_supportImpl : public evse_board_supportImplBase { // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 // insert your private definitions here types::evse_board_support::HardwareCapabilities caps; - bool caps_received{false}; + std::atomic_bool caps_received{false}; std::mutex capsMutex; - types::board_support_common::ProximityPilot last_pp; + CpState last_cp_state{CpState::CpState_STATE_E}; + bool last_relais_state{false}; + types::board_support_common::ProximityPilot last_pp{types::board_support_common::Ampacity::None}; + void wait_for_caps(); // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 }; diff --git a/modules/YetiDriver/yetiR1_2.0-1_firmware.bin b/modules/YetiDriver/yetiR1_2.0-1_firmware.bin new file mode 100755 index 000000000..0a15174ef Binary files /dev/null and b/modules/YetiDriver/yetiR1_2.0-1_firmware.bin differ diff --git a/modules/YetiDriver/yeti_comms/evSerial.cpp b/modules/YetiDriver/yeti_comms/evSerial.cpp index b353405ff..59fb5a2ad 100644 --- a/modules/YetiDriver/yeti_comms/evSerial.cpp +++ b/modules/YetiDriver/yeti_comms/evSerial.cpp @@ -348,6 +348,10 @@ bool evSerial::reset(const int reset_pin) { system(cmd); sprintf(cmd, "echo 0 > /sys/class/gpio/gpio%i/value", reset_pin); system(cmd); + + // clear uart input and output buffer + // tcflush(fd, TCIOFLUSH); + sprintf(cmd, "echo 1 > /sys/class/gpio/gpio%i/value", reset_pin); system(cmd); } else { diff --git a/modules/YetiDriver/yeti_comms/firmware_version.hpp b/modules/YetiDriver/yeti_comms/firmware_version.hpp new file mode 100644 index 000000000..c818faa1c --- /dev/null +++ b/modules/YetiDriver/yeti_comms/firmware_version.hpp @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef YETI_FIRMWARE_VERSION +#define YETI_FIRMWARE_VERSION + +#include +#include + +/* + Helper class to handle yeti Firmware Versions of the following style: + + Reported by MCU: 2.0-1-g2d51638 + Included in file name: yetiR1_2.0-1_firmware.bin + +*/ + +static std::vector split_by_delimeters(const std::string& s, const std::string& delimeters) { + std::regex re("[" + delimeters + "]"); + std::sregex_token_iterator first{s.begin(), s.end(), re, -1}, last; + return {first, last}; +} + +// parse version string from filename +static std::string from_filename(const std::string& v) { + auto tokens = split_by_delimeters(v, "_"); + if (tokens.size() >= 2) { + return tokens[1]; + } + return "0.0-0"; +} + +class YetiFirmwareVersion { + +public: + YetiFirmwareVersion() { + } + + YetiFirmwareVersion(const std::string& _version_string) { + from_string(_version_string); + } + + std::string to_string() { + return std::to_string(major) + "." + std::to_string(minor) + "-" + std::to_string(revision); + } + + void from_string(const std::string& _version_string) { + std::string version_string; + // Is it a full filename or just a version string from the MCU? + if (_version_string.rfind("yetiR", 0) == 0) { + version_string = from_filename(_version_string); + } else { + version_string = _version_string; + } + + auto tokens = split_by_delimeters(version_string, ".-"); + + // parse into major, minor and revision + if (tokens.size() >= 1) { + major = std::stoi(tokens[0]); + } + + if (tokens.size() >= 2) { + minor = std::stoi(tokens[1]); + } + + if (tokens.size() >= 3) { + revision = std::stoi(tokens[2]); + } + } + + YetiFirmwareVersion& operator=(const std::string& s) { + from_string(s); + return *this; + } + + friend bool operator<(const YetiFirmwareVersion& l, const YetiFirmwareVersion& r) { + if (l.major < r.major) { + return true; + } else if (l.major > r.major) { + return false; + } else if (l.minor < r.minor) { + return true; + } else if (l.minor > r.minor) { + return false; + } else if (l.revision < r.revision) { + return true; + } else if (l.revision > r.revision) { + return false; + } + // they are identical + return false; + } + + friend bool operator>(const YetiFirmwareVersion& lhs, const YetiFirmwareVersion& rhs) { + return rhs < lhs; + } + + friend bool operator<=(const YetiFirmwareVersion& lhs, const YetiFirmwareVersion& rhs) { + return not(lhs > rhs); + } + friend bool operator>=(const YetiFirmwareVersion& lhs, const YetiFirmwareVersion& rhs) { + return not(lhs < rhs); + } + +private: + int major{0}; + int minor{0}; + int revision{0}; +}; + +#endif diff --git a/modules/YetiDriver/yeti_fwupdate/CMakeLists.txt b/modules/YetiDriver/yeti_fwupdate/CMakeLists.txt index f9a8276ef..69e0d93b6 100644 --- a/modules/YetiDriver/yeti_fwupdate/CMakeLists.txt +++ b/modules/YetiDriver/yeti_fwupdate/CMakeLists.txt @@ -11,7 +11,6 @@ add_executable(yeti_fwupdate main.cpp) target_include_directories(yeti_fwupdate PUBLIC "${PROJECT_BINARY_DIR}" PUBLIC "../yeti_comms/nanopb" PUBLIC "../yeti_comms/protobuf" PUBLIC "../yeti_comms") target_link_libraries(yeti_fwupdate PRIVATE Pal::Sigslot Threads::Threads yeti_comms everest::framework) -install(TARGETS yeti_fwupdate - DESTINATION ${EVEREST_MOD_YETIDRIVER_DESTINATION}) +install(TARGETS yeti_fwupdate) diff --git a/modules/YetiDriver/yeti_fwupdate/main.cpp b/modules/YetiDriver/yeti_fwupdate/main.cpp index 2a4b7ccbb..f3ddf18d8 100644 --- a/modules/YetiDriver/yeti_fwupdate/main.cpp +++ b/modules/YetiDriver/yeti_fwupdate/main.cpp @@ -4,16 +4,20 @@ #include #include "evSerial.h" +#include #include +#include "firmware_version.hpp" #include "yeti.pb.h" #include -volatile bool sw_version_received = false; +std::atomic_bool sw_version_received = false; +YetiFirmwareVersion installed_fw_version; +std::string installed_fw_version_orig; void recvKeepAliveLo(KeepAliveLo s) { - printf("Current Yeti SW Version: %s (Protocol %i.%i)\n", s.sw_version_string, s.protocol_version_major, - s.protocol_version_minor); + installed_fw_version = s.sw_version_string; + installed_fw_version_orig = s.sw_version_string; sw_version_received = true; } @@ -29,7 +33,7 @@ int main(int argc, char* argv[]) { exit(0); } const char* device = argv[1]; - const char* filename = argv[2]; + std::filesystem::path filename = argv[2]; evSerial* p = new evSerial(); @@ -42,6 +46,17 @@ int main(int argc, char* argv[]) { break; usleep(100); } + + YetiFirmwareVersion update_file_fw_version{filename.filename()}; + printf("Installed Yeti Firmware Version: %s (%s)\n", installed_fw_version.to_string().c_str(), + installed_fw_version_orig.c_str()); + printf("Update File Firmware Version: %s\n", update_file_fw_version.to_string().c_str()); + + if (installed_fw_version >= update_file_fw_version) { + printf("Latest version already installed. Exiting.\n"); + exit(1); + } + printf("\nRebooting Yeti in ROM Bootloader mode...\n"); // send some dummy commands to make sure protocol is in sync p->keepAlive(); @@ -54,7 +69,7 @@ int main(int argc, char* argv[]) { sleep(1); char cmd[1000]; - sprintf(cmd, "stm32flash -b 115200 %.100s -v -w %.100s -R", device, filename); + sprintf(cmd, "stm32flash -b 115200 %.100s -v -w %.100s -R", device, filename.string().c_str()); // sprintf(cmd, "stm32flash -b115200 %.100s", device); printf("Executing %s ...\n", cmd); system(cmd); diff --git a/types/evse_security.yaml b/types/evse_security.yaml index ad9821e47..7709aca3b 100644 --- a/types/evse_security.yaml +++ b/types/evse_security.yaml @@ -21,6 +21,7 @@ types: - CSMS - V2G - MF + - MO CertificateType: description: Enum specifies certificate type of leaf and CA certificates type: string