From dea0de06b05834f74e8b4f56536d048c486229ab Mon Sep 17 00:00:00 2001 From: Cornelius Claussen Date: Tue, 11 Jul 2023 17:33:00 +0200 Subject: [PATCH] First draft of new BSP interface. SIL is working, all other BSP drivers are broken for now. add publish of capabilities for runtime changes add MREC fault types for EvseManager and bsp interface add more specific error codes for connector lock. support A_63_3ph_70_1ph add separate pp_ampacity var add draft for connector lock interface Allow non IEC compliant EVs to pause/resume Start adding error definitions. SIL is still waiting for JS support for errors Add errors for evse_manager allow resume charging after everest restart remove extra stop power on unplug. This may lead to a short pause state if C->A happens without going through B move connector_type into hw_caps Simulation works again. Errors can be set from nodered. Handling of errors still broken charging continues once all faults are cleared adapt to new iso interface Signed-off-by: Cornelius Claussen --- config/CMakeLists.txt | 1 - config/config-hil.yaml | 105 --- config/config-sil-dc-sae-v2g.yaml | 2 + config/config-sil-dc-sae-v2h.yaml | 2 + config/config-sil-dc.yaml | 3 +- config/config-sil-energy-management.yaml | 6 +- config/config-sil-ocpp-custom-extension.yaml | 6 +- config/config-sil-ocpp-pnc.yaml | 4 + config/config-sil-ocpp.yaml | 6 +- config/config-sil-ocpp201.yaml | 6 +- config/config-sil-two-evse-dc.yaml | 6 +- config/config-sil-two-evse.yaml | 6 +- config/config-sil.yaml | 7 +- config/nodered/config-sil-flow.json | 801 +++++++++++++++++- errors/connector_lock.yaml | 27 + errors/evse_board_support.yaml | 50 ++ errors/evse_manager.yaml | 11 + interfaces/ac_rcd.yaml | 27 + interfaces/board_support_AC.yaml | 98 --- interfaces/board_support_AC_debug.yaml | 40 - interfaces/connector_lock.yaml | 9 + interfaces/evse_board_support.yaml | 125 +++ interfaces/evse_manager.yaml | 6 +- interfaces/yeti_extras.yaml | 29 - modules/API/API.cpp | 4 +- modules/API/API.hpp | 10 +- modules/API/CMakeLists.txt | 5 +- modules/API/limit_decimal_places.cpp | 25 +- modules/API/limit_decimal_places.hpp | 4 +- modules/API/manifest.yaml | 25 +- modules/Auth/Auth.cpp | 6 +- modules/Auth/Auth.hpp | 1 + modules/Auth/include/AuthHandler.hpp | 3 +- modules/Auth/lib/AuthHandler.cpp | 17 +- modules/Auth/manifest.yaml | 14 + modules/EvseManager/CMakeLists.txt | 2 + modules/EvseManager/Charger.cpp | 398 ++++----- modules/EvseManager/Charger.hpp | 51 +- modules/EvseManager/ErrorHandling.cpp | 215 +++++ modules/EvseManager/ErrorHandling.hpp | 105 +++ modules/EvseManager/EvseManager.cpp | 110 +-- modules/EvseManager/EvseManager.hpp | 32 +- modules/EvseManager/IECStateMachine.cpp | 369 ++++++++ modules/EvseManager/IECStateMachine.hpp | 120 +++ modules/EvseManager/Timeout.hpp | 89 +- .../EvseManager/energy_grid/energyImpl.hpp | 2 +- modules/EvseManager/evse/evse_managerImpl.cpp | 26 +- modules/EvseManager/manifest.yaml | 21 +- modules/MicroMegaWattBSP/CMakeLists.txt | 2 +- modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp | 6 +- .../board_support/board_support_ACImpl.cpp | 123 --- .../board_support/evse_board_supportImpl.cpp | 129 +++ ..._ACImpl.hpp => evse_board_supportImpl.hpp} | 32 +- modules/MicroMegaWattBSP/manifest.yaml | 2 +- modules/YetiDriver/CMakeLists.txt | 5 +- modules/YetiDriver/YetiDriver.cpp | 4 - modules/YetiDriver/YetiDriver.hpp | 18 +- ..._ACImpl.cpp => evse_board_supportImpl.cpp} | 121 ++- ..._ACImpl.hpp => evse_board_supportImpl.hpp} | 31 +- modules/YetiDriver/manifest.yaml | 13 +- modules/YetiDriver/rcd/ac_rcdImpl.cpp | 25 + .../ac_rcdImpl.hpp} | 23 +- .../yeti_extras/yeti_extrasImpl.cpp | 27 - .../yeti_simulation_controlImpl.cpp | 65 -- .../yeti_simulation_controlImpl.hpp | 63 -- modules/simulation/JsCarSimulator/index.js | 2 + modules/simulation/JsYetiSimulator/index.js | 538 ++++++++---- .../simulation/JsYetiSimulator/manifest.yaml | 24 +- types/board_support_common.yaml | 48 ++ ...d_support.yaml => evse_board_support.yaml} | 71 +- types/evse_manager.yaml | 30 +- 71 files changed, 3113 insertions(+), 1326 deletions(-) delete mode 100644 config/config-hil.yaml create mode 100644 errors/connector_lock.yaml create mode 100644 errors/evse_board_support.yaml create mode 100644 errors/evse_manager.yaml create mode 100644 interfaces/ac_rcd.yaml delete mode 100644 interfaces/board_support_AC.yaml delete mode 100644 interfaces/board_support_AC_debug.yaml create mode 100644 interfaces/connector_lock.yaml create mode 100644 interfaces/evse_board_support.yaml delete mode 100644 interfaces/yeti_extras.yaml create mode 100644 modules/EvseManager/ErrorHandling.cpp create mode 100644 modules/EvseManager/ErrorHandling.hpp create mode 100644 modules/EvseManager/IECStateMachine.cpp create mode 100644 modules/EvseManager/IECStateMachine.hpp delete mode 100644 modules/MicroMegaWattBSP/board_support/board_support_ACImpl.cpp create mode 100644 modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.cpp rename modules/MicroMegaWattBSP/board_support/{board_support_ACImpl.hpp => evse_board_supportImpl.hpp} (58%) rename modules/YetiDriver/board_support/{board_support_ACImpl.cpp => evse_board_supportImpl.cpp} (58%) rename modules/YetiDriver/board_support/{board_support_ACImpl.hpp => evse_board_supportImpl.hpp} (59%) create mode 100644 modules/YetiDriver/rcd/ac_rcdImpl.cpp rename modules/YetiDriver/{yeti_extras/yeti_extrasImpl.hpp => rcd/ac_rcdImpl.hpp} (66%) delete mode 100644 modules/YetiDriver/yeti_extras/yeti_extrasImpl.cpp delete mode 100644 modules/YetiDriver/yeti_simulation_control/yeti_simulation_controlImpl.cpp delete mode 100644 modules/YetiDriver/yeti_simulation_control/yeti_simulation_controlImpl.hpp create mode 100644 types/board_support_common.yaml rename types/{board_support.yaml => evse_board_support.yaml} (72%) diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 3b600b1402..583f88282b 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -7,7 +7,6 @@ generate_config_run_script(CONFIG sil-dc-sae-v2g) generate_config_run_script(CONFIG sil-dc-sae-v2h) generate_config_run_script(CONFIG sil-two-evse-dc) generate_config_run_script(CONFIG sil-energy-management) -generate_config_run_script(CONFIG hil) generate_config_run_script(CONFIG sil-gen-pm) generate_config_run_script(CONFIG sil-ocpp) generate_config_run_script(CONFIG sil-ocpp-custom-extension) diff --git a/config/config-hil.yaml b/config/config-hil.yaml deleted file mode 100644 index 69368aec7a..0000000000 --- a/config/config-hil.yaml +++ /dev/null @@ -1,105 +0,0 @@ -active_modules: - emgr_logger: - module: JsEmgrLogger - config_implementation: - main: - file_path_prefix: emgr_data - connections: - emgr: - - module_id: energy_manager - implementation_id: main - pm_grid: - - module_id: sma_modbus_meter - implementation_id: main - pm_ev: - - module_id: yeti_driver - implementation_id: powermeter - energy_manager: - module: JsEnergyManager - config_implementation: - main: - pid_setpoint: 500 - pid_p_weight: 0.46 - pid_i_weight: 0.2 - pid_d_weight: 0.5 - pid_output_interval: 5000 - pid_i_limit: -1 - pid_min_output: 1380 - pid_max_output: 7360 - connections: - chargingdriver: - - module_id: evse_manager - implementation_id: evse - chargingdriverenergy: - - module_id: evse_manager - implementation_id: evse_energy_control - gridpowermeter: - - module_id: sma_modbus_meter - implementation_id: main - chargingdriverpowermeter: - - module_id: yeti_driver - implementation_id: powermeter - iso15118_charger: - module: EvseV2G - config_module: - device: auto - tls_security: allow - connections: - security: - - module_id: evse_security - implementation_id: main - evse_manager: - module: EvseManager - config_module: - three_phases: true - has_ventilation: true - country_code: DE - rcd_enabled: true - connections: - bsp: - - module_id: yeti_driver - implementation_id: board_support - powermeter: - - module_id: yeti_driver - implementation_id: powermeter - auth: - - module_id: auth - implementation_id: main - yeti_driver: - module: YetiDriver - config_module: - serial_port: /dev/ttyUSB0 - baud_rate: 115200 - car_simulator: - module: JsCarSimulator - connections: - simulation_control: - - module_id: yeti_driver - implementation_id: yeti_simulation_control - auth: - module: JsAuth - connections: - tokenProvider: - - module_id: token_provider_1 - implementation_id: main - tokenValidator: - - module_id: token_validator - implementation_id: main - evse_security: - module: EvseSecurity - config_module: - private_key_password: "123456" - token_provider_1: - module: DummyTokenProvider - connections: - evse: - - module_id: evse_manager - implementation_id: evse - token_validator: - module: DummyTokenValidator - config_implementation: - main: - validation_result: Accepted - validation_reason: Token seems valid - sleep: 0.25 -x-module-layout: {} diff --git a/config/config-sil-dc-sae-v2g.yaml b/config/config-sil-dc-sae-v2g.yaml index af94a72e04..0e56b1238e 100644 --- a/config/config-sil-dc-sae-v2g.yaml +++ b/config/config-sil-dc-sae-v2g.yaml @@ -52,6 +52,8 @@ active_modules: module: JsDCSupplySimulator yeti_driver: module: JsYetiSimulator + config_module: + connector_id: 1 slac: module: JsSlacSimulator imd: diff --git a/config/config-sil-dc-sae-v2h.yaml b/config/config-sil-dc-sae-v2h.yaml index 96c5d151e0..476a25a759 100644 --- a/config/config-sil-dc-sae-v2h.yaml +++ b/config/config-sil-dc-sae-v2h.yaml @@ -52,6 +52,8 @@ active_modules: module: JsDCSupplySimulator yeti_driver: module: JsYetiSimulator + config_module: + connector_id: 1 slac: module: JsSlacSimulator imd: diff --git a/config/config-sil-dc.yaml b/config/config-sil-dc.yaml index 9716ca77ff..07566523c7 100644 --- a/config/config-sil-dc.yaml +++ b/config/config-sil-dc.yaml @@ -19,7 +19,6 @@ active_modules: config_module: connector_id: 1 country_code: DE - rcd_enabled: true evse_id: DE*PNX*E12345*1 evse_id_din: 49A80737A45678 session_logging: true @@ -50,6 +49,8 @@ active_modules: module: JsDCSupplySimulator yeti_driver: module: JsYetiSimulator + config_module: + connector_id: 1 slac: module: JsSlacSimulator imd: diff --git a/config/config-sil-energy-management.yaml b/config/config-sil-energy-management.yaml index e6a2387017..5b382294a8 100644 --- a/config/config-sil-energy-management.yaml +++ b/config/config-sil-energy-management.yaml @@ -20,7 +20,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: DE*PNX*E12345*1 session_logging: true session_logging_xml: false @@ -48,7 +47,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: DE*PNX*E12345*2 session_logging: true session_logging_xml: false @@ -65,8 +63,12 @@ active_modules: implementation_id: powermeter 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: diff --git a/config/config-sil-ocpp-custom-extension.yaml b/config/config-sil-ocpp-custom-extension.yaml index 81cf665c94..f8250729f9 100644 --- a/config/config-sil-ocpp-custom-extension.yaml +++ b/config/config-sil-ocpp-custom-extension.yaml @@ -20,7 +20,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: "1" session_logging: true session_logging_xml: false @@ -48,7 +47,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: "2" session_logging: true session_logging_xml: false @@ -71,8 +69,12 @@ active_modules: 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: diff --git a/config/config-sil-ocpp-pnc.yaml b/config/config-sil-ocpp-pnc.yaml index f9c83bcd59..e576bb673c 100644 --- a/config/config-sil-ocpp-pnc.yaml +++ b/config/config-sil-ocpp-pnc.yaml @@ -74,8 +74,12 @@ active_modules: 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: diff --git a/config/config-sil-ocpp.yaml b/config/config-sil-ocpp.yaml index bf3d22fe57..4067f3e954 100644 --- a/config/config-sil-ocpp.yaml +++ b/config/config-sil-ocpp.yaml @@ -20,7 +20,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: "1" session_logging: true session_logging_xml: false @@ -49,7 +48,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: "2" session_logging: true session_logging_xml: false @@ -73,8 +71,12 @@ active_modules: 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: diff --git a/config/config-sil-ocpp201.yaml b/config/config-sil-ocpp201.yaml index 595497ba82..91c080d1a7 100644 --- a/config/config-sil-ocpp201.yaml +++ b/config/config-sil-ocpp201.yaml @@ -20,7 +20,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: "1" session_logging: true session_logging_xml: false @@ -48,7 +47,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: "2" session_logging: true session_logging_xml: false @@ -71,8 +69,12 @@ active_modules: 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: diff --git a/config/config-sil-two-evse-dc.yaml b/config/config-sil-two-evse-dc.yaml index 73273bad60..90f2845908 100644 --- a/config/config-sil-two-evse-dc.yaml +++ b/config/config-sil-two-evse-dc.yaml @@ -19,7 +19,6 @@ active_modules: config_module: connector_id: 1 country_code: DE - rcd_enabled: true evse_id: DE*PNX*E12345*1 evse_id_din: 49A80737A45678 session_logging: true @@ -52,7 +51,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: DE*PNX*E12345*2 session_logging: true session_logging_xml: false @@ -69,8 +67,12 @@ active_modules: implementation_id: powermeter yeti_driver_1: module: JsYetiSimulator + config_module: + connector_id: 1 yeti_driver_2: module: JsYetiSimulator + config_module: + connector_id: 2 slac_1: module: JsSlacSimulator powersupply_dc: diff --git a/config/config-sil-two-evse.yaml b/config/config-sil-two-evse.yaml index a6161a643c..c92e2cf503 100644 --- a/config/config-sil-two-evse.yaml +++ b/config/config-sil-two-evse.yaml @@ -20,7 +20,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: DE*PNX*E12345*1 session_logging: true session_logging_xml: false @@ -48,7 +47,6 @@ active_modules: three_phases: true has_ventilation: true country_code: DE - rcd_enabled: true evse_id: DE*PNX*E12345*2 session_logging: true session_logging_xml: false @@ -65,8 +63,12 @@ active_modules: implementation_id: powermeter 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: diff --git a/config/config-sil.yaml b/config/config-sil.yaml index 6591ec0044..9fcccecda5 100644 --- a/config/config-sil.yaml +++ b/config/config-sil.yaml @@ -12,6 +12,7 @@ active_modules: connection_timeout: 10 prioritize_authorization_over_stopping_transaction: true selection_algorithm: FindFirst + ignore_connector_faults: true connections: evse_manager: - implementation_id: evse @@ -62,7 +63,6 @@ active_modules: max_current_export_A: 32 payment_enable_contract: true payment_enable_eim: true - rcd_enabled: true session_logging: true session_logging_path: /tmp/everest-logs session_logging_xml: false @@ -80,6 +80,9 @@ active_modules: slac: - implementation_id: evse module_id: slac + ac_rcd: + - implementation_id: rcd + module_id: connector_1_powerpath module: EvseManager telemetry: id: 1 @@ -158,6 +161,8 @@ active_modules: connections: {} module: DummyTokenValidator connector_1_powerpath: + config_module: + connector_id: 1 connections: {} module: JsYetiSimulator telemetry: diff --git a/config/nodered/config-sil-flow.json b/config/nodered/config-sil-flow.json index 674bce4790..e743492e53 100644 --- a/config/nodered/config-sil-flow.json +++ b/config/nodered/config-sil-flow.json @@ -13,6 +13,14 @@ "disabled": false, "info": "" }, + { + "id": "cb7609df6138407d", + "type": "tab", + "label": "Errors", + "disabled": false, + "info": "", + "env": [] + }, { "id": "af1e1eeac9c4b704", "type": "group", @@ -331,6 +339,17 @@ "disabled": false, "hidden": false }, + { + "id": "18b792e585c4a82f", + "type": "ui_group", + "name": "Errors", + "tab": "d3ada9fa4cf6ac53", + "order": 1, + "disp": true, + "width": "6", + "collapse": false, + "className": "" + }, { "id": "c8955752ad17f297", "type": "mqtt in", @@ -1809,7 +1828,7 @@ }, { "label": "AC RCD Error", - "value": "sleep 1;rcd_current 10.3;sleep 10;rcd_current 0.1sleep 36000#unplug", + "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 3;rcd_current 10.5;sleep 5;rcd_current 0.2;sleep 36000#unplug", "type": "str" }, { @@ -1949,5 +1968,785 @@ "620b0d248a89ece0" ] ] + }, + { + "id": "14a551bb80c5552d", + "type": "change", + "z": "cb7609df6138407d", + "name": "", + "rules": [ + { + "t": "set", + "p": "connector_number", + "pt": "flow", + "to": "1", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 440, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "2ffed6827d01d00d", + "type": "inject", + "z": "cb7609df6138407d", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": 0.1, + "topic": "", + "payloadType": "date", + "x": 190, + "y": 180, + "wires": [ + [ + "14a551bb80c5552d" + ] + ] + }, + { + "id": "ca7308d14419cbd2", + "type": "change", + "z": "cb7609df6138407d", + "name": "Insert Connector number", + "rules": [ + { + "t": "change", + "p": "topic", + "pt": "msg", + "from": "#", + "fromt": "str", + "to": "connector_number", + "tot": "flow" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 770, + "y": 560, + "wires": [ + [ + "513a3b91748dc03c" + ] + ] + }, + { + "id": "513a3b91748dc03c", + "type": "mqtt out", + "z": "cb7609df6138407d", + "name": "", + "topic": "", + "qos": "1", + "retain": "false", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "fc8686af.48d178", + "x": 970, + "y": 560, + "wires": [] + }, + { + "id": "cf15f41427168f59", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "BrownOut", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"BrownOut\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"BrownOut\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 160, + "y": 280, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "b0c90949862e4acf", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "DiodeFault", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"DiodeFault\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"DiodeFault\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 170, + "y": 240, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "40c2bf363920110e", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "EnergyManagement", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"EnergyManagement\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"EnergyManagement\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 200, + "y": 320, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "94a8973cf4eb4963", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "PermanentFault", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"PermanentFault\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"PermanentFault\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 180, + "y": 360, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "db13aa747d6ffd3a", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC2GroundFailure", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC2GroundFailure\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC2GroundFailure\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 200, + "y": 400, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "a18a831e962fe2fb", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC4OverCurrentFailure", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC4OverCurrentFailure\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC4OverCurrentFailure\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 220, + "y": 440, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "2fc7fdfe4ae8b520", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC5OverVoltage", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC5OverVoltage\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC5OverVoltage\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 200, + "y": 480, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "9efbf3ce40d6881c", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC6UnderVoltage", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC6UnderVoltage\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC6UnderVoltage\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 200, + "y": 520, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "d6bbb4c57751877b", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC8EmergencyStop", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC8EmergencyStop\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC8EmergencyStop\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 210, + "y": 560, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "639a0db868e0ed18", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC10InvalidVehicleMode", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC10InvalidVehicleMode\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC10InvalidVehicleMode\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 220, + "y": 600, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "7b28a940fe45f635", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC14PilotFault", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC14PilotFault\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC14PilotFault\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 190, + "y": 640, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "0232fd01e1e02fe1", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC15PowerLoss", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC15PowerLoss\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC15PowerLoss\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 200, + "y": 680, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "202ebd4c388a5817", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC17EVSEContactorFault", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC17EVSEContactorFault\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC17EVSEContactorFault\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 230, + "y": 720, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "0a81c071b35d54d3", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC18CableOverTempDerate", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC18CableOverTempDerate\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC18CableOverTempDerate\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 230, + "y": 760, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "3eb9a5937aed34a7", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC19CableOverTempStop", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC19CableOverTempStop\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC19CableOverTempStop\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 230, + "y": 800, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "b6208cc76ab8ba93", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC20PartialInsertion", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC20PartialInsertion\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC20PartialInsertion\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 210, + "y": 840, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "1205c62ee631e717", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC23ProximityFault", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC23ProximityFault\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC23ProximityFault\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 210, + "y": 880, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "df7bf0f1e692028c", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC24ConnectorVoltageHigh", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC24ConnectorVoltageHigh\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC24ConnectorVoltageHigh\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 230, + "y": 920, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "5a669dee09d69d6c", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC25BrokenLatch", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC25BrokenLatch\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC25BrokenLatch\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 200, + "y": 960, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] + }, + { + "id": "25cadcb508c05cfc", + "type": "ui_switch", + "z": "cb7609df6138407d", + "name": "", + "label": "MREC26CutCable", + "tooltip": "", + "group": "18b792e585c4a82f", + "order": 1, + "width": 0, + "height": 0, + "passthru": true, + "decouple": "false", + "topic": "everest_external/nodered/#/carsim/error", + "topicType": "str", + "style": "", + "onvalue": "{\"error_type\":\"MREC26CutCable\",\"raise\":\"true\"}", + "onvalueType": "json", + "onicon": "", + "oncolor": "", + "offvalue": "{\"error_type\":\"MREC26CutCable\",\"raise\":\"false\"}", + "offvalueType": "json", + "officon": "", + "offcolor": "", + "animate": false, + "className": "", + "x": 190, + "y": 1000, + "wires": [ + [ + "ca7308d14419cbd2" + ] + ] } ] \ No newline at end of file diff --git a/errors/connector_lock.yaml b/errors/connector_lock.yaml new file mode 100644 index 0000000000..c08fe6aff7 --- /dev/null +++ b/errors/connector_lock.yaml @@ -0,0 +1,27 @@ +description: >- + Errors for Connector Lock + + These following error types give more details about connector lock errors. + You should consider setting a MREC_1_ConnectorLockFailure to indicated a generic lock failure with an MREC error code as well + as one of the more detailed but custom error codes to specify the reason: + + All error codes that begin with MREC needs to be implemented to fulfill the Minimum Required Error Codes defined by the ChargeX consortium: + + https://inl.gov/content/uploads/2023/07/ChargeX_MREC_Rev5_09.12.23.pdf +errors: + - name: ConnectorLockCapNotCharged + description: The capacitor for connector lock motor failed to charge within expected time + - name: ConnectorLockUnexpectedOpen + description: The connector lock feedback does return open while it should be close + - name: ConnectorLockUnexpectedClose + description: The connector lock feedback does return closed while it should be open + - name: ConnectorLockFailedLock + description: The connector lock failed to lock (feedback still returns open) + - name: ConnectorLockFailedUnlock + description: The connector lock failed to unlock (feedback still returns closed) + - name: MREC1ConnectorLockFailure + description: >- + Failure to lock or unlock connector on the vehicle side as per MREC definition. + How should that be implemented? We can only find out about locking on the EVSE side, + so we will use this error to report EVSE side lock failures. It is probably a mistake in the MREC definition. + \ No newline at end of file diff --git a/errors/evse_board_support.yaml b/errors/evse_board_support.yaml new file mode 100644 index 0000000000..d61dffc473 --- /dev/null +++ b/errors/evse_board_support.yaml @@ -0,0 +1,50 @@ +description: >- + Errors for the evse_board_support interface. + All error codes that begin with MREC needs to be implemented to fulfill the Minimum Required Error Codes defined by the ChargeX consortium: + + https://inl.gov/content/uploads/2023/07/ChargeX_MREC_Rev5_09.12.23.pdf +errors: + - name: DiodeFault + description: The CP diode in the EV is shorted. + - name: VentilationNotAvailable + description: EV requested state D but no ventilation is available. + - name: BrownOut + description: The hardware/MCU detected a brown out. + - name: EnergyManagement + description: Energy could not be delivered because an (external) energy management failed. + - name: PermanentFault + description: The EVSE is permanently broken and requires repair. + - name: MREC2GroundFailure + description: Ground fault circuit interrupter has been activated. + - name: MREC3HighTemperature + description: High temperature inside the EVSE is derating power delivery. + - name: MREC4OverCurrentFailure + description: Over current protection device has tripped. + - name: MREC5OverVoltage + description: Input voltage to the vehicle has risen above an acceptable level. + - name: MREC6UnderVoltage + description: Input voltage to the vehicle has dropped below an acceptable level. + - name: MREC8EmergencyStop + description: Emergency stop is pressed by the user (required if equipped). + - name: MREC10InvalidVehicleMode + description: The vehicle is in an invalid mode for charging (Reported by IEC stack) + - name: MREC14PilotFault + description: The control pilot voltage is out of range. + - name: MREC15PowerLoss + description: The EVSE is unable to supply any power due to mains failure + - name: MREC17EVSEContactorFault + description: Contactors fail to open or close on EVSE's side. May also include welding related errors. + - name: MREC18CableOverTempDerate + description: Temperature of charging cable or connector assembly is too high, resulting in reduced power operation. + - name: MREC19CableOverTempStop + description: Temperature of charging cable or connector assembly is too high, resulting in a stopped charging session. + - name: MREC20PartialInsertion + description: Cable latch is raised due to incomplete insertion into the vehicle charging port. + - name: MREC23ProximityFault + description: The proximity voltage is out of range. + - name: MREC24ConnectorVoltageHigh + description: The output voltage of EVSE is high before charging starts or after charging ends. + - name: MREC25BrokenLatch + description: The latch on the connector is broken. + - name: MREC26CutCable + description: The output cable has been severed from the EVSE. diff --git a/errors/evse_manager.yaml b/errors/evse_manager.yaml new file mode 100644 index 0000000000..d3d3fce95c --- /dev/null +++ b/errors/evse_manager.yaml @@ -0,0 +1,11 @@ +description: >- + Errors for EvseManager + + All error codes that begin with MREC needs to be implemented to fulfill the Minimum Required Error Codes defined by the ChargeX consortium: + + https://inl.gov/content/uploads/2023/07/ChargeX_MREC_Rev5_09.12.23.pdf +errors: + - name: Internal + description: Internal error of the state machine + - name: MREC4OverCurrentFailure + description: Over current event \ No newline at end of file diff --git a/interfaces/ac_rcd.yaml b/interfaces/ac_rcd.yaml new file mode 100644 index 0000000000..be32a014a1 --- /dev/null +++ b/interfaces/ac_rcd.yaml @@ -0,0 +1,27 @@ +description: >- + This interface defines an AC Residual Current Monitor (RCD). Actual emergency switch off is done in HW directly, + but this interface allows some control and telemetry. +cmds: + self_test: + description: >- + Executes a self test of the RCD. Once finished, the var self_test_result should be published. Since + the test can take quite long we do not use a synchronous return value here. + reset: + description: >- + Resets the RCD after a trigger. May not be supported by actual hardware. + result: + description: 'True: Reset successfull, False: Reset failed.' + type: boolean +vars: + rcd_current_mA: + description: Residual current in mA. Note that this does not trigger anything, it is merely for reporting. + type: number + fault_ac: + description: Indicates an AC residual current fault (informational only, hardware needs to shut down power on its own) + type: 'null' + fault_dc: + description: Indicates an DC residual current fault (on the AC wires, informational only, hardware needs to shut down power on its own). + type: 'null' + self_test_result: + description: Result of a triggered self test + type: boolean diff --git a/interfaces/board_support_AC.yaml b/interfaces/board_support_AC.yaml deleted file mode 100644 index 76e09f0169..0000000000 --- a/interfaces/board_support_AC.yaml +++ /dev/null @@ -1,98 +0,0 @@ -description: >- - This interface defines the board support driver for AC power path: ControlPilot, - Relais, RCD and motor lock -cmds: - setup: - description: Setup config options - arguments: - three_phases: - description: 'true: Three phases enabled, false: only single phase' - type: boolean - has_ventilation: - description: 'true: Allow mode D charging, false: do not allow mode D charging' - type: boolean - country_code: - description: A two-letter country code in ISO 3166-1 alpha-2 format - type: string - rcd_enabled: - description: 'true: enable RCD, false: disable RCD' - type: boolean - get_hw_capabilities: - description: Get Hardware capability/limits - result: - description: Hardware capability/limits - type: object - $ref: /board_support#/HardwareCapabilities - enable: - description: >- - Enables or disables the EVSE. Typically disabled results in control - pilot state F. It must not accept cars for new charging sessions if disabled. - arguments: - value: - description: 'True: enabled, false: disabled.' - type: boolean - pwm_on: - description: Turns PWM on with duty cycle - arguments: - value: - description: PWM duty cycle (>0, <1) - type: number - minimum: 0 - maximum: 1 - pwm_off: - description: Turns PWM off (constant high voltage) - pwm_F: - description: Turns PWM off with Error F (constant negative voltage) - allow_power_on: - description: >- - Sets allow_power_on flag. If false, Relais must never be switched - on. - arguments: - value: - description: 'True: allow power on, false: do not allow power on.' - type: boolean - force_unlock: - description: Force unlock motor lock - result: - description: Returns true if unlocking sequence was successfully executed - type: boolean - switch_three_phases_while_charging: - description: >- - Special command to force switching between one and three phases while - charging is active. HW must go through some special sequence to ensure safe - operation. - arguments: - value: - description: 'True: switch to 3ph, False: switch to 1ph' - type: boolean - evse_replug: - description: >- - Special command initiate a virtual replug sequence without restarting - session. Emits a EvseReplugStarted event if supported and started. BSP will - take care to not emit other events such as CarPluggedIn/Out during that time. - Once finished it will emit a EvseReplugFinished. - arguments: - value: - description: Time in ms for the duration of the replug sequence - type: integer - read_pp_ampacity: - description: >- - Read the current carrying capacity of the connected cable in ampere. - In case of a fixed cable the configured maximum current the hardware can handle will be reported. - result: - description: Returns the current carrying capacity of the connected cable in ampere - type: number -vars: - event: - description: Event from ControlPilot signal/Relais/RCD - type: string - $ref: /board_support#/Event - nr_of_phases_available: - description: Instantaneous phase count available to car - type: integer - minimum: 1 - maximum: 3 - telemetry: - description: Other telemetry - type: object - $ref: /board_support#/Telemetry diff --git a/interfaces/board_support_AC_debug.yaml b/interfaces/board_support_AC_debug.yaml deleted file mode 100644 index 78b7c1d592..0000000000 --- a/interfaces/board_support_AC_debug.yaml +++ /dev/null @@ -1,40 +0,0 @@ -description: >- - This interface defines the board support debug information that is not - used for actual control -vars: - three_phases: - description: config option for three phase/single phase operation - type: boolean - three_phases_active: - description: True if three phases is enabled for current charging session - type: boolean - has_ventilation: - description: True if ventilated charging is allowed - type: boolean - pwm_running: - description: True if ventilated charging is allowed - type: boolean - simplified_mode: - description: True if car uses simplified mode of IEC61851 - type: boolean - rcd_reclosing_allowed: - description: True if RCD may reclose after fault according to local regulations - type: boolean - is_power_on: - description: True if Relais are currently closed (power on) - type: boolean - cp_hi_voltage: - description: Voltage of high part of PWM - type: number - cp_lo_voltage: - description: Voltage of low part of PWM - type: number - supply_12V_voltage: - description: Voltage of +12V supply - type: number - supply_N12V_voltage: - description: Voltage of -12V supply - type: number - rcd_current: - description: Residual current measurement - type: number diff --git a/interfaces/connector_lock.yaml b/interfaces/connector_lock.yaml new file mode 100644 index 0000000000..6043722423 --- /dev/null +++ b/interfaces/connector_lock.yaml @@ -0,0 +1,9 @@ +description: >- + This interface defines one connector locking motor (e.g. for AC sockets with no fixed attached cable) +cmds: + lock: + description: Lock connector lock + unlock: + description: Unlock connector lock (e.g. normal unlock or enforced by OCPP) +errors: + - reference: /errors/connector_lock diff --git a/interfaces/evse_board_support.yaml b/interfaces/evse_board_support.yaml new file mode 100644 index 0000000000..d0a168dad5 --- /dev/null +++ b/interfaces/evse_board_support.yaml @@ -0,0 +1,125 @@ +description: >- + This interface defines the board support driver for AC or DC minimal power path: ControlPilot, + output contactors. + Other components of the power path such as IMD(DC)/RCD(AC)/Connector Lock etc have their own interfaces. +cmds: + setup: + description: Setup config options + arguments: + three_phases: + description: 'true: Three phases enabled, false: only single phase' + type: boolean + has_ventilation: + description: 'true: Allow mode D charging, false: do not allow mode D charging' + type: boolean + country_code: + description: A two-letter country code in ISO 3166-1 alpha-2 format + type: string + get_hw_capabilities: + description: >- + Get Hardware capability/limits. For AC these are the limits of the power path (e.g. relais etc). + For DC, these are the limits for the AC input of the ACDC converter stack, i.e. the complete AC input. + Note that DC output limits are reported by the DC power supply itself. + result: + description: Hardware capability/limits + type: object + $ref: /evse_board_support#/HardwareCapabilities + enable: + description: >- + Enables or disables the charging port. Typically disabled results in control + pilot state F. It must not accept cars for new charging sessions if disabled. + arguments: + value: + description: 'True: enabled, false: disabled.' + type: boolean + pwm_on: + description: Turns PWM on with duty cycle (in percent) + arguments: + value: + description: PWM duty cycle (>0, <100) + type: number + minimum: 0 + maximum: 100 + pwm_off: + description: Turns PWM off (constant high voltage) + pwm_F: + description: Turns PWM off with Error F (constant negative voltage) + allow_power_on: + description: >- + Sets allow_power_on flag. If false, Relais must never be switched + on. + arguments: + value: + description: Flag and context + type: object + $ref: /evse_board_support#/PowerOnOff + ac_switch_three_phases_while_charging: + description: >- + Special command to force switching between one and three phases while + charging is active. HW must go through some special sequence to ensure safe + operation. + arguments: + value: + description: 'True: switch to 3ph, False: switch to 1ph' + type: boolean + evse_replug: + description: >- + Special command initiate a virtual replug sequence without restarting + session. Emits a EvseReplugStarted event if supported and started. BSP will + take care to not emit other events such as CarPluggedIn/Out during that time. + Once finished it will emit a EvseReplugFinished. This is for testing purposes, + don't implement for production use. + arguments: + value: + description: Time in ms for the duration of the replug sequence + type: integer + ac_read_pp_ampacity: + description: >- + Read the current carrying capacity of the connected cable in ampere for AC charging + with a socket. This function will be used by EvseManager to get the PP value at + a distinct time. You should also publish the var pp_ampacity whenever the PP ampacity reading changes + to signal changes e.g. during the charging time. + This has no meaning for DC or AC charging with a fixed attached cable, it does not + need to be implemented and the returned value is not used in those cases. + result: + description: Returns the current carrying capacity of the connected cable + type: object + $ref: /board_support_common#/ProximityPilot + ac_set_overcurrent_limit_A: + description: >- + Sets the limit that should be used for over current detection in the BSP. + Do not use this to set PWM, use this value only for fast OC detection in the MCU. + This will set the actual charging value, so for 16A charging you will get 16A here, + you will need to add a margin in the BSP to avoid false triggers. + arguments: + value: + description: Ampere value + type: number +vars: + event: + description: Event from ControlPilot signal/output relais + type: object + $ref: /board_support_common#/BspEvent + ac_nr_of_phases_available: + description: Instantaneous phase count available to car + type: integer + minimum: 1 + maximum: 3 + telemetry: + description: Other telemetry + type: object + $ref: /evse_board_support#/Telemetry + capabilities: + description: Hardware capabilities/limits. This can be published during runtime whenever it changes to e.g. lower the limits when the hardware overheats. + type: object + $ref: /evse_board_support#/HardwareCapabilities + pp_ampacity: + description: >- + Current carrying capacity of the connected cable in ampere for AC charging + with a socket. Publish whenever it changes. + This has no meaning for DC or AC charging with a fixed attached cable, it does not + need to be implemented and the returned value is not used in those cases. + type: object + $ref: /board_support_common#/ProximityPilot +errors: + - reference: /errors/evse_board_support diff --git a/interfaces/evse_manager.yaml b/interfaces/evse_manager.yaml index c722753551..82225b19ff 100644 --- a/interfaces/evse_manager.yaml +++ b/interfaces/evse_manager.yaml @@ -163,7 +163,7 @@ vars: telemetry: description: Other telemetry type: object - $ref: /board_support#/Telemetry + $ref: /evse_board_support#/Telemetry powermeter: description: Measured dataset type: object @@ -174,7 +174,7 @@ vars: hw_capabilities: description: "Hardware capability/limits" type: object - $ref: /board_support#/HardwareCapabilities + $ref: /evse_board_support#/HardwareCapabilities iso15118_certificate_request: description: >- The vehicle requests the SECC to deliver the certificate that belong @@ -198,3 +198,5 @@ vars: description: >- Contains the selected protocol used for charging for informative purposes type: string +errors: + - reference: /errors/evse_manager \ No newline at end of file diff --git a/interfaces/yeti_extras.yaml b/interfaces/yeti_extras.yaml deleted file mode 100644 index d60b65098e..0000000000 --- a/interfaces/yeti_extras.yaml +++ /dev/null @@ -1,29 +0,0 @@ -description: >- - This interface defines Yeti extra funtionality not found in the generic - interfaces -cmds: - firmware_update: - description: This command reboots Yeti in firmware upgrade mode - arguments: - firmware_binary: - description: Path to firmware binary file that should be sent to Yeti Controller - type: string -vars: - time_stamp: - description: Provides the current time stamp - type: integer - hw_type: - description: Provides the hw_type - type: integer - hw_revision: - description: Provides the hw_revision - type: integer - protocol_version_major: - description: Provides the protocol_version_major - type: integer - protocol_version_minor: - description: Provides the protocol_version_minor - type: integer - sw_version_string: - description: Provides the sw_version_string - type: string diff --git a/modules/API/API.cpp b/modules/API/API.cpp index 18a0ef1881..02cb39b106 100644 --- a/modules/API/API.cpp +++ b/modules/API/API.cpp @@ -194,7 +194,7 @@ void API::init() { std::string var_hw_caps = var_base + "hardware_capabilities"; evse->subscribe_hw_capabilities( - [this, var_hw_caps, &hw_caps](types::board_support::HardwareCapabilities hw_capabilities) { + [this, var_hw_caps, &hw_caps](types::evse_board_support::HardwareCapabilities hw_capabilities) { hw_caps = this->limit_decimal_places->limit(hw_capabilities); this->mqtt.publish(var_hw_caps, hw_caps); }); @@ -217,7 +217,7 @@ void API::init() { }); std::string var_telemetry = var_base + "telemetry"; - evse->subscribe_telemetry([this, var_telemetry](types::board_support::Telemetry telemetry) { + evse->subscribe_telemetry([this, var_telemetry](types::evse_board_support::Telemetry telemetry) { this->mqtt.publish(var_telemetry, this->limit_decimal_places->limit(telemetry)); }); diff --git a/modules/API/API.hpp b/modules/API/API.hpp index 5ead0ed750..7c169d73be 100644 --- a/modules/API/API.hpp +++ b/modules/API/API.hpp @@ -88,12 +88,13 @@ struct Conf { int hw_caps_max_current_import_decimal_places; int hw_caps_min_current_export_decimal_places; int hw_caps_min_current_import_decimal_places; + int hw_caps_max_gun_temperature_C_decimal_places; int limits_max_current_decimal_places; - int telemetry_temperature_decimal_places; + int telemetry_evse_temperature_C_decimal_places; int telemetry_fan_rpm_decimal_places; int telemetry_supply_voltage_12V_decimal_places; int telemetry_supply_voltage_minus_12V_decimal_places; - int telemetry_rcd_current_decimal_places; + int telemetry_gun_temperature_C_decimal_places; double powermeter_energy_import_round_to; double powermeter_energy_export_round_to; double powermeter_power_round_to; @@ -105,12 +106,13 @@ struct Conf { double hw_caps_max_current_import_round_to; double hw_caps_min_current_export_round_to; double hw_caps_min_current_import_round_to; + double hw_caps_max_gun_temperature_C_round_to; double limits_max_current_round_to; - double telemetry_temperature_round_to; + double telemetry_evse_temperature_C_round_to; double telemetry_fan_rpm_round_to; double telemetry_supply_voltage_12V_round_to; double telemetry_supply_voltage_minus_12V_round_to; - double telemetry_rcd_current_round_to; + double telemetry_gun_temperature_C_round_to; }; class API : public Everest::ModuleBase { diff --git a/modules/API/CMakeLists.txt b/modules/API/CMakeLists.txt index b8a5c91527..7146700eba 100644 --- a/modules/API/CMakeLists.txt +++ b/modules/API/CMakeLists.txt @@ -13,12 +13,15 @@ target_link_libraries(${MODULE_NAME} PRIVATE ryml::ryml ) +target_sources(${MODULE_NAME} + PRIVATE + "limit_decimal_places.cpp" +) # ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 target_sources(${MODULE_NAME} PRIVATE "main/emptyImpl.cpp" - "limit_decimal_places.cpp" ) # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/API/limit_decimal_places.cpp b/modules/API/limit_decimal_places.cpp index 43607fe4bb..6548b7a0c6 100644 --- a/modules/API/limit_decimal_places.cpp +++ b/modules/API/limit_decimal_places.cpp @@ -205,7 +205,7 @@ std::string LimitDecimalPlaces::limit(const types::powermeter::Powermeter& power return power_meter_stream.str(); } -std::string LimitDecimalPlaces::limit(const types::board_support::HardwareCapabilities& hw_capabilities) { +std::string LimitDecimalPlaces::limit(const types::evse_board_support::HardwareCapabilities& hw_capabilities) { ryml::Tree tree; ryml::NodeRef root = tree.rootref(); root |= ryml::MAP; @@ -236,6 +236,14 @@ std::string LimitDecimalPlaces::limit(const types::board_support::HardwareCapabi this->round_to_nearest_step(hw_capabilities.min_current_A_export, this->config.hw_caps_min_current_export_round_to), this->config.hw_caps_min_current_export_decimal_places); + if (hw_capabilities.max_gun_temperature_C.has_value()) { + root["max_gun_temperature_C"] << ryml::fmt::real( + this->round_to_nearest_step(hw_capabilities.max_gun_temperature_C.value(), + this->config.hw_caps_max_gun_temperature_C_round_to), + this->config.hw_caps_max_gun_temperature_C_decimal_places); + } + + root["connector_type"] << types::evse_board_support::connector_type_to_string(hw_capabilities.connector_type); std::stringstream hardware_capabilities_stream; hardware_capabilities_stream << ryml::as_json(tree); @@ -263,7 +271,7 @@ std::string LimitDecimalPlaces::limit(const types::evse_manager::Limits& limits) return limits_stream.str(); } -std::string LimitDecimalPlaces::limit(const types::board_support::Telemetry& telemetry) { +std::string LimitDecimalPlaces::limit(const types::evse_board_support::Telemetry& telemetry) { ryml::Tree tree; ryml::NodeRef root = tree.rootref(); root |= ryml::MAP; @@ -272,8 +280,8 @@ std::string LimitDecimalPlaces::limit(const types::board_support::Telemetry& tel // limit decimal places root["temperature"] << ryml::fmt::real( - this->round_to_nearest_step(telemetry.temperature, this->config.telemetry_temperature_round_to), - this->config.telemetry_temperature_decimal_places); + this->round_to_nearest_step(telemetry.evse_temperature_C, this->config.telemetry_evse_temperature_C_round_to), + this->config.telemetry_evse_temperature_C_decimal_places); root["fan_rpm"] << ryml::fmt::real( this->round_to_nearest_step(telemetry.fan_rpm, this->config.telemetry_fan_rpm_round_to), this->config.telemetry_fan_rpm_decimal_places); @@ -284,9 +292,12 @@ std::string LimitDecimalPlaces::limit(const types::board_support::Telemetry& tel this->round_to_nearest_step(telemetry.supply_voltage_minus_12V, this->config.telemetry_supply_voltage_minus_12V_round_to), this->config.telemetry_supply_voltage_minus_12V_decimal_places); - root["rcd_current"] << ryml::fmt::real( - this->round_to_nearest_step(telemetry.rcd_current, this->config.telemetry_rcd_current_round_to), - this->config.telemetry_rcd_current_decimal_places); + if (telemetry.gun_temperature_C.has_value()) { + root["gun_temperature_C"] << ryml::fmt::real( + this->round_to_nearest_step(telemetry.gun_temperature_C.value(), + this->config.telemetry_gun_temperature_C_round_to), + this->config.telemetry_gun_temperature_C_decimal_places); + } std::stringstream telemetry_stream; telemetry_stream << ryml::as_json(tree); return telemetry_stream.str(); diff --git a/modules/API/limit_decimal_places.hpp b/modules/API/limit_decimal_places.hpp index f833f4fe2b..2971552c28 100644 --- a/modules/API/limit_decimal_places.hpp +++ b/modules/API/limit_decimal_places.hpp @@ -15,9 +15,9 @@ class LimitDecimalPlaces { public: LimitDecimalPlaces(const Conf& config) : config(config){}; std::string limit(const types::powermeter::Powermeter& powermeter); - std::string limit(const types::board_support::HardwareCapabilities& hw_capabilities); + std::string limit(const types::evse_board_support::HardwareCapabilities& hw_capabilities); std::string limit(const types::evse_manager::Limits& limits); - std::string limit(const types::board_support::Telemetry& telemetry); + std::string limit(const types::evse_board_support::Telemetry& telemetry); double round_to_nearest_step(double value, double step); private: diff --git a/modules/API/manifest.yaml b/modules/API/manifest.yaml index 956a2df5de..b9b883473c 100644 --- a/modules/API/manifest.yaml +++ b/modules/API/manifest.yaml @@ -52,7 +52,7 @@ config: default: 2 minimum: 0 hw_caps_min_current_export_decimal_places: - description: Maximum number of decimal places for minimum import current in the hardware capabilities + description: Maximum number of decimal places for minimum export current in the hardware capabilities type: integer default: 2 minimum: 0 @@ -61,13 +61,18 @@ config: type: integer default: 2 minimum: 0 + hw_caps_max_gun_temperature_C_decimal_places: + description: Maximum number of decimal places for max_gun_temperature_C in the hardware capabilities + type: integer + default: 2 + minimum: 0 limits_max_current_decimal_places: description: Maximum number of decimal places for maximum current in the limits type: integer default: 2 minimum: 0 - telemetry_temperature_decimal_places: - description: Maximum number of decimal places for temperature in telemetry + telemetry_evse_temperature_C_decimal_places: + description: Maximum number of decimal places for evse_temperature_C in telemetry type: integer default: 2 minimum: 0 @@ -86,7 +91,7 @@ config: type: integer default: 2 minimum: 0 - telemetry_rcd_current_decimal_places: + telemetry_gun_temperature_C_decimal_places: description: Maximum number of decimal places for RCD current in telemetry type: integer default: 2 @@ -135,12 +140,16 @@ config: description: Round minimum import current in hardware limits to the nearest step. Ignored if value is 0 type: number default: 0 + hw_caps_max_gun_temperature_C_round_to: + description: Round max_gun_temperature_C in hardware limits to the nearest step. Ignored if value is 0 + type: number + default: 0 limits_max_current_round_to: description: Round maximum current in limits to the nearest step. Ignored if value is 0 type: number default: 0 - telemetry_temperature_round_to: - description: Round temperature in telemetry to the nearest step. Ignored if value is 0 + telemetry_evse_temperature_C_round_to: + description: Round evse_temperature_C in telemetry to the nearest step. Ignored if value is 0 type: number default: 0 telemetry_fan_rpm_round_to: @@ -155,8 +164,8 @@ config: description: Round supply voltage -12V in telemetry to the nearest step. Ignored if value is 0 type: number default: 0 - telemetry_rcd_current_round_to: - description: Round RCD current in telemetry to the nearest step. Ignored if value is 0 + telemetry_gun_temperature_C_round_to: + description: Round gun_temperature_C in telemetry to the nearest step. Ignored if value is 0 type: number default: 0 provides: diff --git a/modules/Auth/Auth.cpp b/modules/Auth/Auth.cpp index 49f35fc712..1f22b84393 100644 --- a/modules/Auth/Auth.cpp +++ b/modules/Auth/Auth.cpp @@ -12,9 +12,9 @@ void Auth::init() { invoke_init(*p_main); invoke_init(*p_reservation); - this->auth_handler = std::make_unique(string_to_selection_algorithm(this->config.selection_algorithm), - this->config.connection_timeout, - this->config.prioritize_authorization_over_stopping_transaction); + this->auth_handler = std::make_unique( + string_to_selection_algorithm(this->config.selection_algorithm), this->config.connection_timeout, + this->config.prioritize_authorization_over_stopping_transaction, this->config.ignore_connector_faults); for (const auto& token_provider : this->r_token_provider) { token_provider->subscribe_provided_token([this](ProvidedIdToken provided_token) { diff --git a/modules/Auth/Auth.hpp b/modules/Auth/Auth.hpp index 42349ec0df..3cfe76febf 100644 --- a/modules/Auth/Auth.hpp +++ b/modules/Auth/Auth.hpp @@ -34,6 +34,7 @@ struct Conf { std::string selection_algorithm; int connection_timeout; bool prioritize_authorization_over_stopping_transaction; + bool ignore_connector_faults; }; class Auth : public Everest::ModuleBase { diff --git a/modules/Auth/include/AuthHandler.hpp b/modules/Auth/include/AuthHandler.hpp index 8ac69e5953..5747bd973c 100644 --- a/modules/Auth/include/AuthHandler.hpp +++ b/modules/Auth/include/AuthHandler.hpp @@ -48,7 +48,7 @@ class AuthHandler { public: AuthHandler(const SelectionAlgorithm& selection_algorithm, const int connection_timeout, - bool prioritize_authorization_over_stopping_transaction); + bool prioritize_authorization_over_stopping_transaction, bool ignore_connector_faults); virtual ~AuthHandler(); /** @@ -181,6 +181,7 @@ class AuthHandler { SelectionAlgorithm selection_algorithm; int connection_timeout; bool prioritize_authorization_over_stopping_transaction; + bool ignore_faults; ReservationHandler reservation_handler; std::map> connectors; diff --git a/modules/Auth/lib/AuthHandler.cpp b/modules/Auth/lib/AuthHandler.cpp index 86e49a678f..ad1b04f95a 100644 --- a/modules/Auth/lib/AuthHandler.cpp +++ b/modules/Auth/lib/AuthHandler.cpp @@ -38,10 +38,11 @@ std::string token_handling_result_to_string(const TokenHandlingResult& result) { } // namespace conversions AuthHandler::AuthHandler(const SelectionAlgorithm& selection_algorithm, const int connection_timeout, - bool prioritize_authorization_over_stopping_transaction) : + bool prioritize_authorization_over_stopping_transaction, bool ignore_faults) : selection_algorithm(selection_algorithm), connection_timeout(connection_timeout), - prioritize_authorization_over_stopping_transaction(prioritize_authorization_over_stopping_transaction){}; + prioritize_authorization_over_stopping_transaction(prioritize_authorization_over_stopping_transaction), + ignore_faults(ignore_faults){}; AuthHandler::~AuthHandler() { } @@ -463,13 +464,19 @@ void AuthHandler::handle_session_event(const int connector_id, const SessionEven this->connectors.at(connector_id)->timeout_timer.stop(); break; case SessionEventEnum::AllErrorsCleared: - this->connectors.at(connector_id)->connector.submit_event(ConnectorEvent::ERROR_CLEARED); + if (not ignore_faults) { + this->connectors.at(connector_id)->connector.submit_event(ConnectorEvent::ERROR_CLEARED); + } break; case SessionEventEnum::PermanentFault: - this->connectors.at(connector_id)->connector.submit_event(ConnectorEvent::FAULTED); + if (not ignore_faults) { + this->connectors.at(connector_id)->connector.submit_event(ConnectorEvent::FAULTED); + } break; case SessionEventEnum::Error: - this->connectors.at(connector_id)->connector.submit_event(ConnectorEvent::FAULTED); + if (not ignore_faults) { + this->connectors.at(connector_id)->connector.submit_event(ConnectorEvent::FAULTED); + } break; case SessionEventEnum::Disabled: diff --git a/modules/Auth/manifest.yaml b/modules/Auth/manifest.yaml index 36f4fc8e86..f159c02b77 100644 --- a/modules/Auth/manifest.yaml +++ b/modules/Auth/manifest.yaml @@ -25,6 +25,20 @@ config: transaction can be stopped using the given parent_id_token type: boolean default: true + ignore_connector_faults: + description: >- + Boolean value to describe the handling of faults on connectors. + + If true, faults reported on connectors are ignored, i.e. they can still be authorized. This should be disabled in + most use cases, but e.g. in free charging applications it may be useful to allow a charging session in the following case: + A connector e.g. has an overtemperature fault that at some point will clear once it is cooled down. A car is plugged in before + the error is cleared. The user would expect that the charging starts once it is cooled down. When this option is set to false, + it will not be authorized on plug in as the connector is in fault state and it will never recover until the car is replugged. + If it is set to true, the authorization happens on the faulty connector and charging will start once the fault is cleared. + + If false, faulty connectors are treated as not available and will not be authorized. This is a good setting for e.g. public chargers. + type: boolean + default: false provides: main: description: This implements the auth interface for EVerest diff --git a/modules/EvseManager/CMakeLists.txt b/modules/EvseManager/CMakeLists.txt index d891c73599..ecf54561d6 100644 --- a/modules/EvseManager/CMakeLists.txt +++ b/modules/EvseManager/CMakeLists.txt @@ -15,6 +15,8 @@ target_sources(${MODULE_NAME} SessionLog.cpp v2gMessage.cpp CarManufacturer.cpp + IECStateMachine.cpp + ErrorHandling.cpp ) target_link_libraries(${MODULE_NAME} diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index 32afd1187b..d61a5a548a 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -17,13 +17,14 @@ namespace module { -Charger::Charger(const std::unique_ptr& r_bsp, const std::string& connector_type) : - r_bsp(r_bsp), connector_type(connector_type) { - +Charger::Charger(const std::unique_ptr& bsp, const std::unique_ptr& error_handling, + const types::evse_board_support::Connector_type& connector_type) : + bsp(bsp), error_handling(error_handling), connector_type(connector_type) { connectorEnabled = true; - maxCurrent = 6.0; - maxCurrentCable = r_bsp->call_read_pp_ampacity(); + if (connector_type == types::evse_board_support::Connector_type::IEC62196Type2Socket) { + maxCurrentCable = bsp->read_pp_ampacity(); + } authorized = false; update_pwm_last_dc = 0.; @@ -35,8 +36,8 @@ Charger::Charger(const std::unique_ptr& r_bsp, const std:: currentDrawnByVehicle[1] = 0.; currentDrawnByVehicle[2] = 0.; - t_step_EF_returnState = EvseState::Faulted; - t_step_X1_returnState = EvseState::Faulted; + t_step_EF_returnState = EvseState::Idle; + t_step_X1_returnState = EvseState::Idle; matching_started = false; @@ -44,40 +45,49 @@ Charger::Charger(const std::unique_ptr& r_bsp, const std:: session_active = false; hlc_use_5percent_current_session = false; + + // Register callbacks for errors/error clearings + error_handling->signal_error.connect([this]() { + std::scoped_lock lock(stateMutex); + signalEvent(types::evse_manager::SessionEventEnum::Error); + error_prevent_charging_flag = true; + }); + + error_handling->signal_all_errors_cleared.connect([this]() { + EVLOG_info << "All errors cleared"; + signalEvent(types::evse_manager::SessionEventEnum::AllErrorsCleared); + std::scoped_lock lock(stateMutex); + error_prevent_charging_flag = false; + }); } Charger::~Charger() { - r_bsp->call_pwm_F(); + pwm_F(); } void Charger::mainThread() { - // Enable CP output - r_bsp->call_enable(true); + bsp->enable(true); // publish initial values signalMaxCurrent(getMaxCurrent()); signalState(currentState); - signalError(errorState); while (true) { - if (mainThreadHandle.shouldExit()) { break; } std::this_thread::sleep_for(MAINLOOP_UPDATE_RATE); - stateMutex.lock(); - - // update power limits - powerAvailable(); - - // Run our own state machine update (i.e. run everything that needs - // to be done on regular intervals independent from events) - runStateMachine(); - - stateMutex.unlock(); + { + std::scoped_lock lock(stateMutex); + // update power limits + powerAvailable(); + // Run our own state machine update (i.e. run everything that needs + // to be done on regular intervals independent from events) + runStateMachine(); + } } } @@ -85,8 +95,11 @@ void Charger::runStateMachine() { // run over state machine loop until currentState does not change anymore do { - bool new_in_state = last_state_detect_state_change != currentState; - if (new_in_state) { + // If a state change happened or an error recovered during a state we reinitialize the state + bool initialize_state = (last_state_detect_state_change != currentState) || + (last_error_prevent_charging_flag != error_prevent_charging_flag); + + if (initialize_state) { session_log.evse(false, fmt::format("Charger state: {}->{}", evseStateToString(last_state_detect_state_change), evseStateToString(currentState))); @@ -94,6 +107,7 @@ void Charger::runStateMachine() { last_state = last_state_detect_state_change; last_state_detect_state_change = currentState; + last_error_prevent_charging_flag = error_prevent_charging_flag; auto now = std::chrono::system_clock::now(); @@ -104,12 +118,9 @@ void Charger::runStateMachine() { return; } - if (new_in_state) { + if (initialize_state) { currentStateStarted = now; signalState(currentState); - if (currentState == EvseState::Error) { - signalError(errorState); - } } auto timeInCurrentState = @@ -117,14 +128,14 @@ void Charger::runStateMachine() { switch (currentState) { case EvseState::Disabled: - if (new_in_state) { + if (initialize_state) { signalEvent(types::evse_manager::SessionEventEnum::Disabled); pwm_F(); } break; case EvseState::Replug: - if (new_in_state) { + if (initialize_state) { signalEvent(types::evse_manager::SessionEventEnum::ReplugStarted); // start timer in case we need to if (ac_with_soc_timeout) @@ -135,7 +146,7 @@ void Charger::runStateMachine() { case EvseState::Idle: // make sure we signal availability to potential new cars - if (new_in_state) { + if (initialize_state) { bcb_toggle_reset(); iec_allow_close_contactor = false; hlc_charging_active = false; @@ -153,8 +164,8 @@ void Charger::runStateMachine() { // Explicitly do not allow to be powered on. This is important // to make sure control_pilot does not switch on relais even if // we start PWM here - if (new_in_state) { - r_bsp->call_allow_power_on(false); + if (initialize_state) { + bsp->allow_power_on(false, types::evse_board_support::Reason::PowerOff); if (last_state == EvseState::Replug) { signalEvent(types::evse_manager::SessionEventEnum::ReplugFinished); @@ -177,8 +188,7 @@ void Charger::runStateMachine() { hlc_use_5percent_current_session = true; } else { // unsupported charging mode, give up here. - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::Internal; + error_handling->raise_internal_error("Unsupported charging mode."); } if (hlc_use_5percent_current_session) { @@ -190,8 +200,9 @@ void Charger::runStateMachine() { } // Read PP value in case of AC socket - if (connector_type == IEC62196Type2Socket && maxCurrentCable == 0) { - maxCurrentCable = r_bsp->call_read_pp_ampacity(); + if (connector_type == types::evse_board_support::Connector_type::IEC62196Type2Socket && + maxCurrentCable == 0) { + maxCurrentCable = bsp->read_pp_ampacity(); // retry if the value is not yet available. Some BSPs may take some time to measure the PP. if (maxCurrentCable == 0) { break; @@ -306,9 +317,7 @@ void Charger::runStateMachine() { currentState = targetState; } else { // unsupported charging mode, give up here. - EVLOG_error << "Unsupported charging mode."; - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::Internal; + error_handling->raise_internal_error("Unsupported charging mode."); } } else if (AuthorizedPnC()) { @@ -336,16 +345,14 @@ void Charger::runStateMachine() { currentState = targetState; } else { // unsupported charging mode, give up here. - EVLOG_error << "Unsupported charging mode."; - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::Internal; + error_handling->raise_internal_error("Unsupported charging mode."); } } break; case EvseState::T_step_EF: - if (new_in_state) { + if (initialize_state) { session_log.evse(false, "Enter T_step_EF"); pwm_F(); } @@ -360,7 +367,7 @@ void Charger::runStateMachine() { break; case EvseState::T_step_X1: - if (new_in_state) { + if (initialize_state) { session_log.evse(false, "Enter T_step_X1"); pwm_off(); } @@ -376,24 +383,27 @@ void Charger::runStateMachine() { case EvseState::PrepareCharging: - if (new_in_state) { + if (initialize_state) { signalEvent(types::evse_manager::SessionEventEnum::PrepareCharging); bcb_toggle_reset(); - - if (hlc_use_5percent_current_session) { - update_pwm_now_if_changed(PWM_5_PERCENT); - } } if (charge_mode == ChargeMode::DC) { if (hlc_allow_close_contactor && iec_allow_close_contactor) { - r_bsp->call_allow_power_on(true); + bsp->allow_power_on(true, types::evse_board_support::Reason::DCCableCheck); } } + // Wait here until all errors are cleared + if (errors_prevent_charging()) { + break; + } + // make sure we are enabling PWM if (!hlc_use_5percent_current_session) { update_pwm_now_if_changed(ampereToDutyCycle(getMaxCurrent())); + } else { + update_pwm_now_if_changed(PWM_5_PERCENT); } if (charge_mode == ChargeMode::AC) { @@ -434,10 +444,15 @@ void Charger::runStateMachine() { break; case EvseState::Charging: - if (new_in_state) { + if (initialize_state) { hlc_charging_terminate_pause = HlcTerminatePause::Unknown; } + // Wait here until all errors are cleared + if (errors_prevent_charging()) { + break; + } + if (charge_mode == ChargeMode::DC) { // FIXME: handle DC pause/resume here // FIXME: handle DC no power available from Energy management @@ -446,15 +461,16 @@ void Charger::runStateMachine() { if (!powerAvailable()) { pauseChargingWaitForPower(); + // FIXME new in state breaks break; } - if (new_in_state) { + if (initialize_state) { if (last_state != EvseState::PrepareCharging) { signalEvent(types::evse_manager::SessionEventEnum::ChargingResumed); } - r_bsp->call_allow_power_on(true); + bsp->allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging); // make sure we are enabling PWM if (hlc_use_5percent_current_session) update_pwm_now_if_changed(PWM_5_PERCENT); @@ -486,9 +502,9 @@ void Charger::runStateMachine() { if (hlc_charging_active) { // This is for HLC charging (both AC and DC) - if (new_in_state) { + if (initialize_state) { bcb_toggle_reset(); - r_bsp->call_allow_power_on(false); + bsp->allow_power_on(false, types::evse_board_support::Reason::PowerOff); if (charge_mode == ChargeMode::DC) { signal_DC_supply_off(); } @@ -524,18 +540,14 @@ void Charger::runStateMachine() { break; } - if (new_in_state) { + if (initialize_state) { signalEvent(types::evse_manager::SessionEventEnum::ChargingPausedEV); - r_bsp->call_allow_power_on(true); - // make sure we are enabling PWM - if (hlc_use_5percent_current_session) { - update_pwm_now(PWM_5_PERCENT); - } else { - update_pwm_now(ampereToDutyCycle(getMaxCurrent())); - } + // bsp->allow_power_on(true, types::evse_board_support::Reason::FullPowerCharging); + // make sure we are enabling PWM + // update_pwm_now(ampereToDutyCycle(getMaxCurrent())); } else { // update PWM if it has changed and 5 seconds have passed since last update - if (!hlc_use_5percent_current_session) { + if (!errors_prevent_charging()) { update_pwm_max_every_5seconds(ampereToDutyCycle(getMaxCurrent())); } } @@ -543,7 +555,7 @@ void Charger::runStateMachine() { break; case EvseState::ChargingPausedEVSE: - if (new_in_state) { + if (initialize_state) { signalEvent(types::evse_manager::SessionEventEnum::ChargingPausedEVSE); if (hlc_charging_active) { // currentState = EvseState::StoppingCharging; @@ -558,7 +570,7 @@ void Charger::runStateMachine() { break; case EvseState::WaitingForEnergy: - if (new_in_state) { + if (initialize_state) { signalEvent(types::evse_manager::SessionEventEnum::WaitingForEnergy); if (!hlc_use_5percent_current_session) pwm_off(); @@ -566,7 +578,7 @@ void Charger::runStateMachine() { break; case EvseState::StoppingCharging: - if (new_in_state) { + if (initialize_state) { bcb_toggle_reset(); if (transactionActive() || sessionActive()) { signalEvent(types::evse_manager::SessionEventEnum::StoppingCharging); @@ -605,7 +617,7 @@ void Charger::runStateMachine() { case EvseState::Finished: - if (new_in_state) { + if (initialize_state) { // Transaction may already be stopped when it was cancelled earlier. // In that case, do not sent a second transactionFinished event. if (transactionActive()) @@ -622,57 +634,30 @@ void Charger::runStateMachine() { restart(); break; - - case EvseState::Error: - if (new_in_state) { - signalEvent(types::evse_manager::SessionEventEnum::Error); - pwm_off(); - } - // Do not set F here, as F cannot detect unplugging of car! - break; - - case EvseState::Faulted: - if (new_in_state) { - signalEvent(types::evse_manager::SessionEventEnum::PermanentFault); - pwm_F(); - } - break; } } while (last_state_detect_state_change != currentState); } -void Charger::processEvent(types::board_support::Event cp_event) { - +void Charger::processEvent(CPEvent cp_event) { switch (cp_event) { - case ControlPilotEvent::CarPluggedIn: - case ControlPilotEvent::CarRequestedPower: - case ControlPilotEvent::CarRequestedStopPower: - case ControlPilotEvent::CarUnplugged: - case ControlPilotEvent::ErrorDF: - case ControlPilotEvent::ErrorE: - case ControlPilotEvent::BCDtoEF: - case ControlPilotEvent::EFtoBCD: - session_log.car(false, fmt::format("Event {}", types::board_support::event_to_string(cp_event))); + case CPEvent::CarPluggedIn: + case CPEvent::CarRequestedPower: + case CPEvent::CarRequestedStopPower: + case CPEvent::CarUnplugged: + case CPEvent::BCDtoEF: + case CPEvent::EFtoBCD: + session_log.car(false, fmt::format("Event {}", cpevent_to_string(cp_event))); break; - case ControlPilotEvent::ErrorOverCurrent: - case ControlPilotEvent::ErrorRCD: - case ControlPilotEvent::ErrorRelais: - case ControlPilotEvent::ErrorVentilationNotAvailable: - case ControlPilotEvent::PermanentFault: - case ControlPilotEvent::PowerOff: - case ControlPilotEvent::PowerOn: - case ControlPilotEvent::EvseReplugStarted: - case ControlPilotEvent::EvseReplugFinished: - default: - session_log.evse(false, fmt::format("Event {}", types::board_support::event_to_string(cp_event))); + session_log.evse(false, fmt::format("Event {}", cpevent_to_string(cp_event))); break; } std::lock_guard lock(stateMutex); - if (cp_event == ControlPilotEvent::PowerOn) { + + if (cp_event == CPEvent::PowerOn) { contactors_closed = true; - } else if (cp_event == ControlPilotEvent::PowerOff) { + } else if (cp_event == CPEvent::PowerOff) { contactors_closed = false; } @@ -689,43 +674,43 @@ void Charger::processEvent(types::board_support::Event cp_event) { runStateMachine(); } -void Charger::processCPEventsState(ControlPilotEvent cp_event) { +void Charger::processCPEventsState(CPEvent cp_event) { switch (currentState) { case EvseState::Idle: - if (cp_event == ControlPilotEvent::CarPluggedIn) { + if (cp_event == CPEvent::CarPluggedIn) { currentState = EvseState::WaitingForAuthentication; } break; case EvseState::WaitingForAuthentication: - if (cp_event == ControlPilotEvent::CarRequestedPower) { + if (cp_event == CPEvent::CarRequestedPower) { session_log.car(false, "B->C transition before PWM is enabled at this stage violates IEC61851-1"); iec_allow_close_contactor = true; - } else if (cp_event == ControlPilotEvent::CarRequestedStopPower) { + } else if (cp_event == CPEvent::CarRequestedStopPower) { session_log.car(false, "C->B transition at this stage violates IEC61851-1"); iec_allow_close_contactor = false; } break; case EvseState::PrepareCharging: - if (cp_event == ControlPilotEvent::CarRequestedPower) { + if (cp_event == CPEvent::CarRequestedPower) { iec_allow_close_contactor = true; - } else if (cp_event == ControlPilotEvent::CarRequestedStopPower) { + } else if (cp_event == CPEvent::CarRequestedStopPower) { iec_allow_close_contactor = false; // currentState = EvseState::StoppingCharging; } break; case EvseState::Charging: - if (cp_event == ControlPilotEvent::CarRequestedStopPower) { + if (cp_event == CPEvent::CarRequestedStopPower) { iec_allow_close_contactor = false; currentState = EvseState::ChargingPausedEV; } break; case EvseState::ChargingPausedEV: - if (cp_event == ControlPilotEvent::CarRequestedPower) { + if (cp_event == CPEvent::CarRequestedPower) { iec_allow_close_contactor = true; // For BASIC charging we can simply switch back to Charging if (charge_mode == ChargeMode::AC && !hlc_charging_active) { @@ -735,7 +720,7 @@ void Charger::processCPEventsState(ControlPilotEvent cp_event) { } } - if (cp_event == ControlPilotEvent::CarRequestedStopPower && !pwm_running && hlc_charging_active) { + if (cp_event == CPEvent::CarRequestedStopPower && !pwm_running && hlc_charging_active) { bcb_toggle_detect_stop_pulse(); } break; @@ -743,9 +728,9 @@ void Charger::processCPEventsState(ControlPilotEvent cp_event) { case EvseState::StoppingCharging: // Allow session restart from EV after SessionStop.terminate with BCB toggle if (hlc_charging_active && !pwm_running) { - if (cp_event == ControlPilotEvent::CarRequestedPower) { + if (cp_event == CPEvent::CarRequestedPower) { bcb_toggle_detect_start_pulse(); - } else if (cp_event == ControlPilotEvent::CarRequestedStopPower) { + } else if (cp_event == CPEvent::CarRequestedStopPower) { bcb_toggle_detect_stop_pulse(); } } @@ -756,88 +741,21 @@ void Charger::processCPEventsState(ControlPilotEvent cp_event) { } } -void Charger::processCPEventsIndependent(ControlPilotEvent cp_event) { - +void Charger::processCPEventsIndependent(CPEvent cp_event) { switch (cp_event) { - case ControlPilotEvent::EvseReplugStarted: + case CPEvent::EvseReplugStarted: currentState = EvseState::Replug; break; - case ControlPilotEvent::EvseReplugFinished: + case CPEvent::EvseReplugFinished: currentState = EvseState::WaitingForAuthentication; break; - case ControlPilotEvent::CarUnplugged: - if (currentState == EvseState::Error) - signalEvent(types::evse_manager::SessionEventEnum::AllErrorsCleared); + case CPEvent::CarUnplugged: if (!hlc_charging_active) { currentState = EvseState::StoppingCharging; } else { currentState = EvseState::Finished; } break; - case ControlPilotEvent::ErrorE: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::Car; - break; - case ControlPilotEvent::ErrorDF: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::CarDiodeFault; - break; - case ControlPilotEvent::ErrorRelais: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::Relais; - break; - case ControlPilotEvent::ErrorRCD: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::RCD; - break; - case ControlPilotEvent::ErrorVentilationNotAvailable: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::VentilationNotAvailable; - break; - case ControlPilotEvent::ErrorOverCurrent: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::OverCurrent; - break; - case ControlPilotEvent::ErrorRCD_DC: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::RCDDC; - break; - case ControlPilotEvent::ErrorOverVoltage: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::OverVoltage; - break; - case ControlPilotEvent::ErrorUnderVoltage: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::UnderVoltage; - break; - case ControlPilotEvent::ErrorMotorLock: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::MotorLock; - break; - case ControlPilotEvent::ErrorOverTemperature: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::OverTemperature; - break; - case ControlPilotEvent::ErrorBrownOut: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::BrownOut; - break; - case ControlPilotEvent::ErrorCablePP: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::CablePP; - break; - case ControlPilotEvent::ErrorEnergyManagement: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::EnergyManagement; - break; - case ControlPilotEvent::ErrorNeutralPEN: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::NeutralPEN; - break; - case ControlPilotEvent::ErrorCpDriver: - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::CpDriver; - break; default: break; } @@ -857,7 +775,7 @@ void Charger::update_pwm_now(float dc) { auto start = date::utc_clock::now(); update_pwm_last_dc = dc; pwm_running = true; - r_bsp->call_pwm_on(dc); + bsp->set_pwm(dc); session_log.evse( false, @@ -876,14 +794,14 @@ void Charger::pwm_off() { session_log.evse(false, "Set PWM Off"); pwm_running = false; update_pwm_last_dc = 1.; - r_bsp->call_pwm_off(); + bsp->set_pwm_off(); } void Charger::pwm_F() { session_log.evse(false, "Set PWM F"); pwm_running = false; update_pwm_last_dc = 0.; - r_bsp->call_pwm_F(); + bsp->set_pwm_F(); } void Charger::run() { @@ -923,19 +841,7 @@ bool Charger::setMaxCurrent(float c, std::chrono::time_point va if (validUntil > date::utc_clock::now()) { maxCurrent = c; maxCurrentValidUntil = validUntil; - signalMaxCurrent(c); - return true; - } - } - return false; -} - -bool Charger::setMaxCurrent(float c) { - if (c >= 0.0 && c <= CHARGER_ABSOLUTE_MAX_CURRENT) { - std::lock_guard lock(configMutex); - // is it still valid? - if (maxCurrentValidUntil > date::utc_clock::now()) { - maxCurrent = c; + bsp->set_overcurrent_limit(c); signalMaxCurrent(c); return true; } @@ -999,7 +905,7 @@ bool Charger::evseReplug() { // If BSP never executes the replug, we also never change state and nothing happens. // After replugging finishes, BSP will emit EvseReplugFinished event and we will go back to WaitingForAuth EVLOG_info << fmt::format("Calling evse_replug({})...", t_replug_ms); - r_bsp->call_evse_replug(t_replug_ms); + bsp->evse_replug(t_replug_ms); return true; } @@ -1085,17 +991,18 @@ types::evse_manager::StartSessionReason Charger::getSessionStartedReason() { } bool Charger::switchThreePhasesWhileCharging(bool n) { - r_bsp->call_switch_three_phases_while_charging(n); + bsp->switch_three_phases_while_charging(n); return false; // FIXME: implement real return value when protobuf has sync calls } -void Charger::setup(bool three_phases, bool has_ventilation, const std::string& country_code, bool rcd_enabled, +void Charger::setup(bool three_phases, bool has_ventilation, const std::string& country_code, const ChargeMode _charge_mode, bool _ac_hlc_enabled, bool _ac_hlc_use_5percent, bool _ac_enforce_hlc, bool _ac_with_soc_timeout, float _soft_over_current_tolerance_percent, float _soft_over_current_measurement_noise_A) { std::lock_guard lock(configMutex); // set up board support package - r_bsp->call_setup(three_phases, has_ventilation, country_code, rcd_enabled); + bsp->setup(three_phases, has_ventilation, country_code); + // cache our config variables charge_mode = _charge_mode; ac_hlc_enabled_current_session = ac_hlc_enabled = _ac_hlc_enabled; @@ -1186,12 +1093,10 @@ bool Charger::AuthorizedPnC() { } bool Charger::DeAuthorize() { - if (sessionActive()) { auto s = getCurrentState(); - if (s == EvseState::Disabled || s == EvseState::Idle || s == EvseState::WaitingForAuthentication || - s == EvseState::Error || s == EvseState::Faulted) { + if (s == EvseState::Disabled || s == EvseState::Idle || s == EvseState::WaitingForAuthentication) { // We can safely remove auth as it is not in use right now std::lock_guard lock(configMutex); @@ -1207,11 +1112,6 @@ bool Charger::DeAuthorize() { return false; } -types::evse_manager::ErrorEnum Charger::getErrorState() { - std::lock_guard lock(stateMutex); - return errorState; -} - bool Charger::disable(int connector_id) { std::lock_guard lock(stateMutex); if (connector_id != 0) { @@ -1240,8 +1140,7 @@ bool Charger::enable(int connector_id) { void Charger::set_faulted() { std::lock_guard lock(stateMutex); - currentState = EvseState::Faulted; - signalEvent(types::evse_manager::SessionEventEnum::PermanentFault); + error_prevent_charging_flag = true; } bool Charger::restart() { @@ -1280,12 +1179,6 @@ std::string Charger::evseStateToString(EvseState s) { case EvseState::Finished: return ("Finished"); break; - case EvseState::Error: - return ("Error"); - break; - case EvseState::Faulted: - return ("Faulted"); - break; case EvseState::T_step_EF: return ("T_step_EF"); break; @@ -1309,17 +1202,14 @@ float Charger::getMaxCurrent() { std::lock_guard lock(configMutex); auto maxc = maxCurrent; - if (connector_type == IEC62196Type2Socket && maxCurrentCable < maxc && currentState != EvseState::Idle) { + if (connector_type == types::evse_board_support::Connector_type::IEC62196Type2Socket && maxCurrentCable < maxc && + currentState != EvseState::Idle) { maxc = maxCurrentCable; } return maxc; } -bool Charger::forceUnlock() { - return r_bsp->call_force_unlock(); -} - void Charger::setCurrentDrawnByVehicle(float l1, float l2, float l3) { std::lock_guard lock(configMutex); currentDrawnByVehicle[0] = l1; @@ -1349,11 +1239,11 @@ void Charger::checkSoftOverCurrent() { auto timeSinceOverCurrentStarted = std::chrono::duration_cast(now - lastOverCurrentEvent).count(); if (overCurrent && timeSinceOverCurrentStarted >= softOverCurrentTimeout) { - session_log.evse(false, fmt::format("Soft overcurrent event (L1:{}, L2:{}, L3:{}, limit {}) triggered", - currentDrawnByVehicle[0], currentDrawnByVehicle[1], - currentDrawnByVehicle[2], limit)); - currentState = EvseState::Error; - errorState = types::evse_manager::ErrorEnum::OverCurrent; + auto errstr = fmt::format("Soft overcurrent event (L1:{}, L2:{}, L3:{}, limit {}) triggered", + currentDrawnByVehicle[0], currentDrawnByVehicle[1], currentDrawnByVehicle[2], limit); + session_log.evse(false, errstr); + // raise the OC error + error_handling->raise_overcurrent_error(errstr); } } @@ -1485,8 +1375,7 @@ void Charger::set_hlc_allow_close_contactor(bool on) { void Charger::set_hlc_error(types::evse_manager::ErrorEnum e) { std::lock_guard lock(stateMutex); - currentState = EvseState::Error; - errorState = e; + error_prevent_charging_flag = true; } // this resets the BCB sequence (which may contain 1-3 toggle pulses) @@ -1509,6 +1398,9 @@ void Charger::bcb_toggle_detect_start_pulse() { // call this on C->B transitions void Charger::bcb_toggle_detect_stop_pulse() { + if (!hlc_bcb_sequence_started) + return; + // This is probably and end of BCB toggle, verify it was not too long or too short auto pulse_length = std::chrono::steady_clock::now() - hlc_ev_pause_start_of_bcb; @@ -1541,4 +1433,34 @@ bool Charger::bcb_toggle_detected() { return false; } +void Charger::set_rcd_error() { + std::lock_guard lock(stateMutex); + error_prevent_charging_flag = true; +} + +bool Charger::errors_prevent_charging() { + if (error_prevent_charging_flag) { + graceful_stop_charging(); + return true; + } + return false; +} + +void Charger::graceful_stop_charging() { + + if (pwm_running) { + pwm_off(); + } + + // Shutdown DC power supplies + if (charge_mode == ChargeMode::DC) { + signal_DC_supply_off(); + } + + // open contactors + if (contactors_closed) { + bsp->allow_power_on(false, types::evse_board_support::Reason::PowerOff); + } +} + } // namespace module diff --git a/modules/EvseManager/Charger.hpp b/modules/EvseManager/Charger.hpp index 86231ba21d..6175d180eb 100644 --- a/modules/EvseManager/Charger.hpp +++ b/modules/EvseManager/Charger.hpp @@ -30,23 +30,24 @@ #include #include #include -#include #include #include #include #include #include +#include "ErrorHandling.hpp" +#include "IECStateMachine.hpp" + namespace module { const std::string IEC62196Type2Cable = "IEC62196Type2Cable"; const std::string IEC62196Type2Socket = "IEC62196Type2Socket"; -using ControlPilotEvent = types::board_support::Event; - class Charger { public: - Charger(const std::unique_ptr& r_bsp, const std::string& connector_type); + Charger(const std::unique_ptr& bsp, const std::unique_ptr& error_handling, + const types::evse_board_support::Connector_type& connector_type); ~Charger(); // Public interface to configure Charger @@ -57,8 +58,7 @@ class Charger { // external input to charger: update max_current and new validUntil bool setMaxCurrent(float ampere, std::chrono::time_point validUntil); - // update only max_current but keep the current validUntil - bool setMaxCurrent(float ampere); + float getMaxCurrent(); sigslot::signal signalMaxCurrent; @@ -67,15 +67,15 @@ class Charger { DC }; - void setup(bool three_phases, bool has_ventilation, const std::string& country_code, bool rcd_enabled, - const ChargeMode charge_mode, bool ac_hlc_enabled, bool ac_hlc_use_5percent, bool ac_enforce_hlc, - bool ac_with_soc_timeout, float soft_over_current_tolerance_percent, - float soft_over_current_measurement_noise_A); + void setup(bool three_phases, bool has_ventilation, const std::string& country_code, const ChargeMode charge_mode, + bool ac_hlc_enabled, bool ac_hlc_use_5percent, bool ac_enforce_hlc, bool ac_with_soc_timeout, + float soft_over_current_tolerance_percent, float soft_over_current_measurement_noise_A); bool enable(int connector_id); bool disable(int connector_id); void set_faulted(); void set_hlc_error(types::evse_manager::ErrorEnum e); + void set_rcd_error(); // switch to next charging session after Finished bool restart(); @@ -131,10 +131,7 @@ class Charger { sigslot::signal<> signal_hlc_stop_charging; - // Request more details about the error that happend - types::evse_manager::ErrorEnum getErrorState(); - - void processEvent(types::board_support::Event event); + void processEvent(CPEvent event); void run(); @@ -161,8 +158,6 @@ class Charger { ChargingPausedEVSE, StoppingCharging, Finished, - Error, - Faulted, T_step_EF, T_step_X1, Replug @@ -178,8 +173,8 @@ class Charger { EvseState getCurrentState(); sigslot::signal signalState; - sigslot::signal signalError; - // /Deprecated + // sigslot::signal signalError; + // /Deprecated void inform_new_evse_max_hlc_limits(const types::iso15118_charger::DC_EVSEMaximumLimits& l); types::iso15118_charger::DC_EVSEMaximumLimits get_evse_max_hlc_limits(); @@ -191,6 +186,8 @@ class Charger { void set_hlc_charging_active(); void set_hlc_allow_close_contactor(bool on); + bool errors_prevent_charging(); + private: void bcb_toggle_reset(); void bcb_toggle_detect_start_pulse(); @@ -200,14 +197,15 @@ class Charger { // main Charger thread Everest::Thread mainThreadHandle; - const std::unique_ptr& r_bsp; - const std::string& connector_type; + const std::unique_ptr& bsp; + const std::unique_ptr& error_handling; + const types::evse_board_support::Connector_type& connector_type; void mainThread(); float maxCurrent; std::chrono::time_point maxCurrentValidUntil; - float maxCurrentCable; + float maxCurrentCable{0.}; bool powerAvailable(); @@ -236,11 +234,16 @@ class Charger { EvseState currentState; EvseState last_state; EvseState last_state_detect_state_change; + types::evse_manager::ErrorEnum errorState{types::evse_manager::ErrorEnum::Internal}; std::chrono::system_clock::time_point currentStateStarted; bool connectorEnabled; + bool error_prevent_charging_flag{false}; + bool last_error_prevent_charging_flag{false}; + void graceful_stop_charging(); + float ampereToDutyCycle(float ampere); void checkSoftOverCurrent(); @@ -265,10 +268,8 @@ class Charger { bool matching_started; - ControlPilotEvent string_to_control_pilot_event(const types::board_support::Event& event); - - void processCPEventsIndependent(ControlPilotEvent cp_event); - void processCPEventsState(ControlPilotEvent cp_event); + void processCPEventsIndependent(CPEvent cp_event); + void processCPEventsState(CPEvent cp_event); void runStateMachine(); bool authorized; diff --git a/modules/EvseManager/ErrorHandling.cpp b/modules/EvseManager/ErrorHandling.cpp new file mode 100644 index 0000000000..b84236dc2a --- /dev/null +++ b/modules/EvseManager/ErrorHandling.cpp @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Pionix GmbH and Contributors to EVerest + +#include "ErrorHandling.hpp" + +namespace module { + +ErrorHandling::ErrorHandling(const std::unique_ptr& _r_bsp, + const std::vector>& _r_hlc) : + r_bsp(_r_bsp), r_hlc(_r_hlc) { + + if (r_hlc.size() > 0) { + hlc = true; + } + + // Subscribe to bsp driver to receive BspEvents from the hardware + r_bsp->subscribe_all_errors( + [this](const Everest::error::Error& error) { + if (modify_error_bsp(error, true)) { + // signal to charger a new error has been set + signal_error(); + }; + }, + [this](const Everest::error::Error& error) { + modify_error_bsp(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) { + // raise externally + // FIXME raise_evse_manager_MREC4OverCurrentFailure(description); + + if (modify_error_evse_manager("evse_manager/MREC4OverCurrentFailure", true)) { + // signal to charger a new error has been set + signal_error(); + }; +} + +void ErrorHandling::clear_overcurrent_error() { + // clear externally + // FIXME clear_evse_manager_MREC4OverCurrentFailure(); + + modify_error_evse_manager("evse_manager/MREC4OverCurrentFailure", 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_internal_error(const std::string& description) { + // raise externally + // FIXME raise_evse_manager_MREC4OverCurrentFailure(description); + + if (modify_error_evse_manager("evse_manager/Internal", true)) { + // signal to charger a new error has been set + signal_error(); + }; +} + +void ErrorHandling::clear_internal_error() { + // clear externally + // FIXME clear_evse_manager_MREC4OverCurrentFailure(); + + modify_error_evse_manager("evse_manager/Internal", 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) { + 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 == "evse_board_support/DiodeFault") { + active_errors.bsp.DiodeFault = active; + } else if (error_type == "evse_board_support/VentilationNotAvailable") { + active_errors.bsp.VentilationNotAvailable = active; + } else if (error_type == "evse_board_support/BrownOut") { + active_errors.bsp.BrownOut = active; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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 + } + return true; // Error stops charging +}; + +bool ErrorHandling::modify_error_evse_manager(const std::string& error_type, bool active) { + if (error_type == "evse_manager/MREC4OverCurrentFailure") { + active_errors.bsp.MREC4OverCurrentFailure = active; + 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 + } + return true; // Error stops charging + + // missing: + // r_hlc[0]->call_set_RCD_Error(false); +}; + +} // namespace module diff --git a/modules/EvseManager/ErrorHandling.hpp b/modules/EvseManager/ErrorHandling.hpp new file mode 100644 index 0000000000..6080779392 --- /dev/null +++ b/modules/EvseManager/ErrorHandling.hpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest + +/* + The ErrorHandling class handles all errors from BSP/ConnectorLock etc + and classifies them for charging: + + If we can continue charging despite the error, we treat them as warnings and do not track them here. If e.g. the BSP + raises a high temperature error it needs to limit the output power by itself. + + The decision whether an error requires a replug or not to clear depends on the reporting module. It will need to clear + them at the appropriate time. +*/ + +#ifndef SRC_ERROR_HANDLING_H_ +#define SRC_ERROR_HANDLING_H_ + +#include "ld-ev.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +#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}; + } bsp; + + struct evse_manager_errors { + std::atomic_bool MREC4OverCurrentFailure{false}; + std::atomic_bool Internal{false}; + } evse_manager; + + 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); + } +}; + +class ErrorHandling { +public: + // We need the r_bsp reference to be able to talk to the bsp driver module + explicit ErrorHandling(const std::unique_ptr& r_bsp, + const std::vector>& r_hlc); + + // Signal for internal events type + sigslot::signal<> signal_error; + sigslot::signal<> signal_all_errors_cleared; + + void raise_overcurrent_error(const std::string& description); + void clear_overcurrent_error(); + + void raise_internal_error(const std::string& description); + void clear_internal_error(); + +private: + const std::unique_ptr& r_bsp; + const std::vector>& r_hlc; + + bool modify_error_bsp(const Everest::error::Error& error, bool active); + bool modify_error_evse_manager(const std::string& error_type, bool active); + bool hlc{false}; + + ActiveErrors active_errors; +}; + +} // namespace module + +#endif // SRC_BSP_STATE_MACHINE_H_ diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index d65428d23f..1f38c6cb33 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -1,10 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest #include "EvseManager.hpp" -#include "SessionLog.hpp" -#include "Timeout.hpp" + #include #include + +#include "IECStateMachine.hpp" +#include "SessionLog.hpp" +#include "Timeout.hpp" using namespace std::literals::chrono_literals; namespace module { @@ -67,6 +70,26 @@ void EvseManager::init() { if (config.charge_mode == "DC" && r_imd.empty()) { EVLOG_warning << "DC mode without isolation monitoring configured, please check your national regulations."; } + if (r_ac_rcd.size() > 0) { + r_ac_rcd[0]->subscribe_fault_ac([this] { + session_log.evse(true, "RCD: AC Fault"); + // Inform charger + charger->set_rcd_error(); + // Inform HLC + if (hlc_enabled) { + r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_RCD); + } + }); + r_ac_rcd[0]->subscribe_fault_dc([this] { + session_log.evse(true, "RCD: DC Fault"); + // Inform charger + charger->set_rcd_error(); + // Inform HLC + if (hlc_enabled) { + r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_RCD); + } + }); + } reserved = false; reservation_id = 0; @@ -79,7 +102,12 @@ void EvseManager::init() { } void EvseManager::ready() { - charger = std::unique_ptr(new Charger(r_bsp, config.connector_type)); + bsp = std::unique_ptr(new IECStateMachine(r_bsp)); + error_handling = std::unique_ptr(new ErrorHandling(r_bsp, r_hlc)); + + hw_capabilities = r_bsp->call_get_hw_capabilities(); + + charger = std::unique_ptr(new Charger(bsp, error_handling, hw_capabilities.connector_type)); if (get_hlc_enabled()) { @@ -485,8 +513,8 @@ void EvseManager::ready() { // switch to DC mode for first session for AC with SoC if (config.ac_with_soc) { - r_bsp->subscribe_event([this](const types::board_support::Event& event) { - if (event == types::board_support::Event::CarUnplugged) { + bsp->signal_event.connect([this](const CPEvent event) { + if (event == CPEvent::CarUnplugged) { // configure for DC again for next session. Will reset to AC when SoC is received switch_DC_mode(); } @@ -505,8 +533,6 @@ void EvseManager::ready() { }); } - hw_capabilities = r_bsp->call_get_hw_capabilities(); - // Maybe override with user setting for this EVSE if (config.max_current_import_A < hw_capabilities.max_current_A_import) { hw_capabilities.max_current_A_import = config.max_current_import_A; @@ -535,15 +561,15 @@ void EvseManager::ready() { hw_capabilities.max_phase_count_import); } - r_bsp->subscribe_event([this](const types::board_support::Event event) { + bsp->signal_event.connect([this](const CPEvent event) { // Forward events from BSP to SLAC module before we process the events in the charger if (slac_enabled) { - if (event == types::board_support::Event::EFtoBCD) { + if (event == CPEvent::EFtoBCD) { // this means entering BCD from E|F r_slac[0]->call_enter_bcd(); - } else if (event == types::board_support::Event::BCDtoEF) { + } else if (event == CPEvent::BCDtoEF) { r_slac[0]->call_leave_bcd(); - } else if (event == types::board_support::Event::CarPluggedIn) { + } else if (event == CPEvent::CarPluggedIn) { // CC: right now we dont support energy saving mode, so no need to reset slac here. // It is more important to start slac as early as possible to avoid unneccesary retries // e.g. by Tesla cars which send the first SLAC_PARM_REQ directly after plugin. @@ -553,7 +579,7 @@ void EvseManager::ready() { // This is entering BCD from state A car_manufacturer = types::evse_manager::CarManufacturer::Unknown; r_slac[0]->call_enter_bcd(); - } else if (event == types::board_support::Event::CarUnplugged) { + } else if (event == CPEvent::CarUnplugged) { r_slac[0]->call_leave_bcd(); r_slac[0]->call_reset(false); } @@ -564,7 +590,7 @@ void EvseManager::ready() { // Forward some events to HLC if (get_hlc_enabled()) { // Reset HLC auth waiting flags on new session - if (event == types::board_support::Event::CarPluggedIn) { + if (event == CPEvent::CarPluggedIn) { r_hlc[0]->call_reset_error(); r_hlc[0]->call_ac_contactor_closed(false); r_hlc[0]->call_stop_charging(false); @@ -577,32 +603,12 @@ void EvseManager::ready() { } } - if (event == types::board_support::Event::ErrorRelais) { - session_log.evse(false, "Error Relais"); - r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Contactor); - } - - if (event == types::board_support::Event::ErrorRCD) { - session_log.evse(false, "Error RCD"); - r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_RCD); - } - - if (event == types::board_support::Event::PermanentFault) { - session_log.evse(false, "Error Permanent Fault"); - r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_Malfunction); - } - - if (event == types::board_support::Event::ErrorOverCurrent) { - session_log.evse(false, "Error Over Current"); - r_hlc[0]->call_send_error(types::iso15118_charger::EvseError::Error_EmergencyShutdown); - } - - if (event == types::board_support::Event::PowerOn) { + if (event == CPEvent::PowerOn) { contactor_open = false; r_hlc[0]->call_ac_contactor_closed(true); } - if (event == types::board_support::Event::PowerOff) { + if (event == CPEvent::PowerOff) { contactor_open = true; latest_target_voltage = 0; latest_target_current = 0; @@ -617,7 +623,7 @@ void EvseManager::ready() { }); }); - r_bsp->subscribe_nr_of_phases_available([this](int n) { signalNrOfPhasesAvailable(n); }); + r_bsp->subscribe_ac_nr_of_phases_available([this](int n) { signalNrOfPhasesAvailable(n); }); if (r_powermeter_billing().size() > 0) { r_powermeter_billing()[0]->subscribe_powermeter([this](types::powermeter::Powermeter p) { @@ -662,7 +668,6 @@ void EvseManager::ready() { } if (slac_enabled) { - // Reset once on startup and disable modem r_slac[0]->call_reset(false); @@ -734,7 +739,7 @@ void EvseManager::ready() { if (config.ac_with_soc) { setup_fake_DC_mode(); } else { - charger->setup(local_three_phases, config.has_ventilation, config.country_code, config.rcd_enabled, + charger->setup(local_three_phases, config.has_ventilation, config.country_code, (config.charge_mode == "DC" ? Charger::ChargeMode::DC : Charger::ChargeMode::AC), hlc_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, false, config.soft_over_current_tolerance_percent, config.soft_over_current_measurement_noise_A); @@ -867,7 +872,7 @@ types::powermeter::Powermeter EvseManager::get_latest_powermeter_data_billing() return latest_powermeter_data_billing; } -types::board_support::HardwareCapabilities EvseManager::get_hw_capabilities() { +types::evse_board_support::HardwareCapabilities EvseManager::get_hw_capabilities() { return hw_capabilities; } @@ -889,8 +894,8 @@ void EvseManager::switch_AC_mode() { // This sets up a fake DC mode that is just supposed to work until we get the SoC. // It is only used for AC<>DC<>AC<>DC mode to get AC charging with SoC. void EvseManager::setup_fake_DC_mode() { - charger->setup(local_three_phases, config.has_ventilation, config.country_code, config.rcd_enabled, - Charger::ChargeMode::DC, hlc_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, false, + charger->setup(local_three_phases, config.has_ventilation, config.country_code, Charger::ChargeMode::DC, + hlc_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, false, config.soft_over_current_tolerance_percent, config.soft_over_current_measurement_noise_A); types::iso15118_charger::EVSEID evseid = {config.evse_id, config.evse_id_din}; @@ -933,8 +938,8 @@ void EvseManager::setup_fake_DC_mode() { } void EvseManager::setup_AC_mode() { - charger->setup(local_three_phases, config.has_ventilation, config.country_code, config.rcd_enabled, - Charger::ChargeMode::AC, hlc_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, true, + charger->setup(local_three_phases, config.has_ventilation, config.country_code, Charger::ChargeMode::AC, + hlc_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, true, config.soft_over_current_tolerance_percent, config.soft_over_current_measurement_noise_A); types::iso15118_charger::EVSEID evseid = {config.evse_id, config.evse_id_din}; @@ -1072,7 +1077,7 @@ bool EvseManager::reserve(int32_t id) { } // is the evse faulted? - if (charger->getCurrentState() == Charger::EvseState::Faulted) { + if (charger->errors_prevent_charging()) { return false; } @@ -1193,7 +1198,8 @@ void EvseManager::cable_check() { session_log.car(true, "DC HLC Close contactor (in CableCheck)"); charger->set_hlc_allow_close_contactor(true); - Timeout timeout(CABLECHECK_CONTACTORS_CLOSE_TIMEOUT); + Timeout timeout; + timeout.start(CABLECHECK_CONTACTORS_CLOSE_TIMEOUT); while (!timeout.reached()) { if (!contactor_open) @@ -1398,14 +1404,17 @@ bool EvseManager::powersupply_DC_set(double _voltage, double _current) { } void EvseManager::powersupply_DC_off() { - session_log.evse(false, "DC power supply OFF"); - r_powersupply_DC[0]->call_setMode(types::power_supply_DC::Mode::Off); - powersupply_dc_is_on = false; + if (powersupply_dc_is_on) { + session_log.evse(false, "DC power supply OFF"); + r_powersupply_DC[0]->call_setMode(types::power_supply_DC::Mode::Off); + powersupply_dc_is_on = false; + } } bool EvseManager::wait_powersupply_DC_voltage_reached(double target_voltage) { // wait until the voltage has rised to the target value - Timeout timeout(30s); + Timeout timeout; + timeout.start(30s); bool voltage_ok = false; while (!timeout.reached()) { types::power_supply_DC::VoltageCurrent m; @@ -1425,7 +1434,8 @@ 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(30s); + Timeout timeout; + timeout.start(30s); bool voltage_ok = false; while (!timeout.reached()) { types::power_supply_DC::VoltageCurrent m; diff --git a/modules/EvseManager/EvseManager.hpp b/modules/EvseManager/EvseManager.hpp index 0206ac09cd..5f563ad46b 100644 --- a/modules/EvseManager/EvseManager.hpp +++ b/modules/EvseManager/EvseManager.hpp @@ -17,7 +17,9 @@ // headers for required interface implementations #include -#include +#include +#include +#include #include #include #include @@ -25,10 +27,6 @@ // ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 // insert your custom include headers here -#include "CarManufacturer.hpp" -#include "Charger.hpp" -#include "SessionLog.hpp" -#include "VarContainer.hpp" #include #include #include @@ -38,6 +36,12 @@ #include #include #include + +#include "CarManufacturer.hpp" +#include "Charger.hpp" +#include "ErrorHandling.hpp" +#include "SessionLog.hpp" +#include "VarContainer.hpp" // ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 namespace module { @@ -56,7 +60,6 @@ struct Conf { bool three_phases; bool has_ventilation; std::string country_code; - bool rcd_enabled; double max_current_import_A; double max_current_export_A; std::string charge_mode; @@ -71,7 +74,6 @@ struct Conf { bool switch_to_minimum_voltage_after_cable_check; bool hack_skoda_enyaq; int hack_present_current_offset; - std::string connector_type; bool hack_pause_imd_during_precharge; bool hack_allow_bpt_with_iso2; bool autocharge_use_slac_instead_of_hlc; @@ -92,7 +94,8 @@ class EvseManager : public Everest::ModuleBase { EvseManager(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, Everest::TelemetryProvider& telemetry, std::unique_ptr p_evse, std::unique_ptr p_energy_grid, std::unique_ptr p_token_provider, - std::unique_ptr r_bsp, + std::unique_ptr r_bsp, std::vector> r_ac_rcd, + std::vector> r_connector_lock, std::vector> r_powermeter_grid_side, std::vector> r_powermeter_car_side, std::vector> r_slac, std::vector> r_hlc, @@ -105,6 +108,8 @@ class EvseManager : public Everest::ModuleBase { p_energy_grid(std::move(p_energy_grid)), p_token_provider(std::move(p_token_provider)), r_bsp(std::move(r_bsp)), + r_ac_rcd(std::move(r_ac_rcd)), + r_connector_lock(std::move(r_connector_lock)), r_powermeter_grid_side(std::move(r_powermeter_grid_side)), r_powermeter_car_side(std::move(r_powermeter_car_side)), r_slac(std::move(r_slac)), @@ -118,7 +123,9 @@ class EvseManager : public Everest::ModuleBase { const std::unique_ptr p_evse; const std::unique_ptr p_energy_grid; const std::unique_ptr p_token_provider; - const std::unique_ptr r_bsp; + const std::unique_ptr r_bsp; + const std::vector> r_ac_rcd; + const std::vector> r_connector_lock; const std::vector> r_powermeter_grid_side; const std::vector> r_powermeter_car_side; const std::vector> r_slac; @@ -132,7 +139,7 @@ class EvseManager : public Everest::ModuleBase { std::unique_ptr charger; sigslot::signal signalNrOfPhasesAvailable; types::powermeter::Powermeter get_latest_powermeter_data_billing(); - types::board_support::HardwareCapabilities get_hw_capabilities(); + types::evse_board_support::HardwareCapabilities get_hw_capabilities(); bool updateLocalMaxCurrentLimit(float max_current); // deprecated bool updateLocalMaxWattLimit(float max_watt); // deprecated bool updateLocalEnergyLimit(types::energy::ExternalLimits l); @@ -184,7 +191,7 @@ class EvseManager : public Everest::ModuleBase { types::powermeter::Powermeter latest_powermeter_data_billing; Everest::Thread energyThreadHandle; - types::board_support::HardwareCapabilities hw_capabilities; + types::evse_board_support::HardwareCapabilities hw_capabilities; bool local_three_phases; types::energy::ExternalLimits local_energy_limits; const float EVSE_ABSOLUTE_MAX_CURRENT = 80.0; @@ -248,6 +255,9 @@ class EvseManager : public Everest::ModuleBase { static constexpr auto CABLECHECK_CONTACTORS_CLOSE_TIMEOUT{std::chrono::seconds(5)}; std::atomic_bool current_demand_active{false}; + + std::unique_ptr bsp; + std::unique_ptr error_handling; // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 }; diff --git a/modules/EvseManager/IECStateMachine.cpp b/modules/EvseManager/IECStateMachine.cpp new file mode 100644 index 0000000000..855acc7b4c --- /dev/null +++ b/modules/EvseManager/IECStateMachine.cpp @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2923 Pionix GmbH and Contributors to EVerest + +#include "IECStateMachine.hpp" + +#include +#include + +namespace module { + +static std::variant from_bsp_event(types::board_support_common::Event e) { + switch (e) { + case types::board_support_common::Event::A: + return RawCPState::A; + case types::board_support_common::Event::B: + return RawCPState::B; + case types::board_support_common::Event::C: + return RawCPState::C; + case types::board_support_common::Event::D: + return RawCPState::D; + case types::board_support_common::Event::E: + return RawCPState::E; + case types::board_support_common::Event::F: + return RawCPState::F; + case types::board_support_common::Event::PowerOn: + return CPEvent::PowerOn; + case types::board_support_common::Event::PowerOff: + return CPEvent::PowerOff; + case types::board_support_common::Event::EvseReplugStarted: + return CPEvent::EvseReplugStarted; + case types::board_support_common::Event::EvseReplugFinished: + return CPEvent::EvseReplugFinished; + default: + return RawCPState::Disabled; + } +} + +/// \brief Converts the given Event \p e to human readable string +/// \returns a string representation of the Event +const std::string cpevent_to_string(CPEvent e) { + switch (e) { + case CPEvent::CarPluggedIn: + return "CarPluggedIn"; + case CPEvent::CarRequestedPower: + return "CarRequestedPower"; + case CPEvent::PowerOn: + return "PowerOn"; + case CPEvent::PowerOff: + return "PowerOff"; + case CPEvent::CarRequestedStopPower: + return "CarRequestedStopPower"; + case CPEvent::CarUnplugged: + return "CarUnplugged"; + case CPEvent::EFtoBCD: + return "EFtoBCD"; + case CPEvent::BCDtoEF: + return "BCDtoEF"; + case CPEvent::EvseReplugStarted: + return "EvseReplugStarted"; + case CPEvent::EvseReplugFinished: + return "EvseReplugFinished"; + } + throw std::out_of_range("No known string conversion for provided enum of type CPEvent"); +} + +IECStateMachine::IECStateMachine(const std::unique_ptr& r_bsp) : r_bsp(r_bsp) { + // feed the state machine whenever the timer expires + timeout_state_c1.signal_reached.connect(&IECStateMachine::feed_state_machine, this); + + // Subscribe to bsp driver to receive BspEvents from the hardware + r_bsp->subscribe_event([this](const types::board_support_common::BspEvent event) { + // feed into state machine + process_bsp_event(event); + }); +} + +void IECStateMachine::process_bsp_event(types::board_support_common::BspEvent bsp_event) { + + auto event = from_bsp_event(bsp_event.event); + + // If it was a CP change: feed state machine, else forward error + if (std::holds_alternative(event)) { + { + std::lock_guard l(state_mutex); + cp_state = std::get(event); + } + feed_state_machine(); + } else { + signal_event(std::get(event)); + } +} + +void IECStateMachine::feed_state_machine() { + auto events = state_machine(); + + // Process all events + while (not events.empty()) { + signal_event(events.front()); + events.pop(); + } +} + +// Main IEC state machine. Needs to be called whenever: +// - CP state changes (both events from hardware as well as duty cycle changes) +// - Allow power on changes +// - The C1 6s timer expires +std::queue IECStateMachine::state_machine() { + + std::queue events; + std::lock_guard lock(state_mutex); + + switch (cp_state) { + + case RawCPState::Disabled: + if (last_cp_state != RawCPState::Disabled) { + pwm_running = false; + r_bsp->call_pwm_off(); + ev_simplified_mode = false; + timeout_state_c1.stop(); + call_allow_power_on_bsp(false); + } + break; + + case RawCPState::A: + if (last_cp_state != RawCPState::A) { + pwm_running = false; + r_bsp->call_pwm_off(); + ev_simplified_mode = false; + car_plugged_in = false; + call_allow_power_on_bsp(false); + timeout_state_c1.stop(); + } + + // Table A.6: Sequence 2.1 Unplug at state Bx (or any other + // state) Table A.6: Sequence 2.2 Unplug at state Cx, Dx + if (last_cp_state != RawCPState::A && last_cp_state != RawCPState::Disabled) { + events.push(CPEvent::CarUnplugged); + } + break; + + case RawCPState::B: + // Table A.6: Sequence 7 EV stops charging + // Table A.6: Sequence 8.2 EV supply equipment + // responds to EV opens S2 (w/o PWM) + + if (last_cp_state != RawCPState::A && last_cp_state != RawCPState::B) { + + events.push(CPEvent::CarRequestedStopPower); + // Need to switch off according to Table A.6 Sequence 8.1 + // within 100ms + call_allow_power_on_bsp(false); + timeout_state_c1.stop(); + } + + // Table A.6: Sequence 1.1 Plug-in + if (last_cp_state == RawCPState::A || last_cp_state == RawCPState::Disabled || + (!car_plugged_in && last_cp_state == RawCPState::F)) { + events.push(CPEvent::CarPluggedIn); + ev_simplified_mode = false; + } + + if (last_cp_state == RawCPState::E || last_cp_state == RawCPState::F) { + // Triggers SLAC start + events.push(CPEvent::EFtoBCD); + } + break; + + case RawCPState::D: + // If state D is not supported switch off. + if (not has_ventilation) { + call_allow_power_on_bsp(false); + timeout_state_c1.stop(); + break; + } + // no break, intended fall through: If we support state D it is handled the same way as state C + case RawCPState::C: + // Table A.6: Sequence 1.2 Plug-in + if (last_cp_state == RawCPState::A || (!car_plugged_in && last_cp_state == RawCPState::F)) { + events.push(CPEvent::CarPluggedIn); + EVLOG_info << "Detected simplified mode."; + ev_simplified_mode = true; + } + if (last_cp_state == RawCPState::B) { + events.push(CPEvent::CarRequestedPower); + } + + if (!pwm_running && last_pwm_running) { // X2->C1 + // Table A.6 Sequence 10.2: EV does not stop drawing power + // even if PWM stops. Stop within 6 seconds (E.g. Kona1!) + timeout_state_c1.start(std::chrono::seconds(6)); + } + + // PWM switches on while in state C + if (pwm_running && !last_pwm_running) { + // when resuming after a pause before the EV goes to state B, stop the timer. + timeout_state_c1.stop(); + + // If we resume charging and the EV never left state C during pause we allow non-compliant EVs to switch + // on again. + if (power_on_allowed) { + call_allow_power_on_bsp(true); + } + } + + if (timeout_state_c1.reached()) { + EVLOG_warning << "Timeout of 6 seconds reached, EV did not go back to state B after PWM was switch off. " + "Power off under load."; + // We are still in state C, but the 6 seconds timeout has been reached. Now force power off under load. + call_allow_power_on_bsp(false); + } + + if (pwm_running) { // C2 + // 1) When we come from state B: switch on if we are allowed to + // 2) When we are in C2 for a while now and finally get a delayed power_on_allowed: also switch on + if (power_on_allowed && (!last_power_on_allowed || last_cp_state == RawCPState::B)) { + // Table A.6: Sequence 4 EV ready to charge. + // Must enable power within 3 seconds. + call_allow_power_on_bsp(true); + } + + // Simulate Request power Event here for simplified mode + // to ensure that this mode behaves similar for higher + // layers. Note this does not work with 5% mode + // correctly, but simplified mode does not support HLC + // anyway. + if (!last_pwm_running && ev_simplified_mode) { + events.push(CPEvent::CarRequestedPower); + } + } + break; + + case RawCPState::E: + if (last_cp_state != RawCPState::E) { + timeout_state_c1.stop(); + call_allow_power_on_bsp(false); + } + break; + + case RawCPState::F: + timeout_state_c1.stop(); + call_allow_power_on_bsp(false); + break; + } + + last_cp_state = cp_state; + last_pwm_running = pwm_running; + last_power_on_allowed = power_on_allowed; + + return events; +} + +// High level state machine sets PWM duty cycle +void IECStateMachine::set_pwm(double value) { + { + std::scoped_lock lock(state_mutex); + if (value > 0 && value < 1) { + pwm_running = true; + } else { + pwm_running = false; + } + } + + r_bsp->call_pwm_on(value * 100); + feed_state_machine(); +} + +// High level state machine sets state X1 +void IECStateMachine::set_pwm_off() { + { + std::scoped_lock lock(state_mutex); + pwm_running = false; + } + r_bsp->call_pwm_off(); + feed_state_machine(); +} + +// High level state machine sets state F +void IECStateMachine::set_pwm_F() { + { + std::scoped_lock lock(state_mutex); + pwm_running = false; + } + r_bsp->call_pwm_F(); + feed_state_machine(); +} + +// The higher level state machine in Charger.cpp calls this to indicate it allows contactors to be switched on +void IECStateMachine::allow_power_on(bool value, types::evse_board_support::Reason reason) { + { + std::scoped_lock lock(state_mutex); + // Only set the flags here in case of power on. + power_on_allowed = value; + power_on_reason = reason; + // In case of power off, we can directly forward this to the BSP driver here + if (not power_on_allowed) { + call_allow_power_on_bsp(false); + } + } + // The actual power on will be handled in the state machine to verify it is in the correct CP state etc. + feed_state_machine(); +} + +// Private member function used to actually call the BSP driver's allow_power_on +// No need to lock mutex as this will be called from state machine or locked context only +void IECStateMachine::call_allow_power_on_bsp(bool value) { + if (not value) { + power_on_allowed = false; + power_on_reason = types::evse_board_support::Reason::PowerOff; + } + r_bsp->call_allow_power_on({value, power_on_reason}); +} + +// High level state machine requests reading PP ampacity value. +// We forward this request to the BSP driver. The high level state machine will never call this if it is not used +// (e.g. in DC or AC tethered charging) +double IECStateMachine::read_pp_ampacity() { + auto a = r_bsp->call_ac_read_pp_ampacity(); + switch (a.ampacity) { + case types::board_support_common::Ampacity::A_13: + return 13.; + case types::board_support_common::Ampacity::A_20: + return 20.; + case types::board_support_common::Ampacity::A_32: + return 32.; + case types::board_support_common::Ampacity::A_63_3ph_70_1ph: + if (three_phases) { + return 63.; + } else { + return 70.; + } + default: + return 0.; + } +} + +// Forward special replug request. Only for testing setups. +void IECStateMachine::evse_replug(int ms) { + r_bsp->call_evse_replug(ms); +} + +// Forward special request to switch the number of phases during charging. BSP will need to implement a special +// sequence to not destroy cars. +void IECStateMachine::switch_three_phases_while_charging(bool n) { + r_bsp->call_ac_switch_three_phases_while_charging(n); +} + +// Forwards config parameters from EvseManager module config to BSP +void IECStateMachine::setup(bool three_phases, bool has_ventilation, std::string country_code) { + r_bsp->call_setup(three_phases, has_ventilation, country_code); + this->has_ventilation = has_ventilation; + this->three_phases = three_phases; +} + +// enable/disable the charging port and CP signal +void IECStateMachine::enable(bool en) { + r_bsp->call_enable(en); +} + +// Forward the over current detection limit to the BSP. Many BSP MCUs monitor the charge current and trigger a fault +// in case of over current. This sets the target charging current value to be used in OC detection. It cannot be +// derived from the PWM duty cycle, use this value instead. +void IECStateMachine::set_overcurrent_limit(double amps) { + if (amps != last_amps) { + r_bsp->call_ac_set_overcurrent_limit_A(amps); + last_amps = amps; + } +} + +} // namespace module diff --git a/modules/EvseManager/IECStateMachine.hpp b/modules/EvseManager/IECStateMachine.hpp new file mode 100644 index 0000000000..7fe266fefb --- /dev/null +++ b/modules/EvseManager/IECStateMachine.hpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest + +/* + The IECStateMachine class provides an adapter between the board support package driver (in a seperate module) and the + high level state machine in Charger.cpp. + + Typically the CP signal generation/reading and control of contactors/RCD etc is handled by a dedicated MCU. This MCU + and/or the HW is responsible for the basic electrical safety of the system (such as safely shut down in case of RCD + trigger or Linux crashing). The BSP driver is just a simple HW abstraction layer that translates the commands for + setting PWM duty cycle/allow contactors on as well as the CP signal readings/error conditions into the everest world. + It should not need to implement any logic or understanding of the IEC61851-1 or any higher protocol. + + This IECStateMachine is the low level state machine translating the IEC61851-1 CP states ABCDEF into more useful + abstract events such as "CarPluggedIn/CarRequestedPower" etc. These events drive the high level state machine in + Charger.cpp which handles the actual charging session and coordinates IEC/ISO/SLAC. + +*/ + +#ifndef SRC_BSP_STATE_MACHINE_H_ +#define SRC_BSP_STATE_MACHINE_H_ + +#include "ld-ev.hpp" + +#include +#include +#include + +#include +#include + +#include "Timeout.hpp" +#include "utils/thread.hpp" + +namespace module { + +// Abstract events that drive the higher level state machine in Charger.cpp +enum class CPEvent { + CarPluggedIn, + CarRequestedPower, + PowerOn, + PowerOff, + CarRequestedStopPower, + CarUnplugged, + EFtoBCD, + BCDtoEF, + EvseReplugStarted, + EvseReplugFinished, +}; + +// Just a helper for log printing +const std::string cpevent_to_string(CPEvent e); + +// Raw (valid) CP states for the IECStateMachine +enum class RawCPState { + Disabled, + A, + B, + C, + D, + E, + F +}; + +class IECStateMachine { +public: + // We need the r_bsp reference to be able to talk to the bsp driver module + explicit IECStateMachine(const std::unique_ptr& r_bsp); + + // Call when new events from BSP requirement come in. Will signal internal events + void process_bsp_event(types::board_support_common::BspEvent bsp_event); + // Allow power on from Charger state machine + void allow_power_on(bool value, types::evse_board_support::Reason reason); + + double read_pp_ampacity(); + void evse_replug(int ms); + void switch_three_phases_while_charging(bool n); + void setup(bool three_phases, bool has_ventilation, std::string country_code); + bool force_unlock(); + void set_overcurrent_limit(double amps); + + void set_pwm(double value); + void set_pwm_off(); + void set_pwm_F(); + + void enable(bool en); + + // Signal for internal events type + sigslot::signal signal_event; + +private: + const std::unique_ptr& r_bsp; + + bool pwm_running{false}; + bool last_pwm_running{false}; + + bool ev_simplified_mode{false}; + bool has_ventilation{false}; + bool power_on_allowed{false}; + bool last_power_on_allowed{false}; + std::atomic last_amps{-1}; + + bool car_plugged_in{false}; + + RawCPState cp_state{RawCPState::Disabled}, last_cp_state{RawCPState::Disabled}; + AsyncTimeout timeout_state_c1; + + std::mutex state_mutex; + void feed_state_machine(); + std::queue state_machine(); + + types::evse_board_support::Reason power_on_reason{types::evse_board_support::Reason::PowerOff}; + void call_allow_power_on_bsp(bool value); + + std::atomic_bool three_phases{true}; +}; + +} // namespace module + +#endif // SRC_BSP_STATE_MACHINE_H_ diff --git a/modules/EvseManager/Timeout.hpp b/modules/EvseManager/Timeout.hpp index 77d5d36a39..0e98972378 100644 --- a/modules/EvseManager/Timeout.hpp +++ b/modules/EvseManager/Timeout.hpp @@ -4,27 +4,106 @@ #define TIMEOUT_HPP #include +#include + +#include "utils/thread.hpp" + using namespace std::chrono; /* - Simple helper class for a timeout + Simple helper class for a polling timeout */ class Timeout { public: - explicit Timeout(milliseconds _t) : t(_t), start(steady_clock::now()) { + void start(milliseconds _t) { + t = _t; + start_time = steady_clock::now(); + running = true; + } + + void stop() { + running = false; + } + + bool is_running() { + return running; } bool reached() { - if ((steady_clock::now() - start) > t) + if (!running) + return false; + if ((steady_clock::now() - start_time) > t) { + running = false; return true; - else + } else { return false; + } } private: milliseconds t; - time_point start; + time_point start_time; + bool running{false}; +}; + +/* Simple helper class for a timeout with internal thread */ +class AsyncTimeout { +public: + void start(milliseconds _t) { + if (running) { + stop(); + } + t = _t; + start_time = steady_clock::now(); + running = true; + // start waiting thread + wait_thread = std::thread([this]() { + while (not wait_thread.shouldExit()) { + std::this_thread::sleep_for(resolution); + if (reached()) { + // Note the order is important here. + // We first signal reached which will call all callbacks. + // The timer is still running in this in those callbacks, so they can also call reached() and get a + // true as return value. + signal_reached(); + // After all signal handlers are called, we stop the timer. + running = false; + return; + } + } + }); + } + + // Note that stopping the timer may take up to "resolution" amount of time to return + void stop() { + wait_thread.stop(); + running = false; + } + + bool is_running() { + return running; + } + + bool reached() { + if (!running) { + return false; + } + if ((steady_clock::now() - start_time) > t) { + return true; + } else { + return false; + } + } + + sigslot::signal<> signal_reached; + +private: + constexpr static auto resolution = 500ms; + milliseconds t; + time_point start_time; + std::atomic_bool running{false}; + Everest::Thread wait_thread; }; #endif diff --git a/modules/EvseManager/energy_grid/energyImpl.hpp b/modules/EvseManager/energy_grid/energyImpl.hpp index 95928d6e52..d0e2101269 100644 --- a/modules/EvseManager/energy_grid/energyImpl.hpp +++ b/modules/EvseManager/energy_grid/energyImpl.hpp @@ -58,7 +58,7 @@ class energyImpl : public energyImplBase { void clear_export_request_schedule(); void clear_request_schedules(); void request_energy_from_energy_manager(); - types::board_support::HardwareCapabilities hw_caps; + types::evse_board_support::HardwareCapabilities hw_caps; // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 }; diff --git a/modules/EvseManager/evse/evse_managerImpl.cpp b/modules/EvseManager/evse/evse_managerImpl.cpp index 8da41e77e9..90d52d3f91 100644 --- a/modules/EvseManager/evse/evse_managerImpl.cpp +++ b/modules/EvseManager/evse/evse_managerImpl.cpp @@ -106,10 +106,10 @@ void evse_managerImpl::ready() { } }); - mod->r_bsp->subscribe_telemetry([this](types::board_support::Telemetry telemetry) { + mod->r_bsp->subscribe_telemetry([this](types::evse_board_support::Telemetry telemetry) { // external Nodered interface mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/temperature", mod->config.connector_id), - telemetry.temperature); + telemetry.evse_temperature_C); // /external Nodered interface publish_telemetry(telemetry); }); @@ -245,7 +245,8 @@ void evse_managerImpl::ready() { se.transaction_finished.emplace(transaction_finished); } else if (e == types::evse_manager::SessionEventEnum::Error) { types::evse_manager::Error error; - error.error_code = mod->charger->getErrorState(); + // FIXME this should report something useful instead! + error.error_code = types::evse_manager::ErrorEnum::Other; se.error = error; } else if (e == types::evse_manager::SessionEventEnum::Enabled or e == types::evse_manager::SessionEventEnum::Disabled) { @@ -281,13 +282,6 @@ void evse_managerImpl::ready() { mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/state", mod->config.connector_id), static_cast(s)); }); - - mod->charger->signalError.connect([this](types::evse_manager::ErrorEnum s) { - mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/error_type", mod->config.connector_id), - static_cast(s)); - mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/error_string", mod->config.connector_id), - types::evse_manager::error_enum_to_string(s)); - }); // /Deprecated } @@ -364,10 +358,6 @@ bool evse_managerImpl::handle_stop_transaction(types::evse_manager::StopTransact return mod->charger->cancelTransaction(request); }; -bool evse_managerImpl::handle_force_unlock(int& connector_id) { - return mod->charger->forceUnlock(); -}; - std::string evse_managerImpl::generate_session_uuid() { return boost::uuids::to_string(boost::uuids::random_generator()()); } @@ -405,5 +395,13 @@ bool evse_managerImpl::handle_external_ready_to_start_charging() { return false; } +bool evse_managerImpl::handle_force_unlock(int& connector_id) { + if (mod->r_connector_lock.size() > 0) { + mod->r_connector_lock[0]->call_unlock(); + return true; + } + return false; +}; + } // namespace evse } // namespace module diff --git a/modules/EvseManager/manifest.yaml b/modules/EvseManager/manifest.yaml index b694eceaf6..d014e53868 100644 --- a/modules/EvseManager/manifest.yaml +++ b/modules/EvseManager/manifest.yaml @@ -54,10 +54,6 @@ config: description: Country Code type: string default: DE - rcd_enabled: - description: Enable or disable RCD - type: boolean - default: true max_current_import_A: description: User configurable current limit for this EVSE in Ampere type: number @@ -133,13 +129,6 @@ config: Set to 0 unless you really know what you are doing. type: integer default: 0 - connector_type: - description: Type of charging connector available at this EVSE - type: string - enum: - - IEC62196Type2Cable - - IEC62196Type2Socket - default: IEC62196Type2Cable hack_pause_imd_during_precharge: description: >- Disable IMD at the end of CableCheck and re-enable when current is flowing in CurrentDemand. @@ -218,7 +207,15 @@ provides: interface: auth_token_provider requires: bsp: - interface: board_support_AC + interface: evse_board_support + ac_rcd: + interface: ac_rcd + min_connections: 0 + max_connections: 1 + connector_lock: + interface: connector_lock + min_connections: 0 + max_connections: 1 powermeter_grid_side: interface: powermeter min_connections: 0 diff --git a/modules/MicroMegaWattBSP/CMakeLists.txt b/modules/MicroMegaWattBSP/CMakeLists.txt index 16bcfbecae..bd4c41b3ef 100644 --- a/modules/MicroMegaWattBSP/CMakeLists.txt +++ b/modules/MicroMegaWattBSP/CMakeLists.txt @@ -28,7 +28,7 @@ target_sources(${MODULE_NAME} PRIVATE "dc_supply/power_supply_DCImpl.cpp" "powermeter/powermeterImpl.cpp" - "board_support/board_support_ACImpl.cpp" + "board_support/evse_board_supportImpl.cpp" ) # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp b/modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp index c8f5b61c2f..d6ea3c4cf5 100644 --- a/modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp +++ b/modules/MicroMegaWattBSP/MicroMegaWattBSP.hpp @@ -11,7 +11,7 @@ #include "ld-ev.hpp" // headers for provided interface implementations -#include +#include #include #include @@ -35,7 +35,7 @@ class MicroMegaWattBSP : public Everest::ModuleBase { MicroMegaWattBSP(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, std::unique_ptr p_dc_supply, std::unique_ptr p_powermeter, - std::unique_ptr p_board_support, Conf& config) : + std::unique_ptr p_board_support, Conf& config) : ModuleBase(info), mqtt(mqtt_provider), p_dc_supply(std::move(p_dc_supply)), @@ -46,7 +46,7 @@ class MicroMegaWattBSP : public Everest::ModuleBase { Everest::MqttProvider& mqtt; const std::unique_ptr p_dc_supply; const std::unique_ptr p_powermeter; - const std::unique_ptr p_board_support; + const std::unique_ptr p_board_support; const Conf& config; // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 diff --git a/modules/MicroMegaWattBSP/board_support/board_support_ACImpl.cpp b/modules/MicroMegaWattBSP/board_support/board_support_ACImpl.cpp deleted file mode 100644 index 9524a6963b..0000000000 --- a/modules/MicroMegaWattBSP/board_support/board_support_ACImpl.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Pionix GmbH and Contributors to EVerest - -#include "board_support_ACImpl.hpp" - -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; - } - - EVLOG_error << "Received an unknown interface event from uMWC: " << (int)e.type; - return types::board_support::Event::ErrorVentilationNotAvailable; -} - -void board_support_ACImpl::init() { - { - std::lock_guard lock(capsMutex); - - caps.min_current_A_import = 0; - caps.max_current_A_import = 6; - caps.min_phase_count_import = 1; - caps.max_phase_count_import = 3; - caps.supports_changing_phases_during_charging = false; - - caps.min_current_A_export = 0; - caps.max_current_A_export = 6; - 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)); - }); -} - -void board_support_ACImpl::ready() { -} - -void board_support_ACImpl::handle_setup(bool& three_phases, bool& has_ventilation, std::string& country_code, - bool& rcd_enabled){}; - -types::board_support::HardwareCapabilities board_support_ACImpl::handle_get_hw_capabilities() { - std::lock_guard lock(capsMutex); - return caps; -}; - -void board_support_ACImpl::handle_enable(bool& value) { - if (value) - mod->serial.enable(); - else - mod->serial.disable(); -}; - -void board_support_ACImpl::handle_pwm_on(double& value) { - mod->serial.setPWM(1, value); -}; - -void board_support_ACImpl::handle_pwm_off() { - mod->serial.setPWM(0, 0.); -}; - -void board_support_ACImpl::handle_pwm_F() { - mod->serial.setPWM(2, 0.); -}; - -void board_support_ACImpl::handle_allow_power_on(bool& value) { - mod->serial.allowPowerOn(value); -}; - -bool board_support_ACImpl::handle_force_unlock() { - return true; -}; - -void board_support_ACImpl::handle_switch_three_phases_while_charging(bool& value){}; - -void board_support_ACImpl::handle_evse_replug(int& value) { - mod->serial.replug(value); -}; - -double board_support_ACImpl::handle_read_pp_ampacity() { - std::lock_guard lock(capsMutex); - return caps.max_current_A_import; -}; - -} // namespace board_support -} // namespace module diff --git a/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.cpp b/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.cpp new file mode 100644 index 0000000000..fbbd490838 --- /dev/null +++ b/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.cpp @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "evse_board_supportImpl.hpp" + +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; +} + +EVLOG_error << "Received an unknown interface event from uMWC: " << (int)e.type; +return types::board_support::Event::ErrorVentilationNotAvailable; +} +*/ + +void evse_board_supportImpl::init() { + { + std::lock_guard lock(capsMutex); + + caps.min_current_A_import = 0; + caps.max_current_A_import = 6; + caps.min_phase_count_import = 1; + caps.max_phase_count_import = 3; + caps.supports_changing_phases_during_charging = false; + + caps.min_current_A_export = 0; + caps.max_current_A_export = 6; + 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)); + });*/ +} + +void evse_board_supportImpl::ready() { +} + +void evse_board_supportImpl::handle_setup(bool& three_phases, bool& has_ventilation, std::string& country_code) { + // your code for cmd setup goes here +} + +types::evse_board_support::HardwareCapabilities evse_board_supportImpl::handle_get_hw_capabilities() { + std::lock_guard lock(capsMutex); + return caps; +} + +void evse_board_supportImpl::handle_enable(bool& value) { + if (value) + mod->serial.enable(); + else + mod->serial.disable(); +} + +void evse_board_supportImpl::handle_pwm_on(double& value) { + mod->serial.setPWM(1, value); +} + +void evse_board_supportImpl::handle_pwm_off() { + mod->serial.setPWM(0, 0.); +} + +void evse_board_supportImpl::handle_pwm_F() { + mod->serial.setPWM(2, 0.); +} + +void evse_board_supportImpl::handle_allow_power_on(types::evse_board_support::PowerOnOff& value) { + mod->serial.allowPowerOn(value.allow_power_on); +} + +void evse_board_supportImpl::handle_ac_switch_three_phases_while_charging(bool& value) { + // your code for cmd ac_switch_three_phases_while_charging goes here +} + +void evse_board_supportImpl::handle_evse_replug(int& value) { + mod->serial.replug(value); +} + +types::board_support_common::ProximityPilot evse_board_supportImpl::handle_ac_read_pp_ampacity() { + return {types::board_support_common::Ampacity::A_13}; +} + +void evse_board_supportImpl::handle_ac_set_overcurrent_limit_A(double& value) { + // your code for cmd ac_set_overcurrent_limit_A goes here +} + +} // namespace board_support +} // namespace module diff --git a/modules/MicroMegaWattBSP/board_support/board_support_ACImpl.hpp b/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.hpp similarity index 58% rename from modules/MicroMegaWattBSP/board_support/board_support_ACImpl.hpp rename to modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.hpp index 8e89dc0823..c149ac48e3 100644 --- a/modules/MicroMegaWattBSP/board_support/board_support_ACImpl.hpp +++ b/modules/MicroMegaWattBSP/board_support/evse_board_supportImpl.hpp @@ -1,14 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest -#ifndef BOARD_SUPPORT_BOARD_SUPPORT_AC_IMPL_HPP -#define BOARD_SUPPORT_BOARD_SUPPORT_AC_IMPL_HPP +#ifndef BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP +#define BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP // // AUTO GENERATED - MARKED REGIONS WILL BE KEPT // template version 3 // -#include +#include #include "../MicroMegaWattBSP.hpp" @@ -21,11 +21,12 @@ namespace board_support { struct Conf {}; -class board_support_ACImpl : public board_support_ACImplBase { +class evse_board_supportImpl : public evse_board_supportImplBase { public: - board_support_ACImpl() = delete; - board_support_ACImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : - board_support_ACImplBase(ev, "board_support"), mod(mod), config(config){}; + evse_board_supportImpl() = delete; + evse_board_supportImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, + Conf& config) : + evse_board_supportImplBase(ev, "board_support"), mod(mod), config(config){}; // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 // insert your public definitions here @@ -33,18 +34,17 @@ class board_support_ACImpl : public board_support_ACImplBase { protected: // command handler functions (virtual) - virtual void handle_setup(bool& three_phases, bool& has_ventilation, std::string& country_code, - bool& rcd_enabled) override; - virtual types::board_support::HardwareCapabilities handle_get_hw_capabilities() override; + virtual void handle_setup(bool& three_phases, bool& has_ventilation, std::string& country_code) override; + virtual types::evse_board_support::HardwareCapabilities handle_get_hw_capabilities() override; virtual void handle_enable(bool& value) override; virtual void handle_pwm_on(double& value) override; virtual void handle_pwm_off() override; virtual void handle_pwm_F() override; - virtual void handle_allow_power_on(bool& value) override; - virtual bool handle_force_unlock() override; - virtual void handle_switch_three_phases_while_charging(bool& value) override; + virtual void handle_allow_power_on(types::evse_board_support::PowerOnOff& value) override; + virtual void handle_ac_switch_three_phases_while_charging(bool& value) override; virtual void handle_evse_replug(int& value) override; - virtual double handle_read_pp_ampacity() override; + virtual types::board_support_common::ProximityPilot handle_ac_read_pp_ampacity() override; + virtual void handle_ac_set_overcurrent_limit_A(double& value) override; // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 // insert your protected definitions here @@ -59,7 +59,7 @@ class board_support_ACImpl : public board_support_ACImplBase { // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 // insert your private definitions here - types::board_support::HardwareCapabilities caps; + types::evse_board_support::HardwareCapabilities caps; std::mutex capsMutex; // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 }; @@ -71,4 +71,4 @@ class board_support_ACImpl : public board_support_ACImplBase { } // namespace board_support } // namespace module -#endif // BOARD_SUPPORT_BOARD_SUPPORT_AC_IMPL_HPP +#endif // BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP diff --git a/modules/MicroMegaWattBSP/manifest.yaml b/modules/MicroMegaWattBSP/manifest.yaml index 5ddfb65f6f..b32d4d542e 100644 --- a/modules/MicroMegaWattBSP/manifest.yaml +++ b/modules/MicroMegaWattBSP/manifest.yaml @@ -30,7 +30,7 @@ provides: interface: powermeter description: provides the Yeti Internal Power Meter board_support: - interface: board_support_AC + interface: evse_board_support description: provides the board support Interface to low level control control pilot, relais, rcd, motor lock enable_external_mqtt: true metadata: diff --git a/modules/YetiDriver/CMakeLists.txt b/modules/YetiDriver/CMakeLists.txt index 16df973d9a..2355513dea 100644 --- a/modules/YetiDriver/CMakeLists.txt +++ b/modules/YetiDriver/CMakeLists.txt @@ -29,9 +29,8 @@ target_link_libraries(${MODULE_NAME} target_sources(${MODULE_NAME} PRIVATE "powermeter/powermeterImpl.cpp" - "board_support/board_support_ACImpl.cpp" - "yeti_extras/yeti_extrasImpl.cpp" - "yeti_simulation_control/yeti_simulation_controlImpl.cpp" + "board_support/evse_board_supportImpl.cpp" + "rcd/ac_rcdImpl.cpp" ) # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/YetiDriver/YetiDriver.cpp b/modules/YetiDriver/YetiDriver.cpp index 0456893418..c71108acbf 100644 --- a/modules/YetiDriver/YetiDriver.cpp +++ b/modules/YetiDriver/YetiDriver.cpp @@ -84,8 +84,6 @@ void YetiDriver::init() { {"error", false}}; // invoke_init(*p_powermeter); - invoke_init(*p_yeti_extras); - invoke_init(*p_yeti_simulation_control); invoke_init(*p_board_support); } @@ -104,8 +102,6 @@ void YetiDriver::ready() { serial.setControlMode(str_to_control_mode(config.control_mode)); invoke_ready(*p_powermeter); - invoke_ready(*p_yeti_extras); - invoke_ready(*p_yeti_simulation_control); invoke_ready(*p_board_support); serial.signalKeepAliveLo.connect([this](const KeepAliveLo& k) { diff --git a/modules/YetiDriver/YetiDriver.hpp b/modules/YetiDriver/YetiDriver.hpp index 1ad6706a2a..658c1ef160 100644 --- a/modules/YetiDriver/YetiDriver.hpp +++ b/modules/YetiDriver/YetiDriver.hpp @@ -11,10 +11,9 @@ #include "ld-ev.hpp" // headers for provided interface implementations -#include +#include +#include #include -#include -#include // ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 #include "yeti_comms/evSerial.h" @@ -35,24 +34,21 @@ class YetiDriver : public Everest::ModuleBase { YetiDriver() = delete; YetiDriver(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, Everest::TelemetryProvider& telemetry, std::unique_ptr p_powermeter, - std::unique_ptr p_board_support, - std::unique_ptr p_yeti_extras, - std::unique_ptr p_yeti_simulation_control, Conf& config) : + std::unique_ptr p_board_support, std::unique_ptr p_rcd, + Conf& config) : ModuleBase(info), mqtt(mqtt_provider), telemetry(telemetry), p_powermeter(std::move(p_powermeter)), p_board_support(std::move(p_board_support)), - p_yeti_extras(std::move(p_yeti_extras)), - p_yeti_simulation_control(std::move(p_yeti_simulation_control)), + p_rcd(std::move(p_rcd)), config(config){}; Everest::MqttProvider& mqtt; Everest::TelemetryProvider& telemetry; const std::unique_ptr p_powermeter; - const std::unique_ptr p_board_support; - const std::unique_ptr p_yeti_extras; - const std::unique_ptr p_yeti_simulation_control; + const std::unique_ptr p_board_support; + const std::unique_ptr p_rcd; const Conf& config; // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 diff --git a/modules/YetiDriver/board_support/board_support_ACImpl.cpp b/modules/YetiDriver/board_support/evse_board_supportImpl.cpp similarity index 58% rename from modules/YetiDriver/board_support/board_support_ACImpl.cpp rename to modules/YetiDriver/board_support/evse_board_supportImpl.cpp index c79cbff5a9..31249057d8 100644 --- a/modules/YetiDriver/board_support/board_support_ACImpl.cpp +++ b/modules/YetiDriver/board_support/evse_board_supportImpl.cpp @@ -1,54 +1,54 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest -#include "board_support_ACImpl.hpp" +// Copyright Pionix GmbH and Contributors to EVerest -using namespace std::chrono_literals; +#include "evse_board_supportImpl.hpp" namespace module { namespace board_support { - -types::board_support::Event cast_event_type(const Event& e) { +/* + types::board_support_common::Event cast_event_type(const Event& e) { switch (e.type) { case Event_InterfaceEvent_CAR_PLUGGED_IN: - return types::board_support::Event::CarPluggedIn; + return types::board_support_common::Event::CarPluggedIn; case Event_InterfaceEvent_CAR_REQUESTED_POWER: - return types::board_support::Event::CarRequestedPower; + return types::board_support_common::Event::CarRequestedPower; case Event_InterfaceEvent_POWER_ON: - return types::board_support::Event::PowerOn; + return types::board_support_common::Event::PowerOn; case Event_InterfaceEvent_POWER_OFF: - return types::board_support::Event::PowerOff; + return types::board_support_common::Event::PowerOff; case Event_InterfaceEvent_CAR_REQUESTED_STOP_POWER: - return types::board_support::Event::CarRequestedStopPower; + return types::board_support_common::Event::CarRequestedStopPower; case Event_InterfaceEvent_CAR_UNPLUGGED: - return types::board_support::Event::CarUnplugged; + return types::board_support_common::Event::CarUnplugged; case Event_InterfaceEvent_ERROR_E: - return types::board_support::Event::ErrorE; + return types::board_support_common::Event::ErrorE; case Event_InterfaceEvent_ERROR_DF: - return types::board_support::Event::ErrorDF; + return types::board_support_common::Event::ErrorDF; case Event_InterfaceEvent_ERROR_RELAIS: - return types::board_support::Event::ErrorRelais; + return types::board_support_common::Event::ErrorRelais; case Event_InterfaceEvent_ERROR_RCD: - return types::board_support::Event::ErrorRCD; + return types::board_support_common::Event::ErrorRCD; case Event_InterfaceEvent_ERROR_VENTILATION_NOT_AVAILABLE: - return types::board_support::Event::ErrorVentilationNotAvailable; + return types::board_support_common::Event::ErrorVentilationNotAvailable; case Event_InterfaceEvent_ERROR_OVER_CURRENT: - return types::board_support::Event::ErrorOverCurrent; + return types::board_support_common::Event::ErrorOverCurrent; case Event_InterfaceEvent_ENTER_BCD: - return types::board_support::Event::EFtoBCD; + return types::board_support_common::Event::EFtoBCD; case Event_InterfaceEvent_LEAVE_BCD: - return types::board_support::Event::BCDtoEF; + return types::board_support_common::Event::BCDtoEF; case Event_InterfaceEvent_PERMANENT_FAULT: - return types::board_support::Event::PermanentFault; + return types::board_support_common::Event::PermanentFault; case Event_InterfaceEvent_EVSE_REPLUG_STARTED: - return types::board_support::Event::EvseReplugStarted; + return types::board_support_common::Event::EvseReplugStarted; case Event_InterfaceEvent_EVSE_REPLUG_FINISHED: - return types::board_support::Event::EvseReplugFinished; + return types::board_support_common::Event::EvseReplugFinished; } EVLOG_AND_THROW(Everest::EverestConfigError("Received an unknown interface event from Yeti")); -} -void board_support_ACImpl::init() { +}*/ + +void evse_board_supportImpl::init() { { std::lock_guard lock(capsMutex); @@ -65,19 +65,19 @@ void board_support_ACImpl::init() { caps.supports_changing_phases_during_charging = false; } - mod->serial.signalEvent.connect([this](Event e) { publish_event(cast_event_type(e)); }); + mod->serial.signalEvent.connect([this](Event e) { /*publish_event(cast_event_type(e));*/ }); // FIXME // Everything used here should be moved out of debug update in protobuf mod->serial.signalDebugUpdate.connect([this](DebugUpdate d) { - publish_nr_of_phases_available((d.use_three_phases ? 3 : 1)); + publish_ac_nr_of_phases_available((d.use_three_phases ? 3 : 1)); - types::board_support::Telemetry telemetry; - telemetry.temperature = d.cpu_temperature; + types::evse_board_support::Telemetry telemetry; + telemetry.evse_temperature_C = d.cpu_temperature; telemetry.fan_rpm = 0.; telemetry.supply_voltage_12V = d.supply_voltage_12V; telemetry.supply_voltage_minus_12V = d.supply_voltage_N12V; - telemetry.rcd_current = d.rcd_current; + // FIXME telemetry.rcd_current = d.rcd_current; telemetry.relais_on = d.relais_on; publish_telemetry(telemetry); @@ -101,9 +101,9 @@ void board_support_ACImpl::init() { caps.supports_changing_phases_during_charging = l.supports_changing_phases_during_charging; caps_received = true; }); -} // namespace board_support +} -void board_support_ACImpl::ready() { +void evse_board_supportImpl::ready() { // Wait for caps to be received at least once int i; for (i = 0; i < 50; i++) { @@ -113,7 +113,7 @@ void board_support_ACImpl::ready() { break; } - std::this_thread::sleep_for(100ms); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (i == 49) { EVLOG_AND_THROW( @@ -121,59 +121,58 @@ void board_support_ACImpl::ready() { } } -void board_support_ACImpl::handle_setup(bool& three_phases, bool& has_ventilation, std::string& country_code, - bool& rcd_enabled) { +void evse_board_supportImpl::handle_setup(bool& three_phases, bool& has_ventilation, std::string& country_code) { mod->serial.setCountryCode(country_code.c_str()); mod->serial.setHasVentilation(has_ventilation); mod->serial.setThreePhases(three_phases); - mod->serial.enableRCD(rcd_enabled); -}; + // mod->serial.enableRCD(rcd_enabled); +} -void board_support_ACImpl::handle_enable(bool& value) { +types::evse_board_support::HardwareCapabilities evse_board_supportImpl::handle_get_hw_capabilities() { + std::lock_guard lock(capsMutex); + return caps; +} + +void evse_board_supportImpl::handle_enable(bool& value) { if (value) mod->serial.enable(); else mod->serial.disable(); -}; +} -void board_support_ACImpl::handle_pwm_on(double& value) { +void evse_board_supportImpl::handle_pwm_on(double& value) { mod->serial.setPWM(1, value); -}; +} -void board_support_ACImpl::handle_pwm_off() { +void evse_board_supportImpl::handle_pwm_off() { mod->serial.setPWM(0, 0.); -}; +} -void board_support_ACImpl::handle_pwm_F() { +void evse_board_supportImpl::handle_pwm_F() { mod->serial.setPWM(2, 0.); -}; - -void board_support_ACImpl::handle_allow_power_on(bool& value) { - mod->serial.allowPowerOn(value); -}; +} -bool board_support_ACImpl::handle_force_unlock() { - return mod->serial.forceUnlock(); -}; +void evse_board_supportImpl::handle_allow_power_on(types::evse_board_support::PowerOnOff& value) { + mod->serial.allowPowerOn(value.allow_power_on); +} -void board_support_ACImpl::handle_switch_three_phases_while_charging(bool& value) { +void evse_board_supportImpl::handle_ac_switch_three_phases_while_charging(bool& value) { mod->serial.switchThreePhasesWhileCharging(value); -}; +} -void board_support_ACImpl::handle_evse_replug(int& value) { +void evse_board_supportImpl::handle_evse_replug(int& value) { mod->serial.replug(value); -}; +} -double board_support_ACImpl::handle_read_pp_ampacity() { +types::board_support_common::ProximityPilot evse_board_supportImpl::handle_ac_read_pp_ampacity() { // FIXME: read PP ampacity from yeti, report back maximum current the hardware can handle for now std::lock_guard lock(capsMutex); - return caps.max_current_A_import; + return {types::board_support_common::Ampacity::A_32}; } -types::board_support::HardwareCapabilities board_support_ACImpl::handle_get_hw_capabilities() { - std::lock_guard lock(capsMutex); - return caps; -}; +void evse_board_supportImpl::handle_ac_set_overcurrent_limit_A(double& value) { + // your code for cmd ac_set_overcurrent_limit_A goes here +} } // namespace board_support } // namespace module diff --git a/modules/YetiDriver/board_support/board_support_ACImpl.hpp b/modules/YetiDriver/board_support/evse_board_supportImpl.hpp similarity index 59% rename from modules/YetiDriver/board_support/board_support_ACImpl.hpp rename to modules/YetiDriver/board_support/evse_board_supportImpl.hpp index 0141988b2e..936fb53d64 100644 --- a/modules/YetiDriver/board_support/board_support_ACImpl.hpp +++ b/modules/YetiDriver/board_support/evse_board_supportImpl.hpp @@ -1,14 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest -#ifndef BOARD_SUPPORT_BOARD_SUPPORT_AC_IMPL_HPP -#define BOARD_SUPPORT_BOARD_SUPPORT_AC_IMPL_HPP +#ifndef BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP +#define BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP // // AUTO GENERATED - MARKED REGIONS WILL BE KEPT // template version 3 // -#include +#include #include "../YetiDriver.hpp" @@ -21,11 +21,11 @@ namespace board_support { struct Conf {}; -class board_support_ACImpl : public board_support_ACImplBase { +class evse_board_supportImpl : public evse_board_supportImplBase { public: - board_support_ACImpl() = delete; - board_support_ACImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : - board_support_ACImplBase(ev, "board_support"), mod(mod), config(config){}; + evse_board_supportImpl() = delete; + evse_board_supportImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : + evse_board_supportImplBase(ev, "board_support"), mod(mod), config(config){}; // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 // insert your public definitions here @@ -33,18 +33,17 @@ class board_support_ACImpl : public board_support_ACImplBase { protected: // command handler functions (virtual) - virtual void handle_setup(bool& three_phases, bool& has_ventilation, std::string& country_code, - bool& rcd_enabled) override; - virtual types::board_support::HardwareCapabilities handle_get_hw_capabilities() override; + virtual void handle_setup(bool& three_phases, bool& has_ventilation, std::string& country_code) override; + virtual types::evse_board_support::HardwareCapabilities handle_get_hw_capabilities() override; virtual void handle_enable(bool& value) override; virtual void handle_pwm_on(double& value) override; virtual void handle_pwm_off() override; virtual void handle_pwm_F() override; - virtual void handle_allow_power_on(bool& value) override; - virtual bool handle_force_unlock() override; - virtual void handle_switch_three_phases_while_charging(bool& value) override; + virtual void handle_allow_power_on(types::evse_board_support::PowerOnOff& value) override; + virtual void handle_ac_switch_three_phases_while_charging(bool& value) override; virtual void handle_evse_replug(int& value) override; - virtual double handle_read_pp_ampacity() override; + virtual types::board_support_common::ProximityPilot handle_ac_read_pp_ampacity() override; + virtual void handle_ac_set_overcurrent_limit_A(double& value) override; // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 // insert your protected definitions here @@ -59,7 +58,7 @@ class board_support_ACImpl : public board_support_ACImplBase { // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 // insert your private definitions here - types::board_support::HardwareCapabilities caps; + types::evse_board_support::HardwareCapabilities caps; bool caps_received{false}; std::mutex capsMutex; // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 @@ -72,4 +71,4 @@ class board_support_ACImpl : public board_support_ACImplBase { } // namespace board_support } // namespace module -#endif // BOARD_SUPPORT_BOARD_SUPPORT_AC_IMPL_HPP +#endif // BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP diff --git a/modules/YetiDriver/manifest.yaml b/modules/YetiDriver/manifest.yaml index 42a4e5e666..9c97e7f66c 100644 --- a/modules/YetiDriver/manifest.yaml +++ b/modules/YetiDriver/manifest.yaml @@ -33,14 +33,11 @@ provides: interface: powermeter description: provides the Yeti Internal Power Meter board_support: - interface: board_support_AC - description: provides the board support Interface to low level control control pilot, relais, rcd, motor lock - yeti_extras: - interface: yeti_extras - description: extra functionality special for Yeti - yeti_simulation_control: - interface: yeti_simulation_control - description: Interface for the Yeti HIL simulator + interface: evse_board_support + description: provides the board support Interface to low level control control pilot, relais, motor lock + rcd: + interface: ac_rcd + description: RCD interface of the onboard RCD enable_external_mqtt: true enable_telemetry: true metadata: diff --git a/modules/YetiDriver/rcd/ac_rcdImpl.cpp b/modules/YetiDriver/rcd/ac_rcdImpl.cpp new file mode 100644 index 0000000000..a1c868f27a --- /dev/null +++ b/modules/YetiDriver/rcd/ac_rcdImpl.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "ac_rcdImpl.hpp" + +namespace module { +namespace rcd { + +void ac_rcdImpl::init() { +} + +void ac_rcdImpl::ready() { +} + +void ac_rcdImpl::handle_self_test() { + // your code for cmd self_test goes here +} + +bool ac_rcdImpl::handle_reset() { + // your code for cmd reset goes here + return true; +} + +} // namespace rcd +} // namespace module diff --git a/modules/YetiDriver/yeti_extras/yeti_extrasImpl.hpp b/modules/YetiDriver/rcd/ac_rcdImpl.hpp similarity index 66% rename from modules/YetiDriver/yeti_extras/yeti_extrasImpl.hpp rename to modules/YetiDriver/rcd/ac_rcdImpl.hpp index 2500653dd6..19cfe20aaa 100644 --- a/modules/YetiDriver/yeti_extras/yeti_extrasImpl.hpp +++ b/modules/YetiDriver/rcd/ac_rcdImpl.hpp @@ -1,14 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest -#ifndef YETI_EXTRAS_YETI_EXTRAS_IMPL_HPP -#define YETI_EXTRAS_YETI_EXTRAS_IMPL_HPP +#ifndef RCD_AC_RCD_IMPL_HPP +#define RCD_AC_RCD_IMPL_HPP // // AUTO GENERATED - MARKED REGIONS WILL BE KEPT // template version 3 // -#include +#include #include "../YetiDriver.hpp" @@ -17,15 +17,15 @@ // ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 namespace module { -namespace yeti_extras { +namespace rcd { struct Conf {}; -class yeti_extrasImpl : public yeti_extrasImplBase { +class ac_rcdImpl : public ac_rcdImplBase { public: - yeti_extrasImpl() = delete; - yeti_extrasImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : - yeti_extrasImplBase(ev, "yeti_extras"), mod(mod), config(config){}; + ac_rcdImpl() = delete; + ac_rcdImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : + ac_rcdImplBase(ev, "rcd"), mod(mod), config(config){}; // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 // insert your public definitions here @@ -33,7 +33,8 @@ class yeti_extrasImpl : public yeti_extrasImplBase { protected: // command handler functions (virtual) - virtual void handle_firmware_update(std::string& firmware_binary) override; + virtual void handle_self_test() override; + virtual bool handle_reset() override; // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 // insert your protected definitions here @@ -55,7 +56,7 @@ class yeti_extrasImpl : public yeti_extrasImplBase { // insert other definitions here // ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 -} // namespace yeti_extras +} // namespace rcd } // namespace module -#endif // YETI_EXTRAS_YETI_EXTRAS_IMPL_HPP +#endif // RCD_AC_RCD_IMPL_HPP diff --git a/modules/YetiDriver/yeti_extras/yeti_extrasImpl.cpp b/modules/YetiDriver/yeti_extras/yeti_extrasImpl.cpp deleted file mode 100644 index 33b60f295c..0000000000 --- a/modules/YetiDriver/yeti_extras/yeti_extrasImpl.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest -#include "yeti_extrasImpl.hpp" - -namespace module { -namespace yeti_extras { - -void yeti_extrasImpl::init() { - mod->serial.signalKeepAliveLo.connect([this](const KeepAliveLo& k) { - publish_time_stamp(k.time_stamp); - publish_hw_type(k.hw_type); - publish_hw_revision(k.hw_revision); - publish_protocol_version_major(k.protocol_version_major); - publish_protocol_version_minor(k.protocol_version_minor); - publish_sw_version_string(k.sw_version_string); - }); -} - -void yeti_extrasImpl::ready() { -} - -void yeti_extrasImpl::handle_firmware_update(std::string& firmware_binary){ - // your code for cmd firmware_update goes here -}; - -} // namespace yeti_extras -} // namespace module diff --git a/modules/YetiDriver/yeti_simulation_control/yeti_simulation_controlImpl.cpp b/modules/YetiDriver/yeti_simulation_control/yeti_simulation_controlImpl.cpp deleted file mode 100644 index 01ed7d279f..0000000000 --- a/modules/YetiDriver/yeti_simulation_control/yeti_simulation_controlImpl.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest -#include "yeti_simulation_controlImpl.hpp" - -namespace module { -namespace yeti_simulation_control { - -Everest::json simulation_feedback_to_json(const SimulationFeedback& s) { - Everest::json j; - - j["pwm_duty_cycle"] = s.pwmDutyCycle; - j["relais_on"] = s.relais_on; - j["evse_pwm_running"] = s.evse_pwm_running; - j["evse_pwm_voltage_hi"] = s.evse_pwm_voltage_hi; - j["evse_pwm_voltage_lo"] = s.evse_pwm_voltage_lo; - - return j; -} - -SimulationData json_to_simulation_data(Object& v) { - SimulationData s; - - s.cp_voltage = v["cp_voltage"]; - s.diode_fail = v["diode_fail"]; - s.error_e = v["error_e"]; - s.pp_resistor = v["pp_resistor"]; - s.rcd_current = v["rcd_current"]; - - s.currentL1 = v["currents"]["L1"]; - s.currentL2 = v["currents"]["L2"]; - s.currentL3 = v["currents"]["L3"]; - s.currentN = v["currents"]["N"]; - - s.voltageL1 = v["voltages"]["L1"]; - s.voltageL2 = v["voltages"]["L2"]; - s.voltageL3 = v["voltages"]["L3"]; - - s.freqL1 = v["frequencies"]["L1"]; - s.freqL2 = v["frequencies"]["L2"]; - s.freqL3 = v["frequencies"]["L3"]; - - return s; -} - -void yeti_simulation_controlImpl::init() { - mod->serial.signalSimulationFeedback.connect( - [this](const SimulationFeedback& s) { publish_simulation_feedback(simulation_feedback_to_json(s)); }); -} - -void yeti_simulation_controlImpl::ready() { -} - -void yeti_simulation_controlImpl::handle_enable(bool& value) { - EVLOG_info << "void YetiDriverModule::Yeti_simulation_control::enable: " << value; - mod->serial.enableSimulation(value); -}; - -void yeti_simulation_controlImpl::handle_setSimulationData(::types::yeti::SimulationData& value) { - json j_value = value; - Object o_value = j_value; - mod->serial.setSimulationData(json_to_simulation_data(o_value)); -}; - -} // namespace yeti_simulation_control -} // namespace module diff --git a/modules/YetiDriver/yeti_simulation_control/yeti_simulation_controlImpl.hpp b/modules/YetiDriver/yeti_simulation_control/yeti_simulation_controlImpl.hpp deleted file mode 100644 index 51ba8db8ab..0000000000 --- a/modules/YetiDriver/yeti_simulation_control/yeti_simulation_controlImpl.hpp +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Pionix GmbH and Contributors to EVerest -#ifndef YETI_SIMULATION_CONTROL_YETI_SIMULATION_CONTROL_IMPL_HPP -#define YETI_SIMULATION_CONTROL_YETI_SIMULATION_CONTROL_IMPL_HPP - -// -// AUTO GENERATED - MARKED REGIONS WILL BE KEPT -// template version 3 -// - -#include - -#include "../YetiDriver.hpp" - -// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 -// insert your custom include headers here -// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 - -namespace module { -namespace yeti_simulation_control { - -struct Conf {}; - -class yeti_simulation_controlImpl : public yeti_simulation_controlImplBase { -public: - yeti_simulation_controlImpl() = delete; - yeti_simulation_controlImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, - Conf& config) : - yeti_simulation_controlImplBase(ev, "yeti_simulation_control"), mod(mod), config(config){}; - - // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 - // insert your public definitions here - // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 - -protected: - // command handler functions (virtual) - virtual void handle_enable(bool& value) override; - virtual void handle_setSimulationData(types::yeti::SimulationData& value) override; - - // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 - // insert your protected definitions here - // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 - -private: - const Everest::PtrContainer& mod; - const Conf& config; - - virtual void init() override; - virtual void ready() override; - - // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 - // insert your private definitions here - // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 -}; - -// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 -// insert other definitions here -// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 - -} // namespace yeti_simulation_control -} // namespace module - -#endif // YETI_SIMULATION_CONTROL_YETI_SIMULATION_CONTROL_IMPL_HPP diff --git a/modules/simulation/JsCarSimulator/index.js b/modules/simulation/JsCarSimulator/index.js index e8a6605cc5..36514f4427 100644 --- a/modules/simulation/JsCarSimulator/index.js +++ b/modules/simulation/JsCarSimulator/index.js @@ -239,10 +239,12 @@ function car_statemachine(mod) { break; case 'error_e': + mod.simdata_setting.cp_voltage = 0.0; drawPower(mod, 0, 0, 0, 0); mod.simdata.error_e = true; break; case 'diode_fail': + mod.simdata_setting.cp_voltage = 9.0; drawPower(mod, 0, 0, 0, 0); mod.simdata.diode_fail = true; break; diff --git a/modules/simulation/JsYetiSimulator/index.js b/modules/simulation/JsYetiSimulator/index.js index 1f1d095782..2486e3ea29 100644 --- a/modules/simulation/JsYetiSimulator/index.js +++ b/modules/simulation/JsYetiSimulator/index.js @@ -12,26 +12,41 @@ const STATE_C = 3; const STATE_D = 4; const STATE_E = 5; const STATE_F = 6; -const STATE_DF = 7; - -const Event_CarPluggedIn = 0; -const Event_CarRequestedPower = 1; -const Event_PowerOn = 2; -const Event_PowerOff = 3; -const Event_CarRequestedStopPower = 4; -const Event_CarUnplugged = 5; -const Event_Error_E = 6; -const Event_Error_DF = 7; -const Event_Error_Relais = 8; -const Event_Error_RCD = 9; -const Event_Error_VentilationNotAvailable = 10; -const Event_EFtoBCD = 11; -const Event_BCDtoEF = 12; -const Event_PermanentFault = 13; + +const Event_PowerOn = 8; +const Event_PowerOff = 9; var module_id; let global_info; +var active_errors = { + DiodeFault: false, + BrownOut: false, + EnergyManagement: false, + PermanentFault: false, + MREC2GroundFailure: false, + MREC3HighTemperature: false, + MREC4OverCurrentFailure: false, + MREC5OverVoltage: false, + MREC6UnderVoltage: false, + MREC8EmergencyStop: false, + MREC10InvalidVehicleMode: false, + MREC14PilotFault: false, + MREC15PowerLoss: false, + MREC17EVSEContactorFault: false, + MREC18CableOverTempDerate: false, + MREC19CableOverTempStop: false, + MREC20PartialInsertion: false, + MREC23ProximityFault: false, + MREC24ConnectorVoltageHigh: false, + MREC25BrokenLatch: false, + MREC26CutCable: false, +} + +function publish_ac_nr_of_phases_available(mod, n) { + mod.provides.board_support.publish.ac_nr_of_phases_available(n); +} + boot_module(async ({ setup, info, config, mqtt, }) => { @@ -39,11 +54,9 @@ boot_module(async ({ // register commands setup.provides.yeti_simulation_control.register.enable(enable_simulation); setup.provides.yeti_simulation_control.register.setSimulationData((mod, args) => { - /* evlog.error(args.value); */ mod.simulation_data = args.value; }); - setup.provides.yeti_extras.register.firmware_update((mod, args) => { }); setup.provides.powermeter.register.stop_transaction((mod, args) => ({ status: 'NOT_IMPLEMENTED', error: 'YetiDriver does not support stop transaction request.', @@ -54,8 +67,10 @@ boot_module(async ({ mod.three_phases = args.three_phases; mod.has_ventilation = args.has_ventilation; mod.country_code = args.country_code; - mod.rcd_enabled = args.rcd_enabled; - publish_nr_of_phases_available(mod, (mod.use_three_phases_confirmed ? 3 : 1)); + publish_ac_nr_of_phases_available(mod, (mod.use_three_phases_confirmed ? 3 : 1)); + }); + + setup.provides.board_support.register.ac_set_overcurrent_limit_A((mod, args) => { }); setup.provides.board_support.register.enable((mod, args) => { @@ -64,20 +79,21 @@ boot_module(async ({ } else mod.current_state = STATE_DISABLED; }); - setup.provides.board_support.register.pwm_on((mod, args) => { pwmOn(mod, args.value); }); + setup.provides.board_support.register.pwm_on((mod, args) => { pwmOn(mod, args.value / 100.0); }); setup.provides.board_support.register.pwm_off((mod, args) => { pwmOff(mod); }); setup.provides.board_support.register.pwm_F((mod, args) => { pwmF(mod); }); setup.provides.board_support.register.evse_replug((mod, args) => { evlog.error('Replugging not supported'); }); + setup.provides.board_support.register.allow_power_on((mod, args) => { - mod.power_on_allowed = args.value; + mod.power_on_allowed = args.value.allow_power_on; }); - setup.provides.board_support.register.force_unlock((mod, args) => /* lock/unlock not implemented */ true); - setup.provides.board_support.register.switch_three_phases_while_charging((mod, args) => { + + setup.provides.board_support.register.ac_switch_three_phases_while_charging((mod, args) => { mod.use_three_phases = args.value; mod.use_three_phases_confirmed = args.value; - publish_nr_of_phases_available(mod, (mod.use_three_phases_confirmed ? 3 : 1)); + ac_publish_nr_of_phases_available(mod, (mod.use_three_phases_confirmed ? 3 : 1)); }); setup.provides.board_support.register.get_hw_capabilities((mod, args) => ({ max_current_A_import: 32.0, @@ -89,13 +105,87 @@ boot_module(async ({ max_phase_count_export: 3, min_phase_count_export: 1, supports_changing_phases_during_charging: true, + connector_type: "IEC62196Type2Cable", })); - setup.provides.board_support.register.read_pp_ampacity((mod, args) => { - let amp = read_pp_ampacity(mod); + setup.provides.board_support.register.ac_read_pp_ampacity((mod, args) => { + let amp = { ampacity: read_pp_ampacity(mod) }; return amp; }); - // subscribe vars of used modules + // Subscribe to nodered error injection + mqtt.subscribe(`everest_external/nodered/${config.module.connector_id}/carsim/error`, (mod, en) => { + let e = JSON.parse(en); + let raise = e.raise == 'true'; + + switch (e.error_type) { + case "DiodeFault": + error_DiodeFault(mod, raise); + break; + case "BrownOut": + error_BrownOut(mod, raise); + break; + case "EnergyManagement": + error_EnergyManagement(mod, raise); + break; + case "PermanentFault": + error_PermanentFault(mod, raise); + break; + case "MREC2GroundFailure": + error_MREC2GroundFailure(mod, raise); + break; + case "MREC3HighTemperature": + error_MREC3HighTemperature(mod, raise); + break; + case "MREC4OverCurrentFailure": + error_MREC4OverCurrentFailure(mod, raise); + break; + case "MREC5OverVoltage": + error_MREC5OverVoltage(mod, raise); + break; + case "MREC6UnderVoltage": + error_MREC6UnderVoltage(mod, raise); + break; + case "MREC8EmergencyStop": + error_MREC8EmergencyStop(mod, raise); + break; + case "MREC10InvalidVehicleMode": + error_MREC10InvalidVehicleMode(mod, raise); + break; + case "MREC14PilotFault": + error_MREC14PilotFault(mod, raise); + break; + case "MREC15PowerLoss": + error_MREC15PowerLoss(mod, raise); + break; + case "MREC17EVSEContactorFault": + error_MREC17EVSEContactorFault(mod, raise); + break; + case "MREC18CableOverTempDerate": + error_MREC18CableOverTempDerate(mod, raise); + break; + case "MREC19CableOverTempStop": + error_MREC19CableOverTempStop(mod, raise); + break; + case "MREC20PartialInsertion": + error_MREC20PartialInsertion(mod, raise); + break; + case "MREC23ProximityFault": + error_MREC23ProximityFault(mod, raise); + break; + case "MREC24ConnectorVoltageHigh": + error_MREC24ConnectorVoltageHigh(mod, raise); + break; + case "MREC25BrokenLatch": + error_MREC25BrokenLatch(mod, raise); + break; + case "MREC26CutCable": + error_MREC26CutCable(mod, raise); + break; + default: + evlog.error("Unknown error raised via MQTT"); + } + }); + }).then((mod) => { mod.pubCnt = 0; clearData(mod); @@ -117,7 +207,7 @@ function telemetry_fast(mod) { mod.telemetry_data.power_path_controller.timestamp = date.toISOString(); mod.telemetry_data.power_path_controller.cp_voltage_high = mod.cpHi; mod.telemetry_data.power_path_controller.cp_voltage_low = mod.cpLo; - mod.telemetry_data.power_path_controller.cp_pwm_duty_cycle = mod.pwm_duty_cycle * 100.; + mod.telemetry_data.power_path_controller.cp_pwm_duty_cycle = mod.pwm_duty_cycle * 100.0; mod.telemetry_data.power_path_controller.cp_state = stateToString(mod); mod.telemetry_data.power_path_controller.temperature_controller = mod.powermeter.tempL1; @@ -143,13 +233,14 @@ function telemetry_fast(mod) { } function publish_event(mod, event) { - // console.log("------------ EVENT PUB "+event); - mod.provides.board_support.publish.event(event_to_enum(event)); + //console.log("------------ EVENT PUB " + event); + mod.provides.board_support.publish.event({ event: event_to_enum(event) }); } + function enable_simulation(mod, args) { if (mod.simulation_enabled && !args.value) { - publish_event(mod, Event_CarUnplugged); + publish_event(mod, Event_A); clearData(mod); } mod.simulation_enabled = args.value; @@ -172,7 +263,6 @@ function simulation_loop(mod) { publish_telemetry(mod); break; case 2: - publish_yeti_extras(mod); break; case 3: publish_keepalive(mod); @@ -187,6 +277,10 @@ function simulation_loop(mod) { // state machine for the evse function simulation_statemachine(mod) { + if (mod.last_state != mod.current_state) { + publish_event(mod, mod.current_state); + } + switch (mod.current_state) { case STATE_DISABLED: powerOff(); @@ -199,15 +293,9 @@ function simulation_statemachine(mod) { reset_powermeter(mod); mod.simplified_mode = false; - if (mod.last_state === STATE_B || mod.last_state === STATE_C || mod.last_state === STATE_D) { - publish_event(mod, Event_BCDtoEF); - } - if (mod.last_state != STATE_A && mod.last_state != STATE_DISABLED && mod.last_state != STATE_F) { - publish_event(mod, Event_CarRequestedStopPower); powerOff(mod); - publish_event(mod, Event_CarUnplugged); // If car was unplugged, reset RCD flag. mod.rcd_current = 0.1; @@ -220,70 +308,41 @@ function simulation_statemachine(mod) { // responds to EV opens S2 (w/o PWM) if (mod.last_state != STATE_A && mod.last_state != STATE_B) { - publish_event(mod, Event_CarRequestedStopPower); + // Need to switch off according to Table A.6 Sequence 8.1 within powerOff(mod); } // Table A.6: Sequence 1.1 Plug-in if (mod.last_state === STATE_A) { - publish_event(mod, Event_CarPluggedIn); mod.simplified_mode = false; } - if (mod.last_state === STATE_E || mod.last_state === STATE_F) { - publish_event(mod, Event_EFtoBCD); - } break; case STATE_C: // Table A.6: Sequence 1.2 Plug-in if (mod.last_state === STATE_A) { - publish_event(mod, Event_CarPluggedIn); mod.simplified_mode = true; } - if (mod.last_state === STATE_B) { - publish_event(mod, Event_CarRequestedPower); - } - if (mod.last_state === STATE_E || mod.last_state === STATE_F) { - publish_event(mod, Event_EFtoBCD); - } if (!mod.pwm_running) { // C1 // Table A.6 Sequence 10.2: EV does not stop drawing power even // if PWM stops. Stop within 6 seconds (E.g. Kona1!) - // if (mod.last_pwm_running) FIXME implement 6 second timer! - // startTimer(6000); - // if (timerElapsed()) { - // force power off under load - powerOff(mod); - // } + // This is implemented in EvseManager + if (!mod.power_on_allowed) powerOff(mod); } else { // C2 if (mod.power_on_allowed) { // Table A.6: Sequence 4 EV ready to charge. // Must enable power within 3 seconds. powerOn(mod); - - // Simulate Request power Event here for simplified mode to - // ensure that this mode behaves similar for higher layers. - // Note this does not work with 5% mode correctly, but - // simplified mode does not support HLC anyway. - if (!mod.last_pwm_running && mod.simplified_mode) publish_event(mod, Event_CarRequestedPower); } } break; case STATE_D: // Table A.6: Sequence 1.2 Plug-in (w/ventilation) if (mod.last_state === STATE_A) { - publish_event(mod, Event_CarPluggedIn); - publish_event(mod, Event_CarRequestedPower); mod.simplified_mode = true; } - if (mod.last_state === STATE_B) { - publish_event(mod, Event_CarRequestedPower); - } - if (mod.last_state === STATE_E || mod.last_state === STATE_F) { - publish_event(mod, Event_EFtoBCD); - } if (!mod.pwm_running) { // Table A.6 Sequence 10.2: EV does not stop drawing power @@ -298,34 +357,15 @@ function simulation_statemachine(mod) { if (mod.power_on_allowed && !mod.relais_on) { // Table A.6: Sequence 4 EV ready to charge. // Must enable power within 3 seconds. - if (!mod.has_ventilation) publish_event(mod, Event_Error_VentilationNotAvailable); - else powerOn(mod); - } - if (mod.last_state === STATE_C) { - // Car switches to ventilation while charging. - if (!mod.has_ventilation) publish_event(mod, Event_Error_VentilationNotAvailable); + if (mod.has_ventilation) powerOn(mod); } } break; case STATE_E: - if (mod.last_state != mod.current_state) publish_event(mod, Event_Error_E); - if (mod.last_state === STATE_B || mod.last_state === STATE_C || mod.last_state === STATE_D) { - publish_event(mod, Event_BCDtoEF); - } powerOff(mod); pwmOff(mod); break; case STATE_F: - if (mod.last_state === STATE_B || mod.last_state === STATE_C || mod.last_state === STATE_D) { - publish_event(mod, Event_BCDtoEF); - } - powerOff(mod); - break; - case STATE_DF: - if (mod.last_state != mod.current_state) publish_event(mod, Event_Error_DF); - if (mod.last_state === STATE_B || mod.last_state === STATE_C || mod.last_state === STATE_D) { - publish_event(mod, Event_BCDtoEF); - } powerOff(mod); break; } @@ -335,46 +375,38 @@ function simulation_statemachine(mod) { function check_error_rcd(mod) { if (mod.rcd_enabled && mod.simulation_data.rcd_current > 5.0) { - publish_event(mod, Event_Error_RCD); + if (!mod.rcd_error_reported) { + mod.provides.rcd.publish.fault_dc(); + mod.rcd_error_reported = true; + } + } else { + mod.rcd_error_reported = false; } -} - -function publish_nr_of_phases_available(mod, n) { - // console.log("------------ NR PHASE PUB "+n); - mod.provides.board_support.publish.nr_of_phases_available(n); + mod.provides.rcd.publish.rcd_current_mA = mod.simulation_data.rcd_current; } function event_to_enum(event) { switch (event) { - case Event_CarPluggedIn: - return 'CarPluggedIn'; - case Event_CarRequestedPower: - return 'CarRequestedPower'; + case STATE_A: + return 'A'; + case STATE_B: + return 'B'; + case STATE_C: + return 'C'; + case STATE_D: + return 'D'; + case STATE_E: + return 'E'; + case STATE_F: + return 'F'; + case STATE_DISABLED: + return 'F'; case Event_PowerOn: return 'PowerOn'; case Event_PowerOff: return 'PowerOff'; - case Event_CarRequestedStopPower: - return 'CarRequestedStopPower'; - case Event_CarUnplugged: - return 'CarUnplugged'; - case Event_Error_E: - return 'ErrorE'; - case Event_Error_DF: - return 'ErrorDF'; - case Event_Error_Relais: - return 'ErrorRelais'; - case Event_Error_RCD: - return 'ErrorRCD'; - case Event_Error_VentilationNotAvailable: - return 'VentilationNotAvailable'; - case Event_EFtoBCD: - return 'EFtoBCD'; - case Event_BCDtoEF: - return 'BCDtoEF'; - case Event_PermanentFault: - return 'PermanentFault'; default: + evlog.error("Invalid event: " + event); return 'invalid'; } } @@ -448,10 +480,13 @@ function read_from_car(mod) { if (is_voltage_in_range(cpLo, 0.0) && is_voltage_in_range(cpHi, 0.0)) mod.current_state = STATE_E; // Diode fault else if (is_voltage_in_range(cpHi + cpLo, 0.0)) { - mod.current_state = STATE_DF; + // throw DF error + error_DiodeFault(mod, true); } else return; } else if (is_voltage_in_range(cpHi, 12.0)) { // +12V State A IDLE (open circuit) + // clear all errors that clear on disconnection + clear_disconnect_errors(mod); mod.current_state = STATE_A; } else if (is_voltage_in_range(cpHi, 9.0)) { mod.current_state = STATE_B; @@ -466,6 +501,227 @@ function read_from_car(mod) { } } +function error_BrownOut(mod, raise) { + if (!active_errors.BrownOut) { + mod.provides.board_support.raise.evse_board_support_BrownOut('Simulated fault event', 'High'); + active_errors.BrownOut = true; + } else if (!raise && active_errors.BrownOut) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_BrownOut(); + active_errors.BrownOut = false; + } +} + +function error_EnergyManagement(mod, raise) { + if (!active_errors.EnergyManagement) { + mod.provides.board_support.raise.evse_board_support_EnergyManagement('Simulated fault event', 'High'); + active_errors.EnergyManagement = true; + } else if (!raise && active_errors.EnergyManagement) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_EnergyManagement(); + active_errors.EnergyManagement = false; + } +} + +function error_PermanentFault(mod, raise) { + if (!active_errors.PermanentFault) { + mod.provides.board_support.raise.evse_board_support_PermanentFault('Simulated fault event', 'High'); + active_errors.PermanentFault = true; + } else if (!raise && active_errors.PermanentFault) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_PermanentFault(); + active_errors.PermanentFault = false; + } +} + +function error_MREC2GroundFailure(mod, raise) { + if (!active_errors.MREC2GroundFailure) { + mod.provides.board_support.raise.evse_board_support_MREC2GroundFailure('Simulated fault event', 'High'); + active_errors.MREC2GroundFailure = true; + } else if (!raise && active_errors.MREC2GroundFailure) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC2GroundFailure(); + active_errors.MREC2GroundFailure = false; + } +} + +function error_MREC3HighTemperature(mod, raise) { + if (!active_errors.MREC3HighTemperature) { + mod.provides.board_support.raise.evse_board_support_MREC3HighTemperature('Simulated fault event', 'High'); + active_errors.MREC3HighTemperature = true; + } else if (!raise && active_errors.MREC3HighTemperature) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC3HighTemperature(); + active_errors.MREC3HighTemperature = false; + } +} + +function error_MREC4OverCurrentFailure(mod, raise) { + if (!active_errors.MREC4OverCurrentFailure) { + mod.provides.board_support.raise.evse_board_support_MREC4OverCurrentFailure('Simulated fault event', 'High'); + active_errors.MREC4OverCurrentFailure = true; + } else if (!raise && active_errors.MREC4OverCurrentFailure) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC4OverCurrentFailure(); + active_errors.MREC4OverCurrentFailure = false; + } +} + +function error_MREC5OverVoltage(mod, raise) { + if (!active_errors.MREC5OverVoltage) { + mod.provides.board_support.raise.evse_board_support_MREC5OverVoltage('Simulated fault event', 'High'); + active_errors.MREC5OverVoltage = true; + } else if (!raise && active_errors.MREC5OverVoltage) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC5OverVoltage(); + active_errors.MREC5OverVoltage = false; + } +} + +function error_MREC6UnderVoltage(mod, raise) { + if (!active_errors.MREC6UnderVoltage) { + mod.provides.board_support.raise.evse_board_support_MREC6UnderVoltage('Simulated fault event', 'High'); + active_errors.MREC6UnderVoltage = true; + } else if (!raise && active_errors.MREC6UnderVoltage) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC6UnderVoltage(); + active_errors.MREC6UnderVoltage = false; + } +} + +function error_MREC8EmergencyStop(mod, raise) { + if (!active_errors.MREC8EmergencyStop) { + mod.provides.board_support.raise.evse_board_support_MREC8EmergencyStop('Simulated fault event', 'High'); + active_errors.MREC8EmergencyStop = true; + } else if (!raise && active_errors.MREC8EmergencyStop) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC8EmergencyStop(); + active_errors.MREC8EmergencyStop = false; + } +} + +function error_MREC10InvalidVehicleMode(mod, raise) { + if (!active_errors.MREC10InvalidVehicleMode) { + mod.provides.board_support.raise.evse_board_support_MREC10InvalidVehicleMode('Simulated fault event', 'High'); + active_errors.MREC10InvalidVehicleMode = true; + } else if (!raise && active_errors.MREC10InvalidVehicleMode) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC10InvalidVehicleMode(); + active_errors.MREC10InvalidVehicleMode = false; + } +} + +function error_MREC14PilotFault(mod, raise) { + if (!active_errors.MREC14PilotFault) { + mod.provides.board_support.raise.evse_board_support_MREC14PilotFault('Simulated fault event', 'High'); + active_errors.MREC14PilotFault = true; + } else if (!raise && active_errors.MREC14PilotFault) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC14PilotFault(); + active_errors.MREC14PilotFault = false; + } +} + +function error_MREC15PowerLoss(mod, raise) { + if (!active_errors.MREC15PowerLoss) { + mod.provides.board_support.raise.evse_board_support_MREC15PowerLoss('Simulated fault event', 'High'); + active_errors.MREC15PowerLoss = true; + } else if (!raise && active_errors.MREC15PowerLoss) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC15PowerLoss(); + active_errors.MREC15PowerLoss = false; + } +} + +function error_MREC17EVSEContactorFault(mod, raise) { + if (!active_errors.MREC17EVSEContactorFault) { + mod.provides.board_support.raise.evse_board_support_MREC17EVSEContactorFault('Simulated fault event', 'High'); + active_errors.MREC17EVSEContactorFault = true; + } else if (!raise && active_errors.MREC17EVSEContactorFault) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC17EVSEContactorFault(); + active_errors.MREC17EVSEContactorFault = false; + } +} + +function error_MREC18CableOverTempDerate(mod, raise) { + if (!active_errors.MREC18CableOverTempDerate) { + mod.provides.board_support.raise.evse_board_support_MREC18CableOverTempDerate('Simulated fault event', 'High'); + active_errors.MREC18CableOverTempDerate = true; + } else if (!raise && active_errors.MREC18CableOverTempDerate) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC18CableOverTempDerate(); + active_errors.MREC18CableOverTempDerate = false; + } +} + +function error_MREC19CableOverTempStop(mod, raise) { + if (!active_errors.MREC19CableOverTempStop) { + mod.provides.board_support.raise.evse_board_support_MREC19CableOverTempStop('Simulated fault event', 'High'); + active_errors.MREC19CableOverTempStop = true; + } else if (!raise && active_errors.MREC19CableOverTempStop) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC19CableOverTempStop(); + active_errors.MREC19CableOverTempStop = false; + } +} + +function error_MREC20PartialInsertion(mod, raise) { + if (!active_errors.MREC20PartialInsertion) { + mod.provides.board_support.raise.evse_board_support_MREC20PartialInsertion('Simulated fault event', 'High'); + active_errors.MREC20PartialInsertion = true; + } else if (!raise && active_errors.MREC20PartialInsertion) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC20PartialInsertion(); + active_errors.MREC20PartialInsertion = false; + } +} + +function error_MREC23ProximityFault(mod, raise) { + if (!active_errors.MREC23ProximityFault) { + mod.provides.board_support.raise.evse_board_support_MREC23ProximityFault('Simulated fault event', 'High'); + active_errors.MREC23ProximityFault = true; + } else if (!raise && active_errors.MREC23ProximityFault) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC23ProximityFault(); + active_errors.MREC23ProximityFault = false; + } +} + + +function error_MREC24ConnectorVoltageHigh(mod, raise) { + if (!active_errors.MREC24ConnectorVoltageHigh) { + mod.provides.board_support.raise.evse_board_support_MREC24ConnectorVoltageHigh('Simulated fault event', 'High'); + active_errors.MREC24ConnectorVoltageHigh = true; + } else if (!raise && active_errors.MREC24ConnectorVoltageHigh) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC24ConnectorVoltageHigh(); + active_errors.MREC24ConnectorVoltageHigh = false; + } +} + +function error_MREC25BrokenLatch(mod, raise) { + if (!active_errors.MREC25BrokenLatch) { + mod.provides.board_support.raise.evse_board_support_MREC25BrokenLatch('Simulated fault event', 'High'); + active_errors.MREC25BrokenLatch = true; + } else if (!raise && active_errors.MREC25BrokenLatch) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC25BrokenLatch(); + active_errors.MREC25BrokenLatch = false; + } +} + +function error_MREC26CutCable(mod, raise) { + if (raise && !active_errors.MREC26CutCable) { + mod.provides.board_support.raise.evse_board_support_MREC26CutCable('Simulated fault event', 'High'); + active_errors.MREC26CutCable = true; + } else if (!raise && active_errors.MREC26CutCable) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_MREC26CutCable('Simulated fault event', 'High'); + active_errors.MREC26CutCable = false; + } +} + +function error_DiodeFault(mod, raise) { + if (raise && !active_errors.DiodeFault) { + mod.provides.board_support.raise.evse_board_support_DiodeFault('Simulated fault event', 'High'); + active_errors.DiodeFault = true; + } else if (!raise && active_errors.DiodeFault) { + mod.provides.board_support.request_clear_all_of_type.evse_board_support_DiodeFault(); + active_errors.DiodeFault = false; + } +} + +// Example of automatically reset errors up on disconnection of the vehicle. +// All other errors need to be cleared explicitly. +// Note that in real life the clearing of errors may differ between BSPs depending on the +// hardware implementation. +function clear_disconnect_errors(mod) { + if (active_errors.DiodeFault) { + error_DiodeFault(mod, false); + } +} + // checks if voltage is within center+-interval function is_voltage_in_range(voltage, center) { const interval = 1.1; @@ -516,6 +772,7 @@ function clearData(mod) { mod.rcd_current = 0.1; mod.rcd_enabled = true; mod.rcd_error = false; + mod.rcd_error_reported = false; mod.simulation_enabled = false; mod.pwm_duty_cycle = 0; @@ -653,9 +910,6 @@ function stateToString(mod) { case STATE_F: return 'F'; break; - case STATE_DF: - return 'DF'; - break; } } @@ -699,9 +953,6 @@ function power_meter_external(p) { function publish_powermeter(mod) { mod.provides.powermeter.publish.powermeter(power_meter_external(mod.powermeter)); - // Publish raw packet for debugging purposes - mod.provides.debug_powermeter.publish.debug_json(mod.powermeter); - // Deprecated external stuff mod.mqtt.publish('/external/powermeter/vrmsL1', mod.powermeter.vrmsL1); mod.mqtt.publish('/external/powermeter/phaseSeqError', false); @@ -743,24 +994,14 @@ function publish_keepalive(mod) { function publish_telemetry(mod) { mod.provides.board_support.publish.telemetry({ - temperature: mod.powermeter.tempL1, + evse_temperature_C: mod.powermeter.tempL1, fan_rpm: 1500.0, supply_voltage_12V: 12.01, supply_voltage_minus_12V: -11.8, - rcd_current: mod.rcd_current, relais_on: mod.relais_on, }); } -function publish_yeti_extras(mod) { - mod.provides.yeti_extras.publish.time_stamp(Math.round(new Date().getTime() / 1000)); - mod.provides.yeti_extras.publish.hw_type(0); - mod.provides.yeti_extras.publish.hw_revision(0); - mod.provides.yeti_extras.publish.protocol_version_major(0); - mod.provides.yeti_extras.publish.protocol_version_minor(1); - mod.provides.yeti_extras.publish.sw_version_string('simulation'); -} - function publish_yeti_simulation_control(mod) { mod.provides.yeti_simulation_control.publish.enabled(mod.simulation_enabled); mod.provides.yeti_simulation_control.publish.simulation_feedback({ @@ -770,13 +1011,6 @@ function publish_yeti_simulation_control(mod) { evse_pwm_voltage_hi: mod.pwm_voltage_hi, evse_pwm_voltage_lo: mod.pwm_voltage_lo, }); - /* evlog.error({ - pwm_duty_cycle: mod.pwm_duty_cycle, - relais_on: (mod.relais_on?(mod.use_three_phases_confirmed?3:1):0), - evse_pwm_running: mod.pwm_running, - evse_pwm_voltage_hi: mod.pwm_voltage_hi, - evse_pwm_voltage_lo: mod.pwm_voltage_lo - }); */ } function simulate_powermeter(mod) { @@ -828,19 +1062,19 @@ function read_pp_ampacity(mod) { let pp_resistor = mod.simulation_data.pp_resistor; if (pp_resistor < 80.0 || pp_resistor > 2460) { evlog.error(`PP resistor value "${pp_resistor}" Ohm seems to be outside the allowed range.`); - return 0.0 + return "None" } // PP resistor value in spec, use a conservative interpretation of the resistance ranges if (pp_resistor > 936.0 && pp_resistor <= 2460.0) { - return 13.0; + return "A_13"; } else if (pp_resistor > 308.0 && pp_resistor <= 936.0) { - return 20.0; + return "A_20"; } else if (pp_resistor > 140.0 && pp_resistor <= 308.0) { - return 32.0; + return "A_32"; } else if (pp_resistor > 80.0 && pp_resistor <= 140.0) { - return 63.0; + return "A_63"; } - return 0.0; + return "None"; } diff --git a/modules/simulation/JsYetiSimulator/manifest.yaml b/modules/simulation/JsYetiSimulator/manifest.yaml index e009ecd942..ea2a03b58d 100644 --- a/modules/simulation/JsYetiSimulator/manifest.yaml +++ b/modules/simulation/JsYetiSimulator/manifest.yaml @@ -1,29 +1,21 @@ description: SIL simulator for YETI hardware v1.0 +config: + connector_id: + description: Connector id of the evse manager to which this simulator is connected to + type: integer provides: powermeter: interface: powermeter description: provides the Yeti Internal Power Meter board_support: - interface: board_support_AC + interface: evse_board_support description: provides the board support Interface to low level control control pilot, relais, rcd, motor lock - yeti_extras: - interface: yeti_extras - description: extra functionality special for Yeti - debug_yeti: - interface: debug_json - description: provides the debug information of the charging driver - debug_powermeter: - interface: debug_json - description: Provides the powermeter as a json object - debug_state: - interface: debug_json - description: Provides the state as a json object - debug_keepalive: - interface: debug_json - description: Provides the keepalive as a json object yeti_simulation_control: interface: yeti_simulation_control description: Interface for the Yeti HIL simulator + rcd: + interface: ac_rcd + description: Interface for the simulated AC RCD enable_external_mqtt: true enable_telemetry: true metadata: diff --git a/types/board_support_common.yaml b/types/board_support_common.yaml new file mode 100644 index 0000000000..53a13aedb7 --- /dev/null +++ b/types/board_support_common.yaml @@ -0,0 +1,48 @@ +description: EV and EVSE board support types +types: + BspEvent: + description: >- + Event stream from ControlPilot signal/Relais. + + A-F: CP states as defined in IEC61851-1 + PowerOn: Hardware confirms that contactors switched on correctly (typically mirror contacts indicated successful switch on) + PowerOff: Hardware confirms that contactors switched off correctly and are not welded (typically mirror contacts indicated successful switch off) + + EvseReplugStarted: Special testing sequence: virtual replugging started + EvseReplugFinished: Special testing sequence: virtual replugging stopped + Disconnected: Only used on EV side: Not connected to a charging station. Do not use on EVSE side. + type: object + additionalProperties: false + required: + - event + properties: + event: + type: string + enum: + - 'A' + - 'B' + - 'C' + - 'D' + - 'E' + - 'F' + - 'PowerOn' + - 'PowerOff' + - 'EvseReplugStarted' + - 'EvseReplugFinished' + - 'Disconnected' + ProximityPilot: + description: Current capability (ampacity) of the cable + type: object + additionalProperties: false + required: + - ampacity + properties: + ampacity: + description: Ampacity value of the cable assembly + type: string + enum: + - None + - A_13 + - A_20 + - A_32 + - A_63_3ph_70_1ph diff --git a/types/board_support.yaml b/types/evse_board_support.yaml similarity index 72% rename from types/board_support.yaml rename to types/evse_board_support.yaml index 965f1d2791..210a55cf2f 100644 --- a/types/board_support.yaml +++ b/types/evse_board_support.yaml @@ -14,6 +14,7 @@ types: - max_phase_count_export - min_phase_count_export - supports_changing_phases_during_charging + - connector_type properties: max_current_A_import: description: Maximum current (ampere) the hardware can handle (import from grid) @@ -56,52 +57,31 @@ types: Indicates whether changing number of phases is supported during charging (true) or not (false) type: boolean - Event: - description: Event from ControlPilot signal/Relais/RCD - type: string - enum: - - CarPluggedIn - - CarRequestedPower - - PowerOn - - PowerOff - - CarRequestedStopPower - - CarUnplugged - - ErrorE - - ErrorDF - - ErrorRelais - - ErrorRCD - - ErrorRCD_DC - - ErrorVentilationNotAvailable - - ErrorOverCurrent - - ErrorOverVoltage - - ErrorUnderVoltage - - ErrorMotorLock - - ErrorOverTemperature - - ErrorBrownOut - - ErrorCablePP - - ErrorEnergyManagement - - ErrorNeutralPEN - - ErrorCpDriver - - EFtoBCD - - BCDtoEF - - PermanentFault - - EvseReplugStarted - - EvseReplugFinished + max_gun_temperature_C: + type: number + connector_type: + description: Type of charging connector available at this EVSE + type: string + enum: + - IEC62196Type2Cable + - IEC62196Type2Socket Telemetry: description: Other telemetry type: object additionalProperties: false required: - - temperature + - evse_temperature_C - fan_rpm - supply_voltage_12V - supply_voltage_minus_12V - - rcd_current - relais_on properties: - temperature: + evse_temperature_C: description: Current temperature of the EVSE in degree celsius type: number + gun_temperature_C: + description: Current temperature of the gun in degree celsius + type: number fan_rpm: description: RPM of the fan. 0 if off or no fan available. type: number @@ -111,9 +91,26 @@ types: supply_voltage_minus_12V: description: Internal -12V supply voltage type: number - rcd_current: - description: Residual current in mA - type: number relais_on: description: true if power to the car is currently on, false if off type: boolean + PowerOnOff: + description: Flag and context for switching power on/off. In some architectures e.g. DC power + train needs to know whether it should switch on with limited current or full current on the output contactors. + type: object + additionalProperties: false + required: + - allow_power_on + - reason + properties: + allow_power_on: + description: Allow switching on (true) or force switching off (false) + type: boolean + reason: + description: Reason for switching on/off + type: string + enum: + - DCCableCheck + - DCPreCharge + - FullPowerCharging + - PowerOff \ No newline at end of file diff --git a/types/evse_manager.yaml b/types/evse_manager.yaml index acc4783af6..b1abc1f947 100644 --- a/types/evse_manager.yaml +++ b/types/evse_manager.yaml @@ -212,48 +212,20 @@ types: type: string ErrorEnum: description: >- - Details on error type - Car: Signals a car error - CarDiodeFault: Signals a car diode fault - Relais: Signals a relais error - RCD: Signals an RCD error (AC) - RCDDC: Signal an RCD error (DC) - VentilationNotAvailable: Signals that ventilation is not available - OverCurrent: Signals an overcurrent error - OverVoltage: Signals on overvoltage error - UnderVoltage: Signals on overvoltage error - Internal: Signals an internal error - SLAC: Signals a SLAC communication error - HLC: Signals a HLC error - MotorLock: Signals an error with the motor lock of the connector - OverTemperature: Signals an over temperature error - BrownOut: Brown out detected in electronics - CablePP: PP resistor value in cable is out of range - EnergyManagement: An error from the energy management was reported - NeutralPEN: Nutral/PEN fault - CpDriver: The CP output driver has a fault + Note this is only kept for compatibility with the legacy error handling and will be removed soon. type: string enum: - Car - CarDiodeFault - - Relais - RCD - RCDDC - VentilationNotAvailable - - OverCurrent - - OverVoltage - - UnderVoltage - Internal - SLAC - HLC - Other - - MotorLock - - OverTemperature - BrownOut - - CablePP - EnergyManagement - - NeutralPEN - - CpDriver Error: description: >- Error object that contains information about the error and optional vendor error information