From 34489f92c5800cd8c37e8f3b5a9eeffd570b8d1d Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Tue, 30 Apr 2024 16:06:10 +0200 Subject: [PATCH] Adapt CableCheck to IEC-23(2023) Refactor power supply dc interface Add errors to imd interface Adapt umwc driver to latest firmware Fix umwc fw update Add powermeter to umwc Added Communication Errors to interfaces Signed-off-by: Cornelius Claussen --- config/config-sil-dc.yaml | 3 + dependencies.yaml | 2 +- errors/evse_board_support.yaml | 4 + errors/isolation_monitor.yaml | 20 ++ errors/powermeter.yaml | 6 + errors/system.yaml | 6 + interfaces/isolation_monitor.yaml | 19 +- interfaces/powermeter.yaml | 2 + interfaces/system.yaml | 2 + modules/EvseManager/Charger.cpp | 7 +- modules/EvseManager/Charger.hpp | 2 +- modules/EvseManager/ErrorHandling.cpp | 95 +++++- modules/EvseManager/ErrorHandling.hpp | 19 +- modules/EvseManager/EvseManager.cpp | 319 +++++++++++++----- modules/EvseManager/EvseManager.hpp | 14 +- modules/EvseManager/manifest.yaml | 20 +- modules/EvseManager/scoped_lock_timeout.hpp | 3 + modules/MicroMegaWattBSP/MicroMegaWattBSP.cpp | 18 +- modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp | 1 + .../board_support/evse_board_supportImpl.cpp | 110 +++--- .../board_support/evse_board_supportImpl.hpp | 3 + .../dc_supply/power_supply_DCImpl.cpp | 18 +- modules/MicroMegaWattBSP/manifest.yaml | 17 +- .../powermeter/powermeterImpl.cpp | 40 +-- .../umwc_comms/CMakeLists.txt | 1 + .../MicroMegaWattBSP/umwc_comms/evSerial.cpp | 30 +- .../MicroMegaWattBSP/umwc_comms/evSerial.h | 4 +- .../umwc_comms/protobuf/umwc.pb.c | 7 +- .../umwc_comms/protobuf/umwc.pb.h | 191 +++++------ .../umwc_comms/protobuf/umwc.proto | 24 +- .../umwc_fwupdate/CMakeLists.txt | 2 +- .../MicroMegaWattBSP/umwc_fwupdate/main.cpp | 10 + .../main/serial_communication_hubImpl.cpp | 4 +- .../main/serial_communication_hubImpl.hpp | 1 + modules/SerialCommHub/manifest.yaml | 4 + modules/SerialCommHub/tiny_modbus_rtu.cpp | 9 +- modules/SerialCommHub/tiny_modbus_rtu.hpp | 2 +- modules/YetiDriver/YetiDriver.cpp | 8 +- modules/YetiDriver/YetiDriver.hpp | 1 + .../board_support/evse_board_supportImpl.cpp | 10 +- modules/YetiDriver/manifest.yaml | 4 + .../main/power_supply_DCImpl.hpp | 2 +- .../main/isolation_monitorImpl.cpp | 16 +- .../main/isolation_monitorImpl.hpp | 8 +- modules/simulation/IMDSimulator/manifest.yaml | 4 + types/evse_manager.yaml | 1 + 46 files changed, 695 insertions(+), 398 deletions(-) create mode 100644 errors/isolation_monitor.yaml create mode 100644 errors/powermeter.yaml create mode 100644 errors/system.yaml diff --git a/config/config-sil-dc.yaml b/config/config-sil-dc.yaml index ef2f029410..0002d575b4 100644 --- a/config/config-sil-dc.yaml +++ b/config/config-sil-dc.yaml @@ -54,6 +54,9 @@ active_modules: slac: module: JsSlacSimulator imd: + config_implementation: + main: + selftest_success: true module: IMDSimulator ev_manager: module: JsEvManager diff --git a/dependencies.yaml b/dependencies.yaml index 3cbe51a9d5..2e0dc1dd43 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -4,7 +4,7 @@ --- everest-framework: git: https://github.com/EVerest/everest-framework.git - git_tag: v0.14.0 + git_tag: 82117ad705f835e1cf8a3c6d61cd70aa4d44d372 options: ["BUILD_TESTING OFF"] sigslot: git: https://github.com/palacaze/sigslot diff --git a/errors/evse_board_support.yaml b/errors/evse_board_support.yaml index c577ef2650..df0a1be64c 100644 --- a/errors/evse_board_support.yaml +++ b/errors/evse_board_support.yaml @@ -54,3 +54,7 @@ errors: - name: VendorWarning description: >- Vendor specific error code. Charging may continue. + - name: CommunicationFault + description: >- + The communication to the hardware or underlying driver is lost or has errors. + diff --git a/errors/isolation_monitor.yaml b/errors/isolation_monitor.yaml new file mode 100644 index 0000000000..9d1f44d634 --- /dev/null +++ b/errors/isolation_monitor.yaml @@ -0,0 +1,20 @@ +description: >- + Errors for Isolation Monitor + + Note that actual isolation faults should just be reported as resistance values, + EvseManager will interpret them according to the limits given in the norm and stop charging. + + This is only to report device errors to indicate valid isolation resistance measurements etc + are no longer possible. +errors: + - name: DeviceFault + description: The IMD device is not fully functional anymore and cannot be used to monitor the isolation resistance. + - name: CommunicationFault + description: >- + The communication to the hardware or underlying driver is lost or has errors. + - name: VendorError + description: >- + Vendor specific error code. Will stop charging session. + - name: VendorWarning + description: >- + Vendor specific error code. Charging may continue. diff --git a/errors/powermeter.yaml b/errors/powermeter.yaml new file mode 100644 index 0000000000..d387d1fcf0 --- /dev/null +++ b/errors/powermeter.yaml @@ -0,0 +1,6 @@ +description: >- + Errors for Powermeter +errors: + - name: CommunicationFault + description: >- + The communication to the hardware or underlying driver is lost or has errors. diff --git a/errors/system.yaml b/errors/system.yaml new file mode 100644 index 0000000000..51c238357e --- /dev/null +++ b/errors/system.yaml @@ -0,0 +1,6 @@ +description: >- + Errors for System +errors: + - name: CommunicationFault + description: >- + The communication to the hardware or underlying driver is lost or has errors. diff --git a/interfaces/isolation_monitor.yaml b/interfaces/isolation_monitor.yaml index 1aba8a513f..16e39dee59 100644 --- a/interfaces/isolation_monitor.yaml +++ b/interfaces/isolation_monitor.yaml @@ -12,8 +12,25 @@ cmds: description: >- Stop recurring measurements. The device should stop to monitor the isolation resistance and stop publishing the data. + start_self_test: + description: >- + Start self test. This will be done during the CableCheck phase, so a DC voltage will be present + according to IEC 61851-23 (2023). The command should return immediately. + The "self_test_result" variable must be published once the self testing is done. + Note that on many hardware devices this can take a long time (e.g. 20 seconds). + arguments: + test_voltage_V: + description: >- + Specifies the test voltage [V] that is applied on the DC pins during self test. + This can be used to verify the internal voltage measurement of the IMD. + type: number vars: - IsolationMeasurement: + isolation_measurement: description: Isolation monitoring measurement results type: object $ref: /isolation_monitor#/IsolationMeasurement + self_test_result: + description: Indicates the self test is done and publishes the result. Set "true" for success, "false" for failure. + type: boolean +errors: + - reference: /errors/isolation_monitor diff --git a/interfaces/powermeter.yaml b/interfaces/powermeter.yaml index 90acfe2309..0c8f5a9a82 100644 --- a/interfaces/powermeter.yaml +++ b/interfaces/powermeter.yaml @@ -26,3 +26,5 @@ vars: description: Measured dataset type: object $ref: /powermeter#/Powermeter +errors: + - reference: /errors/powermeter diff --git a/interfaces/system.yaml b/interfaces/system.yaml index 7363fdc542..e7977ae707 100644 --- a/interfaces/system.yaml +++ b/interfaces/system.yaml @@ -75,3 +75,5 @@ vars: type: object $ref: /system#/LogStatus +errors: + - reference: /errors/system diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index 3036eba338..190688f1ab 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -962,7 +962,8 @@ bool Charger::set_max_current(float c, std::chrono::time_point // is it still valid? if (validUntil > date::utc_clock::now()) { { - std::lock_guard lock(state_machine_mutex); + Everest::scoped_lock_timeout lock(state_machine_mutex, + Everest::MutexDescription::Charger_pause_charging); shared_context.max_current = c; shared_context.max_current_valid_until = validUntil; } @@ -1524,7 +1525,9 @@ void Charger::check_soft_over_current() { // i.e. max_current is in valid range bool Charger::power_available() { if (shared_context.max_current_valid_until < date::utc_clock::now()) { - EVLOG_warning << "Power budget expired, falling back to 0."; + EVLOG_warning << "Power budget expired, falling back to 0. Last update: " + << Everest::Date::to_rfc3339(shared_context.max_current_valid_until) + << " Now:" << Everest::Date::to_rfc3339(date::utc_clock::now()); if (shared_context.max_current > 0.) { shared_context.max_current = 0.; signal_max_current(shared_context.max_current); diff --git a/modules/EvseManager/Charger.hpp b/modules/EvseManager/Charger.hpp index a2269a1513..20f24c6357 100644 --- a/modules/EvseManager/Charger.hpp +++ b/modules/EvseManager/Charger.hpp @@ -348,7 +348,7 @@ class Charger { EventQueue error_handling_event_queue; // constants - static constexpr float CHARGER_ABSOLUTE_MAX_CURRENT{80.}; + static constexpr float CHARGER_ABSOLUTE_MAX_CURRENT{1000.}; constexpr static int LEGACY_WAKEUP_TIMEOUT{30000}; // valid Length of BCB toggles static constexpr auto TP_EV_VALD_STATE_DURATION_MIN = diff --git a/modules/EvseManager/ErrorHandling.cpp b/modules/EvseManager/ErrorHandling.cpp index 2239fcc96f..b3aacddbe1 100644 --- a/modules/EvseManager/ErrorHandling.cpp +++ b/modules/EvseManager/ErrorHandling.cpp @@ -21,8 +21,14 @@ ErrorHandling::ErrorHandling(const std::unique_ptr& _r_b const std::vector>& _r_hlc, const std::vector>& _r_connector_lock, const std::vector>& _r_ac_rcd, - const std::unique_ptr& _p_evse) : - r_bsp(_r_bsp), r_hlc(_r_hlc), r_connector_lock(_r_connector_lock), r_ac_rcd(_r_ac_rcd), p_evse(_p_evse) { + const std::unique_ptr& _p_evse, + const std::vector>& _r_imd) : + r_bsp(_r_bsp), + r_hlc(_r_hlc), + r_connector_lock(_r_connector_lock), + r_ac_rcd(_r_ac_rcd), + p_evse(_p_evse), + r_imd(_r_imd) { if (r_hlc.size() > 0) { hlc = true; @@ -163,6 +169,52 @@ ErrorHandling::ErrorHandling(const std::unique_ptr& _r_b } }); } + + // Subscribe to ac_rcd to receive errors from IMD hardware + if (r_imd.size() > 0) { + r_imd[0]->subscribe_all_errors( + [this](const Everest::error::Error& error) { + types::evse_manager::ErrorEnum evse_error{types::evse_manager::ErrorEnum::VendorWarning}; + types::evse_manager::Error output_error; + output_error.error_description = error.description; + output_error.error_severity = to_evse_manager_severity(error.severity); + + if (modify_error_imd(error, true, evse_error)) { + // signal to charger a new error has been set that prevents charging + output_error.error_code = evse_error; + signal_error(output_error, true); + } else { + // signal an error that does not prevent charging + output_error.error_code = evse_error; + signal_error(output_error, false); + } + }, + [this](const Everest::error::Error& error) { + types::evse_manager::ErrorEnum evse_error{types::evse_manager::ErrorEnum::VendorWarning}; + types::evse_manager::Error output_error; + output_error.error_description = error.description; + output_error.error_severity = to_evse_manager_severity(error.severity); + + if (modify_error_imd(error, 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(); + } + } + }); + } } void ErrorHandling::raise_overcurrent_error(const std::string& description) { @@ -577,4 +629,43 @@ bool ErrorHandling::modify_error_evse_manager(const std::string& error_type, boo return true; }; +bool ErrorHandling::modify_error_imd(const Everest::error::Error& error, bool active, + types::evse_manager::ErrorEnum& evse_error) { + const std::string& error_type = error.type; + + if (active) { + EVLOG_error << "Raised error " << error_type << ": " << error.description << " (" << error.message << ")"; + } else { + EVLOG_info << "Cleared error " << error_type << ": " << error.description << " (" << error.message << ")"; + } + + if (error_type == "isolation_monitor/DeviceFault") { + active_errors.imd.set(IMDErrors::DeviceFault, active); + evse_error = types::evse_manager::ErrorEnum::IMDFault; + if (hlc && active) { + r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); + } + } else if (error_type == "isolation_monitor/CommunicationFault") { + active_errors.imd.set(IMDErrors::CommunicationFault, active); + evse_error = types::evse_manager::ErrorEnum::IMDFault; + if (hlc && active) { + r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); + } + } else if (error_type == "isolation_monitor/VendorError") { + 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); + } + } else { + // Errors that do not stop charging + if (error_type == "isolation_monitor/VendorWarning") { + evse_error = types::evse_manager::ErrorEnum::VendorWarning; + } + return false; + } + // Error stops charging + return true; +}; + } // namespace module diff --git a/modules/EvseManager/ErrorHandling.hpp b/modules/EvseManager/ErrorHandling.hpp index 1fc06a12c6..0c5e0c9698 100644 --- a/modules/EvseManager/ErrorHandling.hpp +++ b/modules/EvseManager/ErrorHandling.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "EnumFlags.hpp" @@ -93,14 +94,24 @@ enum class ConnectorLockErrors : std::uint8_t { last = VendorError }; +enum class IMDErrors : std::uint8_t { + DeviceFault, + CommunicationFault, + VendorWarning, + VendorError, + last = VendorError +}; + struct ActiveErrors { AtomicEnumFlags bsp; AtomicEnumFlags evse_manager; AtomicEnumFlags ac_rcd; AtomicEnumFlags connector_lock; + AtomicEnumFlags imd; inline bool all_cleared() { - return bsp.all_reset() && evse_manager.all_reset() && ac_rcd.all_reset() && connector_lock.all_reset(); + return bsp.all_reset() && evse_manager.all_reset() && ac_rcd.all_reset() && connector_lock.all_reset() && + imd.all_reset(); }; }; @@ -111,7 +122,8 @@ class ErrorHandling { const std::vector>& r_hlc, const std::vector>& r_connector_lock, const std::vector>& r_ac_rcd, - const std::unique_ptr& _p_evse); + const std::unique_ptr& _p_evse, + const std::vector>& _r_imd); // Signal that one error has been raised. Bool argument is true if it preventing charging. sigslot::signal signal_error; @@ -135,6 +147,7 @@ class ErrorHandling { const std::vector>& r_connector_lock; const std::vector>& r_ac_rcd; const std::unique_ptr& p_evse; + const std::vector>& r_imd; 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, @@ -144,6 +157,8 @@ class ErrorHandling { bool modify_error_evse_manager(const std::string& error_type, bool active, types::evse_manager::ErrorEnum& evse_error); + bool modify_error_imd(const Everest::error::Error& error, bool active, + types::evse_manager::ErrorEnum& evse_error); bool hlc{false}; ActiveErrors active_errors; diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index 8f8cd9798e..f1f3b6d426 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -117,7 +117,7 @@ void EvseManager::init() { void EvseManager::ready() { bsp = std::unique_ptr(new IECStateMachine(r_bsp)); error_handling = - std::unique_ptr(new ErrorHandling(r_bsp, r_hlc, r_connector_lock, r_ac_rcd, p_evse)); + std::unique_ptr(new ErrorHandling(r_bsp, r_hlc, r_connector_lock, r_ac_rcd, p_evse, r_imd)); hw_capabilities = r_bsp->call_get_hw_capabilities(); @@ -234,11 +234,22 @@ void EvseManager::ready() { imd_stop(); - r_imd[0]->subscribe_IsolationMeasurement([this](types::isolation_monitor::IsolationMeasurement m) { + r_imd[0]->subscribe_isolation_measurement([this](types::isolation_monitor::IsolationMeasurement m) { // new DC isolation monitoring measurement received - session_log.evse(false, fmt::format("Isolation measurement R_F {}.", m.resistance_F_Ohm)); + + // Are we in charge loop? + if (charger->get_current_state() == Charger::EvseState::Charging and + not check_isolation_resistance_in_range(m.resistance_F_Ohm)) { + charger->set_hlc_error(); + r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_EmergencyShutdown); + } isolation_measurement = m; }); + + r_imd[0]->subscribe_self_test_result([this](bool result) { + session_log.evse(false, fmt::format("Isolation monitor self test result: {}", result)); + selftest_result = result; + }); } // Get voltage/current from DC power supply @@ -899,7 +910,8 @@ void EvseManager::ready_to_start_charging() { } this->p_evse->publish_ready(true); - EVLOG_info << fmt::format(fmt::emphasis::bold | fg(fmt::terminal_color::green), "🌀🌀🌀 Ready to start charging 🌀🌀🌀"); + EVLOG_info << fmt::format(fmt::emphasis::bold | fg(fmt::terminal_color::green), + "🌀🌀🌀 Ready to start charging 🌀🌀🌀"); } types::powermeter::Powermeter EvseManager::get_latest_powermeter_data_billing() { @@ -1206,19 +1218,62 @@ void EvseManager::charger_was_authorized() { } } +static double get_cable_check_voltage(double ev_max_cpd, double evse_max_cpd) { + double cable_check_voltage = 500; + // IEC 61851-23 (2023) CC.4.1.2 / Formular CC.1 + if (ev_max_cpd <= 500) { + if ((ev_max_cpd + 50) < cable_check_voltage) { + cable_check_voltage = (ev_max_cpd + 50); + } + if (evse_max_cpd < cable_check_voltage) { + cable_check_voltage = evse_max_cpd; + } + } else { + cable_check_voltage = evse_max_cpd; + if (1.1 * ev_max_cpd < cable_check_voltage) { + cable_check_voltage = 1.1 * ev_max_cpd; + } + } + + return cable_check_voltage; +} + +bool EvseManager::cable_check_should_exit() { + return charger->get_current_state() not_eq Charger::EvseState::PrepareCharging; +} + +bool EvseManager::check_isolation_resistance_in_range(double resistance) { + if (resistance < CABLECHECK_INSULATION_FAULT_RESISTANCE_OHM) { + session_log.evse(false, fmt::format("Isolation measurement FAULT R_F {}.", resistance)); + r_hlc[0]->call_update_isolation_status(types::iso15118_charger::IsolationStatus::Fault); + return false; + } else { + session_log.evse(false, fmt::format("Isolation measurement Ok R_F {}.", resistance)); + r_hlc[0]->call_update_isolation_status(types::iso15118_charger::IsolationStatus::Valid); + } + return true; +} + void EvseManager::cable_check() { if (r_imd.empty()) { // If no IMD is connected, we skip isolation checking. - EVLOG_info << "No IMD: skippint cable check."; + EVLOG_info << "No IMD: skipping cable check."; r_hlc[0]->call_update_isolation_status(types::iso15118_charger::IsolationStatus::No_IMD); r_hlc[0]->call_cable_check_finished(true); return; } + // start cable check in a seperate thread. std::thread t([this]() { session_log.evse(true, "Start cable check..."); - bool ok = false; + + // Verify output is below 60V initially + if (not wait_powersupply_DC_below_voltage(CABLECHECK_SAFE_VOLTAGE)) { + EVLOG_error << "Voltage did not drop below " << CABLECHECK_SAFE_VOLTAGE << "V within timeout."; + fail_cable_check(); + return; + } // normally contactors should be closed before entering cable check routine. // On some hardware implementation it may take some time until the confirmation arrives though, @@ -1230,102 +1285,165 @@ void EvseManager::cable_check() { Timeout timeout; timeout.start(CABLECHECK_CONTACTORS_CLOSE_TIMEOUT); - while (not timeout.reached()) { + while (not timeout.reached() and not cable_check_should_exit()) { if (not contactor_open) { break; } std::this_thread::sleep_for(100ms); } - // verify the relais are really switched on and set 500V output - if (not contactor_open) { - if (powersupply_DC_set(config.dc_isolation_voltage_V, 2)) { - powersupply_DC_on(); - imd_start(); - - // wait until the voltage has rised to the target value - if (not wait_powersupply_DC_voltage_reached(config.dc_isolation_voltage_V)) { - EVLOG_info << "Voltage did not rise to 500V within timeout"; - powersupply_DC_off(); - fail_session(); - ok = false; - imd_stop(); - } else { - auto caps = get_powersupply_capabilities(); - // read out one new isolation resistance - isolation_measurement.clear(); - types::isolation_monitor::IsolationMeasurement m; - if (not isolation_measurement.wait_for(m, 10s)) { - EVLOG_info << "Did not receive isolation measurement from IMD within 10 seconds."; - powersupply_DC_off(); - ok = false; - fail_session(); - } else { - // wait until the voltage is back to safe level - float minvoltage = - (config.switch_to_minimum_voltage_after_cable_check ? caps.min_export_voltage_V - : config.dc_isolation_voltage_V); - - // We do not want to shut down power supply - if (minvoltage < 60) { - minvoltage = 60; - } - powersupply_DC_set(minvoltage, 2); - - if (not wait_powersupply_DC_below_voltage(minvoltage + 20)) { - EVLOG_info << "Voltage did not go back to minimal voltage within timeout."; - ok = false; - fail_session(); - } else { - // verify it is within ranges. Warning level is <500 Ohm/V_max_output_rating, Fault - // is <100 - const double min_resistance_ok = 500. * caps.max_export_voltage_V; - const double min_resistance_warning = 100. * caps.max_export_voltage_V; - - if (m.resistance_F_Ohm < min_resistance_warning) { - session_log.evse( - false, fmt::format("Isolation measurement FAULT R_F {}.", m.resistance_F_Ohm)); - ok = true; // this just means that we are finished measuring, not that we are ok with - // the result - r_hlc[0]->call_update_isolation_status(types::iso15118_charger::IsolationStatus::Fault); - imd_stop(); - fail_session(); - } else if (m.resistance_F_Ohm < min_resistance_ok) { - session_log.evse( - false, fmt::format("Isolation measurement WARNING R_F {}.", m.resistance_F_Ohm)); - ok = true; - r_hlc[0]->call_update_isolation_status( - types::iso15118_charger::IsolationStatus::Warning); - } else { - session_log.evse(false, - fmt::format("Isolation measurement Ok R_F {}.", m.resistance_F_Ohm)); - ok = true; - r_hlc[0]->call_update_isolation_status(types::iso15118_charger::IsolationStatus::Valid); - } - } - } - } - } else { - EVLOG_error << fmt::format("CableCheck Thread: Could not set DC power supply voltage and current."); - fail_session(); + // If relais are still open after timeout, give up + if (contactor_open) { + EVLOG_error << "CableCheck: Contactors are still open after timeout, giving up."; + fail_cable_check(); + return; + } + + // Get correct voltage used to test the isolation + for (int retry_ev_info = 0; retry_ev_info < 10; retry_ev_info++) { + auto ev_info = get_ev_info(); + if (ev_info.maximum_voltage_limit.has_value()) { + break; } + std::this_thread::sleep_for(100ms); + } + + float ev_max_voltage = 500.; + + if (ev_info.maximum_voltage_limit.has_value()) { + EVLOG_info << "EV reports " << ev_info.maximum_voltage_limit.has_value() << " V as maximum voltage"; + ev_max_voltage = ev_info.maximum_voltage_limit.value(); + } else { + EVLOG_error << "CableCheck: Did not receive EV maximum voltage, falling back to 500V"; + } + + auto evse_caps = get_powersupply_capabilities(); + + double cable_check_voltage = get_cable_check_voltage(ev_max_voltage, evse_caps.max_export_voltage_V); + + // Allow overriding the cable check voltage from a configuration value + if (config.dc_isolation_voltage_V > 0) { + cable_check_voltage = config.dc_isolation_voltage_V; + } + + // Set the DC ouput voltage for testing + if (not powersupply_DC_set(cable_check_voltage, CABLECHECK_CURRENT_LIMIT)) { + EVLOG_error << "CableCheck: Could not set DC power supply voltage and current."; + fail_cable_check(); + return; } else { - EVLOG_error << fmt::format("CableCheck Thread: Contactors are still open after timeout, giving up."); - fail_session(); + EVLOG_info << "CableCheck: Using " << cable_check_voltage << " V"; } - if (config.hack_pause_imd_during_precharge) + // Switch on output voltage + powersupply_DC_on(); + + // Wait until the voltage has rised to the target value. + // This also handles the short circuit test according to IEC 61851-23 (2023) 6.3.1.109: + // CC.7.6.20.3: the maximum R for the short circuit test is 110 Ohms. + // CC.7.6.20.7: maximum current should be reduced to <5A within 1s. We set a current limit below 5A, so the + // power supply should always achieve that. + // Within 2.5s present voltage at side B must be below 60V. As the power supply ramp up speed varies greatly, + // we can only achieve this by limiting the current to I < cable_check_voltage/110 Ohm. The hard coded limit + // above fulfills that for all voltage ranges. + if (not wait_powersupply_DC_voltage_reached(cable_check_voltage)) { + EVLOG_error << "CableCheck: Voltage did not rise to " << cable_check_voltage << " V within timeout"; + fail_cable_check(); + return; + } + + // CC 4.1.3: Now relais are closed, voltage is up. We need to perform a self test of the IMD device + if (config.cable_check_enable_imd_self_test) { + selftest_result.clear(); + r_imd[0]->call_start_self_test(cable_check_voltage); + EVLOG_info << "CableCheck: IMD self test started."; + + // Wait for the result of the self test + bool result{false}; + bool result_received{false}; + + for (int wait_seconds = 0; wait_seconds < CABLECHECK_SELFTEST_TIMEOUT; wait_seconds++) { + if (cable_check_should_exit()) { + EVLOG_warning << "Cancel cable check"; + fail_cable_check(); + return; + } + if (selftest_result.wait_for(result, 1s)) { + result_received = true; + break; + } + } + + if (not result_received) { + EVLOG_error << "CableCheck: Did not get a self test result from IMD within timeout"; + fail_cable_check(); + return; + } + + if (not result) { + EVLOG_error << "CableCheck: IMD Self test failed"; + fail_cable_check(); + return; + } + } + + // CC.4.1.4: Perform the insulation resistance check + imd_start(); + + // read out new isolation resistance value + isolation_measurement.clear(); + types::isolation_monitor::IsolationMeasurement m; + + EVLOG_info << "CableCheck: Waiting for " << config.cable_check_wait_number_of_imd_measurements + << " isolation measurement sample(s)"; + // Wait for N isolation measurement values + for (int i = 0; i < config.cable_check_wait_number_of_imd_measurements; i++) { + if (not isolation_measurement.wait_for(m, 5s) or cable_check_should_exit()) { + EVLOG_info << "Did not receive isolation measurement from IMD within 5 seconds."; + imd_stop(); + fail_cable_check(); + return; + } + } + + // Now the value is valid and can be trusted. + // Verify it is within ranges. Fault is <100 kOhm + // Note that 2023 edition removed the warning level which was included in the 2014 edition. + // Refer to IEC 61851-23 (2023) 6.3.1.105 and CC.4.1.2 / CC.4.1.4 + if (not check_isolation_resistance_in_range(m.resistance_F_Ohm)) { imd_stop(); + fail_cable_check(); + return; + } + + // We are done with the isolation measurement and can now report success to the EV, + // but before we do so we need to do a few things for cleanup + + if (config.hack_pause_imd_during_precharge) { + imd_stop(); + } // Sleep before submitting result to spend more time in cable check. This is needed for some solar inverters - // used as DC chargers for them to warm up. - sleep(config.hack_sleep_in_cable_check); + // used as DC chargers for them to warm up. Don't use it. + std::this_thread::sleep_for(std::chrono::seconds(config.hack_sleep_in_cable_check)); if (car_manufacturer == types::evse_manager::CarManufacturer::VolkswagenGroup) { - sleep(config.hack_sleep_in_cable_check_volkswagen); + std::this_thread::sleep_for(std::chrono::seconds(config.hack_sleep_in_cable_check_volkswagen)); + } + + // CC.4.1.2: We need to wait until voltage is below 60V before sending a CableCheck Finished to the EV + powersupply_DC_off(); + + if (not wait_powersupply_DC_below_voltage(CABLECHECK_SAFE_VOLTAGE)) { + EVLOG_error << "Voltage did not drop below " << CABLECHECK_SAFE_VOLTAGE << "V within timeout."; + imd_stop(); + fail_cable_check(); + return; } - // submit result to HLC - r_hlc[0]->call_cable_check_finished(ok); + EVLOG_info << "CableCheck done, output is below " << CABLECHECK_SAFE_VOLTAGE << "V"; + + // Report CableCheck Finished with success to EV + r_hlc[0]->call_cable_check_finished(true); }); // Detach thread and exit command handler right away t.detach(); @@ -1443,9 +1561,17 @@ void EvseManager::powersupply_DC_off() { bool EvseManager::wait_powersupply_DC_voltage_reached(double target_voltage) { // wait until the voltage has rised to the target value Timeout timeout; - timeout.start(30s); + timeout.start(10s); bool voltage_ok = false; while (not timeout.reached()) { + if (cable_check_should_exit()) { + EVLOG_warning << "Cancel cable check wait voltage reached"; + powersupply_DC_off(); + r_hlc[0]->call_cable_check_finished(false); + charger->set_hlc_error(); + r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_EmergencyShutdown); + break; + } types::power_supply_DC::VoltageCurrent m; if (powersupply_measurement.wait_for(m, 2000ms)) { if (fabs(m.voltage_V - target_voltage) < 10) { @@ -1464,9 +1590,17 @@ bool EvseManager::wait_powersupply_DC_voltage_reached(double target_voltage) { bool EvseManager::wait_powersupply_DC_below_voltage(double target_voltage) { // wait until the voltage is below the target voltage Timeout timeout; - timeout.start(30s); + timeout.start(10s); bool voltage_ok = false; while (not timeout.reached()) { + if (cable_check_should_exit()) { + EVLOG_warning << "Cancel cable check wait below voltage"; + powersupply_DC_off(); + r_hlc[0]->call_cable_check_finished(false); + charger->set_hlc_error(); + r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_EmergencyShutdown); + break; + } types::power_supply_DC::VoltageCurrent m; if (powersupply_measurement.wait_for(m, 2000ms)) { if (m.voltage_V < target_voltage) { @@ -1506,12 +1640,17 @@ types::energy::ExternalLimits EvseManager::getLocalEnergyLimits() { return local_energy_limits; } -void EvseManager::fail_session() { - r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_EmergencyShutdown); +void EvseManager::fail_cable_check() { if (config.charge_mode == "DC") { powersupply_DC_off(); + // CC.4.1.2: We need to wait until voltage is below 60V before sending a CableCheck Finished to the EV + if (not wait_powersupply_DC_below_voltage(CABLECHECK_SAFE_VOLTAGE)) { + EVLOG_error << "Voltage did not drop below 60V within timeout, sending CableCheck Finished(false) anyway"; + } + r_hlc[0]->call_cable_check_finished(false); } charger->set_hlc_error(); + r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_EmergencyShutdown); } types::evse_manager::EVInfo EvseManager::get_ev_info() { diff --git a/modules/EvseManager/EvseManager.hpp b/modules/EvseManager/EvseManager.hpp index 5feff5cb14..b64e2e0c5b 100644 --- a/modules/EvseManager/EvseManager.hpp +++ b/modules/EvseManager/EvseManager.hpp @@ -73,7 +73,8 @@ struct Conf { bool dbg_hlc_auth_after_tstep; int hack_sleep_in_cable_check; int hack_sleep_in_cable_check_volkswagen; - bool switch_to_minimum_voltage_after_cable_check; + int cable_check_wait_number_of_imd_measurements; + bool cable_check_enable_imd_self_test; bool hack_skoda_enyaq; int hack_present_current_offset; bool hack_pause_imd_during_precharge; @@ -266,6 +267,7 @@ class EvseManager : public Everest::ModuleBase { VarContainer isolation_measurement; VarContainer powersupply_measurement; + VarContainer selftest_result; double latest_target_voltage; double latest_target_current; @@ -296,6 +298,8 @@ class EvseManager : public Everest::ModuleBase { bool wait_powersupply_DC_voltage_reached(double target_voltage); bool wait_powersupply_DC_below_voltage(double target_voltage); + bool cable_check_should_exit(); + // EV information Everest::timed_mutex_traceable ev_info_mutex; types::evse_manager::EVInfo ev_info; @@ -305,12 +309,18 @@ class EvseManager : public Everest::ModuleBase { void imd_start(); Everest::Thread telemetryThreadHandle; - void fail_session(); + void fail_cable_check(); // setup sae j2847/2 v2h mode void setup_v2h_mode(); + bool check_isolation_resistance_in_range(double resistance); + static constexpr auto CABLECHECK_CONTACTORS_CLOSE_TIMEOUT{std::chrono::seconds(5)}; + static constexpr double CABLECHECK_CURRENT_LIMIT{2}; + static constexpr double CABLECHECK_INSULATION_FAULT_RESISTANCE_OHM{100000.}; + static constexpr double CABLECHECK_SAFE_VOLTAGE{60.}; + static constexpr int CABLECHECK_SELFTEST_TIMEOUT{30}; std::atomic_bool current_demand_active{false}; // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 diff --git a/modules/EvseManager/manifest.yaml b/modules/EvseManager/manifest.yaml index 6ceffbe1b3..2bf11e8754 100644 --- a/modules/EvseManager/manifest.yaml +++ b/modules/EvseManager/manifest.yaml @@ -94,10 +94,10 @@ config: type: boolean default: false dc_isolation_voltage_V: - description: DC voltage used to test isolation in CableCheck. - Set to 500V. + description: Override DC voltage used to test isolation in CableCheck. + Default is 0, which means the voltage will be determined according to IEC 61851-23 (2023) CC.4.1.2 type: integer - default: 500 + default: 0 dbg_hlc_auth_after_tstep: description: >- Special mode: send HLC auth ok only after t_step_XX is finished (true) or directly when available (false) @@ -111,12 +111,18 @@ config: description: "Hack: Additional sleep for Volkswagen cars for n seconds at the end of cable check" type: integer default: 0 - switch_to_minimum_voltage_after_cable_check: + cable_check_wait_number_of_imd_measurements: + description: >- + Amount of isolation measurement samples to collect before the value can be trusted. This does not average, + it will evaluate the last measurement. Some IMDs (e.g. from Bender) need to measure for 10s to really get a trustable result. + In this case, at 1 Hz sample rate, specify 10 samples here. + type: integer + default: 1 + cable_check_enable_imd_self_test: description: >- - When cable check is completed, switch to minimal voltage of DC output. - Normally disabled. + Enable self testing of IMD in cable check. This is required for IEC 61851-23 (2023) compliance. type: boolean - default: false + default: true hack_skoda_enyaq: description: >- Skoda Enyaq requests DC charging voltages below its battery level or even below 0 initially. diff --git a/modules/EvseManager/scoped_lock_timeout.hpp b/modules/EvseManager/scoped_lock_timeout.hpp index 185620710d..11ff5ef361 100644 --- a/modules/EvseManager/scoped_lock_timeout.hpp +++ b/modules/EvseManager/scoped_lock_timeout.hpp @@ -52,6 +52,7 @@ enum class MutexDescription { Charger_set_hlc_allow_close_contactor, Charger_set_hlc_error, Charger_errors_prevent_charging, + Charger_set_max_current, IEC_process_bsp_event, IEC_state_machine, IEC_set_pwm, @@ -163,6 +164,8 @@ static std::string to_string(MutexDescription d) { return "Charger.cpp: set_hlc_error"; case MutexDescription::Charger_errors_prevent_charging: return "Charger.cpp: errors_prevent_charging"; + case MutexDescription::Charger_set_max_current: + return "Charger.cpp: set max current"; case MutexDescription::IEC_process_bsp_event: return "IECStateMachine::process_bsp_event"; case MutexDescription::IEC_state_machine: diff --git a/modules/MicroMegaWattBSP/MicroMegaWattBSP.cpp b/modules/MicroMegaWattBSP/MicroMegaWattBSP.cpp index fe01d1cc51..328c7da4f5 100644 --- a/modules/MicroMegaWattBSP/MicroMegaWattBSP.cpp +++ b/modules/MicroMegaWattBSP/MicroMegaWattBSP.cpp @@ -12,7 +12,6 @@ void MicroMegaWattBSP::init() { return; } - invoke_init(*p_powermeter); invoke_init(*p_board_support); invoke_init(*p_dc_supply); } @@ -20,25 +19,24 @@ void MicroMegaWattBSP::init() { void MicroMegaWattBSP::ready() { serial.run(); - if (!serial.reset(config.reset_gpio)) { - EVLOG_AND_THROW(EVEXCEPTION(Everest::EverestInternalError, "uMWC reset not successful.")); + if (not config.reset_gpio_chip.empty()) { + EVLOG_info << "Perform HW reset with gpio chip " << config.reset_gpio_chip << " line " << config.reset_gpio; + if (!serial.reset(config.reset_gpio_chip, config.reset_gpio)) { + EVLOG_error << "uMWC reset not successful."; + } } - serial.signalSpuriousReset.connect( - [this]() { EVLOG_AND_THROW(EVEXCEPTION(Everest::EverestInternalError, "uMWC uC spurious reset!")); }); - serial.signalConnectionTimeout.connect( - [this]() { EVLOG_AND_THROW(EVEXCEPTION(Everest::EverestInternalError, "uMWC UART timeout!")); }); + serial.signalSpuriousReset.connect([this]() { EVLOG_warning << "uMWC uC spurious reset!"; }); + serial.signalConnectionTimeout.connect([this]() { EVLOG_warning << "uMWC UART timeout!"; }); serial.signalTelemetry.connect([this](Telemetry t) { mqtt.publish("everest_external/umwc/cp_hi", t.cp_hi); mqtt.publish("everest_external/umwc/cp_lo", t.cp_lo); mqtt.publish("everest_external/umwc/pwm_dc", t.pwm_dc); mqtt.publish("everest_external/umwc/relais_on", t.relais_on); + mqtt.publish("everest_external/umwc/output_voltage", t.voltage); }); - serial.signalPowerMeter.connect( - [this](PowerMeter p) { mqtt.publish("everest_external/umwc/output_voltage", p.voltage); }); - invoke_ready(*p_powermeter); invoke_ready(*p_board_support); invoke_ready(*p_dc_supply); } diff --git a/modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp b/modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp index d6ea3c4cf5..bbd2b694f7 100644 --- a/modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp +++ b/modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp @@ -25,6 +25,7 @@ namespace module { struct Conf { std::string serial_port; int baud_rate; + std::string reset_gpio_chip; int reset_gpio; int dc_max_voltage; }; diff --git a/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.cpp b/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.cpp index f1a3949015..769980db19 100644 --- a/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.cpp +++ b/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.cpp @@ -5,73 +5,85 @@ namespace module { namespace board_support { - -/* - - -static types::board_support::Event cast_event_type(const Event& e) { -switch (e.type) { -case Event_InterfaceEvent_CAR_PLUGGED_IN: - return types::board_support::Event::CarPluggedIn; -case Event_InterfaceEvent_CAR_REQUESTED_POWER: - return types::board_support::Event::CarRequestedPower; -case Event_InterfaceEvent_POWER_ON: - return types::board_support::Event::PowerOn; -case Event_InterfaceEvent_POWER_OFF: - return types::board_support::Event::PowerOff; -case Event_InterfaceEvent_CAR_REQUESTED_STOP_POWER: - return types::board_support::Event::CarRequestedStopPower; -case Event_InterfaceEvent_CAR_UNPLUGGED: - return types::board_support::Event::CarUnplugged; -case Event_InterfaceEvent_ERROR_E: - return types::board_support::Event::ErrorE; -case Event_InterfaceEvent_ERROR_DF: - return types::board_support::Event::ErrorDF; -case Event_InterfaceEvent_ERROR_RELAIS: - return types::board_support::Event::ErrorRelais; -case Event_InterfaceEvent_ERROR_RCD: - return types::board_support::Event::ErrorRCD; -case Event_InterfaceEvent_ERROR_VENTILATION_NOT_AVAILABLE: - return types::board_support::Event::ErrorVentilationNotAvailable; -case Event_InterfaceEvent_ERROR_OVER_CURRENT: - return types::board_support::Event::ErrorOverCurrent; -case Event_InterfaceEvent_ENTER_BCD: - return types::board_support::Event::EFtoBCD; -case Event_InterfaceEvent_LEAVE_BCD: - return types::board_support::Event::BCDtoEF; -case Event_InterfaceEvent_PERMANENT_FAULT: - return types::board_support::Event::PermanentFault; -case Event_InterfaceEvent_EVSE_REPLUG_STARTED: - return types::board_support::Event::EvseReplugStarted; -case Event_InterfaceEvent_EVSE_REPLUG_FINISHED: - return types::board_support::Event::EvseReplugFinished; +static types::board_support_common::BspEvent cast_event_type(CpState cp_state) { + types::board_support_common::BspEvent event; + switch (cp_state) { + case CpState_STATE_A: + event.event = types::board_support_common::Event::A; + break; + case CpState_STATE_B: + event.event = types::board_support_common::Event::B; + break; + case CpState_STATE_C: + event.event = types::board_support_common::Event::C; + break; + case CpState_STATE_D: + event.event = types::board_support_common::Event::D; + break; + case CpState_STATE_E: + event.event = types::board_support_common::Event::E; + break; + case CpState_STATE_F: + event.event = types::board_support_common::Event::F; + break; + } + return event; } -EVLOG_error << "Received an unknown interface event from uMWC: " << (int)e.type; -return types::board_support::Event::ErrorVentilationNotAvailable; +static types::board_support_common::BspEvent cast_event_type(bool relais_state) { + types::board_support_common::BspEvent event; + if (relais_state) { + event.event = types::board_support_common::Event::PowerOn; + } else { + event.event = types::board_support_common::Event::PowerOff; + } + return event; } -*/ void evse_board_supportImpl::init() { { std::lock_guard lock(capsMutex); caps.min_current_A_import = 0; - caps.max_current_A_import = 6; + caps.max_current_A_import = 100; 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 = 0; - caps.max_current_A_export = 6; + caps.max_current_A_export = 100; caps.min_phase_count_export = 1; caps.max_phase_count_export = 3; } - /* mod->serial.signalEvent.connect([this](Event e) { - EVLOG_info << "CP EVENT: " << types::board_support::event_to_string(cast_event_type(e)); - publish_event(cast_event_type(e)); - });*/ + mod->serial.signalKeepAliveLo.connect([this](KeepAliveLo l) { + if (not keep_alive_printed) { + EVLOG_info << "uMWC Controller Configuration:"; + EVLOG_info << " Hardware revision: " << l.hw_revision; + EVLOG_info << " Firmware version: " << l.sw_version_string; + } + keep_alive_printed = true; + }); + + mod->serial.signalCPState.connect([this](CpState cp_state) { + 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; + } + }); } void evse_board_supportImpl::ready() { diff --git a/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.hpp b/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.hpp index c149ac48e3..daa58f1f50 100644 --- a/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.hpp +++ b/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.hpp @@ -61,6 +61,9 @@ class evse_board_supportImpl : public evse_board_supportImplBase { // insert your private definitions here types::evse_board_support::HardwareCapabilities caps; std::mutex capsMutex; + std::atomic_bool keep_alive_printed{false}; + CpState last_cp_state{CpState::CpState_STATE_E}; + bool last_relais_state{false}; // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 }; diff --git a/modules/MicroMegaWattBSP/dc_supply/power_supply_DCImpl.cpp b/modules/MicroMegaWattBSP/dc_supply/power_supply_DCImpl.cpp index ffd6ace7c1..98c2532641 100644 --- a/modules/MicroMegaWattBSP/dc_supply/power_supply_DCImpl.cpp +++ b/modules/MicroMegaWattBSP/dc_supply/power_supply_DCImpl.cpp @@ -8,12 +8,22 @@ namespace module { namespace dc_supply { void power_supply_DCImpl::init() { - - mod->serial.signalPowerMeter.connect([this](const PowerMeter& p) { + mod->serial.signalTelemetry.connect([this](Telemetry t) { types::power_supply_DC::VoltageCurrent vc; - vc.voltage_V = p.voltage; - vc.current_A = 0.; + vc.current_A = 0; + vc.voltage_V = t.voltage; publish_voltage_current(vc); + + types::powermeter::Powermeter p; + p.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now()); + p.meter_id = "UMWC"; + types::units::Energy e; + e.total = 0.; + p.energy_Wh_import = e; + types::units::Voltage v; + v.DC = t.voltage; + p.voltage_V = v; + mod->p_powermeter->publish_powermeter(p); }); } diff --git a/modules/MicroMegaWattBSP/manifest.yaml b/modules/MicroMegaWattBSP/manifest.yaml index b32d4d542e..6e5414c8a4 100644 --- a/modules/MicroMegaWattBSP/manifest.yaml +++ b/modules/MicroMegaWattBSP/manifest.yaml @@ -1,21 +1,24 @@ description: Driver module for the Micro Mega Watt DC Charging Tester v1.0 config: serial_port: - description: Serial port the Yeti hardware is connected to + description: Serial port the uMWC hardware is connected to type: string default: /dev/ttyUSB0 baud_rate: - description: Serial baud rate to use when communicating with Yeti hardware + description: Serial baud rate to use when communicating with uMWC hardware type: integer minimum: 9600 maximum: 230400 default: 115200 + reset_gpio_chip: + description: >- + Reset GPIO chip to use to HW reset uMWC. If set to empty string, it is disabled. + type: string + default: 'gpiochip0' reset_gpio: - description: Reset GPIO number to use to HW reset uMWC. If set <0 it is disabled. + description: GPIO line to use to reset uMWC type: integer - minimum: -1 - maximum: 1000 - default: -1 + default: 27 dc_max_voltage: description: Maximum voltage to support type: integer @@ -28,7 +31,7 @@ provides: description: Interface for the DC/DC output supply powermeter: interface: powermeter - description: provides the Yeti Internal Power Meter + description: Interface for the powermeter board_support: interface: evse_board_support description: provides the board support Interface to low level control control pilot, relais, rcd, motor lock diff --git a/modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp b/modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp index 02af0b8412..b20a4b0f66 100644 --- a/modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp +++ b/modules/MicroMegaWattBSP/powermeter/powermeterImpl.cpp @@ -6,47 +6,21 @@ namespace module { namespace powermeter { -static types::powermeter::Powermeter umwc_to_everest(const PowerMeter& p) { - types::powermeter::Powermeter j; - - j.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now()); - j.meter_id = "UMWC_POWERMETER"; - - j.energy_Wh_import.total = 0; - - types::units::Power pwr; - pwr.total = 0; - j.power_W = pwr; - - types::units::Voltage volt; - volt.DC = p.voltage; - j.voltage_V = volt; - - types::units::Current amp; - amp.DC = 0; - j.current_A = amp; - - return j; -} - void powermeterImpl::init() { - mod->serial.signalPowerMeter.connect([this](const PowerMeter& p) { publish_powermeter(umwc_to_everest(p)); }); } void powermeterImpl::ready() { } -types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) { - return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED, - {}, - {}, - "MicroMegaWattBSP powermeter does not support the stop_transaction command"}; -}; - types::powermeter::TransactionStartResponse powermeterImpl::handle_start_transaction(types::powermeter::TransactionReq& value) { - return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED, - "MicroMegaWattBSP powermeter does not support the start_transaction command"}; + // your code for cmd start_transaction goes here + return {}; +} + +types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) { + // your code for cmd stop_transaction goes here + return {}; } } // namespace powermeter diff --git a/modules/MicroMegaWattBSP/umwc_comms/CMakeLists.txt b/modules/MicroMegaWattBSP/umwc_comms/CMakeLists.txt index 395eda75ea..133b91bbf2 100644 --- a/modules/MicroMegaWattBSP/umwc_comms/CMakeLists.txt +++ b/modules/MicroMegaWattBSP/umwc_comms/CMakeLists.txt @@ -30,4 +30,5 @@ target_link_libraries(umwc_comms PRIVATE Pal::Sigslot everest::framework + everest::gpio ) diff --git a/modules/MicroMegaWattBSP/umwc_comms/evSerial.cpp b/modules/MicroMegaWattBSP/umwc_comms/evSerial.cpp index bfff5ae4bc..ac5a87b46a 100644 --- a/modules/MicroMegaWattBSP/umwc_comms/evSerial.cpp +++ b/modules/MicroMegaWattBSP/umwc_comms/evSerial.cpp @@ -18,6 +18,8 @@ #include #include +#include + #include "umwc.pb.h" evSerial::evSerial() { @@ -151,10 +153,6 @@ void evSerial::handlePacket(uint8_t* buf, int len) { // detect connection timeout if keep_alive packets stop coming... last_keep_alive_lo_timestamp = date::utc_clock::now(); break; - case McuToEverest_power_meter_tag: - // printf("Received power_meter %i\n", (int)(msg_in.payload.power_meter.voltage)); - signalPowerMeter(msg_in.payload.power_meter); - break; case McuToEverest_telemetry_tag: /*printf("Received telemetry cp_hi %f cp_lo %f relais_on %i pwm_dc %f\n", msg_in.payload.telemetry.cp_hi, msg_in.payload.telemetry.cp_lo, (int)msg_in.payload.telemetry.relais_on, @@ -164,9 +162,6 @@ void evSerial::handlePacket(uint8_t* buf, int len) { case McuToEverest_cp_state_tag: signalCPState(msg_in.payload.cp_state); break; - case McuToEverest_pp_state_tag: - signalPPState(msg_in.payload.pp_state); - break; case McuToEverest_relais_state_tag: signalRelaisState(msg_in.payload.relais_state); break; @@ -357,22 +352,21 @@ void evSerial::setOutputVoltageCurrent(float v, float c) { linkWrite(&msg_out); } -bool evSerial::reset(const int reset_pin) { +bool evSerial::reset(const std::string& reset_chip, const int reset_line) { reset_done_flag = false; forced_reset = true; - if (reset_pin > 0) { + if (not reset_chip.empty()) { // Try to hardware reset Yeti controller to be in a known state - char cmd[100]; - sprintf(cmd, "echo %i >/sys/class/gpio/export", reset_pin); - system(cmd); - sprintf(cmd, "echo out > /sys/class/gpio/gpio%i/direction", reset_pin); - system(cmd); - sprintf(cmd, "echo 0 > /sys/class/gpio/gpio%i/value", reset_pin); - system(cmd); - sprintf(cmd, "echo 1 > /sys/class/gpio/gpio%i/value", reset_pin); - system(cmd); + Everest::Gpio reset_gpio; + reset_gpio.open(reset_chip, reset_line); + reset_gpio.set_output(true); + reset_gpio.set(true); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + reset_gpio.set(false); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + reset_gpio.set(true); } else { // Try to soft reset Yeti controller to be in a known state EverestToMcu msg_out = EverestToMcu_init_default; diff --git a/modules/MicroMegaWattBSP/umwc_comms/evSerial.h b/modules/MicroMegaWattBSP/umwc_comms/evSerial.h index befa7d3926..40701c09a2 100644 --- a/modules/MicroMegaWattBSP/umwc_comms/evSerial.h +++ b/modules/MicroMegaWattBSP/umwc_comms/evSerial.h @@ -25,7 +25,7 @@ class evSerial { void enable(bool en); void disable(); void replug(); - bool reset(const int reset_pin); + bool reset(const std::string& reset_chip, const int reset_line); void firmwareUpdate(bool rom); void keepAlive(); @@ -35,11 +35,9 @@ class evSerial { void setOutputVoltageCurrent(float v, float c); sigslot::signal signalKeepAliveLo; - sigslot::signal signalPowerMeter; sigslot::signal signalTelemetry; sigslot::signal signalCPState; - sigslot::signal signalPPState; sigslot::signal signalErrorFlags; sigslot::signal signalRelaisState; diff --git a/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.pb.c b/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.pb.c index 74b0f0260a..520292025f 100644 --- a/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.pb.c +++ b/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.6 */ +/* Generated by nanopb-0.4.8 */ #include "umwc.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -27,14 +27,9 @@ PB_BIND(KeepAlive, KeepAlive, AUTO) PB_BIND(Telemetry, Telemetry, AUTO) -PB_BIND(PowerMeter, PowerMeter, AUTO) - - PB_BIND(FirmwareUpdate, FirmwareUpdate, AUTO) - - diff --git a/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.pb.h b/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.pb.h index edd5e9e786..89e64847c8 100644 --- a/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.pb.h +++ b/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.6 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_UMWC_PB_H_INCLUDED #define PB_UMWC_PB_H_INCLUDED @@ -10,53 +10,32 @@ #endif /* Enum definitions */ -typedef enum _CpState { - CpState_STATE_A = 0, - CpState_STATE_B = 1, - CpState_STATE_C = 2, - CpState_STATE_D = 3, - CpState_STATE_E = 4, - CpState_STATE_F = 5 +typedef enum _CpState { + CpState_STATE_A = 0, + CpState_STATE_B = 1, + CpState_STATE_C = 2, + CpState_STATE_D = 3, + CpState_STATE_E = 4, + CpState_STATE_F = 5 } CpState; -typedef enum _ResetReason { - ResetReason_USER = 0, - ResetReason_WATCHDOG = 1 +typedef enum _ResetReason { + ResetReason_USER = 0, + ResetReason_WATCHDOG = 1 } ResetReason; -typedef enum _PpState { - PpState_STATE_NC = 0, - PpState_STATE_13A = 1, - PpState_STATE_20A = 2, - PpState_STATE_32A = 3, - PpState_STATE_70A = 4, - PpState_STATE_FAULT = 5 -} PpState; - -typedef enum _LockState { - LockState_UNDEFINED = 0, - LockState_UNLOCKED = 1, - LockState_LOCKED = 2 -} LockState; - /* Struct definitions */ -typedef struct _ErrorFlags { +typedef struct _SetOutputVoltageCurrent { + float voltage; + float current; +} SetOutputVoltageCurrent; + +typedef struct _ErrorFlags { bool diode_fault; bool cp_signal_fault; } ErrorFlags; -typedef struct _FirmwareUpdate { - bool invoke_rom_bootloader; -} FirmwareUpdate; - -typedef struct _KeepAlive { - uint32_t time_stamp; - uint32_t hw_type; - uint32_t hw_revision; - char sw_version_string[51]; -} KeepAlive; - -typedef struct _KeepAliveLo { +typedef struct _KeepAliveLo { uint32_t time_stamp; uint32_t hw_type; uint32_t hw_revision; @@ -70,24 +49,42 @@ typedef struct _KeepAliveLo { bool supports_changing_phases_during_charging; } KeepAliveLo; -typedef struct _PowerMeter { - float voltage; -} PowerMeter; - -typedef struct _SetOutputVoltageCurrent { - float voltage; - float current; -} SetOutputVoltageCurrent; +typedef struct _KeepAlive { + uint32_t time_stamp; + uint32_t hw_type; + uint32_t hw_revision; + char sw_version_string[51]; +} KeepAlive; -typedef struct _Telemetry { +typedef struct _Telemetry { float cp_hi; float cp_lo; float pwm_dc; float relais_on; + float voltage; } Telemetry; +/* This container message is send from MCU to EVerest and may contain any allowed message in that direction. */ +typedef struct _McuToEverest { + pb_size_t which_payload; + union { + /* Needs to remain the same to allow firmware updates of older versions */ + KeepAliveLo keep_alive; + /* Other IDs are 100+ to avoid compatibility issues with older firmware versions */ + ResetReason reset; + CpState cp_state; + bool relais_state; /* false: relais are off, true: relais are on */ + ErrorFlags error_flags; + Telemetry telemetry; + } payload; +} McuToEverest; + +typedef struct _FirmwareUpdate { + bool invoke_rom_bootloader; +} FirmwareUpdate; + /* This container message is send from EVerest to MCU and may contain any allowed message in that direction. */ -typedef struct _EverestToMcu { +typedef struct _EverestToMcu { pb_size_t which_payload; union { /* Needs to remain the same to allow firmware updates of older versions */ @@ -95,7 +92,6 @@ typedef struct _EverestToMcu { SetOutputVoltageCurrent set_output_voltage_current; /* Other IDs are 100+ to avoid compatibility issues with older firmware versions */ KeepAlive keep_alive; - bool connector_lock; /* false: unlock, true: lock */ uint32_t pwm_duty_cycle; /* in 0.01 %, 0 = State F, 10000 = X1 */ bool allow_power_on; bool reset; @@ -104,24 +100,10 @@ typedef struct _EverestToMcu { } payload; } EverestToMcu; -/* This container message is send from MCU to EVerest and may contain any allowed message in that direction. */ -typedef struct _McuToEverest { - pb_size_t which_payload; - union { - /* Needs to remain the same to allow firmware updates of older versions */ - KeepAliveLo keep_alive; - /* Other IDs are 100+ to avoid compatibility issues with older firmware versions */ - ResetReason reset; - CpState cp_state; - bool relais_state; /* false: relais are off, true: relais are on */ - ErrorFlags error_flags; - Telemetry telemetry; - PpState pp_state; - LockState lock_state; - PowerMeter power_meter; - } payload; -} McuToEverest; +#ifdef __cplusplus +extern "C" { +#endif /* Helper constants for enums */ #define _CpState_MIN CpState_STATE_A @@ -132,18 +114,16 @@ typedef struct _McuToEverest { #define _ResetReason_MAX ResetReason_WATCHDOG #define _ResetReason_ARRAYSIZE ((ResetReason)(ResetReason_WATCHDOG+1)) -#define _PpState_MIN PpState_STATE_NC -#define _PpState_MAX PpState_STATE_FAULT -#define _PpState_ARRAYSIZE ((PpState)(PpState_STATE_FAULT+1)) -#define _LockState_MIN LockState_UNDEFINED -#define _LockState_MAX LockState_LOCKED -#define _LockState_ARRAYSIZE ((LockState)(LockState_LOCKED+1)) +#define McuToEverest_payload_reset_ENUMTYPE ResetReason +#define McuToEverest_payload_cp_state_ENUMTYPE CpState + + + + + -#ifdef __cplusplus -extern "C" { -#endif /* Initializer values for message structs */ #define EverestToMcu_init_default {0, {FirmwareUpdate_init_default}} @@ -152,8 +132,7 @@ extern "C" { #define ErrorFlags_init_default {0, 0} #define KeepAliveLo_init_default {0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0} #define KeepAlive_init_default {0, 0, 0, ""} -#define Telemetry_init_default {0, 0, 0, 0} -#define PowerMeter_init_default {0} +#define Telemetry_init_default {0, 0, 0, 0, 0} #define FirmwareUpdate_init_default {0} #define EverestToMcu_init_zero {0, {FirmwareUpdate_init_zero}} #define McuToEverest_init_zero {0, {KeepAliveLo_init_zero}} @@ -161,18 +140,14 @@ extern "C" { #define ErrorFlags_init_zero {0, 0} #define KeepAliveLo_init_zero {0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0} #define KeepAlive_init_zero {0, 0, 0, ""} -#define Telemetry_init_zero {0, 0, 0, 0} -#define PowerMeter_init_zero {0} +#define Telemetry_init_zero {0, 0, 0, 0, 0} #define FirmwareUpdate_init_zero {0} /* Field tags (for use in manual encoding/decoding) */ +#define SetOutputVoltageCurrent_voltage_tag 1 +#define SetOutputVoltageCurrent_current_tag 2 #define ErrorFlags_diode_fault_tag 1 #define ErrorFlags_cp_signal_fault_tag 6 -#define FirmwareUpdate_invoke_rom_bootloader_tag 1 -#define KeepAlive_time_stamp_tag 1 -#define KeepAlive_hw_type_tag 2 -#define KeepAlive_hw_revision_tag 3 -#define KeepAlive_sw_version_string_tag 6 #define KeepAliveLo_time_stamp_tag 1 #define KeepAliveLo_hw_type_tag 2 #define KeepAliveLo_hw_revision_tag 3 @@ -184,38 +159,36 @@ extern "C" { #define KeepAliveLo_hwcap_max_phase_count_tag 9 #define KeepAliveLo_hwcap_min_phase_count_tag 10 #define KeepAliveLo_supports_changing_phases_during_charging_tag 11 -#define PowerMeter_voltage_tag 2 -#define SetOutputVoltageCurrent_voltage_tag 1 -#define SetOutputVoltageCurrent_current_tag 2 +#define KeepAlive_time_stamp_tag 1 +#define KeepAlive_hw_type_tag 2 +#define KeepAlive_hw_revision_tag 3 +#define KeepAlive_sw_version_string_tag 6 #define Telemetry_cp_hi_tag 1 #define Telemetry_cp_lo_tag 2 #define Telemetry_pwm_dc_tag 3 #define Telemetry_relais_on_tag 4 +#define Telemetry_voltage_tag 5 +#define McuToEverest_keep_alive_tag 3 +#define McuToEverest_reset_tag 101 +#define McuToEverest_cp_state_tag 102 +#define McuToEverest_relais_state_tag 103 +#define McuToEverest_error_flags_tag 104 +#define McuToEverest_telemetry_tag 105 +#define FirmwareUpdate_invoke_rom_bootloader_tag 1 #define EverestToMcu_firmware_update_tag 16 #define EverestToMcu_set_output_voltage_current_tag 50 #define EverestToMcu_keep_alive_tag 100 -#define EverestToMcu_connector_lock_tag 102 #define EverestToMcu_pwm_duty_cycle_tag 103 #define EverestToMcu_allow_power_on_tag 104 #define EverestToMcu_reset_tag 105 #define EverestToMcu_enable_tag 106 #define EverestToMcu_replug_tag 107 -#define McuToEverest_keep_alive_tag 3 -#define McuToEverest_reset_tag 101 -#define McuToEverest_cp_state_tag 102 -#define McuToEverest_relais_state_tag 103 -#define McuToEverest_error_flags_tag 104 -#define McuToEverest_telemetry_tag 105 -#define McuToEverest_pp_state_tag 106 -#define McuToEverest_lock_state_tag 107 -#define McuToEverest_power_meter_tag 108 /* Struct field encoding specification for nanopb */ #define EverestToMcu_FIELDLIST(X, a) \ X(a, STATIC, ONEOF, MESSAGE, (payload,firmware_update,payload.firmware_update), 16) \ X(a, STATIC, ONEOF, MESSAGE, (payload,set_output_voltage_current,payload.set_output_voltage_current), 50) \ X(a, STATIC, ONEOF, MESSAGE, (payload,keep_alive,payload.keep_alive), 100) \ -X(a, STATIC, ONEOF, BOOL, (payload,connector_lock,payload.connector_lock), 102) \ X(a, STATIC, ONEOF, UINT32, (payload,pwm_duty_cycle,payload.pwm_duty_cycle), 103) \ X(a, STATIC, ONEOF, BOOL, (payload,allow_power_on,payload.allow_power_on), 104) \ X(a, STATIC, ONEOF, BOOL, (payload,reset,payload.reset), 105) \ @@ -233,16 +206,12 @@ X(a, STATIC, ONEOF, UENUM, (payload,reset,payload.reset), 101) \ X(a, STATIC, ONEOF, UENUM, (payload,cp_state,payload.cp_state), 102) \ X(a, STATIC, ONEOF, BOOL, (payload,relais_state,payload.relais_state), 103) \ X(a, STATIC, ONEOF, MESSAGE, (payload,error_flags,payload.error_flags), 104) \ -X(a, STATIC, ONEOF, MESSAGE, (payload,telemetry,payload.telemetry), 105) \ -X(a, STATIC, ONEOF, UENUM, (payload,pp_state,payload.pp_state), 106) \ -X(a, STATIC, ONEOF, UENUM, (payload,lock_state,payload.lock_state), 107) \ -X(a, STATIC, ONEOF, MESSAGE, (payload,power_meter,payload.power_meter), 108) +X(a, STATIC, ONEOF, MESSAGE, (payload,telemetry,payload.telemetry), 105) #define McuToEverest_CALLBACK NULL #define McuToEverest_DEFAULT NULL #define McuToEverest_payload_keep_alive_MSGTYPE KeepAliveLo #define McuToEverest_payload_error_flags_MSGTYPE ErrorFlags #define McuToEverest_payload_telemetry_MSGTYPE Telemetry -#define McuToEverest_payload_power_meter_MSGTYPE PowerMeter #define SetOutputVoltageCurrent_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 1) \ @@ -283,15 +252,11 @@ X(a, STATIC, SINGULAR, STRING, sw_version_string, 6) X(a, STATIC, SINGULAR, FLOAT, cp_hi, 1) \ X(a, STATIC, SINGULAR, FLOAT, cp_lo, 2) \ X(a, STATIC, SINGULAR, FLOAT, pwm_dc, 3) \ -X(a, STATIC, SINGULAR, FLOAT, relais_on, 4) +X(a, STATIC, SINGULAR, FLOAT, relais_on, 4) \ +X(a, STATIC, SINGULAR, FLOAT, voltage, 5) #define Telemetry_CALLBACK NULL #define Telemetry_DEFAULT NULL -#define PowerMeter_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, FLOAT, voltage, 2) -#define PowerMeter_CALLBACK NULL -#define PowerMeter_DEFAULT NULL - #define FirmwareUpdate_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, invoke_rom_bootloader, 1) #define FirmwareUpdate_CALLBACK NULL @@ -304,7 +269,6 @@ extern const pb_msgdesc_t ErrorFlags_msg; extern const pb_msgdesc_t KeepAliveLo_msg; extern const pb_msgdesc_t KeepAlive_msg; extern const pb_msgdesc_t Telemetry_msg; -extern const pb_msgdesc_t PowerMeter_msg; extern const pb_msgdesc_t FirmwareUpdate_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ @@ -315,7 +279,6 @@ extern const pb_msgdesc_t FirmwareUpdate_msg; #define KeepAliveLo_fields &KeepAliveLo_msg #define KeepAlive_fields &KeepAlive_msg #define Telemetry_fields &Telemetry_msg -#define PowerMeter_fields &PowerMeter_msg #define FirmwareUpdate_fields &FirmwareUpdate_msg /* Maximum encoded size of messages (where known) */ @@ -325,9 +288,9 @@ extern const pb_msgdesc_t FirmwareUpdate_msg; #define KeepAliveLo_size 106 #define KeepAlive_size 70 #define McuToEverest_size 108 -#define PowerMeter_size 5 #define SetOutputVoltageCurrent_size 10 -#define Telemetry_size 20 +#define Telemetry_size 25 +#define UMWC_PB_H_MAX_SIZE McuToEverest_size #ifdef __cplusplus } /* extern "C" */ diff --git a/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.proto b/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.proto index d757e449a3..8d709b5d67 100644 --- a/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.proto +++ b/modules/MicroMegaWattBSP/umwc_comms/protobuf/umwc.proto @@ -12,7 +12,6 @@ message EverestToMcu { // Other IDs are 100+ to avoid compatibility issues with older firmware versions KeepAlive keep_alive = 100; - bool connector_lock = 102; // false: unlock, true: lock uint32 pwm_duty_cycle = 103; // in 0.01 %, 0 = State F, 10000 = X1 bool allow_power_on = 104; bool reset = 105; @@ -35,9 +34,6 @@ message McuToEverest { bool relais_state = 103; // false: relais are off, true: relais are on ErrorFlags error_flags = 104; Telemetry telemetry = 105; - PpState pp_state = 106; - LockState lock_state = 107; - PowerMeter power_meter = 108; } } @@ -91,25 +87,7 @@ message Telemetry { float cp_lo = 2; float pwm_dc = 3; float relais_on = 4; -} - -enum PpState { - STATE_NC = 0; - STATE_13A = 1; - STATE_20A = 2; - STATE_32A = 3; - STATE_70A = 4; - STATE_FAULT = 5; -} - -enum LockState { - UNDEFINED = 0; - UNLOCKED = 1; - LOCKED = 2; -} - -message PowerMeter { - float voltage = 2; + float voltage = 5; } message FirmwareUpdate { diff --git a/modules/MicroMegaWattBSP/umwc_fwupdate/CMakeLists.txt b/modules/MicroMegaWattBSP/umwc_fwupdate/CMakeLists.txt index e4e1f20644..18ea6f836f 100644 --- a/modules/MicroMegaWattBSP/umwc_fwupdate/CMakeLists.txt +++ b/modules/MicroMegaWattBSP/umwc_fwupdate/CMakeLists.txt @@ -9,7 +9,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # add the executable add_executable(umwc_fwupdate main.cpp) target_include_directories(umwc_fwupdate PUBLIC "${PROJECT_BINARY_DIR}" PUBLIC "../umwc_comms/nanopb" PUBLIC "../umwc_comms/protobuf" PUBLIC "../umwc_comms") -target_link_libraries(umwc_fwupdate PRIVATE Pal::Sigslot Threads::Threads umwc_comms everest::framework) +target_link_libraries(umwc_fwupdate PRIVATE Pal::Sigslot Threads::Threads umwc_comms everest::framework everest::gpio) install(TARGETS umwc_fwupdate DESTINATION ${EVEREST_MOD_YETIDRIVER_DESTINATION}) diff --git a/modules/MicroMegaWattBSP/umwc_fwupdate/main.cpp b/modules/MicroMegaWattBSP/umwc_fwupdate/main.cpp index ca7a71b74c..96e1166252 100644 --- a/modules/MicroMegaWattBSP/umwc_fwupdate/main.cpp +++ b/modules/MicroMegaWattBSP/umwc_fwupdate/main.cpp @@ -10,6 +10,7 @@ #include #include "umwc.pb.h" +#include using namespace std::chrono_literals; @@ -56,6 +57,15 @@ int main(int argc, char* argv[]) { delete p; sleep(1); + // Try to hardware reset Yeti controller to be in a known state + Everest::Gpio reset_gpio; + reset_gpio.open("gpiochip0", 27); + reset_gpio.set_output(true); + reset_gpio.set(true); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + reset_gpio.set(false); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + reset_gpio.set(true); char cmd[1000]; sprintf(cmd, "stm32flash -b 115200 %.100s -v -w %.100s -R", device, filename); // sprintf(cmd, "stm32flash -b115200 %.100s", device); diff --git a/modules/SerialCommHub/main/serial_communication_hubImpl.cpp b/modules/SerialCommHub/main/serial_communication_hubImpl.cpp index 9848fc5ed6..55b6022dc5 100644 --- a/modules/SerialCommHub/main/serial_communication_hubImpl.cpp +++ b/modules/SerialCommHub/main/serial_communication_hubImpl.cpp @@ -39,8 +39,8 @@ void serial_communication_hubImpl::init() { rxtx_gpio_settings.inverted = config.rxtx_gpio_tx_high; if (!modbus.open_device(config.serial_port, config.baudrate, config.ignore_echo, rxtx_gpio_settings, - static_cast(config.parity), milliseconds(config.initial_timeout_ms), - milliseconds(config.within_message_timeout_ms))) { + static_cast(config.parity), config.rtscts, + milliseconds(config.initial_timeout_ms), milliseconds(config.within_message_timeout_ms))) { EVLOG_AND_THROW(Everest::EverestConfigError(fmt::format("Cannot open serial port {}.", config.serial_port))); } } diff --git a/modules/SerialCommHub/main/serial_communication_hubImpl.hpp b/modules/SerialCommHub/main/serial_communication_hubImpl.hpp index 02232bffc9..2a3933f7bf 100644 --- a/modules/SerialCommHub/main/serial_communication_hubImpl.hpp +++ b/modules/SerialCommHub/main/serial_communication_hubImpl.hpp @@ -28,6 +28,7 @@ struct Conf { std::string serial_port; int baudrate; int parity; + bool rtscts; bool ignore_echo; std::string rxtx_gpio_chip; int rxtx_gpio_line; diff --git a/modules/SerialCommHub/manifest.yaml b/modules/SerialCommHub/manifest.yaml index ab187548a7..5a62bc7d39 100644 --- a/modules/SerialCommHub/manifest.yaml +++ b/modules/SerialCommHub/manifest.yaml @@ -20,6 +20,10 @@ provides: minimum: 0 maximum: 2 default: 0 + rtscts: + description: Use RTS/CTS hardware flow control + type: boolean + default: false ignore_echo: description: On some hardware every message that is sent is read back, this setting filters the sent message in the reply. type: boolean diff --git a/modules/SerialCommHub/tiny_modbus_rtu.cpp b/modules/SerialCommHub/tiny_modbus_rtu.cpp index fb5de291a3..39cbb0c78b 100644 --- a/modules/SerialCommHub/tiny_modbus_rtu.cpp +++ b/modules/SerialCommHub/tiny_modbus_rtu.cpp @@ -193,7 +193,7 @@ TinyModbusRTU::~TinyModbusRTU() { } bool TinyModbusRTU::open_device(const std::string& device, int _baud, bool _ignore_echo, - const Everest::GpioSettings& rxtx_gpio_settings, const Parity parity, + const Everest::GpioSettings& rxtx_gpio_settings, const Parity parity, bool rtscts, std::chrono::milliseconds _initial_timeout, std::chrono::milliseconds _within_message_timeout) { @@ -264,7 +264,12 @@ bool TinyModbusRTU::open_device(const std::string& device, int _baud, bool _igno tty.c_cflag &= ~(PARENB | PARODD); // shut off parity } tty.c_cflag &= ~CSTOPB; // 1 Stop bit - tty.c_cflag &= ~CRTSCTS; + + if (rtscts) { + tty.c_cflag |= CRTSCTS; + } else { + tty.c_cflag &= ~CRTSCTS; + } if (tcsetattr(fd, TCSANOW, &tty) != 0) { printf("Serial: error %d from tcsetattr\n", errno); diff --git a/modules/SerialCommHub/tiny_modbus_rtu.hpp b/modules/SerialCommHub/tiny_modbus_rtu.hpp index b80558e204..abcd76ff75 100644 --- a/modules/SerialCommHub/tiny_modbus_rtu.hpp +++ b/modules/SerialCommHub/tiny_modbus_rtu.hpp @@ -57,7 +57,7 @@ class TinyModbusRTU { ~TinyModbusRTU(); bool open_device(const std::string& device, int baud, bool ignore_echo, - const Everest::GpioSettings& rxtx_gpio_settings, const Parity parity, + const Everest::GpioSettings& rxtx_gpio_settings, const Parity parity, bool rtscts, std::chrono::milliseconds initial_timeout, std::chrono::milliseconds within_message_timeout); std::vector txrx(uint8_t device_address, FunctionCode function, uint16_t first_register_address, diff --git a/modules/YetiDriver/YetiDriver.cpp b/modules/YetiDriver/YetiDriver.cpp index 33267b7190..74d7083933 100644 --- a/modules/YetiDriver/YetiDriver.cpp +++ b/modules/YetiDriver/YetiDriver.cpp @@ -69,13 +69,11 @@ void YetiDriver::ready() { serial.run(); if (!serial.reset(config.reset_gpio_chip, config.reset_gpio)) { - EVLOG_AND_THROW(EVEXCEPTION(Everest::EverestInternalError, "Yeti reset not successful.")); + EVLOG_error << "Yeti reset not successful."; } - serial.signalSpuriousReset.connect( - [this]() { EVLOG_AND_THROW(EVEXCEPTION(Everest::EverestInternalError, "Yeti uC spurious reset!")); }); - serial.signalConnectionTimeout.connect( - [this]() { EVLOG_AND_THROW(EVEXCEPTION(Everest::EverestInternalError, "Yeti UART timeout!")); }); + serial.signalSpuriousReset.connect([this]() { EVLOG_error << "Yeti uC spurious reset!"; }); + serial.signalConnectionTimeout.connect([this]() { EVLOG_error << "Yeti UART timeout!"; }); invoke_ready(*p_powermeter); invoke_ready(*p_board_support); diff --git a/modules/YetiDriver/YetiDriver.hpp b/modules/YetiDriver/YetiDriver.hpp index 462368c0cf..a8e2a113db 100644 --- a/modules/YetiDriver/YetiDriver.hpp +++ b/modules/YetiDriver/YetiDriver.hpp @@ -28,6 +28,7 @@ struct Conf { std::string reset_gpio_chip; int reset_gpio; int caps_min_current_A; + int caps_max_current_A; }; class YetiDriver : public Everest::ModuleBase { diff --git a/modules/YetiDriver/board_support/evse_board_supportImpl.cpp b/modules/YetiDriver/board_support/evse_board_supportImpl.cpp index f33fec3f3f..806d00caad 100644 --- a/modules/YetiDriver/board_support/evse_board_supportImpl.cpp +++ b/modules/YetiDriver/board_support/evse_board_supportImpl.cpp @@ -70,14 +70,14 @@ void evse_board_supportImpl::init() { { std::lock_guard lock(capsMutex); - caps.min_current_A_import = mod->config.caps_min_current_A; + caps.min_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.min_current_A_export = 6; caps.max_current_A_export = 16; caps.min_phase_count_export = 1; caps.max_phase_count_export = 3; @@ -113,13 +113,15 @@ void evse_board_supportImpl::init() { caps.min_current_A_import = (mod->config.caps_min_current_A >= 0 ? mod->config.caps_min_current_A : l.hwcap_min_current); - caps.max_current_A_import = l.hwcap_max_current; + caps.max_current_A_import = + (mod->config.caps_max_current_A >= 0 ? mod->config.caps_max_current_A : l.hwcap_max_current); caps.min_phase_count_import = l.hwcap_min_phase_count; caps.max_phase_count_import = l.hwcap_max_phase_count; caps.min_current_A_export = (mod->config.caps_min_current_A >= 0 ? mod->config.caps_min_current_A : l.hwcap_min_current); - caps.max_current_A_export = l.hwcap_max_current; + caps.max_current_A_export = + (mod->config.caps_max_current_A >= 0 ? mod->config.caps_max_current_A : l.hwcap_max_current); caps.min_phase_count_export = l.hwcap_min_phase_count; caps.max_phase_count_export = l.hwcap_max_phase_count; diff --git a/modules/YetiDriver/manifest.yaml b/modules/YetiDriver/manifest.yaml index cd1f738e20..3ba9e54324 100644 --- a/modules/YetiDriver/manifest.yaml +++ b/modules/YetiDriver/manifest.yaml @@ -23,6 +23,10 @@ config: description: Minimal current on AC side. For AC this is typically 6, but for HLC this can be less. -1 means use limit reported by HW. type: integer default: -1 + caps_max_current_A: + description: Maximum current on AC side. For AC this is typically 16 or 32, but for HLC this can be less. -1 means use limit reported by HW. + type: integer + default: -1 provides: powermeter: interface: powermeter diff --git a/modules/simulation/DCSupplySimulator/main/power_supply_DCImpl.hpp b/modules/simulation/DCSupplySimulator/main/power_supply_DCImpl.hpp index 8a4305a002..f70878a55f 100644 --- a/modules/simulation/DCSupplySimulator/main/power_supply_DCImpl.hpp +++ b/modules/simulation/DCSupplySimulator/main/power_supply_DCImpl.hpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright chargebyte GmbH and Contributors to EVerest +// Copyright Pionix GmbH and Contributors to EVerest #ifndef MAIN_POWER_SUPPLY_DC_IMPL_HPP #define MAIN_POWER_SUPPLY_DC_IMPL_HPP diff --git a/modules/simulation/IMDSimulator/main/isolation_monitorImpl.cpp b/modules/simulation/IMDSimulator/main/isolation_monitorImpl.cpp index 862f94b027..00aedf7421 100644 --- a/modules/simulation/IMDSimulator/main/isolation_monitorImpl.cpp +++ b/modules/simulation/IMDSimulator/main/isolation_monitorImpl.cpp @@ -18,9 +18,6 @@ void isolation_monitorImpl::init() { void isolation_monitorImpl::ready() { } -isolation_monitorImpl::~isolation_monitorImpl() { -} - void isolation_monitorImpl::handle_start() { if (this->isolation_monitoring_active == false) { this->isolation_monitoring_active = true; @@ -28,6 +25,10 @@ void isolation_monitorImpl::handle_start() { } }; +void isolation_monitorImpl::handle_start_self_test(double& test_voltage_V) { + selftest_running_countdown = 3 * 1000 / LOOP_SLEEP_MS; +} + void isolation_monitorImpl::isolation_measurement_worker() { while (true) { if (this->isolation_measurement_thread_handle.shouldExit()) { @@ -35,11 +36,18 @@ void isolation_monitorImpl::isolation_measurement_worker() { } if (this->isolation_monitoring_active == true) { - this->mod->p_main->publish_IsolationMeasurement(this->isolation_measurement); + this->mod->p_main->publish_isolation_measurement(this->isolation_measurement); EVLOG_debug << "Simulated isolation measurement finished"; std::this_thread::sleep_for(std::chrono::milliseconds(this->config_interval - this->LOOP_SLEEP_MS)); } + if (this->selftest_running_countdown > 0) { + this->selftest_running_countdown--; + if (this->selftest_running_countdown == 0) { + this->mod->p_main->publish_self_test_result(config.selftest_success); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(this->LOOP_SLEEP_MS)); } } diff --git a/modules/simulation/IMDSimulator/main/isolation_monitorImpl.hpp b/modules/simulation/IMDSimulator/main/isolation_monitorImpl.hpp index 106caa497c..94e3c10e2b 100644 --- a/modules/simulation/IMDSimulator/main/isolation_monitorImpl.hpp +++ b/modules/simulation/IMDSimulator/main/isolation_monitorImpl.hpp @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2023 chargebyte GmbH -// Copyright (C) 2023 Contributors to EVerest +// Copyright Pionix GmbH and Contributors to EVerest #ifndef MAIN_ISOLATION_MONITOR_IMPL_HPP #define MAIN_ISOLATION_MONITOR_IMPL_HPP @@ -26,6 +25,7 @@ namespace main { struct Conf { double resistance_F_Ohm; int interval; + bool selftest_success; }; class isolation_monitorImpl : public isolation_monitorImplBase { @@ -33,7 +33,6 @@ class isolation_monitorImpl : public isolation_monitorImplBase { isolation_monitorImpl() = delete; isolation_monitorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : isolation_monitorImplBase(ev, "main"), mod(mod), config(config){}; - ~isolation_monitorImpl(); // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 // insert your public definitions here @@ -43,6 +42,7 @@ class isolation_monitorImpl : public isolation_monitorImplBase { // command handler functions (virtual) virtual void handle_start() override; virtual void handle_stop() override; + virtual void handle_start_self_test(double& test_voltage_V) override; // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 // insert your protected definitions here @@ -64,6 +64,8 @@ class isolation_monitorImpl : public isolation_monitorImplBase { Everest::Thread isolation_measurement_thread_handle; void isolation_measurement_worker(void); + + std::atomic_int selftest_running_countdown{0}; // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 }; diff --git a/modules/simulation/IMDSimulator/manifest.yaml b/modules/simulation/IMDSimulator/manifest.yaml index df1bf9c287..19a8c1a8df 100644 --- a/modules/simulation/IMDSimulator/manifest.yaml +++ b/modules/simulation/IMDSimulator/manifest.yaml @@ -12,6 +12,10 @@ provides: description: Measurement update interval in milliseconds type: integer default: 1000 + selftest_success: + description: Set to true for successful self testing, false for fault + type: boolean + default: true metadata: license: https://opensource.org/licenses/Apache-2.0 authors: diff --git a/types/evse_manager.yaml b/types/evse_manager.yaml index 0867879504..21109116ff 100644 --- a/types/evse_manager.yaml +++ b/types/evse_manager.yaml @@ -297,6 +297,7 @@ types: - EnergyManagement - PermanentFault - PowermeterTransactionStartFailed + - IMDFault Error: description: >- Error object that contains information about the error and optional vendor error information