diff --git a/README.md b/README.md index f946caec3..1e41862a6 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,13 @@ cd ~/checkout/everest-workspace/everest-utils/ev-dev-tools python3 -m pip install . ``` +Change the directory and install the required packages for ISO15118 communication: + +```bash +cd ~/checkout/everest-workspace/Josev +python3 -m pip install -r requirements.txt +``` + Now we can build EVerest! ```bash diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index fdd71d4cf..2fbf45e16 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -1,7 +1,9 @@ generate_config_run_script(CONFIG sil) generate_config_run_script(CONFIG sil-two-evse) -generate_config_run_script(CONFIG hil) generate_config_run_script(CONFIG sil-ocpp) +generate_config_run_script(CONFIG sil-dc) +generate_config_run_script(CONFIG sil-two-evse-dc) +generate_config_run_script(CONFIG hil) # install configs install( diff --git a/config/config-sil-dc.yaml b/config/config-sil-dc.yaml new file mode 100644 index 000000000..8912f5b50 --- /dev/null +++ b/config/config-sil-dc.yaml @@ -0,0 +1,118 @@ +active_modules: + iso15118_charger: + module: PyJosev + config_module: + device: auto + iso15118_car: + module: JsCarV2G + config_implementation: + main: + stack_implementation: RISE-V2G + mqtt_base_path: everest_external/iso15118/ev + device: auto + evse_manager: + module: EvseManager + config_module: + connector_id: 1 + country_code: DE + rcd_enabled: true + evse_id: DE*PNX*E12345*1 + evse_id_din: 49A80737A45678 + session_logging: true + session_logging_xml: false + session_logging_path: /tmp + charge_mode: DC + connections: + bsp: + - module_id: yeti_driver + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver + implementation_id: powermeter + slac: + - module_id: slac + implementation_id: evse + hlc: + - module_id: iso15118_charger + implementation_id: charger + powersupply_DC: + - module_id: powersupply_dc + implementation_id: main + imd: + - module_id: imd + implementation_id: main + powersupply_dc: + module: JsDCSupplySimulator + yeti_driver: + module: JsYetiSimulator + slac: + module: JsSlacSimulator + imd: + module: JsIMDSimulator + car_simulator: + module: JsCarSimulator + config_module: + connector_id: 1 + auto_enable: true + auto_exec: false + auto_exec_commands: sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug + connections: + simulation_control: + - module_id: yeti_driver + implementation_id: yeti_simulation_control + ev: + - module_id: iso15118_car + implementation_id: ev + slac: + - module_id: slac + implementation_id: ev + auth: + module: Auth + config_module: + connection_timeout: 10 + selection_algorithm: PlugEvents + connections: + token_provider: + - module_id: token_provider + implementation_id: main + token_validator: + - module_id: token_validator + implementation_id: main + evse_manager: + - module_id: evse_manager + implementation_id: evse + token_provider: + module: JsDummyTokenProviderManual + token_validator: + module: JsDummyTokenValidator + config_implementation: + main: + validation_result: Accepted + validation_reason: Token seems valid + sleep: 0.25 + energy_manager: + module: EnergyManager + connections: + energy_trunk: + - module_id: grid_connection_point + implementation_id: energy_grid + grid_connection_point: + module: EnergyNode + config_module: + fuse_limit_A: 40.0 + phase_count: 3 + connections: + price_information: [] + energy_consumer: + - module_id: evse_manager + implementation_id: energy_grid + powermeter: + - module_id: yeti_driver + implementation_id: powermeter + api: + module: API + connections: + evse_manager: + - module_id: evse_manager + implementation_id: evse +x-module-layout: {} diff --git a/config/config-sil-four-evse.yaml b/config/config-sil-four-evse.yaml new file mode 100644 index 000000000..2ab92a421 --- /dev/null +++ b/config/config-sil-four-evse.yaml @@ -0,0 +1,260 @@ +active_modules: + iso15118_charger_1: + module: PyJosev + config_module: + device: auto + iso15118_car_1: + module: JsCarV2G + config_implementation: + main: + stack_implementation: RISE-V2G + mqtt_base_path: everest_external/iso15118/ev_1 + device: auto + evse_manager_1: + module: EvseManager + config_module: + connector_id: 1 + 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 + session_logging_path: /tmp + ac_hlc_enabled: false + ac_hlc_use_5percent: false + ac_enforce_hlc: false + connections: + bsp: + - module_id: yeti_driver_1 + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver_1 + implementation_id: powermeter + evse_manager_2: + module: EvseManager + config_module: + connector_id: 2 + 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 + session_logging_path: /tmp + ac_hlc_enabled: false + ac_hlc_use_5percent: false + ac_enforce_hlc: false + connections: + bsp: + - module_id: yeti_driver_2 + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver_2 + implementation_id: powermeter + evse_manager_3: + module: EvseManager + config_module: + connector_id: 3 + three_phases: true + has_ventilation: true + country_code: DE + rcd_enabled: true + evse_id: DE*PNX*E12345*3 + session_logging: true + session_logging_xml: false + session_logging_path: /tmp + ac_hlc_enabled: false + ac_hlc_use_5percent: false + ac_enforce_hlc: false + connections: + bsp: + - module_id: yeti_driver_3 + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver_3 + implementation_id: powermeter + evse_manager_4: + module: EvseManager + config_module: + connector_id: 4 + three_phases: true + has_ventilation: true + country_code: DE + rcd_enabled: true + evse_id: DE*PNX*E12345*4 + session_logging: true + session_logging_xml: false + session_logging_path: /tmp + ac_hlc_enabled: false + ac_hlc_use_5percent: false + ac_enforce_hlc: false + connections: + bsp: + - module_id: yeti_driver_4 + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver_4 + implementation_id: powermeter + yeti_driver_1: + module: JsYetiSimulator + yeti_driver_2: + module: JsYetiSimulator + yeti_driver_3: + module: JsYetiSimulator + yeti_driver_4: + module: JsYetiSimulator + slac_1: + module: JsSlacSimulator + car_simulator_1: + module: JsCarSimulator + config_module: + connector_id: 1 + auto_enable: true + auto_exec: true + auto_exec_commands: sleep 5;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug + connections: + simulation_control: + - module_id: yeti_driver_1 + implementation_id: yeti_simulation_control + car_simulator_2: + module: JsCarSimulator + config_module: + connector_id: 2 + auto_enable: true + auto_exec: true + auto_exec_commands: sleep 5;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug + connections: + simulation_control: + - module_id: yeti_driver_2 + implementation_id: yeti_simulation_control + car_simulator_3: + module: JsCarSimulator + config_module: + connector_id: 3 + auto_enable: true + auto_exec: true + auto_exec_commands: sleep 7;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug + connections: + simulation_control: + - module_id: yeti_driver_3 + implementation_id: yeti_simulation_control + car_simulator_4: + module: JsCarSimulator + config_module: + connector_id: 4 + auto_enable: true + auto_exec: true + auto_exec_commands: sleep 8;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug + connections: + simulation_control: + - module_id: yeti_driver_4 + implementation_id: yeti_simulation_control + auth: + module: Auth + config_module: + connection_timeout: 10 + selection_algorithm: PlugEvents + connections: + token_provider: + - module_id: token_provider_1 + implementation_id: main + - module_id: token_provider_2 + implementation_id: main + - module_id: token_provider_3 + implementation_id: main + - module_id: token_provider_4 + implementation_id: main + token_validator: + - module_id: token_validator + implementation_id: main + evse_manager: + - module_id: evse_manager_1 + implementation_id: evse + - module_id: evse_manager_2 + implementation_id: evse + - module_id: evse_manager_3 + implementation_id: evse + - module_id: evse_manager_4 + implementation_id: evse + token_provider_1: + module: JsDummyTokenProvider + connections: + evse: + - module_id: evse_manager_1 + implementation_id: evse + config_implementation: + main: + type: dummy + token: CARD1 + token_provider_2: + module: JsDummyTokenProvider + connections: + evse: + - module_id: evse_manager_2 + implementation_id: evse + config_implementation: + main: + type: dummy + token: CARD2 + token_provider_3: + module: JsDummyTokenProvider + connections: + evse: + - module_id: evse_manager_3 + implementation_id: evse + config_implementation: + main: + type: dummy + token: CARD3 + token_provider_4: + module: JsDummyTokenProvider + connections: + evse: + - module_id: evse_manager_4 + implementation_id: evse + config_implementation: + main: + type: dummy + token: CARD4 + token_validator: + module: JsDummyTokenValidator + config_implementation: + main: + validation_result: Accepted + validation_reason: Token seems valid + sleep: 0.25 + energy_manager: + module: EnergyManager + connections: + energy_trunk: + - module_id: grid_connection_point + implementation_id: energy_grid + grid_connection_point: + module: EnergyNode + config_module: + fuse_limit_A: 40.0 + phase_count: 3 + connections: + price_information: [] + energy_consumer: + - module_id: evse_manager_1 + implementation_id: energy_grid + - module_id: evse_manager_2 + implementation_id: energy_grid + - module_id: evse_manager_3 + implementation_id: energy_grid + - module_id: evse_manager_4 + implementation_id: energy_grid + powermeter: + - module_id: yeti_driver_1 + implementation_id: powermeter + api: + module: API + connections: + evse_manager: + - module_id: evse_manager_1 + implementation_id: evse +x-module-layout: {} diff --git a/config/config-sil-ocpp.yaml b/config/config-sil-ocpp.yaml index db20fdde0..eb1d7c962 100644 --- a/config/config-sil-ocpp.yaml +++ b/config/config-sil-ocpp.yaml @@ -20,7 +20,7 @@ active_modules: has_ventilation: true country_code: DE rcd_enabled: true - evse_id: '1' + evse_id: "1" session_logging: true session_logging_xml: false session_logging_path: /tmp @@ -31,7 +31,7 @@ active_modules: bsp: - module_id: yeti_driver_1 implementation_id: board_support - powermeter: + powermeter_grid_side: - module_id: yeti_driver_1 implementation_id: powermeter slac: @@ -48,7 +48,7 @@ active_modules: has_ventilation: true country_code: DE rcd_enabled: true - evse_id: '2' + evse_id: "2" session_logging: true session_logging_xml: false session_logging_path: /tmp @@ -59,7 +59,7 @@ active_modules: bsp: - module_id: yeti_driver_2 implementation_id: board_support - powermeter: + powermeter_grid_side: - module_id: yeti_driver_2 implementation_id: powermeter slac: diff --git a/config/config-sil-two-evse-dc.yaml b/config/config-sil-two-evse-dc.yaml new file mode 100644 index 000000000..23990c42a --- /dev/null +++ b/config/config-sil-two-evse-dc.yaml @@ -0,0 +1,159 @@ +active_modules: + iso15118_charger_1: + module: PyJosev + config_module: + device: auto + supported_DIN70121: true + iso15118_car_1: + module: JsCarV2G + config_implementation: + main: + stack_implementation: RISE-V2G + mqtt_base_path: everest_external/iso15118/ev_1 + device: auto + evse_manager_1: + module: EvseManager + config_module: + connector_id: 1 + three_phases: true + has_ventilation: true + country_code: DE + rcd_enabled: true + evse_id: DE*PNX*E12345*1 + evse_id_din: 49A80737A45678 + session_logging: true + session_logging_xml: false + session_logging_path: /tmp + charge_mode: DC + connections: + bsp: + - module_id: yeti_driver_1 + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver_1 + implementation_id: powermeter + slac: + - module_id: slac_1 + implementation_id: evse + hlc: + - module_id: iso15118_charger_1 + implementation_id: charger + powersupply_DC: + - module_id: powersupply_dc + implementation_id: main + imd: + - module_id: imd + implementation_id: main + evse_manager_2: + module: EvseManager + config_module: + connector_id: 2 + 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 + session_logging_path: /tmp + ac_hlc_enabled: false + ac_hlc_use_5percent: false + ac_enforce_hlc: false + connections: + bsp: + - module_id: yeti_driver_2 + implementation_id: board_support + powermeter_grid_side: + - module_id: yeti_driver_2 + implementation_id: powermeter + yeti_driver_1: + module: JsYetiSimulator + yeti_driver_2: + module: JsYetiSimulator + slac_1: + module: JsSlacSimulator + powersupply_dc: + module: JsDCSupplySimulator + imd: + module: JsIMDSimulator + car_simulator_1: + module: JsCarSimulator + config_module: + connector_id: 1 + auto_enable: true + auto_exec: false + auto_exec_commands: sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug + connections: + simulation_control: + - module_id: yeti_driver_1 + implementation_id: yeti_simulation_control + ev: + - module_id: iso15118_car_1 + implementation_id: ev + slac: + - module_id: slac_1 + implementation_id: ev + car_simulator_2: + module: JsCarSimulator + config_module: + connector_id: 2 + auto_enable: true + auto_exec: false + connections: + simulation_control: + - module_id: yeti_driver_2 + implementation_id: yeti_simulation_control + auth: + module: Auth + config_module: + connection_timeout: 10 + selection_algorithm: PlugEvents + connections: + token_provider: + - module_id: token_provider_1 + implementation_id: main + token_validator: + - module_id: token_validator + implementation_id: main + evse_manager: + - module_id: evse_manager_1 + implementation_id: evse + - module_id: evse_manager_2 + implementation_id: evse + token_provider_1: + module: JsDummyTokenProviderManual + token_validator: + module: JsDummyTokenValidator + config_implementation: + main: + validation_result: Accepted + validation_reason: Token seems valid + sleep: 0.25 + energy_manager: + module: EnergyManager + connections: + energy_trunk: + - module_id: grid_connection_point + implementation_id: energy_grid + grid_connection_point: + module: EnergyNode + config_module: + fuse_limit_A: 40.0 + phase_count: 3 + connections: + price_information: [] + energy_consumer: + - module_id: evse_manager_1 + implementation_id: energy_grid + - module_id: evse_manager_2 + implementation_id: energy_grid + powermeter: + - module_id: yeti_driver_1 + implementation_id: powermeter + api: + module: API + connections: + evse_manager: + - module_id: evse_manager_1 + implementation_id: evse +x-module-layout: {} diff --git a/config/config-sil-two-evse.yaml b/config/config-sil-two-evse.yaml index 1b2d76270..ede4f9209 100644 --- a/config/config-sil-two-evse.yaml +++ b/config/config-sil-two-evse.yaml @@ -1,10 +1,8 @@ active_modules: iso15118_charger: - module: JsRiseV2G - config_implementation: - main: - mqtt_base_path: everest_external/iso15118/java - device: auto + module: PyJosev + config_module: + device: auto iso15118_car: module: JsCarV2G config_implementation: @@ -20,18 +18,18 @@ active_modules: has_ventilation: true country_code: DE rcd_enabled: true - evse_id: '1' + evse_id: DE*PNX*E12345*1 session_logging: true session_logging_xml: false session_logging_path: /tmp - ac_hlc_enabled: false + ac_hlc_enabled: true ac_hlc_use_5percent: false ac_enforce_hlc: false connections: bsp: - module_id: yeti_driver_1 implementation_id: board_support - powermeter: + powermeter_grid_side: - module_id: yeti_driver_1 implementation_id: powermeter slac: @@ -48,7 +46,7 @@ active_modules: has_ventilation: true country_code: DE rcd_enabled: true - evse_id: '2' + evse_id: DE*PNX*E12345*2 session_logging: true session_logging_xml: false session_logging_path: /tmp @@ -59,15 +57,9 @@ active_modules: bsp: - module_id: yeti_driver_2 implementation_id: board_support - powermeter: + powermeter_grid_side: - module_id: yeti_driver_2 implementation_id: powermeter - slac: - - module_id: slac - implementation_id: evse - hlc: - - module_id: iso15118_charger - implementation_id: charger yeti_driver_1: module: JsYetiSimulator yeti_driver_2: @@ -101,12 +93,6 @@ active_modules: simulation_control: - module_id: yeti_driver_2 implementation_id: yeti_simulation_control - ev: - - module_id: iso15118_car - implementation_id: ev - slac: - - module_id: slac - implementation_id: ev auth: module: Auth config_module: diff --git a/config/config-sil.yaml b/config/config-sil.yaml index 21ab04e7d..71b804ac0 100644 --- a/config/config-sil.yaml +++ b/config/config-sil.yaml @@ -47,18 +47,14 @@ active_modules: evse_manager: config_module: ac_enforce_hlc: false - ac_hlc_enabled: false + ac_hlc_enabled: true ac_hlc_use_5percent: false ac_nominal_voltage: 230 - ac_with_soc: false charge_mode: AC connector_id: 1 country_code: DE - dbg_hlc_auth_after_tstep: false - dc_current_regulation_tolerance: 5 - dc_peak_current_ripple: 5 ev_receipt_required: false - evse_id: '1' + evse_id: DE*PNX*E12345*1 has_ventilation: true max_current: 32 payment_enable_contract: true @@ -75,7 +71,7 @@ active_modules: hlc: - implementation_id: charger module_id: iso15118_charger - powermeter: + powermeter_grid_side: - implementation_id: powermeter module_id: yeti_driver slac: @@ -100,12 +96,10 @@ active_modules: connections: {} module: JsCarV2G iso15118_charger: - config_implementation: - main: - device: auto - mqtt_base_path: everest_external/iso15118/java + config_module: + device: auto connections: {} - module: JsRiseV2G + module: PyJosev persistent_store: config_module: sqlite_db_file_path: everest_persistent_store.db diff --git a/config/nodered/config-sil-flow.json b/config/nodered/config-sil-flow.json index d4a719ee8..acb5a88cf 100644 --- a/config/nodered/config-sil-flow.json +++ b/config/nodered/config-sil-flow.json @@ -1801,6 +1801,16 @@ "label": "AC RCD Error", "value": "sleep 1;rcd_current 10.3;sleep 10;rcd_current 0.1;unplug", "type": "str" + }, + { + "label": "AC ISO15118-2", + "value": "sleep 1;iso_wait_slac_matched;iso_start_v2g_session ExternalPayment,AC_three_phase_core;iso_wait_pwr_ready;iso_draw_power_regulated 16,3;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug", + "type": "str" + }, + { + "label": "DC (experimental)", + "value": "sleep 1;iso_wait_slac_matched;iso_start_v2g_session ExternalPayment,DC_extended;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug", + "type": "str" } ], "payload": "", @@ -1878,4 +1888,4 @@ ] ] } -] \ No newline at end of file +] diff --git a/config/nodered/config-sil-two-evse-flow.json b/config/nodered/config-sil-two-evse-flow.json index 1cf152cea..aa90e2b73 100644 --- a/config/nodered/config-sil-two-evse-flow.json +++ b/config/nodered/config-sil-two-evse-flow.json @@ -44,7 +44,9 @@ "1295e032d7ddbc20" ], "x": 1114, - "y": 439 + "y": 439, + "w": 152, + "h": 82 }, { "id": "6459c14573f03fd2", @@ -63,7 +65,9 @@ "22139ab4759c1b51" ], "x": 1094, - "y": 419 + "y": 419, + "w": 152, + "h": 82 }, { "id": "7140803fb3989089", @@ -252,7 +256,7 @@ { "id": "b364f7eb4621082b", "type": "ui_group", - "name": "Connector 1", + "name": "Connector 1 [ISO15118]", "tab": "d3ada9fa4cf6ac53", "order": 2, "disp": true, @@ -272,30 +276,13 @@ { "id": "21e40a4a97a50168", "type": "ui_group", - "name": "Connector 2", + "name": "Connector 2 [Basic Charging]", "tab": "d3ada9fa4cf6ac53", "order": 3, "disp": true, "width": "6", "collapse": false }, - { - "id": "84ddb762.5129f8", - "type": "ui_tab", - "name": "Home", - "icon": "dashboard", - "disabled": false, - "hidden": false - }, - { - "id": "427a83e7f6d33afc", - "type": "ui_tab", - "name": "Home", - "icon": "dashboard", - "order": 1, - "disabled": false, - "hidden": false - }, { "id": "50c487c1.27e508", "type": "ui_tab", @@ -313,15 +300,6 @@ "disabled": false, "hidden": false }, - { - "id": "3c7214a9dd24b0ad", - "type": "ui_tab", - "name": "EVSE Configuration", - "icon": "dashboard", - "order": 1, - "disabled": false, - "hidden": false - }, { "id": "27225dc1005441da", "type": "ui_spacer", @@ -1646,8 +1624,7 @@ "y": 840, "wires": [ [ - "f96ccb60614f9f18", - "fb1511183c9a660f" + "f96ccb60614f9f18" ] ] }, @@ -2052,7 +2029,7 @@ "color": "", "bgcolor": "", "icon": "", - "payload": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug", + "payload": "start", "payloadType": "str", "topic": "everest_external/nodered/#/carsim/cmd/execute_charging_session", "topicType": "str", @@ -2074,20 +2051,20 @@ "width": "3", "height": "1", "passthru": false, - "label": "Car unplug", + "label": "Stop & Unplug", "tooltip": "", "color": "", "bgcolor": "", "icon": "", - "payload": "unplug", + "payload": "stop", "payloadType": "str", "topic": "everest_external/nodered/#/carsim/cmd/modify_charging_session", "topicType": "str", - "x": 170, + "x": 180, "y": 940, "wires": [ [ - "361b3d846c4e6673" + "620b0d248a89ece0" ] ] }, @@ -2107,7 +2084,7 @@ "topic": "everest_external/nodered/#/cmd/set_max_current", "topicType": "str", "min": "6", - "max": "16", + "max": "32", "step": "0.1", "x": 450, "y": 700, @@ -2122,7 +2099,7 @@ "type": "ui_dropdown", "z": "ed603c51db9dcbb9", "name": "", - "label": "Simulation", + "label": "Car Simulation", "tooltip": "", "place": "Select option", "group": "b364f7eb4621082b", @@ -2134,34 +2111,44 @@ "options": [ { "label": "AC 3ph 16A", - "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug", + "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000#unplug", "type": "str" }, { "label": "AC 1ph 32A", - "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 32,1;sleep 36000;unplug", + "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 32,1;sleep 36000#unplug", "type": "str" }, { "label": "AC Diode Fail", - "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 32,3;sleep 5;diode_fail;sleep 36000;unplug", + "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 32,3;sleep 5;diode_fail;sleep 36000#unplug", "type": "str" }, { "label": "AC Error E", - "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 3;error_e;sleep 36000;unplug", + "value": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 3;error_e;sleep 36000#unplug", "type": "str" }, { "label": "AC RCD Error", - "value": "sleep 1;rcd_current 10.3;sleep 10;rcd_current 0.1;unplug", + "value": "sleep 1;rcd_current 10.3;sleep 10;rcd_current 0.1#unplug", + "type": "str" + }, + { + "label": "AC ISO15118-2", + "value": "sleep 1;iso_wait_slac_matched;iso_start_v2g_session ExternalPayment,AC_three_phase_core;iso_wait_pwr_ready;iso_draw_power_regulated 16,3;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug", + "type": "str" + }, + { + "label": "DC (experimental)", + "value": "sleep 1;iso_wait_slac_matched;iso_start_v2g_session ExternalPayment,DC_extended;sleep 36000#iso_stop_charging;iso_wait_v2g_session_stopped;unplug", "type": "str" } ], "payload": "", "topic": "sim_commands", "topicType": "str", - "x": 170, + "x": 180, "y": 1040, "wires": [ [ @@ -2174,7 +2161,7 @@ "type": "function", "z": "ed603c51db9dcbb9", "name": "Buffer sim commands", - "func": "if (msg.topic.indexOf('sim_commands') > -1) {\n flow.set('sim_commands', msg.payload);\n} else {\n msg.payload = flow.get('sim_commands');\n return msg;\n}\n", + "func": "if (msg.topic.indexOf('sim_commands') > -1) {\n const s = msg.payload.split('#');\n flow.set('sim_commands_start', s[0]);\n flow.set('sim_commands_stop', s[1]);\n} else if (msg.payload == 'start') {\n msg.payload = flow.get('sim_commands_start');\n return msg;\n} else if (msg.payload == 'stop') {\n msg.payload = flow.get('sim_commands_stop');\n return msg;\n} else {\n msg.payload = 'NONE';\n return msg;\n}\n", "outputs": 1, "noerr": 0, "initialize": "", @@ -2184,7 +2171,9 @@ "y": 960, "wires": [ [ - "361b3d846c4e6673" + "361b3d846c4e6673", + "cc42c210398a8d50", + "fb1511183c9a660f" ] ] }, @@ -2197,7 +2186,8 @@ "tosidebar": true, "console": false, "tostatus": false, - "complete": "false", + "complete": "payload", + "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 910, @@ -2223,7 +2213,7 @@ "once": true, "onceDelay": "1", "topic": "sim_commands", - "payload": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000;unplug", + "payload": "sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 36000#unplug", "payloadType": "str", "x": 150, "y": 1100, @@ -2864,7 +2854,7 @@ "topic": "everest_external/nodered/#/cmd/set_max_current", "topicType": "str", "min": "6", - "max": "16", + "max": "32", "step": "0.1", "x": 450, "y": 680, @@ -2989,5 +2979,22 @@ "3ce436b7f3df46a5" ] ] + }, + { + "id": "cc42c210398a8d50", + "type": "debug", + "z": "ed603c51db9dcbb9", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "topic", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 900, + "y": 960, + "wires": [] } ] diff --git a/dependencies.yaml b/dependencies.yaml index d42b9e102..62a2d4902 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -27,9 +27,13 @@ libfsm: # JsCarV2G and JsRiseV2G module RISE-V2G: git: https://github.com/EVerest/ext-RISE-V2G.git - git_tag: 8728608121f242f8149270ac1c996d55c937cbcb + git_tag: 2022.09.0 # OCPP libocpp: git: https://github.com/EVerest/libocpp.git - git_tag: v0.4.0 + git_tag: v0.4.1 +# Josev +Josev: + git: git@github.com:EVerest/ext-switchev-iso15118.git + git_tag: 2022.12.0 diff --git a/interfaces/ISO15118_charger.yaml b/interfaces/ISO15118_charger.yaml index 998245688..6632ccce6 100644 --- a/interfaces/ISO15118_charger.yaml +++ b/interfaces/ISO15118_charger.yaml @@ -8,6 +8,9 @@ cmds: EVSEID: description: EVSE ID type: string + EVSEID_DIN: + description: EVSE ID (DIN70121) after DIN SPEC 91286 + type: string set_PaymentOptions: description: >- One Time Setup at Boot - Providing a list of payment options to @@ -50,7 +53,7 @@ cmds: This is the voltage measured between one phases and neutral arguments: EVSENominalVoltage: - description: '[V] Nominal Voltage' + description: "[V] Nominal Voltage" type: number minimum: 0 maximum: 1000 @@ -81,7 +84,7 @@ cmds: the purpose of signing the meter info record arguments: ReceiptRequired: - description: 'True: Receipt is required, False: Receipt is not required' + description: "True: Receipt is required, False: Receipt is not required" type: boolean set_FreeService: description: >- @@ -89,7 +92,7 @@ cmds: free of charge or not arguments: FreeService: - description: 'True: Charging is free, False: Charging is not free' + description: "True: Charging is free, False: Charging is not free" type: boolean set_EVSEEnergyToBeDelivered: description: >- @@ -102,22 +105,19 @@ cmds: minimum: 0 maximum: 200000 enable_debug_mode: - description: 'On Session Setup - Set different modes for the debug mode.' + description: "On Session Setup - Set different modes for the debug mode." arguments: debug_mode: description: The various debug modes type: string - enum: - - None - - Lite - - Full + $ref: /iso15118_charger#/DebugMode set_Auth_Okay_EIM: description: >- Response on Require_Auth_EIM - Set this element if the authorization was either successful or unsuccessful arguments: auth_okay_eim: - description: 'True: Authentication is okay' + description: "True: Authentication is okay" type: boolean set_Auth_Okay_PnC: description: >- @@ -125,7 +125,7 @@ cmds: was either successful or unsuccessful arguments: auth_okay_pnc: - description: 'True: Authentication is okay' + description: "True: Authentication is okay" type: boolean set_FAILED_ContactorError: description: >- @@ -133,7 +133,7 @@ cmds: at the wrong moment arguments: ContactorError: - description: 'True: ContactError happend' + description: "True: ContactError happend" type: boolean set_RCD_Error: description: >- @@ -149,82 +149,45 @@ cmds: description: During charging - Stops the charging process arguments: stop_charging: - description: 'True: Stop, False: Continue' + description: "True: Stop, False: Continue" type: boolean - set_DC_EVSEPresentVoltage: + set_DC_EVSEPresentVoltageCurrent: description: >- Starting from PowerDelivery to WeldingDetection - Set the present - voltage for the EVSE + voltage and current for the EVSE arguments: - EVSEPresentVoltage: - description: '[V] Output voltage of the EVSE as defined in IEC CDV 61851-23' - type: number - minumum: 0 - maximum: 1000 - set_DC_EVSEPresentCurrent: - description: During charging - Set the present current for the EVSE - arguments: - EVSEPresentCurrent: - description: '[A] Present output current of the EVSE' - type: number - minumum: 0 - maximum: 400 + EVSEPresentVoltage_Current: + description: Present values (current and voltage) for the EVSE + type: object + $ref: /iso15118_charger#/DC_EVSEPresentVoltage_Current set_AC_EVSEMaxCurrent: description: >- ChargeParameterDiscovery and during charging - Set the Maximum allowed line current restriction per phase arguments: EVSEMaxCurrent: - description: '[A] Max current' + description: "[A] Max current" type: number minimum: 0 maximum: 400 - set_DC_EVSEMaximumCurrentLimit: + set_DC_EVSEMaximumLimits: description: >- - ChargeParameterDiscovery and during charging - Set the maximum current - for the EVSE + ChargeParameterDiscovery and during charging - Set the maximum current, + power and voltage for the EVSE arguments: - EVSEMaximumCurrentLimit: - description: '[A] Maximum current the EVSE can deliver' - type: number - minimum: 0 - maximum: 400 - set_DC_EVSEMaximumPowerLimit: - description: >- - ChargeParameterDiscovery and during charging - Set the maximum power - for the EVSE - arguments: - EVSEMaximumPowerLimit: - description: '[W] Maximum power the EVSE can deliver' - type: number - minimum: 0 - maximum: 200000 - set_DC_EVSEMaximumVoltageLimit: - description: >- - ChargeParameterDiscovery and during charging - Set the maximum voltage - for the EVSE - arguments: - EVSEMaximumVoltageLimit: - description: '[V] Maximum voltage the EVSE can deliver' - type: number - minimum: 0 - maximum: 1000 - set_DC_EVSEMinimumCurrentLimit: - description: ChargeParameterDiscovery - Set the minimum current for the EVSE - arguments: - EVSEMinimumCurrentLimit: - description: '[A] Minimum current the EVSE can deliver with the expected accuracy' - type: number - minimum: 0 - maximum: 400 - set_DC_EVSEMinimumVoltageLimit: - description: ChargeParameterDiscovery - Set the minimum voltage for the EVSE + EVSEMaximumLimits: + description: Maximum Values (current, power and voltage) the EVSE can deliver + type: object + $ref: /iso15118_charger#/DC_EVSEMaximumLimits + set_DC_EVSEMinimumLimits: + description: + ChargeParameterDiscovery - Set the minimum current and voltage for + the EVSE arguments: - EVSEMinimumVoltageLimit: - description: '[V] Minimum voltage the EVSE can deliver with the expected accuracy' - type: number - minimum: 0 - maximum: 1000 + EVSEMinimumLimits: + description: Minimum Values (current and voltage) the EVSE can deliver + type: object + $ref: /iso15118_charger#/DC_EVSEMinimumLimits set_EVSEIsolationStatus: description: >- From ChargeParameterDiscovery to WeldingDetection (Optional) - Set @@ -233,12 +196,7 @@ cmds: EVSEIsolationStatus: description: Set the isolation status type: string - enum: - - Invalid - - Valid - - Warning - - Fault - - No_IMD + $ref: /iso15118_charger#/IsolationStatus set_EVSE_UtilityInterruptEvent: description: >- From ChargeParameterDiscovery to WeldingDetection - Utility Interrupt @@ -246,7 +204,7 @@ cmds: load arguments: EVSE_UtilityInterruptEvent: - description: 'True: Interrupt Event happend' + description: "True: Interrupt Event happend" type: boolean set_EVSE_Malfunction: description: >- @@ -254,7 +212,7 @@ cmds: Failure, ...). It is permanently faulted arguments: EVSE_Malfunction: - description: 'True: Malfunction fault' + description: "True: Malfunction fault" type: boolean set_EVSE_EmergencyShutdown: description: >- @@ -262,7 +220,7 @@ cmds: 'E-Stop' button pressed at charging station arguments: EVSE_EmergencyShutdown: - description: 'True: Emergency Stop' + description: "True: Emergency Stop" type: boolean set_MeterInfo: description: >- @@ -272,136 +230,14 @@ cmds: powermeter: description: Measured dataset type: object - required: - - timestamp - properties: - timestamp: - description: Timestamp of measurement - type: number - meter_id: - description: A (user defined) meter if (e.g. id printed on the case) - type: string - phase_seq_error: - description: true for 3 phase rotation error (ccw) - type: boolean - energy_Wh_import: - description: Imported energy in Wh (from grid) - type: object - required: - - total - properties: - total: - description: Sum value (which is relevant for billing) - type: number - L1: - description: L1 value only - type: number - L2: - description: L2 value only - type: number - L3: - description: L3 value only - type: number - additionalProperties: false - energy_Wh_export: - description: Exported energy in Wh (to grid) - type: object - required: - - total - properties: - total: - description: Sum value (which is relevant for billing) - type: number - L1: - description: L1 value only - type: number - L2: - description: L2 value only - type: number - L3: - description: L3 value only - type: number - additionalProperties: false - power_W: - description: >- - Instantaneous power in Watt. Negative values are exported, - positive values imported Energy. - type: object - required: - - total - properties: - total: - description: Sum value - type: number - L1: - description: L1 value only - type: number - L2: - description: L2 value only - type: number - L3: - description: L3 value only - type: number - additionalProperties: false - voltage_V: - description: Voltage in Volts - type: object - required: - - L1 - properties: - L1: - description: L1 value only - type: number - L2: - description: L2 value only - type: number - L3: - description: L3 value only - type: number - additionalProperties: false - current_A: - description: Current in ampere - type: object - required: - - L1 - properties: - L1: - description: L1 value only - type: number - L2: - description: L2 value only - type: number - L3: - description: L3 value only - type: number - N: - description: Neutral value only - type: number - additionalProperties: false - frequency_Hz: - description: Grid frequency in Hertz - type: object - required: - - L1 - properties: - L1: - description: L1 value - type: number - L2: - description: L2 value - type: number - L3: - description: L3 value - type: number - additionalProperties: false - additionalProperties: false + $ref: /powermeter#/Powermeter contactor_closed: description: >- Response on AC_Close_Contactor - Set this element if the contactor is closed arguments: status: - description: 'True: Contactor is closed' + description: "True: Contactor is closed" type: boolean contactor_open: description: >- @@ -409,31 +245,48 @@ cmds: is open arguments: status: - description: 'True: Contactor is open' + description: "True: Contactor is open" + type: boolean + cableCheck_Finished: + description: >- + Cable check is finished, voltage is under 20V and insulation resistor + on the cable is alright + arguments: + status: + description: "True: Cable check is okay" type: boolean vars: Require_Auth_EIM: description: An EIM authorization is requiered - type: 'null' + type: "null" Require_Auth_PnC: description: An PnC authorization is requiered - type: 'null' - Require_EVSEIsolationCheck: - description: An insulation check of the DC cable of the EVSE is necessary - type: 'null' + type: "null" AC_Close_Contactor: description: If true, the contactor should be closed type: boolean AC_Open_Contactor: description: If true, the contactor should be opened type: boolean + Start_CableCheck: + description: The charger should now start a cable check + type: "null" + DC_Open_Contactor: + description: If true, the contactor should be opened + type: boolean V2G_Setup_Finished: description: >- V2G_Setup_Finished from ISO15118-3. Trigger when EV sends a PowerDeliveryReq message with ChargeProgess equals Start or Stop - type: 'null' + type: "null" + currentDemand_Started: + description: The charging process has started and the EV wants to be charged + type: "null" + currentDemand_Finished: + description: The charging process was finished + type: "null" EVCCIDD: - description: >- + description: Specifies the EVs identification in a readable format. It contains the MAC address of the EVCC type: string @@ -441,21 +294,13 @@ vars: SelectedPaymentOption: description: This element is used for indicating the payment type type: string - enum: - - Contract - - ExternalPayment + $ref: /iso15118_charger#/PaymentOption RequestedEnergyTransferMode: - description: >- + description: Selected energy transfer mode for charging that is requested by the EVCC. type: string - enum: - - AC_single_phase_core - - AC_three_phase_core - - DC_core - - DC_extended - - DC_combo_core - - DC_unique + $ref: /iso15118_charger#/EnergyTransferMode DepartureTime: description: >- Optional: [RFC3339 UTC] This element is used to indicate when the @@ -478,7 +323,7 @@ vars: minimum: 0 maximum: 1000 AC_EVMaxCurrent: - description: '[A] Maximum current supported by the EV per phase' + description: "[A] Maximum current supported by the EV per phase" type: number minimum: 0 maximum: 400 @@ -490,12 +335,12 @@ vars: minimum: 0 maximum: 400 DC_EVEnergyCapacity: - description: 'Optional: [Wh] Energy capacity of the EV' + description: "Optional: [Wh] Energy capacity of the EV" type: number minimum: 0 maximum: 200000 DC_EVEnergyRequest: - description: 'Optional: [Wh] Amount of energy the EV requests from the EVSE' + description: "Optional: [Wh] Amount of energy the EV requests from the EVSE" type: number minimum: 0 maximum: 200000 @@ -513,38 +358,16 @@ vars: type: number minimum: 0 maximum: 100 - DC_EVReady: - description: If set to TRUE, the EV is ready to charge - type: boolean - DC_EVErrorCode: - description: Indicates the EV internal status - type: string - enum: - - NO_ERROR - - FAILED_RESSTemperatureInhibit - - FAILED_EVShiftPosition - - FAILED_ChargerConnectorLockFault - - FAILED_EVRESSMalfunction - - FAILED_ChargingCurrentdifferentia - - FAILED_ChargingVoltageOutOfRange - - Reserved_A - - Reserved_B - - Reserved_C - - FAILED_ChargingSystemIncompatibility - - NoData - DC_EVRESSSOC: - description: State of charge of the EVs battery (RESS) - type: number - minimum: 0 - maximum: 100 + DC_EVStatus: + description: Current status of the EV + type: object + $ref: /iso15118_charger#/DC_EVStatusType EV_ChargingSession: description: >- ChargingSession indicates the intention of the EVCC to either pause or terminate a V2G Communication Session type: string - enum: - - Terminate - - Pause + $ref: /iso15118_charger#/ChargingSession DC_BulkChargingComplete: description: >- Optional: If set to TRUE, the EV indicates that bulk charge (approx. @@ -555,43 +378,20 @@ vars: Optional: If set to TRUE, the EV indicates that full charge (100% SOC) is complete type: boolean - DC_EVTargetVoltage: - description: '[V] Target Voltage requested by EV' - type: number - minimum: 0 - maximum: 1000 - DC_EVTargetCurrent: - description: '[A] Current demanded by EV' - type: number - minimum: 0 - maximum: 400 - DC_EVMaximumCurrentLimit: - description: '[A] Maximum current supported and allowed by the EV' - type: number - minimum: 0 - maximum: 400 - DC_EVMaximumPowerLimit: - description: 'Optional: [W] Maximum power supported and allowed by the EV' - type: number - minimum: 0 - maximum: 200000 - DC_EVMaximumVoltageLimit: - description: '[V] Maximum voltage supported and allowed by the EV' - type: number - minimum: 0 - maximum: 1000 - EV_RemainingTimeToFullSoC: - description: >- - [RFC3339 UTC] Estimated or calculated time until full charge (100% - SOC) is complete - type: string - format: date-time - EV_RemainingTimeToBulkSoC: - description: >- - [RFC3339 UTC] Estimated or calculated time until bulk charge (approx. - 80% SOC) is complete - type: string - format: date-time + DC_EVTargetVoltageCurrent: + description: Target voltage and current requested by the EV + type: object + $ref: /iso15118_charger#/DC_EVTargetValues + DC_EVMaximumLimits: + description: + Maximum Values (current, power and voltage) supported and allowed + by the EV + type: object + $ref: /iso15118_charger#/DC_EVMaximumLimits + DC_EVRemainingTime: + description: Estimated or calculated time until bulk and full charge is complete + type: object + $ref: /iso15118_charger#/DC_EVRemainingTime EV_AppProtocol: description: >- Debug_Lite - This request message provides a list of charging protocols @@ -645,52 +445,4 @@ vars: Debug - This element contains all V2G elements and should be used for debug purposes only type: object - properties: - V2G_Message_ID: - description: This element contains the id of the v2g message body - type: string - enum: - - SupportedAppProtocolReq - - SupportedAppProtocolRes - - SessionSetupReq - - SessionSetupRes - - ServiceDiscoveryReq - - ServiceDiscoveryRes - - ServiceDetailReq - - ServiceDetailRes - - PaymentServiceSelectionReq - - PaymentServiceSelectionRes - - AuthorizationReq - - AuthorizationRes - - ChargeParameterDiscoveryReq - - ChargeParameterDiscoveryRes - - ChargingStatusReq - - ChargingStatusRes - - PowerDeliveryReq - - PowerDeliveryRes - - CableCheckReq - - CableCheckRes - - PreChargeReq - - PreChargeRes - - CurrentDemandReq - - CurrentDemandRes - - WeldingDetectionReq - - WeldingDetectionRes - - SessionStopReq - - SessionStopRes - V2G_Message_XML: - description: Contains the decoded EXI stream as V2G message XML file - type: string - minLength: 0 - V2G_Message_JSON: - description: Contains the decoded EXI stream as V2G message JSON file - type: string - minLength: 0 - V2G_Message_EXI_Hex: - description: Contains the EXI stream as hex string - type: string - minLength: 0 - V2G_Message_EXI_Base64: - description: Contains the EXI stream as base64 string - type: string - minLength: 0 + $ref: /iso15118_charger#/V2G_Messages diff --git a/interfaces/evse_manager.yaml b/interfaces/evse_manager.yaml index 74cb61d62..0330448d4 100644 --- a/interfaces/evse_manager.yaml +++ b/interfaces/evse_manager.yaml @@ -31,6 +31,9 @@ cmds: id_tag: description: The authorized id tag type: string + pnc: + description: true Pnc auth, false EIM auth + type: boolean withdraw_authorization: description: >- Call to signals that EVSE is not further authorized to start a transaction @@ -104,7 +107,7 @@ cmds: description: Switch three phases while charging arguments: three_phases: - description: 'True: switch to three phases, false: switch to single phase' + description: "True: switch to three phases, false: switch to single phase" type: boolean result: description: Returns success or error code @@ -126,6 +129,10 @@ vars: description: Limits of this evse, published on change type: object $ref: /evse_manager#/Limits + ev_info: + description: More details about the EV if available + type: object + $ref: /evse_manager#/EVInfo telemetry: description: Other telemetry type: object @@ -138,6 +145,6 @@ vars: description: EVSE ID including the connector number, e.g. DE*PNX*E123456*1 type: string hw_capabilities: - description: ' Hardware capability/limits' + description: "Hardware capability/limits" type: object $ref: /board_support#/HardwareCapabilities diff --git a/interfaces/isolation_monitor.yaml b/interfaces/isolation_monitor.yaml index c3ca23eda..1aba8a513 100644 --- a/interfaces/isolation_monitor.yaml +++ b/interfaces/isolation_monitor.yaml @@ -1,17 +1,19 @@ description: >- This interface defines an isolation monitoring device (IMD) according to IEC 61557-8 for DC charging. This is used to verify isolation of the DC lines - before starting high voltage charging. + before starting high voltage charging and during charging. cmds: - startIsolationTest: - description: start a single isolation measurement + start: + description: >- + Start recurring isolation measurements. The device should monitor + the isolation status until stopped and publish the resistance data in regular + intervals. The actual interval is device dependent. + stop: + description: >- + Stop recurring measurements. The device should stop to monitor the + isolation resistance and stop publishing the data. vars: - IsolationStatus: - description: Publishes the isolation status after measurement - type: string - enum: - - Invalid - - Valid - - Warning - - Fault - - No_IMD + IsolationMeasurement: + description: Isolation monitoring measurement results + type: object + $ref: /isolation_monitor#/IsolationMeasurement diff --git a/interfaces/power_supply_DC.yaml b/interfaces/power_supply_DC.yaml new file mode 100644 index 000000000..9008bedf5 --- /dev/null +++ b/interfaces/power_supply_DC.yaml @@ -0,0 +1,48 @@ +description: Interface for power supplies used for DC charging +cmds: + getCapabilities: + description: Get capabilities of power supply + result: + description: Capabilities + type: object + $ref: /power_supply_DC#/Capabilities + setMode: + description: Set operation mode of the bidirectional DC power supply + arguments: + value: + description: Operation mode of power supply + type: string + $ref: /power_supply_DC#/Mode + setExportVoltageCurrent: + description: Set output target voltage limit. Must be within reported limits. + arguments: + voltage: + description: Output voltage in Volt + type: number + current: + description: Output current limit in Ampere + type: number + setImportVoltageCurrent: + description: >- + Set minimal import voltage and current limit. Must be within reported + limits. + arguments: + voltage: + description: Current will be drawn if input is above this voltage (in Volt) + type: number + current: + description: Input current limit in Ampere + type: number +vars: + voltage_current: + description: Voltage/Current at the input/output + type: object + $ref: /power_supply_DC#/VoltageCurrent + mode: + description: Current mode. Published on change. + type: string + $ref: /power_supply_DC#/Mode + fault_code: + description: Fault code. Published when fault happens. + type: string + $ref: /power_supply_DC#/FaultCode diff --git a/interfaces/serial_communication_hub.yaml b/interfaces/serial_communication_hub.yaml index fab3d5470..ae45a1cf9 100644 --- a/interfaces/serial_communication_hub.yaml +++ b/interfaces/serial_communication_hub.yaml @@ -67,6 +67,13 @@ cmds: description: Number of registers to read (16 bit each) type: integer minimum: 1 + pause_between_messages: + description: >- + Ensure a pause between packets on the wire in ms. Some Modbus devices + require a minimal pause between the last message and this message. + Set to 0 if not needed. + type: integer + minimum: 0 result: description: Result of the transfer type: object @@ -92,6 +99,13 @@ cmds: 16 bit words) type: object $ref: /serial_comm_hub_requests#/VectorUint16 + pause_between_messages: + description: >- + Ensure a pause between packets on the wire in ms. Some Modbus devices + require a minimal pause between the last message and this message. + Set to 0 if not needed. + type: integer + minimum: 0 result: description: Status code of the transfer type: string diff --git a/modules/API/API.cpp b/modules/API/API.cpp index bcc11c1b1..5226bbcc8 100644 --- a/modules/API/API.cpp +++ b/modules/API/API.cpp @@ -151,6 +151,12 @@ void API::init() { this->mqtt.publish(var_telemetry, telemetry_json.dump()); }); + std::string var_ev_info = var_base + "ev_info"; + evse->subscribe_ev_info([this, var_ev_info](types::evse_manager::EVInfo ev_info) { + json ev_info_json = ev_info; + this->mqtt.publish(var_ev_info, ev_info_json.dump()); + }); + std::string var_datetime = var_base + "datetime"; std::string var_session_info = var_base + "session_info"; this->datetime_threads.push_back( @@ -201,6 +207,18 @@ void API::init() { evse->call_resume_charging(); // }); + std::string cmd_set_limit = cmd_base + "set_limit"; + this->mqtt.subscribe(cmd_set_limit, [&evse](const std::string& data) { + try { + evse->call_set_local_max_current(std::stof(data)); + } catch (const std::invalid_argument &e) { + EVLOG_warning << "Invalid limit: No conversion of given input could be performed."; + } catch (const std::out_of_range &e) { + EVLOG_warning << "Invalid limit: Out of range."; + } + + }); + count += 1; } } diff --git a/modules/Auth/Auth.cpp b/modules/Auth/Auth.cpp index c21646219..e41110dd7 100644 --- a/modules/Auth/Auth.cpp +++ b/modules/Auth/Auth.cpp @@ -33,7 +33,8 @@ void Auth::ready() { int32_t connector_id = evse_manager->call_get_id(); this->auth_handler->init_connector(connector_id, evse_index); this->auth_handler->register_authorize_callback([this](const int32_t evse_index, const std::string& id_token) { - this->r_evse_manager.at(evse_index)->call_authorize(id_token); + this->r_evse_manager.at(evse_index) + ->call_authorize(id_token, false); // FIXME: this defaults to EIM, needs fixing for PnC support }); this->auth_handler->register_withdraw_authorization_callback( [this](const int32_t evse_index) { this->r_evse_manager.at(evse_index)->call_withdraw_authorization(); }); @@ -51,13 +52,12 @@ void Auth::ready() { evse_manager->subscribe_session_event([this, connector_id](SessionEvent session_event) { this->auth_handler->handle_session_event(connector_id, session_event); }); - this->auth_handler->register_reserved_callback( - [this](const int32_t evse_index, const int32_t& reservation_id) { - this->r_evse_manager.at(evse_index)->call_reserve(reservation_id); - }); + this->auth_handler->register_reserved_callback([this](const int32_t evse_index, const int32_t& reservation_id) { + this->r_evse_manager.at(evse_index)->call_reserve(reservation_id); + }); this->auth_handler->register_reservation_cancelled_callback( [this](const int32_t evse_index) { this->r_evse_manager.at(evse_index)->call_cancel_reservation(); }); - + evse_index++; } } diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 2454c82c3..f23ed1620 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -10,6 +10,7 @@ ev_add_modules( JsCarSimulator JsCarV2G JsIMDSimulator + JsDCSupplySimulator JsDummyTokenProvider JsDummyTokenProviderManual JsDummyTokenValidator @@ -19,10 +20,10 @@ ev_add_modules( JsTibber JsYetiSimulator JsSlacSimulator - ModbusMeter OCPP PersistentStore PN532TokenProvider + PyJosev Setup SerialCommHub Store diff --git a/modules/EvseManager/Charger.cpp b/modules/EvseManager/Charger.cpp index ccb9d5365..d8f10ea62 100644 --- a/modules/EvseManager/Charger.cpp +++ b/modules/EvseManager/Charger.cpp @@ -108,14 +108,14 @@ void Charger::runStateMachine() { switch (currentState) { case EvseState::Disabled: if (new_in_state) { - signalEvent(EvseEvent::Disabled); + signalEvent(types::evse_manager::SessionEventEnum::Disabled); pwm_F(); } break; case EvseState::Replug: if (new_in_state) { - signalEvent(EvseEvent::ReplugStarted); + signalEvent(types::evse_manager::SessionEventEnum::ReplugStarted); // start timer in case we need to if (ac_with_soc_timeout) ac_with_soc_timer = 120000; @@ -141,28 +141,28 @@ void Charger::runStateMachine() { r_bsp->call_allow_power_on(false); if (lastState == EvseState::Replug) { - signalEvent(EvseEvent::ReplugFinished); + signalEvent(types::evse_manager::SessionEventEnum::ReplugFinished); } else { // First user interaction was plug in of car? Start session here. if (!sessionActive()) startSession(false); // External signal on MQTT - signalEvent(EvseEvent::AuthRequired); + signalEvent(types::evse_manager::SessionEventEnum::AuthRequired); } hlc_use_5percent_current_session = false; // switch on HLC if configured. May be switched off later on after retries for this session only. - if (charge_mode == "AC") { + if (charge_mode == ChargeMode::AC) { ac_hlc_enabled_current_session = ac_hlc_enabled; if (ac_hlc_enabled_current_session) { hlc_use_5percent_current_session = ac_hlc_use_5percent; } - } else if (charge_mode == "DC") { + } else if (charge_mode == ChargeMode::DC) { hlc_use_5percent_current_session = true; } else { // unsupported charging mode, give up here. currentState = EvseState::Error; - errorState = ErrorState::Error_Internal; + errorState = types::evse_manager::Error::Internal; } if (hlc_use_5percent_current_session) { @@ -176,10 +176,8 @@ void Charger::runStateMachine() { // FIXME: getAuthorization needs to distinguish between EIM and PnC in Auth mananger // FIXME: Note Fig 7. is not fully supported here yet (AC Auth before plugin - this overides PnC and always - // starts with nominal PWM). Hard to do here at the moment since auth before plugin is cached in auth manager - // and is similar to a very quick auth after plugin. We need to support this as this is the only way a user can - // skip PnC if he does NOT want to use it for this charging session. This basically works as Auth comes very - // fast if it was done before plugin, but we need to ensure that we do not double auth with PnC as well + // starts with nominal PWM). We need to support this as this is the only way a user can + // skip PnC if he does NOT want to use it for this charging session. // FIXME: In case V2G is not successfull after all, it needs // to send a dlink_error request, which then needs to: @@ -195,16 +193,12 @@ void Charger::runStateMachine() { session_log.evse(false, "EIM Authorization received"); startTransaction(); - EvseState targetState; - if (powerAvailable()) { - targetState = EvseState::ChargingPausedEV; - } else { - targetState = EvseState::ChargingPausedEVSE; - } + const EvseState targetState(EvseState::PrepareCharging); + // EIM done and matching process not started -> we need to go through t_step_EF and fall back to nominal // PWM. This is a complete waste of 4 precious seconds. - if (charge_mode == "AC") { + if (charge_mode == ChargeMode::AC) { if (ac_hlc_enabled_current_session) { if (ac_enforce_hlc) { // non standard compliant mode: we just keep 5 percent running all the time like in DC @@ -246,7 +240,7 @@ void Charger::runStateMachine() { currentState = EvseState::T_step_X1; } else { // Figure 6 of ISO15118-3: X1 start, PnC and EIM, matching already started when EIM was - // done. We can go directly to ChargingPausedEV, as we do not need to switch from 5 + // done. We can go directly to PrepareCharging, as we do not need to switch from 5 // percent to nominal first session_log.evse( false, "AC mode, HLC enabled(X1), matching already started. We are in X1 so we can " @@ -258,13 +252,13 @@ void Charger::runStateMachine() { } else { // HLC is disabled for this session. - // simply proceed to ChargingPausedEV, as we are fully authorized to start charging whenever car + // simply proceed to PrepareCharging, as we are fully authorized to start charging whenever car // wants to. session_log.evse(false, "AC mode, HLC disabled. We are in X1 so we can " "go directly to nominal PWM."); currentState = targetState; } - } else if (charge_mode == "DC") { + } else if (charge_mode == ChargeMode::DC) { // Figure 8 of ISO15118-3: DC with EIM before or after plugin or PnC // simple here as we always stay within 5 percent mode anyway. session_log.evse(false, "DC mode. We are in 5percent mode so we can continue without further action."); @@ -273,21 +267,17 @@ void Charger::runStateMachine() { // unsupported charging mode, give up here. EVLOG_error << "Unsupported charging mode."; currentState = EvseState::Error; - errorState = ErrorState::Error_Internal; + errorState = types::evse_manager::Error::Internal; } } else if (AuthorizedPnC()) { startTransaction(); - EvseState targetState; - if (powerAvailable()) { - targetState = EvseState::ChargingPausedEV; - } else { - targetState = EvseState::ChargingPausedEVSE; - } + const EvseState targetState(EvseState::PrepareCharging); + // We got authorization by Plug and Charge session_log.evse(false, "PnC Authorization received"); - if (charge_mode == "AC") { + if (charge_mode == ChargeMode::AC) { // Figures 3,4,5,6 of ISO15118-3: Independent on how we started we can continue with 5 percent // signalling once we got PnC authorization without going through t_step_EF or t_step_X1. @@ -297,7 +287,7 @@ void Charger::runStateMachine() { hlc_use_5percent_current_session = true; currentState = targetState; - } else if (charge_mode == "DC") { + } else if (charge_mode == ChargeMode::DC) { // Figure 8 of ISO15118-3: DC with EIM before or after plugin or PnC // simple here as we always stay within 5 percent mode anyway. session_log.evse(false, "DC mode. We are in 5percent mode so we can continue without further action."); @@ -306,7 +296,7 @@ void Charger::runStateMachine() { // unsupported charging mode, give up here. EVLOG_error << "Unsupported charging mode."; currentState = EvseState::Error; - errorState = ErrorState::Error_Internal; + errorState = types::evse_manager::Error::Internal; } } @@ -342,11 +332,32 @@ void Charger::runStateMachine() { } break; + case EvseState::PrepareCharging: + + if (new_in_state) { + signalEvent(types::evse_manager::SessionEventEnum::PrepareCharging); + 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())); + } + + // if (charge_mode == ChargeMode::DC) { + // DC: wait until car requests power on CP (B->C/D). + // Then we close contactor and wait for instructions from HLC. + // HLC will perform CableCheck, PreCharge, and PowerDelivery. + // These are all controlled from the handlers directly, so there is nothing we need to do here. + // Once HLC informs us about CurrentDemand has started, we will go to Charging in the handler. + //} + + break; + case EvseState::Charging: - if (charge_mode == "DC") { - if (new_in_state) { - r_bsp->call_allow_power_on(false); - } + if (charge_mode == ChargeMode::DC) { + // FIXME: handle DC pause/resume here + // FIXME: handle DC no power available from Energy management } else { checkSoftOverCurrent(); @@ -355,8 +366,8 @@ void Charger::runStateMachine() { break; } - if (new_in_state) { - signalEvent(EvseEvent::ChargingResumed); + if (new_in_state && lastState != EvseState::PrepareCharging) { + signalEvent(types::evse_manager::SessionEventEnum::ChargingResumed); r_bsp->call_allow_power_on(true); // make sure we are enabling PWM if (hlc_use_5percent_current_session) @@ -372,12 +383,13 @@ void Charger::runStateMachine() { break; case EvseState::ChargingPausedEV: - if (charge_mode == "DC") { + if (charge_mode == ChargeMode::DC) { if (new_in_state) { r_bsp->call_allow_power_on(false); - EVLOG_info << "Charger reached ChargingPausedEV for DC. Stopping here."; + // Assume C->B is always the end of charging for DC. + // Pauses needs to be fixed later + currentState = EvseState::StoppingCharging; } - } else { checkSoftOverCurrent(); @@ -387,7 +399,7 @@ void Charger::runStateMachine() { } if (new_in_state) { - signalEvent(EvseEvent::ChargingPausedEV); + 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) { @@ -406,11 +418,34 @@ void Charger::runStateMachine() { case EvseState::ChargingPausedEVSE: if (new_in_state) { - signalEvent(EvseEvent::ChargingPausedEVSE); + signalEvent(types::evse_manager::SessionEventEnum::ChargingPausedEVSE); pwm_off(); } break; + case EvseState::StoppingCharging: + if (new_in_state) { + signalEvent(types::evse_manager::SessionEventEnum::StoppingCharging); + pwm_off(); + + // Transaction may already be stopped when it was cancelled earlier. + // In that case, do not sent a second transactionFinished event. + if (transactionActive()) + stopTransaction(); + + if (charge_mode == ChargeMode::DC) { + // DC supply off - actually this is after relais switched off. + // this is a backup switch off, normally it should be switched off earlier by ISO protocol. + signal_DC_supply_off(); + // Car is maybe not unplugged yet, so for DC wait in this state. We will go to Finished once car is + // unplugged. + } else { + // For AC, we reached StoppingCharging because an unplug happend. + currentState = EvseState::Finished; + } + } + break; + case EvseState::Finished: // We are temporarily unavailable to avoid that new cars plug in // during the cleanup phase. Re-Enable once we are in idle. @@ -419,6 +454,7 @@ void Charger::runStateMachine() { // In that case, do not sent a second transactionFinished event. if (transactionActive()) stopTransaction(); + stopSession(); pwm_F(); } @@ -428,7 +464,7 @@ void Charger::runStateMachine() { case EvseState::Error: if (new_in_state) { - signalEvent(EvseEvent::Error); + signalEvent(types::evse_manager::SessionEventEnum::Error); pwm_off(); } // Do not set F here, as F cannot detect unplugging of car! @@ -436,7 +472,7 @@ void Charger::runStateMachine() { case EvseState::Faulted: if (new_in_state) { - signalEvent(EvseEvent::PermanentFault); + signalEvent(types::evse_manager::SessionEventEnum::PermanentFault); pwm_F(); } break; @@ -542,6 +578,26 @@ void Charger::processCPEventsState(ControlPilotEvent cp_event) { } break; + case EvseState::PrepareCharging: + if (charge_mode == ChargeMode::AC) { + // AC: once the car requests power (B->C/D), we can switch to charging. + signalEvent(types::evse_manager::SessionEventEnum::ChargingStarted); + if (cp_event == ControlPilotEvent::CarRequestedPower) { + if (powerAvailable()) { + + currentState = EvseState::Charging; + } else { + currentState = EvseState::Charging; + pauseChargingWaitForPower(); + } + } + } else { + if (cp_event == ControlPilotEvent::CarRequestedStopPower) { + currentState = EvseState::StoppingCharging; + } + } + break; + case EvseState::Charging: if (cp_event == ControlPilotEvent::CarRequestedStopPower) { currentState = EvseState::ChargingPausedEV; @@ -553,6 +609,7 @@ void Charger::processCPEventsState(ControlPilotEvent cp_event) { currentState = EvseState::Charging; } break; + default: break; } @@ -568,31 +625,37 @@ void Charger::processCPEventsIndependent(ControlPilotEvent cp_event) { currentState = EvseState::WaitingForAuthentication; break; case ControlPilotEvent::CarUnplugged: - currentState = EvseState::Finished; + if (currentState == EvseState::Error) + signalEvent(types::evse_manager::SessionEventEnum::AllErrorsCleared); + if (charge_mode == ChargeMode::AC) { + currentState = EvseState::StoppingCharging; + } else { + currentState = EvseState::Finished; + } break; case ControlPilotEvent::Error_E: currentState = EvseState::Error; - errorState = ErrorState::Error_E; + errorState = types::evse_manager::Error::Car; break; case ControlPilotEvent::Error_DF: currentState = EvseState::Error; - errorState = ErrorState::Error_DF; + errorState = types::evse_manager::Error::CarDiodeFault; break; case ControlPilotEvent::Error_Relais: currentState = EvseState::Error; - errorState = ErrorState::Error_Relais; + errorState = types::evse_manager::Error::Relais; break; case ControlPilotEvent::Error_RCD: currentState = EvseState::Error; - errorState = ErrorState::Error_RCD; + errorState = types::evse_manager::Error::RCD; break; case ControlPilotEvent::Error_VentilationNotAvailable: currentState = EvseState::Error; - errorState = ErrorState::Error_VentilationNotAvailable; + errorState = types::evse_manager::Error::VentilationNotAvailable; break; case ControlPilotEvent::Error_OverCurrent: currentState = EvseState::Error; - errorState = ErrorState::Error_OverCurrent; + errorState = types::evse_manager::Error::OverCurrent; break; default: break; @@ -744,8 +807,7 @@ bool Charger::evseReplug() { bool Charger::cancelTransaction(const types::evse_manager::StopTransactionRequest& request) { std::lock_guard lock(stateMutex); - if (currentState == EvseState::Charging || currentState == EvseState::ChargingPausedEVSE || - currentState == EvseState::ChargingPausedEV) { + if (transactionActive()) { currentState = EvseState::ChargingPausedEVSE; transaction_active = false; @@ -753,7 +815,8 @@ bool Charger::cancelTransaction(const types::evse_manager::StopTransactionReques if (request.id_tag) { stop_transaction_id_tag = request.id_tag.value(); } - signalEvent(EvseEvent::TransactionFinished); + signalEvent(types::evse_manager::SessionEventEnum::ChargingFinished); + signalEvent(types::evse_manager::SessionEventEnum::TransactionFinished); return true; } @@ -778,28 +841,29 @@ void Charger::startSession(bool authfirst) { last_start_session_reason = types::evse_manager::StartSessionReason::Authorized; else last_start_session_reason = types::evse_manager::StartSessionReason::EVConnected; - signalEvent(EvseEvent::SessionStarted); + signalEvent(types::evse_manager::SessionEventEnum::SessionStarted); } void Charger::stopSession() { std::lock_guard lock(stateMutex); session_active = false; authorized = false; - signalEvent(EvseEvent::SessionFinished); + signalEvent(types::evse_manager::SessionEventEnum::SessionFinished); } void Charger::startTransaction() { std::lock_guard lock(stateMutex); stop_transaction_id_tag.clear(); transaction_active = true; - signalEvent(EvseEvent::TransactionStarted); + signalEvent(types::evse_manager::SessionEventEnum::TransactionStarted); } void Charger::stopTransaction() { std::lock_guard lock(stateMutex); transaction_active = false; last_stop_transaction_reason = types::evse_manager::StopTransactionReason::EVDisconnected; - signalEvent(EvseEvent::TransactionFinished); + signalEvent(types::evse_manager::SessionEventEnum::ChargingFinished); + signalEvent(types::evse_manager::SessionEventEnum::TransactionFinished); } std::string Charger::getStopTransactionIdTag() { @@ -823,7 +887,7 @@ bool Charger::switchThreePhasesWhileCharging(bool n) { } void Charger::setup(bool three_phases, bool has_ventilation, const std::string& country_code, bool rcd_enabled, - const std::string& _charge_mode, bool _ac_hlc_enabled, bool _ac_hlc_use_5percent, + const ChargeMode _charge_mode, bool _ac_hlc_enabled, bool _ac_hlc_use_5percent, bool _ac_enforce_hlc, bool _ac_with_soc_timeout) { std::lock_guard lock(configMutex); // set up board support package @@ -937,7 +1001,7 @@ bool Charger::DeAuthorize() { return false; } -Charger::ErrorState Charger::getErrorState() { +types::evse_manager::Error Charger::getErrorState() { std::lock_guard lock(stateMutex); return errorState; } @@ -947,7 +1011,7 @@ Charger::ErrorState Charger::getErrorState() { bool Charger::disable() { std::lock_guard lock(stateMutex); currentState = EvseState::Disabled; - signalEvent(EvseEvent::Disabled); + signalEvent(types::evse_manager::SessionEventEnum::Disabled); return true; } @@ -955,7 +1019,7 @@ bool Charger::enable() { std::lock_guard lock(stateMutex); if (currentState == EvseState::Disabled) { currentState = EvseState::Idle; - signalEvent(EvseEvent::Enabled); + signalEvent(types::evse_manager::SessionEventEnum::Enabled); return true; } return false; @@ -964,7 +1028,7 @@ bool Charger::enable() { void Charger::set_faulted() { std::lock_guard lock(stateMutex); currentState = EvseState::Faulted; - signalEvent(EvseEvent::PermanentFault); + signalEvent(types::evse_manager::SessionEventEnum::PermanentFault); } bool Charger::restart() { @@ -1015,97 +1079,16 @@ std::string Charger::evseStateToString(EvseState s) { case EvseState::Replug: return ("Replug"); break; - } - return "Invalid"; -} - -std::string Charger::evseEventToString(EvseEvent e) { - switch (e) { - case EvseEvent::Enabled: - return ("Enabled"); - break; - case EvseEvent::Disabled: - return ("Disabled"); - break; - case EvseEvent::SessionStarted: - return ("SessionStarted"); - break; - case EvseEvent::TransactionStarted: - return ("TransactionStarted"); - break; - case EvseEvent::AuthRequired: - return ("AuthRequired"); - break; - case EvseEvent::ChargingPausedEV: - return ("ChargingPausedEV"); - break; - case EvseEvent::ChargingPausedEVSE: - return ("ChargingPausedEVSE"); - break; - case EvseEvent::ChargingResumed: - return ("ChargingResumed"); - break; - case EvseEvent::SessionFinished: - return ("SessionFinished"); - break; - case EvseEvent::TransactionFinished: - return ("TransactionFinished"); - break; - case EvseEvent::Error: - return ("Error"); - break; - case EvseEvent::PermanentFault: - return ("PermanentFault"); - break; - case EvseEvent::ReplugStarted: - return ("ReplugStarted"); + case EvseState::PrepareCharging: + return ("PrepareCharging"); break; - case EvseEvent::ReplugFinished: - return ("ReplugFinished"); + case EvseState::StoppingCharging: + return ("StoppingCharging"); break; } return "Invalid"; } -std::string Charger::errorStateToString(ErrorState s) { - switch (s) { - case ErrorState::Error_E: - return ("Car"); - break; - case ErrorState::Error_OverCurrent: - return ("OverCurrent"); - break; - case ErrorState::Error_DF: - return ("CarDiodeFault"); - break; - case ErrorState::Error_Relais: - return ("Relais"); - break; - case ErrorState::Error_VentilationNotAvailable: - return ("VentilationNotAvailable"); - break; - case ErrorState::Error_RCD: - return ("RCD"); - break; - case ErrorState::Error_Internal: - return ("Internal"); - break; - case ErrorState::Error_SLAC: - return ("SLAC"); - break; - case ErrorState::Error_HLC: - return ("HLC"); - break; - } - return "Invalid"; -} - -/* -FIXME -float Charger::getResidualCurrent() { - return control_pilot.getResidualCurrent(); -}*/ - float Charger::getMaxCurrent() { std::lock_guard lock(configMutex); return maxCurrent; @@ -1140,7 +1123,7 @@ void Charger::checkSoftOverCurrent() { std::chrono::duration_cast(now - lastOverCurrentEvent).count(); if (overCurrent && timeSinceOverCurrentStarted >= softOverCurrentTimeout) { currentState = EvseState::Error; - errorState = ErrorState::Error_OverCurrent; + errorState = types::evse_manager::Error::OverCurrent; } } @@ -1177,4 +1160,12 @@ bool Charger::getMatchingStarted() { return matching_started; } +void Charger::notifyCurrentDemandStarted() { + std::lock_guard lock(stateMutex); + if (currentState == EvseState::PrepareCharging) { + signalEvent(types::evse_manager::SessionEventEnum::ChargingStarted); + currentState = EvseState::Charging; + } +} + } // namespace module diff --git a/modules/EvseManager/Charger.hpp b/modules/EvseManager/Charger.hpp index 2c477073b..1c5e695d4 100644 --- a/modules/EvseManager/Charger.hpp +++ b/modules/EvseManager/Charger.hpp @@ -76,8 +76,13 @@ class Charger { float getMaxCurrent(); sigslot::signal signalMaxCurrent; + enum class ChargeMode { + AC, + DC + }; + void setup(bool three_phases, bool has_ventilation, const std::string& country_code, bool rcd_enabled, - const std::string& charge_mode, bool ac_hlc_enabled, bool ac_hlc_use_5percent, bool ac_enforce_hlc, + const ChargeMode charge_mode, bool ac_hlc_enabled, bool ac_hlc_use_5percent, bool ac_enforce_hlc, bool ac_with_soc_timeout); bool enable(); @@ -126,48 +131,15 @@ class Charger { bool forceUnlock(); - // float getResidualCurrent(); - // bool isPowerOn(); - - // Public states for Hi Level - - enum class EvseEvent { - Enabled, - Disabled, - SessionStarted, - TransactionStarted, - AuthRequired, - ChargingPausedEV, - ChargingPausedEVSE, - ChargingResumed, - SessionFinished, - TransactionFinished, - Error, - PermanentFault, - ReplugStarted, - ReplugFinished - }; - - enum class ErrorState { - Error_E, - Error_DF, - Error_Relais, - Error_VentilationNotAvailable, - Error_RCD, - Error_OverCurrent, - Error_Internal, - Error_SLAC, - Error_HLC - }; - // Signal for EvseEvents - sigslot::signal signalEvent; - std::string evseEventToString(EvseEvent e); + sigslot::signal signalEvent; sigslot::signal<> signalACWithSoCTimeout; + sigslot::signal<> signal_DC_supply_off; + // Request more details about the error that happend - ErrorState getErrorState(); + types::evse_manager::Error getErrorState(); void processEvent(types::board_support::Event event); @@ -178,6 +150,8 @@ class Charger { void setMatchingStarted(bool m); bool getMatchingStarted(); + void notifyCurrentDemandStarted(); + // Note: Deprecated, do not use EvseState externally. // Kept for compatibility, will be removed from public interface // in the future. @@ -187,9 +161,11 @@ class Charger { Disabled, Idle, WaitingForAuthentication, + PrepareCharging, Charging, ChargingPausedEV, ChargingPausedEVSE, + StoppingCharging, Finished, Error, Faulted, @@ -199,11 +175,10 @@ class Charger { }; std::string evseStateToString(EvseState s); - std::string errorStateToString(ErrorState s); EvseState getCurrentState(); sigslot::signal signalState; - sigslot::signal signalError; + sigslot::signal signalError; // /Deprecated private: @@ -243,7 +218,7 @@ class Charger { EvseState currentState; EvseState lastState; - ErrorState errorState; + types::evse_manager::Error errorState{types::evse_manager::Error::Internal}; std::chrono::system_clock::time_point currentStateStarted; float ampereToDutyCycle(float ampere); @@ -282,7 +257,7 @@ class Charger { std::string auth_tag; // AC or DC - std::string charge_mode; + ChargeMode charge_mode{0}; // Config option bool ac_hlc_enabled; // HLC enabled in current AC session. This can change during the session if e.g. HLC fails. diff --git a/modules/EvseManager/EvseManager.cpp b/modules/EvseManager/EvseManager.cpp index 558805616..23969e5f9 100644 --- a/modules/EvseManager/EvseManager.cpp +++ b/modules/EvseManager/EvseManager.cpp @@ -1,7 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest #include "EvseManager.hpp" +#include "SessionLog.hpp" +#include "Timeout.hpp" +#include #include +using namespace std::literals::chrono_literals; namespace module { @@ -17,7 +21,7 @@ void EvseManager::init() { invoke_init(*p_evse); invoke_init(*p_energy_grid); invoke_init(*p_token_provider); - authorization_available = false; + // check if a slac module is connected to the optional requirement slac_enabled = !r_slac.empty(); @@ -26,11 +30,24 @@ void EvseManager::init() { slac_enabled = false; } hlc_enabled = !r_hlc.empty(); + + if (config.charge_mode == "DC" && (!hlc_enabled || !slac_enabled || r_powersupply_DC.empty())) { + EVLOG_error << "DC mode requires slac, HLC and powersupply DCDC to be connected"; + exit(255); + } + + if (config.charge_mode == "DC" && r_imd.empty()) { + EVLOG_warning << "DC mode without isolation monitoring configured, please check your national regulations."; + } + reserved = false; reservation_id = 0; hlc_waiting_for_auth_eim = false; hlc_waiting_for_auth_pnc = false; + + latest_target_voltage = 0; + latest_target_current = 0; } void EvseManager::ready() { @@ -39,7 +56,7 @@ void EvseManager::ready() { if (get_hlc_enabled()) { // Set up EVSE ID - r_hlc[0]->call_set_EVSEID(config.evse_id); + r_hlc[0]->call_set_EVSEID(config.evse_id, config.evse_id_din); // Set up auth options for HLC Array payment_options; @@ -63,40 +80,221 @@ void EvseManager::ready() { transfer_modes.insert(transfer_modes.end(), "AC_single_phase_core"); } } else if (config.charge_mode == "DC") { - transfer_modes.insert(transfer_modes.end(), "DC_core"); + // transfer_modes.insert(transfer_modes.end(), "DC_core"); transfer_modes.insert(transfer_modes.end(), "DC_extended"); - transfer_modes.insert(transfer_modes.end(), "DC_combo_core"); - transfer_modes.insert(transfer_modes.end(), "DC_unique"); - r_hlc[0]->call_set_DC_EVSECurrentRegulationTolerance(config.dc_current_regulation_tolerance); - r_hlc[0]->call_set_DC_EVSEPeakCurrentRipple(config.dc_peak_current_ripple); - r_hlc[0]->call_set_DC_EVSEPresentVoltage(400); // FIXME: set a correct values - r_hlc[0]->call_set_DC_EVSEPresentCurrent(0); - r_hlc[0]->call_set_DC_EVSEMaximumCurrentLimit(400); - r_hlc[0]->call_set_DC_EVSEMaximumPowerLimit(200000); - r_hlc[0]->call_set_DC_EVSEMaximumVoltageLimit(1000); - r_hlc[0]->call_set_DC_EVSEMinimumCurrentLimit(0); - r_hlc[0]->call_set_DC_EVSEMinimumVoltageLimit(0); + powersupply_capabilities = r_powersupply_DC[0]->call_getCapabilities(); + + r_hlc[0]->call_set_DC_EVSECurrentRegulationTolerance( + powersupply_capabilities.current_regulation_tolerance_A); + r_hlc[0]->call_set_DC_EVSEPeakCurrentRipple(powersupply_capabilities.peak_current_ripple_A); + + types::iso15118_charger::DC_EVSEPresentVoltage_Current present_values; + present_values.EVSEPresentVoltage = 0; + present_values.EVSEPresentCurrent = 0; + r_hlc[0]->call_set_DC_EVSEPresentVoltageCurrent(present_values); + + r_hlc[0]->call_set_EVSEEnergyToBeDelivered(10000); + + types::iso15118_charger::DC_EVSEMaximumLimits evseMaxLimits; + evseMaxLimits.EVSEMaximumCurrentLimit = powersupply_capabilities.max_export_current_A; + evseMaxLimits.EVSEMaximumPowerLimit = powersupply_capabilities.max_export_power_W; + evseMaxLimits.EVSEMaximumVoltageLimit = powersupply_capabilities.max_export_voltage_V; + r_hlc[0]->call_set_DC_EVSEMaximumLimits(evseMaxLimits); + + types::iso15118_charger::DC_EVSEMinimumLimits evseMinLimits; + evseMinLimits.EVSEMinimumCurrentLimit = powersupply_capabilities.min_export_current_A; + evseMinLimits.EVSEMinimumVoltageLimit = powersupply_capabilities.min_export_voltage_V; + r_hlc[0]->call_set_DC_EVSEMinimumLimits(evseMinLimits); + + // Cable check for DC charging + r_hlc[0]->subscribe_Start_CableCheck([this] { cable_check(); }); + + // Notification that current demand has started + r_hlc[0]->subscribe_currentDemand_Started([this] { charger->notifyCurrentDemandStarted(); }); + + // Isolation monitoring for DC charging handler if (!r_imd.empty()) { - r_hlc[0]->subscribe_Require_EVSEIsolationCheck([this]() { - // start measurement with IMD - EVLOG_info << "Start isolation testing..."; - r_imd[0]->call_startIsolationTest(); + r_imd[0]->subscribe_IsolationMeasurement([this](types::isolation_monitor::IsolationMeasurement m) { + // new DC isolation monitoring measurement received + EVLOG_info << fmt::format("Isolation measurement P {} N {}.", m.resistance_P_Ohm, + m.resistance_N_Ohm); + isolation_measurement = m; }); + } - r_imd[0]->subscribe_IsolationStatus([this](std::string s) { - // Callback after isolation measurement finished - EVLOG_info << fmt::format("Isolation testing finsihed with status {}.", s); - r_hlc[0]->call_set_EVSEIsolationStatus(s); + // Get voltage/current from DC power supply + if (!r_powersupply_DC.empty()) { + r_powersupply_DC[0]->subscribe_voltage_current([this](types::power_supply_DC::VoltageCurrent m) { + powersupply_measurement = m; + types::iso15118_charger::DC_EVSEPresentVoltage_Current present_values; + present_values.EVSEPresentVoltage = (m.voltage_V > 0 ? m.voltage_V : 0.0); + present_values.EVSEPresentCurrent = (m.current_A > 0 ? m.current_A : 0.0); + + if (config.hack_present_current_offset > 0) { + present_values.EVSEPresentCurrent = + present_values.EVSEPresentCurrent.get() + config.hack_present_current_offset; + } + + r_hlc[0]->call_set_DC_EVSEPresentVoltageCurrent(present_values); + // publish EV_info + { + std::scoped_lock lock(ev_info_mutex); + ev_info.present_voltage = present_values.EVSEPresentVoltage; + ev_info.present_current = present_values.EVSEPresentCurrent; + // p_evse->publish_ev_info(ev_info); + } }); } - /* - r_hlc[0]->subscribe_DC_EVTargetCurrent([this](double amps) { - r_hlc[0]->call_set_DC_EVSEPresentCurrent(amps * 0.9); // FIXME - }); - */ - // later - // set_EVSEIsolationStatus + + // Car requests a target voltage and current limit + r_hlc[0]->subscribe_DC_EVTargetVoltageCurrent([this](types::iso15118_charger::DC_EVTargetValues v) { + bool target_changed = false; + + // Hack for Skoda Enyaq that should be fixed in a different way + if (config.hack_skoda_enyaq && (v.DC_EVTargetVoltage < 300 || v.DC_EVTargetCurrent < 0)) + return; + + // Some power supplies don't really like 0 current limits + if (v.DC_EVTargetCurrent < 2) + v.DC_EVTargetCurrent = 2; + + if (v.DC_EVTargetVoltage != latest_target_voltage || v.DC_EVTargetCurrent != latest_target_current) { + latest_target_voltage = v.DC_EVTargetVoltage; + latest_target_current = v.DC_EVTargetCurrent; + target_changed = true; + } + + if (target_changed) { + powersupply_DC_set(v.DC_EVTargetVoltage, v.DC_EVTargetCurrent); + if (!contactor_open) { + powersupply_DC_on(); + } + + { + std::scoped_lock lock(ev_info_mutex); + ev_info.target_voltage = v.DC_EVTargetVoltage; + ev_info.target_current = v.DC_EVTargetCurrent; + p_evse->publish_ev_info(ev_info); + } + } + }); + + // Car requests DC contactor open. We don't actually open but switch off DC supply. + // opening will be done by Charger on C->B CP event. + r_hlc[0]->subscribe_DC_Open_Contactor([this](bool b) { + if (b) + powersupply_DC_off(); + r_imd[0]->call_stop(); + }); + + // Back up switch off - charger signalled that it needs to switch off now. + // During normal operation this should be done earlier before switching off relais by HLC protocol. + charger->signal_DC_supply_off.connect([this] { + powersupply_DC_off(); + r_imd[0]->call_stop(); + }); + + // Current demand has finished - switch off DC supply + r_hlc[0]->subscribe_currentDemand_Finished([this] { powersupply_DC_off(); }); + + r_hlc[0]->subscribe_DC_EVMaximumLimits([this](types::iso15118_charger::DC_EVMaximumLimits l) { + std::scoped_lock lock(ev_info_mutex); + ev_info.maximum_current_limit = l.DC_EVMaximumCurrentLimit; + ev_info.maximum_power_limit = l.DC_EVMaximumPowerLimit; + ev_info.maximum_voltage_limit = l.DC_EVMaximumVoltageLimit; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_DepartureTime([this](const std::string& t) { + std::scoped_lock lock(ev_info_mutex); + ev_info.departure_time = t; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_AC_EAmount([this](double e) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.remaining_energy_needed = e; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_AC_EVMaxVoltage([this](double v) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.maximum_voltage_limit = v; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_AC_EVMaxCurrent([this](double c) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.maximum_current_limit = c; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_AC_EVMinCurrent([this](double c) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.minimum_current_limit = c; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_DC_EVEnergyCapacity([this](double c) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.battery_capacity = c; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_DC_EVEnergyRequest([this](double c) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.remaining_energy_needed = c; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_DC_FullSOC([this](double c) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.battery_full_soc = c; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_DC_BulkSOC([this](double c) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.battery_bulk_soc = c; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_DC_EVRemainingTime([this](types::iso15118_charger::DC_EVRemainingTime t) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.estimated_time_full = t.EV_RemainingTimeToFullSoC; + ev_info.estimated_time_bulk = t.EV_RemainingTimeToBulkSoC; + p_evse->publish_ev_info(ev_info); + }); + + r_hlc[0]->subscribe_DC_EVStatus([this](types::iso15118_charger::DC_EVStatusType s) { + // FIXME send only on change / throttle messages + std::scoped_lock lock(ev_info_mutex); + ev_info.soc = s.DC_EVRESSSOC; + p_evse->publish_ev_info(ev_info); + }); + + // unused vars of HLC for now: + + // AC_Close_Contactor + // AC_Open_Contactor + + // V2G_Setup_Finished + // SelectedPaymentOption + // RequestedEnergyTransferMode + + // EV_ChargingSession + // DC_BulkChargingComplete + // DC_ChargingComplete } else { EVLOG_error << "Unsupported charging mode."; @@ -112,7 +310,8 @@ void EvseManager::ready() { // set up debug mode for HLC if (config.session_logging) { - r_hlc[0]->call_enable_debug_mode("Full"); + types::iso15118_charger::DebugMode debug_mode = types::iso15118_charger::DebugMode::Full; + r_hlc[0]->call_enable_debug_mode(debug_mode); } // reset error flags @@ -121,36 +320,63 @@ void EvseManager::ready() { // implement Auth handlers r_hlc[0]->subscribe_Require_Auth_EIM([this]() { - // Do we have auth already (i.e. delayed HLC after charging already running)? + // EVLOG_info << "--------------------------------------------- Require_Auth_EIM called"; + // Do we have auth already (i.e. delayed HLC after charging already running)? if ((config.dbg_hlc_auth_after_tstep && charger->Authorized_EIM_ready_for_HLC()) || (!config.dbg_hlc_auth_after_tstep && charger->Authorized_EIM())) { + { + std::scoped_lock lock(hlc_mutex); + hlc_waiting_for_auth_eim = false; + hlc_waiting_for_auth_pnc = false; + } r_hlc[0]->call_set_Auth_Okay_EIM(true); + } else { + std::scoped_lock lock(hlc_mutex); + hlc_waiting_for_auth_eim = true; + hlc_waiting_for_auth_pnc = false; } }); // implement Auth handlers r_hlc[0]->subscribe_EVCCIDD([this](const std::string& _token) { - json autocharge_token; + types::authorization::ProvidedIdToken autocharge_token; std::string token = _token; token.erase(remove(token.begin(), token.end(), ':'), token.end()); - autocharge_token["id_token"] = "VID:" + token; - autocharge_token["type"] = "autocharge"; - autocharge_token["timeout"] = 60; + autocharge_token.id_token = "VID:" + token; + autocharge_token.type = types::authorization::TokenType::Autocharge; p_token_provider->publish_provided_token(autocharge_token); + + { + std::scoped_lock lock(ev_info_mutex); + ev_info.evcc_id = _token; + p_evse->publish_ev_info(ev_info); + } }); r_hlc[0]->subscribe_Require_Auth_PnC([this]() { // Do we have auth already (i.e. delayed HLC after charging already running)? if (charger->Authorized_PnC()) { + { + std::scoped_lock lock(hlc_mutex); + hlc_waiting_for_auth_eim = false; + hlc_waiting_for_auth_pnc = false; + } r_hlc[0]->call_set_Auth_Okay_PnC(true); + } else { + std::scoped_lock lock(hlc_mutex); + hlc_waiting_for_auth_eim = false; + hlc_waiting_for_auth_pnc = true; } }); // Install debug V2G Messages handler if session logging is enabled if (config.session_logging) { - r_hlc[0]->subscribe_V2G_Messages([this](Object v2g_message) { log_v2g_message(v2g_message); }); + r_hlc[0]->subscribe_V2G_Messages([this](types::iso15118_charger::V2G_Messages v2g_messages) { + json v2g = v2g_messages; + log_v2g_message(v2g); + }); } // switch to DC mode for first session for AC with SoC if (config.ac_with_soc) { @@ -164,19 +390,11 @@ void EvseManager::ready() { charger->signalACWithSoCTimeout.connect([this]() { switch_DC_mode(); }); - r_hlc[0]->subscribe_DC_EVRESSSOC([this](double soc) { - EVLOG_info << fmt::format("SoC received: {}.", soc); + r_hlc[0]->subscribe_DC_EVStatus([this](types::iso15118_charger::DC_EVStatusType status) { + EVLOG_info << fmt::format("SoC received: {}.", status.DC_EVRESSSOC); switch_AC_mode(); }); } - - // Do not implement for now: - // set_EVSEEnergyToBeDelivered - // Require_EVSEIsolationCheck ignored for now - // AC_Close_Contactor/AC_Open_Contactor: we ignore these events, we still switch on/off with state C/D - // for now. May not work if PWM switch on comes before request through HLC, so implement soon! All DC - // stuff missing V2G_Setup_Finished is useful but ignored for now All other telemetry type info is - // ignored for now } hw_capabilities = r_bsp->call_get_hw_capabilities(); @@ -211,6 +429,13 @@ void EvseManager::ready() { r_hlc[0]->call_set_EVSE_EmergencyShutdown(false); r_hlc[0]->call_contactor_open(true); r_hlc[0]->call_stop_charging(false); + latest_target_voltage = 0; + latest_target_current = 0; + { + std::scoped_lock lock(hlc_mutex); + hlc_waiting_for_auth_eim = false; + hlc_waiting_for_auth_pnc = false; + } } if (event == types::board_support::Event::ErrorRelais) { @@ -234,10 +459,12 @@ void EvseManager::ready() { } if (event == types::board_support::Event::PowerOn) { + contactor_open = false; r_hlc[0]->call_contactor_closed(true); } if (event == types::board_support::Event::PowerOff) { + contactor_open = true; r_hlc[0]->call_contactor_open(true); } } @@ -264,11 +491,14 @@ void EvseManager::ready() { r_bsp->subscribe_nr_of_phases_available([this](int n) { signalNrOfPhasesAvailable(n); }); - if (r_powermeter.size() > 0) { - r_powermeter[0]->subscribe_powermeter([this](types::powermeter::Powermeter powermeter) { - json p = powermeter; + if (r_powermeter_billing().size() > 0) { + r_powermeter_billing()[0]->subscribe_powermeter([this](types::powermeter::Powermeter p) { // Inform charger about current charging current. This is used for slow OC detection. - charger->setCurrentDrawnByVehicle(p["current_A"]["L1"], p["current_A"]["L2"], p["current_A"]["L3"]); + if (p.current_A.is_initialized() && p.current_A.get().L1.is_initialized() && + p.current_A.get().L2.is_initialized() && p.current_A.get().L3.is_initialized()) { + charger->setCurrentDrawnByVehicle(p.current_A.get().L1.get(), p.current_A.get().L2.get(), + p.current_A.get().L3.get()); + } // Inform HLC about the power meter data if (get_hlc_enabled()) { @@ -276,26 +506,39 @@ void EvseManager::ready() { } // Store local cache - latest_powermeter_data = p; + { + std::scoped_lock lock(power_mutex); + latest_powermeter_data_billing = p; + } // External Nodered interface - mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/phaseSeqError", config.connector_id), - powermeter.phase_seq_error.get()); + if (p.phase_seq_error.is_initialized()) { + mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/phaseSeqError", config.connector_id), + p.phase_seq_error.get()); + } + mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/time_stamp", config.connector_id), - (int)powermeter.timestamp); - mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/totalKw", config.connector_id), - (powermeter.power_W.get().L1.get() + powermeter.power_W.get().L2.get() + - powermeter.power_W.get().L3.get()) / - 1000., - 1); + p.timestamp); + + if (p.power_W.is_initialized()) { + mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/totalKw", config.connector_id), + p.power_W.get().total / 1000., 1); + } + mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter/totalKWattHr", config.connector_id), - powermeter.energy_Wh_import.total / 1000.); - mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter_json", config.connector_id), p.dump()); + p.energy_Wh_import.total / 1000.); + json j; + to_json(j, p); + mqtt.publish(fmt::format("everest_external/nodered/{}/powermeter_json", config.connector_id), j.dump()); // /External Nodered interface }); } if (slac_enabled) { + + // Reset once on startup and disable modem + r_slac[0]->call_reset(false); + r_slac[0]->subscribe_state([this](const std::string& s) { session_log.evse(true, fmt::format("SLAC {}", s)); // Notify charger whether matching was started (or is done) or not @@ -320,31 +563,42 @@ void EvseManager::ready() { charger->requestErrorSequence(); }); } - charger->signalEvent.connect([this](Charger::EvseEvent s) { + + charger->signalEvent.connect([this](types::evse_manager::SessionEventEnum s) { // Cancel reservations if charger is disabled or faulted - if (s == Charger::EvseEvent::Disabled || s == Charger::EvseEvent::PermanentFault) { + if (s == types::evse_manager::SessionEventEnum::Disabled || + s == types::evse_manager::SessionEventEnum::PermanentFault) { cancel_reservation(); } + if (s == types::evse_manager::SessionEventEnum::SessionStarted || + s == types::evse_manager::SessionEventEnum::SessionFinished) { + // Reset EV information on Session start and end + ev_info = types::evse_manager::EVInfo(); + p_evse->publish_ev_info(ev_info); + } }); invoke_ready(*p_evse); invoke_ready(*p_energy_grid); invoke_ready(*p_token_provider); if (config.ac_with_soc) { - setup_DC_mode(); + setup_fake_DC_mode(); } else { charger->setup(local_three_phases, config.has_ventilation, config.country_code, config.rcd_enabled, - config.charge_mode, slac_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, false); + (config.charge_mode == "DC" ? Charger::ChargeMode::DC : Charger::ChargeMode::AC), slac_enabled, + config.ac_hlc_use_5percent, config.ac_enforce_hlc, false); } // start with a limit of 0 amps. We will get a budget from EnergyManager that is locally limited by hw // caps. charger->setMaxCurrent(0.0F, date::utc_clock::now()); charger->run(); charger->enable(); + EVLOG_info << fmt::format(fmt::emphasis::bold | fg(fmt::terminal_color::green), "🌀🌀🌀 Ready to start charging 🌀🌀🌀"); } -json EvseManager::get_latest_powermeter_data() { - return latest_powermeter_data; +types::powermeter::Powermeter EvseManager::get_latest_powermeter_data_billing() { + std::scoped_lock lock(power_mutex); + return latest_powermeter_data_billing; } types::board_support::HardwareCapabilities EvseManager::get_hw_capabilities() { @@ -358,7 +612,7 @@ int32_t EvseManager::get_reservation_id() { void EvseManager::switch_DC_mode() { charger->evseReplug(); - setup_DC_mode(); + setup_fake_DC_mode(); } void EvseManager::switch_AC_mode() { @@ -366,9 +620,11 @@ void EvseManager::switch_AC_mode() { setup_AC_mode(); } -void EvseManager::setup_DC_mode() { - charger->setup(local_three_phases, config.has_ventilation, config.country_code, config.rcd_enabled, "DC", - slac_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, false); +// 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, slac_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, false); // Set up energy transfer modes for HLC. For now we only support either DC or AC, not both at the same time. Array transfer_modes; @@ -376,22 +632,31 @@ void EvseManager::setup_DC_mode() { transfer_modes.insert(transfer_modes.end(), "DC_extended"); transfer_modes.insert(transfer_modes.end(), "DC_combo_core"); transfer_modes.insert(transfer_modes.end(), "DC_unique"); - r_hlc[0]->call_set_DC_EVSECurrentRegulationTolerance(config.dc_current_regulation_tolerance); - r_hlc[0]->call_set_DC_EVSEPeakCurrentRipple(config.dc_peak_current_ripple); - r_hlc[0]->call_set_DC_EVSEPresentVoltage(400); // FIXME: set a correct values - r_hlc[0]->call_set_DC_EVSEPresentCurrent(0); - r_hlc[0]->call_set_DC_EVSEMaximumCurrentLimit(400); - r_hlc[0]->call_set_DC_EVSEMaximumPowerLimit(200000); - r_hlc[0]->call_set_DC_EVSEMaximumVoltageLimit(1000); - r_hlc[0]->call_set_DC_EVSEMinimumCurrentLimit(0); - r_hlc[0]->call_set_DC_EVSEMinimumVoltageLimit(0); + r_hlc[0]->call_set_DC_EVSECurrentRegulationTolerance(powersupply_capabilities.current_regulation_tolerance_A); + r_hlc[0]->call_set_DC_EVSEPeakCurrentRipple(powersupply_capabilities.peak_current_ripple_A); + + types::iso15118_charger::DC_EVSEPresentVoltage_Current present_values; + present_values.EVSEPresentVoltage = 400; // FIXME: set a correct values + present_values.EVSEPresentCurrent = 0; + r_hlc[0]->call_set_DC_EVSEPresentVoltageCurrent(present_values); + + types::iso15118_charger::DC_EVSEMaximumLimits evseMaxLimits; + evseMaxLimits.EVSEMaximumCurrentLimit = 400; + evseMaxLimits.EVSEMaximumPowerLimit = 200000; + evseMaxLimits.EVSEMaximumVoltageLimit = 1000; + r_hlc[0]->call_set_DC_EVSEMaximumLimits(evseMaxLimits); + + types::iso15118_charger::DC_EVSEMinimumLimits evseMinLimits; + evseMinLimits.EVSEMinimumCurrentLimit = 0; + evseMinLimits.EVSEMinimumVoltageLimit = 0; + r_hlc[0]->call_set_DC_EVSEMinimumLimits(evseMinLimits); r_hlc[0]->call_set_SupportedEnergyTransferMode(transfer_modes); } void EvseManager::setup_AC_mode() { - charger->setup(local_three_phases, config.has_ventilation, config.country_code, config.rcd_enabled, "AC", - slac_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, true); + charger->setup(local_three_phases, config.has_ventilation, config.country_code, config.rcd_enabled, + Charger::ChargeMode::AC, slac_enabled, config.ac_hlc_use_5percent, config.ac_enforce_hlc, true); // Set up energy transfer modes for HLC. For now we only support either DC or AC, not both at the same time. Array transfer_modes; @@ -401,7 +666,8 @@ void EvseManager::setup_AC_mode() { } else { transfer_modes.insert(transfer_modes.end(), "AC_single_phase_core"); } - r_hlc[0]->call_set_SupportedEnergyTransferMode(transfer_modes); + if (get_hlc_enabled()) + r_hlc[0]->call_set_SupportedEnergyTransferMode(transfer_modes); } bool EvseManager::updateLocalMaxCurrentLimit(float max_current) { @@ -439,8 +705,8 @@ bool EvseManager::reserve(int32_t id) { reservation_id = id; // publish event to other modules - json se; - se["event"] = "ReservationStart"; + types::evse_manager::SessionEvent se; + se.event = types::evse_manager::SessionEventEnum::ReservationStart; signalReservationEvent(se); return true; @@ -457,8 +723,8 @@ void EvseManager::cancel_reservation() { reservation_id = 0; // publish event to other modules - json se; - se["event"] = "ReservationEnd"; + types::evse_manager::SessionEvent se; + se.event = types::evse_manager::SessionEventEnum::ReservationEnd; signalReservationEvent(se); } @@ -499,4 +765,193 @@ void EvseManager::log_v2g_message(Object m) { } } +void EvseManager::charger_was_authorized() { + + std::scoped_lock lock(hlc_mutex); + if (hlc_waiting_for_auth_pnc && charger->Authorized_PnC()) { + r_hlc[0]->call_set_Auth_Okay_PnC(true); + hlc_waiting_for_auth_eim = false; + hlc_waiting_for_auth_pnc = false; + } + + if (hlc_waiting_for_auth_eim && charger->Authorized_EIM()) { + r_hlc[0]->call_set_Auth_Okay_EIM(true); + hlc_waiting_for_auth_eim = false; + hlc_waiting_for_auth_pnc = false; + } +} + +void EvseManager::cable_check() { + + if (r_imd.empty()) { + // If no IMD is connected, we skip isolation checking. + EVLOG_info << "No IMD: skippint cable check."; + r_hlc[0]->call_cableCheck_Finished(false); + r_hlc[0]->call_set_EVSEIsolationStatus(types::iso15118_charger::IsolationStatus::No_IMD); + return; + } + // start cable check in a seperate thread. + std::thread t([this]() { + session_log.evse(true, "Start cable check..."); + bool ok = false; + + // verify the relais are really switched on and set 500V output + if (!contactor_open && powersupply_DC_set(config.dc_isolation_voltage, 2)) { + + powersupply_DC_on(); + r_imd[0]->call_start(); + + // wait until the voltage has rised to the target value + if (!wait_powersupply_DC_voltage_reached(config.dc_isolation_voltage)) { + EVLOG_info << "Voltage did not rise to 500V within timeout"; + powersupply_DC_off(); + ok = false; + r_imd[0]->call_stop(); + } else { + // read out one new isolation resistance + isolation_measurement.clear(); + types::isolation_monitor::IsolationMeasurement m; + if (!isolation_measurement.wait_for(m, 10s)) { + EVLOG_info << "Did not receive isolation measurement from IMD within 10 seconds."; + powersupply_DC_off(); + ok = false; + } else { + // wait until the voltage is back to safe level + float minvoltage = (config.switch_to_minimum_voltage_after_cable_check + ? powersupply_capabilities.min_export_voltage_V + : config.dc_isolation_voltage); + + // We do not want to shut down power supply + if (minvoltage < 60) { + minvoltage = 60; + } + powersupply_DC_set(minvoltage, 2); + + if (!wait_powersupply_DC_below_voltage(minvoltage + 20)) { + EVLOG_info << "Voltage did not go back to minimal voltage within timeout."; + ok = false; + } else { + // verify it is within ranges. Warning level is <500 Ohm/V_max_output_rating, Fault + // is <100 + const double min_resistance_ok = 500. / powersupply_capabilities.max_export_voltage_V; + const double min_resistance_warning = 100. / powersupply_capabilities.max_export_voltage_V; + + if (m.resistance_N_Ohm < min_resistance_warning || + m.resistance_P_Ohm < min_resistance_warning) { + EVLOG_error << fmt::format("Isolation measurement FAULT P {} N {}.", m.resistance_P_Ohm, + m.resistance_N_Ohm); + ok = false; + r_hlc[0]->call_set_EVSEIsolationStatus(types::iso15118_charger::IsolationStatus::Fault); + r_imd[0]->call_stop(); + } else if (m.resistance_N_Ohm < min_resistance_ok || m.resistance_P_Ohm < min_resistance_ok) { + EVLOG_error << fmt::format("Isolation measurement WARNING P {} N {}.", m.resistance_P_Ohm, + m.resistance_N_Ohm); + ok = true; + r_hlc[0]->call_set_EVSEIsolationStatus(types::iso15118_charger::IsolationStatus::Warning); + } else { + EVLOG_info << fmt::format("Isolation measurement Ok P {} N {}.", m.resistance_P_Ohm, + m.resistance_N_Ohm); + ok = true; + r_hlc[0]->call_set_EVSEIsolationStatus(types::iso15118_charger::IsolationStatus::Valid); + } + } + } + } + } + + // Sleep before submitting result to spend more time in cable check. This is needed for some solar inverters + // used as DC chargers for them to warm up. + sleep(config.hack_sleep_in_cable_check); + + // submit result to HLC + r_hlc[0]->call_cableCheck_Finished(ok); + }); + // Detach thread and exit command handler right away + t.detach(); +} + +void EvseManager::powersupply_DC_on() { + r_powersupply_DC[0]->call_setMode(types::power_supply_DC::Mode::Export); +} + +bool EvseManager::powersupply_DC_set(double voltage, double current) { + // check limits of supply + if (voltage >= powersupply_capabilities.min_export_voltage_V && + voltage <= powersupply_capabilities.max_export_voltage_V) { + + if (current > powersupply_capabilities.max_export_current_A) + current = powersupply_capabilities.max_export_current_A; + + if (current < powersupply_capabilities.min_export_current_A) + current = powersupply_capabilities.min_export_current_A; + + if (voltage * current > powersupply_capabilities.max_export_power_W) + current = powersupply_capabilities.max_export_power_W / voltage; + + EVLOG_info << fmt::format("DC voltage/current set: Voltage {} Current {}.", voltage, current); + + r_powersupply_DC[0]->call_setExportVoltageCurrent(voltage, current); + return true; + } + EVLOG_critical << fmt::format("DC voltage/current out of limits requested: Voltage {} Current {}.", voltage, + current); + return false; +} + +void EvseManager::powersupply_DC_off() { + r_powersupply_DC[0]->call_setMode(types::power_supply_DC::Mode::Off); +} + +bool EvseManager::wait_powersupply_DC_voltage_reached(double target_voltage) { + // wait until the voltage has rised to the target value + Timeout timeout(30s); + bool voltage_ok = false; + while (!timeout.reached()) { + types::power_supply_DC::VoltageCurrent m; + if (powersupply_measurement.wait_for(m, 2000ms)) { + if (fabs(m.voltage_V - target_voltage) < 10) { + voltage_ok = true; + break; + } + } else { + EVLOG_info << "Did not receive voltage measurement from power supply within 2 seconds."; + powersupply_DC_off(); + break; + } + } + return voltage_ok; +} + +bool EvseManager::wait_powersupply_DC_below_voltage(double target_voltage) { + // wait until the voltage is below the target voltage + Timeout timeout(30s); + bool voltage_ok = false; + while (!timeout.reached()) { + types::power_supply_DC::VoltageCurrent m; + if (powersupply_measurement.wait_for(m, 2000ms)) { + if (m.voltage_V < target_voltage) { + voltage_ok = true; + break; + } + } else { + EVLOG_info << "Did not receive voltage measurement from power supply within 2 seconds."; + powersupply_DC_off(); + break; + } + } + return voltage_ok; +} + +const std::vector>& EvseManager::r_powermeter_billing() { + if (r_powermeter_car_side.size() > 0) { + return r_powermeter_car_side; + } else { + return r_powermeter_grid_side; + } +} + +const std::vector>& EvseManager::r_powermeter_energy_management() { + return r_powermeter_grid_side; +} + } // namespace module diff --git a/modules/EvseManager/EvseManager.hpp b/modules/EvseManager/EvseManager.hpp index fe5617db6..ed19538f0 100644 --- a/modules/EvseManager/EvseManager.hpp +++ b/modules/EvseManager/EvseManager.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -26,11 +27,16 @@ // insert your custom include headers here #include "Charger.hpp" #include "SessionLog.hpp" +#include "VarContainer.hpp" +#include #include +#include #include #include #include +#include #include +#include // ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 namespace module { @@ -38,11 +44,10 @@ namespace module { struct Conf { int connector_id; std::string evse_id; + std::string evse_id_din; bool payment_enable_eim; bool payment_enable_contract; double ac_nominal_voltage; - double dc_current_regulation_tolerance; - double dc_peak_current_ripple; bool ev_receipt_required; bool session_logging; std::string session_logging_path; @@ -57,7 +62,12 @@ struct Conf { bool ac_hlc_use_5percent; bool ac_enforce_hlc; bool ac_with_soc; + int dc_isolation_voltage; bool dbg_hlc_auth_after_tstep; + int hack_sleep_in_cable_check; + bool switch_to_minimum_voltage_after_cable_check; + bool hack_skoda_enyaq; + int hack_present_current_offset; }; class EvseManager : public Everest::ModuleBase { @@ -66,19 +76,24 @@ class EvseManager : public Everest::ModuleBase { EvseManager(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, std::unique_ptr p_evse, std::unique_ptr p_energy_grid, std::unique_ptr p_token_provider, - std::unique_ptr r_bsp, std::vector> r_powermeter, + std::unique_ptr r_bsp, + std::vector> r_powermeter_grid_side, + std::vector> r_powermeter_car_side, std::vector> r_slac, std::vector> r_hlc, - std::vector> r_imd, Conf& config) : + std::vector> r_imd, + std::vector> r_powersupply_DC, Conf& config) : ModuleBase(info), mqtt(mqtt_provider), p_evse(std::move(p_evse)), p_energy_grid(std::move(p_energy_grid)), p_token_provider(std::move(p_token_provider)), r_bsp(std::move(r_bsp)), - r_powermeter(std::move(r_powermeter)), + 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)), r_hlc(std::move(r_hlc)), r_imd(std::move(r_imd)), + r_powersupply_DC(std::move(r_powersupply_DC)), config(config){}; const Conf& config; @@ -87,16 +102,18 @@ class EvseManager : public Everest::ModuleBase { const std::unique_ptr p_energy_grid; const std::unique_ptr p_token_provider; const std::unique_ptr r_bsp; - const std::vector> r_powermeter; + const std::vector> r_powermeter_grid_side; + const std::vector> r_powermeter_car_side; const std::vector> r_slac; const std::vector> r_hlc; const std::vector> r_imd; + const std::vector> r_powersupply_DC; // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 // insert your public definitions here std::unique_ptr charger; sigslot::signal signalNrOfPhasesAvailable; - json get_latest_powermeter_data(); + types::powermeter::Powermeter get_latest_powermeter_data_billing(); types::board_support::HardwareCapabilities get_hw_capabilities(); bool updateLocalMaxCurrentLimit(float max_current); float getLocalMaxCurrentLimit(); @@ -107,7 +124,12 @@ class EvseManager : public Everest::ModuleBase { int32_t get_reservation_id(); bool get_hlc_enabled(); - sigslot::signal signalReservationEvent; + sigslot::signal signalReservationEvent; + + void charger_was_authorized(); + + const std::vector>& r_powermeter_billing(); + const std::vector>& r_powermeter_energy_management(); // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 protected: @@ -122,8 +144,9 @@ class EvseManager : public Everest::ModuleBase { // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 // insert your private definitions here - json latest_powermeter_data; - bool authorization_available; + std::mutex power_mutex; + types::powermeter::Powermeter latest_powermeter_data_billing; + Everest::Thread energyThreadHandle; types::board_support::HardwareCapabilities hw_capabilities; bool local_three_phases; @@ -131,6 +154,8 @@ class EvseManager : public Everest::ModuleBase { const float EVSE_ABSOLUTE_MAX_CURRENT = 80.0; bool slac_enabled; + std::atomic_bool contactor_open{false}; + std::mutex hlc_mutex; bool hlc_enabled; @@ -138,6 +163,13 @@ class EvseManager : public Everest::ModuleBase { bool hlc_waiting_for_auth_eim; bool hlc_waiting_for_auth_pnc; + types::power_supply_DC::Capabilities powersupply_capabilities; + VarContainer isolation_measurement; + VarContainer powersupply_measurement; + + double latest_target_voltage; + double latest_target_current; + void log_v2g_message(Object m); // Reservations @@ -146,11 +178,24 @@ class EvseManager : public Everest::ModuleBase { std::mutex reservation_mutex; void setup_AC_mode(); - void setup_DC_mode(); + void setup_fake_DC_mode(); // special funtion to switch mode while session is active void switch_AC_mode(); void switch_DC_mode(); + + // DC handlers + void cable_check(); + + void powersupply_DC_on(); + bool powersupply_DC_set(double voltage, double current); + void powersupply_DC_off(); + bool wait_powersupply_DC_voltage_reached(double target_voltage); + bool wait_powersupply_DC_below_voltage(double target_voltage); + + // EV information + std::mutex ev_info_mutex; + types::evse_manager::EVInfo ev_info; // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 }; diff --git a/modules/EvseManager/Timeout.hpp b/modules/EvseManager/Timeout.hpp new file mode 100644 index 000000000..77d5d36a3 --- /dev/null +++ b/modules/EvseManager/Timeout.hpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef TIMEOUT_HPP +#define TIMEOUT_HPP + +#include +using namespace std::chrono; + +/* + Simple helper class for a timeout +*/ + +class Timeout { +public: + explicit Timeout(milliseconds _t) : t(_t), start(steady_clock::now()) { + } + + bool reached() { + if ((steady_clock::now() - start) > t) + return true; + else + return false; + } + +private: + milliseconds t; + time_point start; +}; + +#endif diff --git a/modules/EvseManager/VarContainer.hpp b/modules/EvseManager/VarContainer.hpp new file mode 100644 index 000000000..8d08056d7 --- /dev/null +++ b/modules/EvseManager/VarContainer.hpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef VARCONTAINER_HPP +#define VARCONTAINER_HPP + +#include +#include +#include + +/* + Simple helper class for a thread safe single producer / single consumer pattern + with a queue size of one. +*/ + +template class VarContainer { +public: + T& operator=(T d) { + { + std::scoped_lock lock(data_mutex); + data = d; + unread_data = true; + } + condvar.notify_one(); + return data; + }; + + void clear() { + std::scoped_lock lock(data_mutex); + unread_data = false; + }; + + bool wait_for(T& d, std::chrono::milliseconds timeout) { + std::unique_lock lock(data_mutex); + + if (condvar.wait_for(lock, timeout, [this] { return unread_data; })) { + unread_data = false; + d = data; + return true; + } else { + // Timeout occured in wait_for + return false; + } + }; + +private: + T data; + std::condition_variable condvar; + std::mutex data_mutex; + bool unread_data{false}; +}; + +#endif diff --git a/modules/EvseManager/energy_grid/energyImpl.cpp b/modules/EvseManager/energy_grid/energyImpl.cpp index 98c3e3162..9d01353d6 100644 --- a/modules/EvseManager/energy_grid/energyImpl.cpp +++ b/modules/EvseManager/energy_grid/energyImpl.cpp @@ -19,8 +19,8 @@ void energyImpl::init() { _optimizer_mode = EVSE_OPTIMIZER_MODE_MANUAL_LIMITS; initializeEnergyObject(); - if (mod->r_powermeter.size() > 0) { - mod->r_powermeter[0]->subscribe_powermeter([this](json p) { + if (mod->r_powermeter_energy_management().size()) { + mod->r_powermeter_energy_management()[0]->subscribe_powermeter([this](json p) { // Received new power meter values, update our energy object. std::lock_guard lock(this->energy_mutex); energy["energy_usage"] = p; diff --git a/modules/EvseManager/evse/evse_managerImpl.cpp b/modules/EvseManager/evse/evse_managerImpl.cpp index d7bbdb9a7..610c4dc3e 100644 --- a/modules/EvseManager/evse/evse_managerImpl.cpp +++ b/modules/EvseManager/evse/evse_managerImpl.cpp @@ -26,8 +26,8 @@ bool str_to_bool(const std::string& data) { } void evse_managerImpl::init() { - limits["nr_of_phases_available"] = 1; - limits["max_current"] = 0.; + limits.nr_of_phases_available = 1; + limits.max_current = 0.; // Interface to Node-RED debug UI @@ -58,8 +58,8 @@ void evse_managerImpl::init() { // /Interface to Node-RED debug UI - if (mod->r_powermeter.size() > 0) { - mod->r_powermeter[0]->subscribe_powermeter([this](const json p) { + if (mod->r_powermeter_billing().size() > 0) { + mod->r_powermeter_billing()[0]->subscribe_powermeter([this](const types::powermeter::Powermeter p) { // Republish data on proxy powermeter struct publish_powermeter(p); }); @@ -79,7 +79,7 @@ void evse_managerImpl::ready() { mod->signalNrOfPhasesAvailable.connect([this](const int n) { if (n >= 1 && n <= 3) { - limits["nr_of_phases_available"] = n; + limits.nr_of_phases_available = n; publish_limits(limits); } }); @@ -93,21 +93,21 @@ void evse_managerImpl::ready() { }); // The module code generates the reservation events and we merely publish them here - mod->signalReservationEvent.connect([this](json j) { - if (j["event"] == "ReservationStart") { + mod->signalReservationEvent.connect([this](types::evse_manager::SessionEvent j) { + if (j.event == types::evse_manager::SessionEventEnum::ReservationStart) { set_session_uuid(); } - j["uuid"] = session_uuid; + j.uuid = session_uuid; publish_session_event(j); }); - mod->charger->signalEvent.connect([this](const Charger::EvseEvent& e) { + mod->charger->signalEvent.connect([this](const types::evse_manager::SessionEventEnum& e) { types::evse_manager::SessionEvent se; - se.event = types::evse_manager::string_to_session_event_enum(mod->charger->evseEventToString(e)); + se.event = e; - if (e == Charger::EvseEvent::SessionStarted) { + if (e == types::evse_manager::SessionEventEnum::SessionStarted) { types::evse_manager::SessionStarted session_started; session_started.timestamp = @@ -125,25 +125,18 @@ void evse_managerImpl::ready() { false, fmt::format("Session Started: {}", types::evse_manager::start_session_reason_to_string(reason))); se.session_started = session_started; - } - - if (e == Charger::EvseEvent::SessionFinished) { + } else if (e == types::evse_manager::SessionEventEnum::SessionFinished) { session_log.evse(false, fmt::format("Session Finished")); - } - - if (e == Charger::EvseEvent::TransactionStarted) { + session_log.stopSession(); + } else if (e == types::evse_manager::SessionEventEnum::TransactionStarted) { types::evse_manager::TransactionStarted transaction_started; transaction_started.timestamp = date::format("%FT%TZ", std::chrono::time_point_cast(date::utc_clock::now())); - json p = mod->get_latest_powermeter_data(); - if (p.contains("energy_Wh_import") && p["energy_Wh_import"].contains("total")) { - transaction_started.energy_Wh_import = p["energy_Wh_import"]["total"]; - } else { - transaction_started.energy_Wh_import = 0; - } - if (p.contains("energy_Wh_export") && p["energy_Wh_export"].contains("total")) { - transaction_started.energy_Wh_export.emplace(p["energy_Wh_export"]["total"]); + auto p = mod->get_latest_powermeter_data_billing(); + transaction_started.energy_Wh_import = p.energy_Wh_import.total; + if (p.energy_Wh_export.is_initialized()) { + transaction_started.energy_Wh_export.emplace(p.energy_Wh_export.get().total); } if (mod->is_reserved()) { @@ -157,24 +150,16 @@ void evse_managerImpl::ready() { session_log.evse(false, fmt::format("Transaction Started ({} kWh)", energy_import / 1000.)); se.transaction_started.emplace(transaction_started); - } - - se.uuid = session_uuid; - - if (e == Charger::EvseEvent::TransactionFinished) { + } else if (e == types::evse_manager::SessionEventEnum::TransactionFinished) { types::evse_manager::TransactionFinished transaction_finished; transaction_finished.timestamp = date::format("%FT%TZ", std::chrono::time_point_cast(date::utc_clock::now())); - json p = mod->get_latest_powermeter_data(); - if (p.contains("energy_Wh_import") && p["energy_Wh_import"].contains("total")) { - transaction_finished.energy_Wh_import = p["energy_Wh_import"]["total"]; - } else { - transaction_finished.energy_Wh_import = 0; - } - if (p.contains("energy_Wh_export") && p["energy_Wh_export"].contains("total")) { - transaction_finished.energy_Wh_export.emplace(p["energy_Wh_export"]["total"]); + auto p = mod->get_latest_powermeter_data_billing(); + transaction_finished.energy_Wh_import = p.energy_Wh_import.total; + if (p.energy_Wh_export.is_initialized()) { + transaction_finished.energy_Wh_export.emplace(p.energy_Wh_export.get().total); } auto reason = mod->charger->getTransactionFinishedReason(); @@ -192,15 +177,13 @@ void evse_managerImpl::ready() { energy_import / 1000.)); session_uuid = ""; - session_log.stopSession(); se.transaction_finished.emplace(transaction_finished); + } else if (e == types::evse_manager::SessionEventEnum::Error) { + se.error = mod->charger->getErrorState(); } - if (e == Charger::EvseEvent::Error) { - se.error.emplace( - types::evse_manager::string_to_error(mod->charger->errorStateToString(mod->charger->getErrorState()))); - } + se.uuid = session_uuid; publish_session_event(se); }); @@ -210,8 +193,8 @@ void evse_managerImpl::ready() { mod->charger->signalMaxCurrent.connect([this](float c) { mod->mqtt.publish(fmt::format("everest_external/nodered/{}/state/max_current", mod->config.connector_id), c); - limits["uuid"] = mod->info.id; - limits["max_current"] = c; + limits.uuid = mod->info.id; + limits.max_current = c; publish_limits(limits); }); @@ -222,11 +205,11 @@ void evse_managerImpl::ready() { static_cast(s)); }); - mod->charger->signalError.connect([this](Charger::ErrorState s) { + mod->charger->signalError.connect([this](types::evse_manager::Error 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), - mod->charger->errorStateToString(s)); + types::evse_manager::error_to_string(s)); }); // /Deprecated } @@ -239,8 +222,10 @@ bool evse_managerImpl::handle_enable() { return mod->charger->enable(); }; -void evse_managerImpl::handle_authorize(std::string& id_tag) { - this->mod->charger->Authorize(true, id_tag, false); +void evse_managerImpl::handle_authorize(std::string& id_tag, bool& pnc) { + this->mod->charger->Authorize(true, id_tag, pnc); + // maybe we need to inform HLC layer as well in case it is waiting for auth + mod->charger_was_authorized(); }; void evse_managerImpl::handle_withdraw_authorization() { @@ -306,8 +291,8 @@ evse_managerImpl::handle_switch_three_phases_while_charging(bool& three_phases) }; std::string evse_managerImpl::handle_get_signed_meter_value() { - if (mod->r_powermeter.size() > 0) { - return mod->r_powermeter[0]->call_get_signed_meter_value("FIXME"); + if (mod->r_powermeter_billing().size() > 0) { + return mod->r_powermeter_billing()[0]->call_get_signed_meter_value("FIXME"); } else { return "NOT_AVAILABLE"; } diff --git a/modules/EvseManager/evse/evse_managerImpl.hpp b/modules/EvseManager/evse/evse_managerImpl.hpp index 8611fd6c9..b11b3d169 100644 --- a/modules/EvseManager/evse/evse_managerImpl.hpp +++ b/modules/EvseManager/evse/evse_managerImpl.hpp @@ -37,7 +37,7 @@ class evse_managerImpl : public evse_managerImplBase { virtual int handle_get_id() override; virtual bool handle_enable() override; virtual bool handle_disable() override; - virtual void handle_authorize(std::string& id_tag) override; + virtual void handle_authorize(std::string& id_tag, bool& pnc) override; virtual void handle_withdraw_authorization() override; virtual bool handle_reserve(int& reservation_id) override; virtual void handle_cancel_reservation() override; @@ -64,7 +64,7 @@ class evse_managerImpl : public evse_managerImplBase { // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 // insert your private definitions here - json limits; + types::evse_manager::Limits limits; std::string generate_session_uuid(); void set_session_uuid(); diff --git a/modules/EvseManager/manifest.yaml b/modules/EvseManager/manifest.yaml index dd7fe92ae..27247e811 100644 --- a/modules/EvseManager/manifest.yaml +++ b/modules/EvseManager/manifest.yaml @@ -1,4 +1,7 @@ -description: EVSE Manager +description: >- + EVSE Manager. Grid side power meter: Will be used for energy management. + Will also be used for billing if no car side power meter connected. Car side powermeter: + Will be used for billing if present. config: connector_id: description: Connector id of this evse manager @@ -7,6 +10,10 @@ config: description: EVSE ID type: string default: DE*PNX*E1234567*1 + evse_id_din: + description: EVSE ID DIN after DIN SPEC 91286 + type: string + default: 49A80737A45678 payment_enable_eim: description: Set to true to enable EIM (e.g. RFID card or mobile app) authorization type: boolean @@ -19,16 +26,8 @@ config: description: Nominal AC voltage between phase and neutral in Volt type: number default: 230 - dc_current_regulation_tolerance: - description: DC current regulation tolerance in Ampere - type: number - default: 5 - dc_peak_current_ripple: - description: DC peal current ripple in Ampere - type: number - default: 5 ev_receipt_required: - description: 'Unsupported: request receipt from EV with HLC' + description: "Unsupported: request receipt from EV with HLC" type: boolean default: false session_logging: @@ -94,11 +93,38 @@ config: Special mode that switches between AC and DC charging to get SoC percentage with AC charging type: boolean default: false + dc_isolation_voltage: + description: DC voltage used to test isolation in CableCheck. + Set to 500V. + type: integer + default: 500 dbg_hlc_auth_after_tstep: description: >- Special mode: send HLC auth ok only after t_step_XX is finished (true) or directly when available (false) type: boolean default: false + hack_sleep_in_cable_check: + description: "Hack: Sleep for n seconds at the end of cable check." + type: integer + default: 0 + switch_to_minimum_voltage_after_cable_check: + description: + When cable check is completed, switch to minimal voltage of DC output. + Normally disabled. + type: boolean + default: false + hack_skoda_enyaq: + description: + Skoda Enyaq requests DC charging voltages below its battery level or even below 0 initially. + Set to true to enable dirty workaround. + type: boolean + default: false + hack_present_current_offset: + description: + Adds an offset [A] to the present current reported to the car on HLC. + Set to 0 unless you really know what you are doing. + type: integer + default: 0 provides: evse: interface: evse_manager @@ -112,7 +138,11 @@ provides: requires: bsp: interface: board_support_AC - powermeter: + powermeter_grid_side: + interface: powermeter + min_connections: 0 + max_connections: 1 + powermeter_car_side: interface: powermeter min_connections: 0 max_connections: 1 @@ -128,9 +158,13 @@ requires: interface: isolation_monitor min_connections: 0 max_connections: 1 + powersupply_DC: + interface: power_supply_DC + min_connections: 0 + max_connections: 1 enable_external_mqtt: true metadata: license: https://spdx.org/licenses/Apache-2.0.html authors: - Cornelius Claussen - - Anton Wöllert + - Anton Woellert diff --git a/modules/EvseSlac/CMakeLists.txt b/modules/EvseSlac/CMakeLists.txt index f29f1bb5a..2c0dcab12 100644 --- a/modules/EvseSlac/CMakeLists.txt +++ b/modules/EvseSlac/CMakeLists.txt @@ -20,7 +20,6 @@ target_link_libraries(${MODULE_NAME} slac::slac fsm::fsm ) - # ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 target_sources(${MODULE_NAME} diff --git a/modules/EvseSlac/main/slacImpl.cpp b/modules/EvseSlac/main/slacImpl.cpp index e29f1e933..f6f179dc7 100644 --- a/modules/EvseSlac/main/slacImpl.cpp +++ b/modules/EvseSlac/main/slacImpl.cpp @@ -84,6 +84,8 @@ void slacImpl::run() { fsm.generate_nmk(); + fsm.set_five_percent_mode(config.ac_mode_five_percent); + bool matched = false; auto cur_state_id = fsm.get_initial_state().id.id; fsm_ctrl.reset(fsm.get_initial_state()); @@ -161,7 +163,13 @@ void slacImpl::run() { void slacImpl::handle_reset(bool& enable) { // FIXME (aw): the enable could be used for power saving etc, but it is not implemented yet - send_event(EventReset()); + // CC: as power saving is not implemented, we actually don't need to reset at beginning of session (enable=true): At + // start of everest it is being reset once and then it is enough to reset at the end of each session. This saves + // some hundreds of msecs at the beginning of the charging session as we do not need to set up keys. Then + // EvseManager can switch on 5% PWM basically immediately as SLAC is already ready. + if (!enable) { + send_event(EventReset()); + } }; bool slacImpl::handle_enter_bcd() { diff --git a/modules/EvseSlac/main/slacImpl.hpp b/modules/EvseSlac/main/slacImpl.hpp index e87b68b40..b71abc8d2 100644 --- a/modules/EvseSlac/main/slacImpl.hpp +++ b/modules/EvseSlac/main/slacImpl.hpp @@ -24,6 +24,7 @@ struct Conf { std::string evse_id; std::string nid; int number_of_sounds; + bool ac_mode_five_percent; }; class slacImpl : public slacImplBase { diff --git a/modules/EvseSlac/manifest.yaml b/modules/EvseSlac/manifest.yaml index 2a63d0569..db0b33894 100644 --- a/modules/EvseSlac/manifest.yaml +++ b/modules/EvseSlac/manifest.yaml @@ -20,6 +20,10 @@ provides: description: SLAC number of sounds. type: integer default: 10 + ac_mode_five_percent: + description: Use 5% mode in AC (true). Set to false for DC. The only difference is the handling of retries. + type: boolean + default: false metadata: base_license: https://directory.fsf.org/wiki/License:BSD-3-Clause-Clear license: https://opensource.org/licenses/Apache-2.0 diff --git a/modules/JsCarSimulator/index.js b/modules/JsCarSimulator/index.js index 9072b79b7..f15279931 100644 --- a/modules/JsCarSimulator/index.js +++ b/modules/JsCarSimulator/index.js @@ -86,7 +86,6 @@ function modify_charging_session(mod, args) { boot_module(async ({ setup, info, config, mqtt, }) => { - // Subscribe external cmds for nodered mqtt.subscribe(`everest_external/nodered/${config.module.connector_id}/carsim/cmd/enable`, (mod, en) => { enable(mod, { value: en }); }); mqtt.subscribe(`everest_external/nodered/${config.module.connector_id}/carsim/cmd/execute_charging_session`, (mod, str) => { diff --git a/modules/JsCarSimulator/manifest.yaml b/modules/JsCarSimulator/manifest.yaml index a57f5af8a..8c03776cb 100644 --- a/modules/JsCarSimulator/manifest.yaml +++ b/modules/JsCarSimulator/manifest.yaml @@ -18,7 +18,7 @@ config: description: >- Simulation commands, e.g. sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug type: string - default: '' + default: "" provides: main: interface: car_simulator diff --git a/modules/JsCarV2G/index.js b/modules/JsCarV2G/index.js index 6a8b7c1be..7df365759 100644 --- a/modules/JsCarV2G/index.js +++ b/modules/JsCarV2G/index.js @@ -16,7 +16,7 @@ function JavaStartedDeferred(mqtt_base_path, module_name, mod) { // FIXME: the path hierarchy should be well defined, // i.e where to find 3rd party things - const cwd = `${process.cwd()}/libexec/everest/3rd_party/rise_v2g`; + const cwd = `${__dirname}/../../3rd_party/rise_v2g`; const args = [ '-Djava.net.preferIPv4Stack=false', '-Djava.net.prefecom.v2gclarity.rrIPv6Addresses=true', '-cp', 'rise-v2g-evcc-1.2.6.jar', 'com.v2gclarity.risev2g.evcc.main.StartEVCC', diff --git a/modules/JsDCSupplySimulator/CMakeLists.txt b/modules/JsDCSupplySimulator/CMakeLists.txt new file mode 100644 index 000000000..24137e19a --- /dev/null +++ b/modules/JsDCSupplySimulator/CMakeLists.txt @@ -0,0 +1,13 @@ +get_filename_component(MODULE_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) +add_custom_target(${MODULE_NAME} ALL) + +# install the whole js project +if(CREATE_SYMLINKS) + include("CreateModuleSymlink") +else() + install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}" + PATTERN "CMakeLists.txt" EXCLUDE + PATTERN "CMakeFiles" EXCLUDE) +endif() diff --git a/modules/JsDCSupplySimulator/index.js b/modules/JsDCSupplySimulator/index.js new file mode 100644 index 000000000..abc8c18cf --- /dev/null +++ b/modules/JsDCSupplySimulator/index.js @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest +const { boot_module } = require('everestjs'); +const { setInterval } = require('timers'); + +let config_min_voltage; +let config_max_voltage; +let config_min_current; +let config_max_current; +let config_bidirectional; +let config_max_power; + +let settings_connector_export_voltage; +let settings_connector_import_voltage; +let settings_connector_max_export_current; +let settings_connector_max_import_current; + +let connector_voltage; +let mode; + +let voltage; +let current; + +boot_module(async ({ + setup, config, +}) => { + config_max_voltage = config.impl.main.max_voltage; + config_min_voltage = config.impl.main.min_voltage; + config_max_current = config.impl.main.max_current; + config_min_current = config.impl.main.min_current; + config_bidirectional = config.impl.main.bidirectional; + config_max_power = config.impl.main.max_power; + + connector_voltage = 0.0; + mode = 'Off'; + voltage = 0.0; + current = 0.0; + + // register commands + setup.provides.main.register.getCapabilities((mod, args) => { + const Capabilities = { + bidirectional: config_bidirectional, + max_export_voltage_V: config_max_voltage, + min_export_voltage_V: config_min_voltage, + max_export_current_A: config_max_current, + min_export_current_A: config_min_current, + max_import_voltage_V: config_max_voltage, + min_import_voltage_V: config_min_voltage, + max_import_current_A: config_max_current, + min_import_current_A: config_min_current, + max_export_power_W: config_max_power, + max_import_power_W: config_max_power, + current_regulation_tolerance_A: 2, + peak_current_ripple_A: 2, + }; + return Capabilities; + }); + + setup.provides.main.register.setMode((mod, args) => { + if (args.value === 'Off') { + mode = 'Off'; connector_voltage = 0.0; + } else if (args.value === 'Export') { + mode = 'Export'; connector_voltage = settings_connector_export_voltage; + } else if (args.value === 'Import') { + mode = 'Import'; connector_voltage = settings_connector_import_voltage; + } else if (args.value === 'Fault') { + mode = 'Fault'; connector_voltage = 0.0; + } + mod.provides.main.publish.mode(args.value); + }); + + setup.provides.main.register.setExportVoltageCurrent((mod, args) => { + voltage = args.voltage; + current = args.current; + + if (voltage < config_min_voltage) voltage = config_min_voltage; + if (current < config_min_current) current = config_min_current; + if (voltage > config_max_voltage) voltage = config_max_voltage; + if (current > config_max_current) current = config_max_current; + + settings_connector_max_export_current = current; + settings_connector_export_voltage = voltage; + + if (mode === 'Export') connector_voltage = settings_connector_export_voltage; + }); + + setup.provides.main.register.setImportVoltageCurrent((mod, args) => { + voltage = args.voltage; + current = args.current; + + if (voltage < config_min_voltage) voltage = config_min_voltage; + if (current < config_min_current) current = config_min_current; + if (voltage > config_max_voltage) voltage = config_max_voltage; + if (current > config_max_current) current = config_max_current; + + settings_connector_import_voltage = voltage; + settings_connector_max_import_current = current; + + if (mode === 'Import') connector_voltage = settings_connector_import_voltage; + }); +}).then((mod) => { + setInterval(() => { + mod.provides.main.publish.voltage_current({ + voltage_V: connector_voltage, + current_A: 0.1, + }); + }, 500, mod); +}); diff --git a/modules/JsDCSupplySimulator/manifest.yaml b/modules/JsDCSupplySimulator/manifest.yaml new file mode 100644 index 000000000..b5118537f --- /dev/null +++ b/modules/JsDCSupplySimulator/manifest.yaml @@ -0,0 +1,35 @@ +description: SIL Implementation of a programmable power supply for DC charging +provides: + main: + interface: power_supply_DC + description: Main interface for the power supply + config: + bidirectional: + description: Set to true to for bidirectional supply + type: boolean + default: true + max_power: + description: Max supported power in watt + type: number + default: 150000 + min_voltage: + description: Min supported voltage + type: number + default: 200.0 + max_voltage: + description: Max supported voltage + type: number + default: 900.0 + min_current: + description: Min supported current + type: number + default: 1.0 + max_current: + description: Max supported current + type: number + default: 200.0 +enable_external_mqtt: true +metadata: + license: https://opensource.org/licenses/Apache-2.0 + authors: + - Cornelius Claussen (Pionix GmbH) diff --git a/modules/JsIMDSimulator/index.js b/modules/JsIMDSimulator/index.js index 67060ee6d..8d1baba42 100644 --- a/modules/JsIMDSimulator/index.js +++ b/modules/JsIMDSimulator/index.js @@ -2,25 +2,40 @@ // Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest const { evlog, boot_module } = require('everestjs'); const { setInterval } = require('timers'); -const { inherits } = require('util'); -let config_status; -let config_duration; +let config_resistance_N_Ohm; +let config_resistance_P_Ohm; +let config_interval; +let intervalID; +let interval_running; boot_module(async ({ - setup, info, config, mqtt, + setup, config, }) => { - config_status = config.impl.main.status; - config_duration = config.impl.main.duration; + config_resistance_N_Ohm = config.impl.main.resistance_N_Ohm; + config_resistance_P_Ohm = config.impl.main.resistance_P_Ohm; + config_interval = config.impl.main.interval; // register commands - setup.provides.main.register.startIsolationTest((mod, args) => { - evlog.debug(`Start simulated isolation test with ${config_duration}s delay`); - setTimeout((mod) => { + setup.provides.main.register.start((mod) => { + evlog.debug(`Started simulated isolation monitoring with ${config_interval}ms interval`); + + intervalID = setInterval(() => { evlog.debug('Simulated isolation test finished'); - mod.provides.main.publish.IsolationStatus(config_status); - }, config_duration * 1000, mod); + mod.provides.main.publish.IsolationMeasurement({ + resistance_P_Ohm: config_resistance_P_Ohm, + resistance_N_Ohm: config_resistance_N_Ohm, + }); + }, config_interval, mod); + interval_running = true; }); -}).then((mod) => { -}); + // register commands + setup.provides.main.register.stop(() => { + if (interval_running) { + evlog.debug('Stopped simulated isolation monitoring.'); + clearInterval(intervalID); + interval_running = false; + } + }); +}).then(() => {}); diff --git a/modules/JsIMDSimulator/manifest.yaml b/modules/JsIMDSimulator/manifest.yaml index e6e81d624..e036f0dee 100644 --- a/modules/JsIMDSimulator/manifest.yaml +++ b/modules/JsIMDSimulator/manifest.yaml @@ -4,20 +4,18 @@ provides: interface: isolation_monitor description: Main interface for the IMD config: - status: - description: Status to return after simulated measurement - type: string - enum: - - Invalid - - Valid - - Warning - - Fault - - No_IMD - default: Valid - duration: - description: Measurement duration in seconds + resistance_N_Ohm: + description: Resistance to return for the simulated measurements in Ohm + type: number + default: 900000 + resistance_P_Ohm: + description: Resistance to return for the simulated measurements in Ohm + type: number + default: 910000 + interval: + description: Measurement update interval in milliseconds type: integer - default: 5 + default: 1000 enable_external_mqtt: true metadata: license: https://opensource.org/licenses/Apache-2.0 diff --git a/modules/JsYetiSimulator/index.js b/modules/JsYetiSimulator/index.js index 8aff0c12b..b25261ed8 100644 --- a/modules/JsYetiSimulator/index.js +++ b/modules/JsYetiSimulator/index.js @@ -545,8 +545,9 @@ function stateToString(state) { } } */ function power_meter_external(p) { + const date = new Date(); return ({ - timestamp: p.time_stamp, + timestamp: date.toISOString(), meter_id: 'YETI_POWERMETER', phase_seq_error: false, energy_Wh_import: { @@ -671,9 +672,9 @@ function simulate_powermeter(mod) { const wattL1 = mod.simulation_data.voltages.L1 * mod.simulation_data.currents.L1 * (mod.relais_on ? 1 : 0); const wattL2 = mod.simulation_data.voltages.L2 * mod.simulation_data.currents.L2 - * (mod.relais_on && mod.use_three_phases_confirmed ? 1 : 0); + * (mod.relais_on && mod.use_three_phases_confirmed ? 1 : 0); const wattL3 = mod.simulation_data.voltages.L3 * mod.simulation_data.currents.L3 - * (mod.relais_on && mod.use_three_phases_confirmed ? 1 : 0); + * (mod.relais_on && mod.use_three_phases_confirmed ? 1 : 0); mod.wattHr.L1 += wattL1 * deltat / 1000.0 / 3600.0; mod.wattHr.L2 += wattL2 * deltat / 1000.0 / 3600.0; diff --git a/modules/ModbusMeter/CMakeLists.txt b/modules/ModbusMeter/CMakeLists.txt deleted file mode 100644 index 7f313c9df..000000000 --- a/modules/ModbusMeter/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# AUTO GENERATED - MARKED REGIONS WILL BE KEPT -# template version 3 -# - -# module setup: -# - ${MODULE_NAME}: module name -ev_setup_cpp_module() - -# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 -target_link_libraries(${MODULE_NAME} - PRIVATE - everest::sunspec -) -# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 - -target_sources(${MODULE_NAME} - PRIVATE - "main/powermeterImpl.cpp" -) - -# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 -# insert other things like install cmds etc here -# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/ModbusMeter/ModbusMeter.cpp b/modules/ModbusMeter/ModbusMeter.cpp deleted file mode 100644 index 59b947ea7..000000000 --- a/modules/ModbusMeter/ModbusMeter.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest -#include "ModbusMeter.hpp" -#include -#include -#include - -namespace module { - -void ModbusMeter::init() { - invoke_init(*p_main); -} - -void ModbusMeter::ready() { - invoke_ready(*p_main); -} - -} // namespace module diff --git a/modules/ModbusMeter/ModbusMeter.hpp b/modules/ModbusMeter/ModbusMeter.hpp deleted file mode 100644 index 4e252b46d..000000000 --- a/modules/ModbusMeter/ModbusMeter.hpp +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Pionix GmbH and Contributors to EVerest -#ifndef MODBUS_METER_HPP -#define MODBUS_METER_HPP - -// -// AUTO GENERATED - MARKED REGIONS WILL BE KEPT -// template version 1 -// - -#include "ld-ev.hpp" - -// headers for provided interface implementations -#include - -// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 -// insert your custom include headers here -// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 - -namespace module { - -struct Conf {}; - -class ModbusMeter : public Everest::ModuleBase { -public: - ModbusMeter() = delete; - ModbusMeter(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, - std::unique_ptr p_main, Conf& config) : - ModuleBase(info), mqtt(mqtt_provider), p_main(std::move(p_main)), config(config){}; - - const Conf& config; - Everest::MqttProvider& mqtt; - const std::unique_ptr p_main; - - // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 - // insert your public definitions here - // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 - -protected: - // ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1 - // insert your protected definitions here - // ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1 - -private: - friend class LdEverest; - void init(); - void ready(); - - // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 - // insert your private definitions here - // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 -}; - -// ev@087e516b-124c-48df-94fb-109508c7cda9:v1 -// insert other definitions here -// ev@087e516b-124c-48df-94fb-109508c7cda9:v1 - -} // namespace module - -#endif // MODBUS_METER_HPP diff --git a/modules/ModbusMeter/main/powermeterImpl.cpp b/modules/ModbusMeter/main/powermeterImpl.cpp deleted file mode 100644 index a90744de2..000000000 --- a/modules/ModbusMeter/main/powermeterImpl.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest -#include "powermeterImpl.hpp" - - -using namespace everest; - -namespace module { -namespace main { - -void powermeterImpl::init() { - this->init_modbus_client(); -} - -void powermeterImpl::ready() { - this->meter_loop_thread = std::thread( [this] { run_meter_loop(); } ); -} - -void powermeterImpl::run_meter_loop() { - EVLOG_debug << "Starting ModbusMeter loop"; - int32_t power_in, power_out, energy_in, energy_out, power_surplus, energy_surplus; - while (true) { - - // Reading meter values from MODBUS device - power_in = (uint32_t) this->read_power_in(); - power_out = (uint32_t) this->read_power_out(); - energy_in = (uint32_t) this->read_energy_in(); - energy_out = (uint32_t) this->read_energy_out(); - - uint64_t timestamp = std::chrono::duration_cast(date::utc_clock::now().time_since_epoch()).count(); - - // Publishing relevant vars - json j; - j["time_stamp"] = timestamp; - j["meter_id"] = "MODBUS_POWERMETER"; - j["energy_Wh_import"]["total"] = energy_in; - j["energy_Wh_export"]["total"] = energy_out; - - j["power_W"]["total"] = power_in-power_out; - std::this_thread::sleep_for(std::chrono::milliseconds(config.update_interval)); - - } -} - -void powermeterImpl::init_modbus_client() { - this->tcp_conn = std::make_unique(config.modbus_ip_address, config.modbus_port); - this->modbus_client = std::make_unique(*this->tcp_conn); -} - -uint32_t powermeterImpl::read_power_in() { - std::vector bytevector = this->modbus_client->read_holding_register(config.power_unit_id, config.power_in_register, config.power_in_length); - modbus::utils::print_message_hex(bytevector); - return sunspec::conversion::bytevector_to_uint32(bytevector); -} - -uint32_t powermeterImpl::read_power_out() { - std::vector bytevector = this->modbus_client->read_holding_register(config.power_unit_id, config.power_out_register, config.power_out_length); - return sunspec::conversion::bytevector_to_uint32(bytevector); -} - -uint32_t powermeterImpl::read_energy_in() { - std::vector bytevector = this->modbus_client->read_holding_register(config.energy_unit_id, config.energy_in_register, config.energy_in_length); - return sunspec::conversion::bytevector_to_uint32(bytevector); -} - -uint32_t powermeterImpl::read_energy_out() { - std::vector bytevector = this->modbus_client->read_holding_register(config.energy_unit_id, config.energy_out_register, config.energy_out_length); - return sunspec::conversion::bytevector_to_uint32(bytevector); -} - -std::string powermeterImpl::handle_get_signed_meter_value(std::string& auth_token) { - return "NOT_AVAILABLE"; -} - -} // namespace main -} // namespace module diff --git a/modules/ModbusMeter/main/powermeterImpl.hpp b/modules/ModbusMeter/main/powermeterImpl.hpp deleted file mode 100644 index 092037d94..000000000 --- a/modules/ModbusMeter/main/powermeterImpl.hpp +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Pionix GmbH and Contributors to EVerest -#ifndef MAIN_POWERMETER_IMPL_HPP -#define MAIN_POWERMETER_IMPL_HPP - -// -// AUTO GENERATED - MARKED REGIONS WILL BE KEPT -// template version 3 -// - -#include - -#include "../ModbusMeter.hpp" - -// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 -#include -#include - -#include -#include -#include -#include -#include -#include -#include -// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 - -namespace module { -namespace main { - -struct Conf { - std::string modbus_ip_address; - int modbus_port; - int power_unit_id; - int power_in_register; - int power_in_length; - int power_out_register; - int power_out_length; - int energy_unit_id; - int energy_in_register; - int energy_in_length; - int energy_out_register; - int energy_out_length; - int update_interval; -}; - -class powermeterImpl : public powermeterImplBase { -public: - powermeterImpl() = delete; - powermeterImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : - powermeterImplBase(ev, "main"), mod(mod), config(config){}; - - // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 - void run_meter_loop(); - - uint32_t read_power_in(); - uint32_t read_power_out(); - uint32_t read_energy_in(); - uint32_t read_energy_out(); - // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 - -protected: - // command handler functions (virtual) - virtual std::string handle_get_signed_meter_value(std::string& auth_token) 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 - void init_modbus_client(); - Everest::Thread meter_loop_thread; - - std::unique_ptr tcp_conn; - std::unique_ptr modbus_client; - // 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 main -} // namespace module - -#endif // MAIN_POWERMETER_IMPL_HPP diff --git a/modules/ModbusMeter/manifest.yaml b/modules/ModbusMeter/manifest.yaml deleted file mode 100644 index c723d8bc1..000000000 --- a/modules/ModbusMeter/manifest.yaml +++ /dev/null @@ -1,70 +0,0 @@ -description: Module that collects power and energy measurements from a MODBUS device -provides: - main: - description: This is the main unit of the module - interface: powermeter - config: - modbus_ip_address: - description: The ip address which should be used to get the modbus values - type: string - pattern: ^(?:[0-9]{1,3}.){3}[0-9]{1,3}$ - modbus_port: - description: The port which should be used to get the modbus values - type: integer - minimum: 0 - power_unit_id: - description: Modbus unit_id, mostly 1 - type: integer - minimum: 1 - maximum: 255 - power_in_register: - description: Modbus register for power in Watts imported - type: integer - minimum: 0 - power_in_length: - description: Amount of modbus registers uint16 = 1, uint32 = 2, uint64 = 4 - type: integer - enum: - - 2 - power_out_register: - description: Modbus register for power in Watts exported - type: integer - minimum: 0 - power_out_length: - description: Amount of modbus registers uint16 = 1, uint32 = 2, uint64 = 4 - type: integer - enum: - - 2 - energy_unit_id: - description: Modbus unit_id, mostly 1 - type: integer - minimum: 1 - maximum: 255 - energy_in_register: - description: Modbus register for energy in Watthours imported - type: integer - minimum: 0 - energy_in_length: - description: Amount of modbus registers uint16 = 1, uint32 = 2, uint64 = 4 - type: integer - enum: - - 2 - energy_out_register: - description: Modbus register for energy in Watthours imported - type: integer - minimum: 0 - energy_out_length: - description: Amount of modbus registers uint16 = 1, uint32 = 2, uint64 = 4 - type: integer - enum: - - 2 - update_interval: - description: Update interval in milliseconds. - type: integer - minimum: 0 -enable_external_mqtt: true -metadata: - license: https://opensource.org/licenses/Apache-2.0 - authors: - - Andreas Heinrich - - Leonardo Fernandes diff --git a/modules/PowermeterBSM/lib/transport.cpp b/modules/PowermeterBSM/lib/transport.cpp index c27b70493..1ce6becde 100644 --- a/modules/PowermeterBSM/lib/transport.cpp +++ b/modules/PowermeterBSM/lib/transport.cpp @@ -103,7 +103,7 @@ transport::DataVector SerialCommHubTransport::fetch(protocol_related_types::Suns remaining_register_to_read > max_regiser_read ? max_regiser_read : remaining_register_to_read; types::serial_comm_hub_requests::Result serial_com_hub_result = - m_serial_hub.call_modbus_read_holding_registers(m_unit_id, read_address.val, register_to_read); + m_serial_hub.call_modbus_read_holding_registers(m_unit_id, read_address.val, register_to_read, 0); if (not serial_com_hub_result.value.has_value()) throw std::runtime_error("no result from serial com hub!"); @@ -157,7 +157,7 @@ bool SerialCommHubTransport::trigger_snapshot_generation( types::serial_comm_hub_requests::VectorUint16 trigger_create_snapshot_command{{0x0002}}; types::serial_comm_hub_requests::StatusCodeEnum write_result = - m_serial_hub.call_modbus_write_multiple_registers(m_unit_id, snapshot_trigger_register.val, trigger_create_snapshot_command); + m_serial_hub.call_modbus_write_multiple_registers(m_unit_id, snapshot_trigger_register.val, trigger_create_snapshot_command, 0); if (not(types::serial_comm_hub_requests::StatusCodeEnum::Success == write_result)) throw(""s + __PRETTY_FUNCTION__ + " SerialCommHub error : "s + @@ -168,7 +168,7 @@ bool SerialCommHubTransport::trigger_snapshot_generation( while (counter-- > 0) { types::serial_comm_hub_requests::Result serial_com_hub_result = - m_serial_hub.call_modbus_read_holding_registers(m_unit_id, snapshot_trigger_register.val, true); + m_serial_hub.call_modbus_read_holding_registers(m_unit_id, snapshot_trigger_register.val, true, 0); if (not serial_com_hub_result.value.has_value()) throw std::runtime_error("no result from serial com hub!"); diff --git a/modules/PowermeterBSM/main/powermeterImpl.cpp b/modules/PowermeterBSM/main/powermeterImpl.cpp index b516c00f2..cd2ac3cea 100644 --- a/modules/PowermeterBSM/main/powermeterImpl.cpp +++ b/modules/PowermeterBSM/main/powermeterImpl.cpp @@ -105,8 +105,7 @@ void powermeterImpl::worker() { sunspec_model::ACMeter acmeter(data); types::powermeter::Powermeter result; - result.timestamp = 0; // FIXME: current implementation is a float that cant store a 32/31 bit - result.timestamp_RFC3339_UTC = Everest::Date::to_rfc3339(date::utc_clock::now()); + result.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now()); result.meter_id = mod->config.meter_id; @@ -132,9 +131,9 @@ void powermeterImpl::worker() { float scale_factor_reactive_power = pow(10, acmeter.VAR_SF()); result.VAR = types::units::ReactivePower{ .total = static_cast(acmeter.VAR() * scale_factor_reactive_power), - .VARphA = static_cast(acmeter.VARphA() * scale_factor_reactive_power), - .VARphB = static_cast(acmeter.VARphB() * scale_factor_reactive_power), - .VARphC = static_cast(acmeter.VARphC() * scale_factor_reactive_power)}; + .L1 = static_cast(acmeter.VARphA() * scale_factor_reactive_power), + .L2 = static_cast(acmeter.VARphB() * scale_factor_reactive_power), + .L3 = static_cast(acmeter.VARphC() * scale_factor_reactive_power)}; publish_powermeter(result); diff --git a/modules/PyJosev/CMakeLists.txt b/modules/PyJosev/CMakeLists.txt new file mode 100644 index 000000000..dd23376e8 --- /dev/null +++ b/modules/PyJosev/CMakeLists.txt @@ -0,0 +1,14 @@ +get_filename_component(MODULE_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) +add_custom_target(${MODULE_NAME} ALL) + +# add_dependencies(${MODULE_NAME} everestpy) +set(EVEREST_MOD_DESTINATION "modules/${MODULE_NAME}") + +# install the whole python project +install( + FILES + module.py + everest_iso15118.py + manifest.yaml + DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}" +) diff --git a/modules/PyJosev/everest_iso15118.py b/modules/PyJosev/everest_iso15118.py new file mode 100644 index 000000000..f137107a3 --- /dev/null +++ b/modules/PyJosev/everest_iso15118.py @@ -0,0 +1,292 @@ +Setup = None +ChargerWrapper = None + +INT_16_MAX = 2**15 - 1 + +def init_Setup(new_setup): + global Setup + Setup = new_setup + +def p_Charger(): + return Setup.p_charger + +class Charger_Wrapper(object): + + def __init__(self) -> None: + # Common + self.EVSEID = "" + self.EVSEID_DIN = "" + self.PaymentOptions = [] + self.SupportedEnergyTransferMode = [] + self.ReceiptRequired = False + self.FreeService = False + self.EVSEEnergyToBeDelivered = 0 + self.debug_mode = "None" + self.stop_charging = False + self.auth_okay_eim = False + self.auth_okay_pnc = False + self.EVSE_UtilityInterruptEvent = False + self.EVSE_EmergencyShutdown = False + self.EVSE_Malfunction = False + self.powermeter = dict() + + # AC + self.EVSENominalVoltage = 0 + self.ContactorError = False + self.RCD_Error = False + self.EVSEMaxCurrent = 0 + self.contactorClosed = False + self.contactorOpen = True + + # DC + self.EVSEPeakCurrentRipple = 0 + self.EVSECurrentRegulationTolerance = 0 + self.EVSEPresentVoltage = 0 + self.EVSEPresentCurrent = 0 + self.EVSEMaximumCurrentLimit = 0 + self.EVSEMaximumPowerLimit = 0 + self.EVSEMaximumVoltageLimit = 0 + self.EVSEMinimumCurrentLimit = 0 + self.EVSEMinimumVoltageLimit = 0 + self.EVSEIsolationStatus = "Invalid" + self.cableCheck_Finished = False + + # Reset values every SessionSetup + def reset(self) -> None: + # Common + self.stop_charging = False + self.auth_okay_eim = False + self.auth_okay_pnc = False + self.EVSE_UtilityInterruptEvent = False + self.EVSE_EmergencyShutdown = False + self.EVSE_Malfunction = False + + # AC + self.ContactorError = False + self.contactorClosed = False + self.contactorOpen = True + self.RCD_Error = False + + # DC + self.EVSEIsolationStatus = "Invalid" + self.cableCheck_Finished = False + + # SessionSetup, CurrentDemand, ChargingStatus + def set_EVSEID(self, EVSEID: str): + self.EVSEID = EVSEID + def get_EVSEID(self) -> str: + return self.EVSEID + + # SessionSetup, CurrentDemand, ChargingStatus + def set_EVSEID_DIN(self, EVSEID_DIN: str): + self.EVSEID_DIN = EVSEID_DIN + def get_EVSEID_DIN(self) -> str: + return self.EVSEID_DIN + + # ServiceDiscovery, PaymentServiceSelection + def set_PaymentOptions(self, PaymentOptions: list): + self.PaymentOptions = PaymentOptions + def get_PaymentOptions(self) -> list: + return self.PaymentOptions + + # ServiceDisovery, ChargeParameterDiscovery + def set_SupportedEnergyTransferMode(self, SupportedEnergyTransferMode: list): + self.SupportedEnergyTransferMode = SupportedEnergyTransferMode + def get_SupportedEnergyTransferMode(self) -> list: + return self.SupportedEnergyTransferMode + + # ChargeParameterDiscovery + def set_AC_EVSENominalVoltage(self, EVSENominalVoltage: float): + self.EVSENominalVoltage = EVSENominalVoltage + def get_AC_EVSENominalVoltage(self) -> float: + return self.EVSENominalVoltage + + # ChargeParameterDiscovery + def set_DC_EVSECurrentRegulationTolerance(self, EVSECurrentRegulationTolerance: float): + self.EVSECurrentRegulationTolerance = EVSECurrentRegulationTolerance + def get_DC_EVSECurrentRegulationTolerance(self) -> float: + return self.EVSECurrentRegulationTolerance + + # ChargeParameterDisovery + def set_DC_EVSEPeakCurrentRipple(self, EVSEPeakCurrentRipple: float): + self.EVSEPeakCurrentRipple = EVSEPeakCurrentRipple + def get_DC_EVSEPeakCurrentRipple(self) -> float: + return self.EVSEPeakCurrentRipple + + # ChargingStatus and CurrentDemand + def set_ReceiptRequired(self, ReceiptRequired: bool): + self.ReceiptRequired = ReceiptRequired + def get_ReceiptRequired(self) -> bool: + return self.ReceiptRequired + + # Authorization, ServiceDiscovery + def set_FreeService(self, FreeService: bool): + self.FreeService = FreeService + def get_FreeService(self) -> bool: + return self.FreeService + + # ChargeParameterDisovery + def set_EVSEEnergyToBeDelivered(self, EVSEEnergyToBeDelivered: float): + self.EVSEEnergyToBeDelivered = EVSEEnergyToBeDelivered + def get_EVSEEnergyToBeDelivered(self) -> float: + return self.EVSEEnergyToBeDelivered + + # V2GCommunicationSessionSECC IncomingMessage, Send + def enable_debug_mode(self, debug_mode: str): + self.debug_mode = debug_mode + def get_debug_mode(self) -> str: + return self.debug_mode + + # Authorization + def set_Auth_Okay_EIM(self, auth_okay_eim: bool): + self.auth_okay_eim = auth_okay_eim + def get_Auth_Okay_EIM(self) -> bool: + return self.auth_okay_eim + + # Authorization + def set_Auth_Okay_PnC(self, auth_okay_pnc: bool): + self.auth_okay_pnc = auth_okay_pnc + def get_Auth_Okay_PnC(self) -> bool: + return self.auth_okay_pnc + + # TODO: Not yet implemented + def set_FAILED_ContactorError(self, ContactorError: bool): + self.ContactorError = ContactorError + def get_FAILED_ContactorError(self) -> bool: + return self.ContactorError + + # ChargeParameter, ChargingStatus, MeteringReceipt, PowerDelivery + def set_RCD_Error(self, RCD: bool): + self.RCD_Error = RCD + def get_RCD_Error(self) -> bool: + return self.RCD_Error + + # CableCheck, ChargingStatus, CurrentDemand, PowerDelivery, PreCharge, WeldingDetection + # ChargeParameterDiscovery + def set_stop_charging(self, stop_charging: bool): + self.stop_charging = stop_charging + def get_stop_charging(self) -> bool: + return self.stop_charging + + # CurrentDemand, PreCharge, WeldingDetection + def set_DC_EVSEPresentVoltage(self, EVSEPresentVoltage: float): + self.EVSEPresentVoltage = EVSEPresentVoltage + def get_DC_EVSEPresentVoltage(self) -> float: + return self.EVSEPresentVoltage + + # CurrentDemand + def set_DC_EVSEPresentCurrent(self, EVSEPresentCurrent: float): + self.EVSEPresentCurrent = EVSEPresentCurrent + def get_DC_EVSEPresentCurrent(self) -> float: + return self.EVSEPresentCurrent + + # ChargingStatus, ChargeParameter + def set_AC_EVSEMaxCurrent(self, EVSEMaxCurrent: float): + self.EVSEMaxCurrent = EVSEMaxCurrent + def get_AC_EVSEMaxCurrent(self) -> float: + return self.EVSEMaxCurrent + + # ChargeParameter, CurrentDemand + def set_DC_EVSEMaximumCurrentLimit(self, EVSEMaximumCurrentLimit: float): + self.EVSEMaximumCurrentLimit = EVSEMaximumCurrentLimit + def get_DC_EVSEMaximumCurrentLimit(self) -> float: + return self.EVSEMaximumCurrentLimit + + # ChargeParameter, CurrentDemand + def set_DC_EVSEMaximumPowerLimit(self, EVSEMaximumPowerLimit: float): + self.EVSEMaximumPowerLimit = EVSEMaximumPowerLimit + def get_DC_EVSEMaximumPowerLimit(self) -> float: + return self.EVSEMaximumPowerLimit + + # ChargeParameter, CurrentDemand + def set_DC_EVSEMaximumVoltageLimit(self, EVSEMaximumVoltageLimit: float): + self.EVSEMaximumVoltageLimit = EVSEMaximumVoltageLimit + def get_DC_EVSEMaximumVoltageLimit(self) -> float: + return self.EVSEMaximumVoltageLimit + + # ChargeParameter + def set_DC_EVSEMinimumCurrentLimit(self, EVSEMinimumCurrentLimit: float): + self.EVSEMinimumCurrentLimit = EVSEMinimumCurrentLimit + def get_DC_EVSEMinimumCurrentLimit(self) -> float: + return self.EVSEMinimumCurrentLimit + + # ChargeParameter + def set_DC_EVSEMinimumVoltageLimit(self, EVSEMinimumVoltageLimit: float): + self.EVSEMinimumVoltageLimit = EVSEMinimumVoltageLimit + def get_DC_EVSEMinimumVoltageLimit(self) -> float: + return self.EVSEMinimumVoltageLimit + + # CableCheck, CurrentDemand, MeteringReceipt, PowerDelivery, PreCharge, WeldingDetection + def set_EVSEIsolationStatus(self, EVSEIsolationStatus: str): + self.EVSEIsolationStatus = EVSEIsolationStatus + def get_EVSEIsolationStatus(self) -> str: + return self.EVSEIsolationStatus + + # CableCheck, CurrentDemand, MeteringReceipt, PowerDelivery, PreCharge, WeldingDetection + def set_EVSE_UtilityInterruptEvent(self, EVSE_UtilityInterruptEvent: bool): + self.EVSE_UtilityInterruptEvent = EVSE_UtilityInterruptEvent + def get_EVSE_UtilityInterruptEvent(self) -> bool: + return self.EVSE_UtilityInterruptEvent + + # CableCheck, CurrentDemand, MeteringReceipt, PowerDelivery, PreCharge, WeldingDetection + def set_EVSE_Malfunction(self, EVSE_Malfunction: bool): + self.EVSE_Malfunction = EVSE_Malfunction + def get_EVSE_Malfunction(self) -> bool: + return self.EVSE_Malfunction + + # CableCheck, CurrentDemand, MeteringReceipt, PowerDelivery, PreCharge, WeldingDetection + def set_EVSE_EmergencyShutdown(self, EVSE_EmergencyShutdown: bool): + self.EVSE_EmergencyShutdown = EVSE_EmergencyShutdown + def get_EVSE_EmergencyShutdown(self) -> bool: + return self.EVSE_EmergencyShutdown + + # ChargingStatus, CurrentDemand + def set_MeterInfo(self, powermeter: dict): + self.powermeter = powermeter + def get_MeterInfo(self) -> dict: + return self.powermeter + + # PowerDelivery + def contactor_closed(self, status: bool): + if (status is True): + self.contactorClosed = True + self.contactorOpen = False + def contactor_open(self, status: bool): + if (status is True): + self.contactorClosed = False + self.contactorOpen = True + def get_contactor_closed_status(self) -> bool: + return self.contactorClosed + def get_contactor_opened_status(self) -> bool: + return self.contactorOpen + + def set_cableCheck_Finished(self, status: bool): + self.cableCheck_Finished = status + def get_cableCheck_Finished(self) -> bool: + return self.cableCheck_Finished + + +ChargerWrapper = Charger_Wrapper() + +def float2Value_Multiplier(value:float): + p_value: int = 0 + p_multiplier: int = 0 + exponent: int = 0 + + # Check if it is an integer or a decimal number + if (value - int(value)) != 0: + exponent = 2 + + for x in range(exponent, -4, -1): + if (value * pow(10, x)) < INT_16_MAX: + exponent = x + break + + p_multiplier = int(-exponent) + p_value = int(value * pow(10, exponent)) + + return p_value, p_multiplier + +def PhysicalValueType2float(p_value:int, p_multiplier:int) -> float: + value:float = p_value * pow(10, p_multiplier) + return value diff --git a/modules/PyJosev/manifest.yaml b/modules/PyJosev/manifest.yaml new file mode 100644 index 000000000..c131f3d7a --- /dev/null +++ b/modules/PyJosev/manifest.yaml @@ -0,0 +1,56 @@ +description: >- + This module includes a DIN70121, ISO15118-2 and ISO15118-20 implementation + by the Josev project +config: + device: + description: >- + Ethernet device used for HLC. Any local interface that has an ipv6 + link-local and a MAC addr will work. + type: string + default: eth0 + enforce_tls: + description: The SECC will enforce a TLS connection + type: boolean + default: false + supported_DIN70121: + description: The EVSE supports the DIN SPEC + type: boolean + default: false + supported_ISO15118_2: + description: The EVSE supports ISO15118-2 + type: boolean + default: true + supported_ISO15118_20_AC: + description: The EVSE supports ISO15118-20 AC + type: boolean + default: false + supported_ISO15118_20_DC: + description: The EVSE supports ISO15118-20 DC + type: boolean + default: false + free_cert_install_service: + description: The certificate install service is free + type: boolean + default: false + allow_cert_install_service: + description: The SECC allows the car to install certificates when necessary + type: boolean + default: false + ignore_physical_values_limits: + description: >- + Set to True if Josev should ignore the physical value limits. This + way the charging process will not be terminated if e.g. the EV requests more + than 400A + type: boolean + default: false +provides: + charger: + interface: ISO15118_charger + description: + This module implements the ISO15118-2 implementation of an AC or + DC charger +enable_external_mqtt: true +metadata: + license: https://opensource.org/licenses/Apache-2.0 + authors: + - Sebastian Lukas diff --git a/modules/PyJosev/module.py b/modules/PyJosev/module.py new file mode 100644 index 000000000..9d4727644 --- /dev/null +++ b/modules/PyJosev/module.py @@ -0,0 +1,169 @@ +from everestpy import log +import asyncio +import logging +import netifaces +from pathlib import Path + +import sys +JOSEV_WORK_DIR = Path(__file__).parent / '../../3rd_party/josev' +sys.path.append(JOSEV_WORK_DIR.as_posix()) + +from iso15118.secc import SECCHandler +from iso15118.secc.controller.simulator import SimEVSEController +from iso15118.shared.exificient_exi_codec import ExificientEXICodec +from iso15118.secc.secc_settings import Config + +from everest_iso15118 import init_Setup +from everest_iso15118 import ChargerWrapper + +logger = logging.getLogger(__name__) + +async def main(): + """ + Entrypoint function that starts the ISO 15118 code running on + the SECC (Supply Equipment Communication Controller) + """ + config = Config() + config.load_everest_config(module_config_) + + sim_evse_controller = await SimEVSEController.create() + await SECCHandler( + exi_codec=ExificientEXICodec(), evse_controller=sim_evse_controller, config=config + ).start(config.iface) + + +def run(): + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.debug("SECC program terminated manually") + + +setup_ = None +module_config_ = None +impl_configs_ = None + +def pre_init(setup, module_config, impl_configs): + global setup_, module_config_, impl_configs_ + setup_ = setup + module_config_ = module_config + impl_configs_ = impl_configs + init_Setup(setup_) + +def init(): + log.debug("init") + + net_iface: str = str(module_config_["device"]) + if net_iface == "auto": + module_config_["device"] = choose_first_ipv6_local() + elif not check_network_interfaces(net_iface=net_iface): + log.warning(f"The network interface {net_iface} was not found!") + +def ready(): + log.debug("ready") + + log.debug("Josev is starting ...") + run() + +### Charger Command Callbacks ### + +def charger_set_EVSEID(EVSEID: str, EVSEID_DIN: str): + ChargerWrapper.set_EVSEID(EVSEID) + ChargerWrapper.set_EVSEID_DIN(EVSEID_DIN) + +def charger_set_PaymentOptions(PaymentOptions: list): + ChargerWrapper.set_PaymentOptions(PaymentOptions) + +def charger_set_SupportedEnergyTransferMode(SupportedEnergyTransferMode: list): + ChargerWrapper.set_SupportedEnergyTransferMode(SupportedEnergyTransferMode) + +def charger_set_AC_EVSENominalVoltage(EVSENominalVoltage: float): + ChargerWrapper.set_AC_EVSENominalVoltage(EVSENominalVoltage) + +def charger_set_DC_EVSECurrentRegulationTolerance(EVSECurrentRegulationTolerance: float): + ChargerWrapper.set_DC_EVSECurrentRegulationTolerance(EVSECurrentRegulationTolerance) + +def charger_set_DC_EVSEPeakCurrentRipple(EVSEPeakCurrentRipple: float): + ChargerWrapper.set_DC_EVSEPeakCurrentRipple(EVSEPeakCurrentRipple) + +def charger_set_ReceiptRequired(ReceiptRequired: bool): + ChargerWrapper.set_ReceiptRequired(ReceiptRequired) + +def charger_set_FreeService(FreeService: bool): + ChargerWrapper.set_FreeService(FreeService) + +def charger_set_EVSEEnergyToBeDelivered(EVSEEnergyToBeDelivered: float): + ChargerWrapper.set_EVSEEnergyToBeDelivered(EVSEEnergyToBeDelivered) + +def charger_enable_debug_mode(debug_mode: str): + ChargerWrapper.enable_debug_mode(debug_mode) + +def charger_set_Auth_Okay_EIM(auth_okay_eim: bool): + ChargerWrapper.set_Auth_Okay_EIM(auth_okay_eim) + +def charger_set_Auth_Okay_PnC(auth_okay_pnc: bool): + ChargerWrapper.set_Auth_Okay_PnC(auth_okay_pnc) + +def charger_set_FAILED_ContactorError(ContactorError: bool): + ChargerWrapper.set_FAILED_ContactorError(ContactorError) + +def charger_set_RCD_Error(RCD: bool): + ChargerWrapper.set_RCD_Error(RCD) + +def charger_stop_charging(stop_charging: bool): + ChargerWrapper.set_stop_charging(stop_charging) + +def charger_set_DC_EVSEPresentVoltageCurrent(EVSEPresentVoltage_Current: dict): + ChargerWrapper.set_DC_EVSEPresentVoltage(EVSEPresentVoltage_Current["EVSEPresentVoltage"]) + ChargerWrapper.set_DC_EVSEPresentCurrent(EVSEPresentVoltage_Current["EVSEPresentCurrent"]) + +def charger_set_AC_EVSEMaxCurrent(EVSEMaxCurrent: float): + ChargerWrapper.set_AC_EVSEMaxCurrent(EVSEMaxCurrent) + +def charger_set_DC_EVSEMaximumLimits(EVSEMaximumLimits: dict): + ChargerWrapper.set_DC_EVSEMaximumCurrentLimit(EVSEMaximumLimits["EVSEMaximumCurrentLimit"]) + ChargerWrapper.set_DC_EVSEMaximumPowerLimit(EVSEMaximumLimits["EVSEMaximumPowerLimit"]) + ChargerWrapper.set_DC_EVSEMaximumVoltageLimit(EVSEMaximumLimits["EVSEMaximumVoltageLimit"]) + +def charger_set_DC_EVSEMinimumLimits(EVSEMinimumLimits: dict): + ChargerWrapper.set_DC_EVSEMinimumCurrentLimit(EVSEMinimumLimits["EVSEMinimumCurrentLimit"]) + ChargerWrapper.set_DC_EVSEMinimumVoltageLimit(EVSEMinimumLimits["EVSEMinimumVoltageLimit"]) + +def charger_set_EVSEIsolationStatus(EVSEIsolationStatus: str): + ChargerWrapper.set_EVSEIsolationStatus(EVSEIsolationStatus) + +def charger_set_EVSE_UtilityInterruptEvent(EVSE_UtilityInterruptEvent: bool): + ChargerWrapper.set_EVSE_UtilityInterruptEvent(EVSE_UtilityInterruptEvent) + +def charger_set_EVSE_Malfunction(EVSE_Malfunction: bool): + ChargerWrapper.set_EVSE_Malfunction(EVSE_Malfunction) + +def charger_set_EVSE_EmergencyShutdown(EVSE_EmergencyShutdown: bool): + ChargerWrapper.set_EVSE_EmergencyShutdown(EVSE_EmergencyShutdown) + +def charger_set_MeterInfo(powermeter: dict): + ChargerWrapper.set_MeterInfo(powermeter) + +def charger_contactor_closed(status: bool): + ChargerWrapper.contactor_closed(status) + +def charger_contactor_open(status: bool): + ChargerWrapper.contactor_open(status) + +def charger_cableCheck_Finished(status): + ChargerWrapper.set_cableCheck_Finished(status) + +def check_network_interfaces(net_iface: str) -> bool: + if (net_iface in netifaces.interfaces()) is True: + return True + return False + +def choose_first_ipv6_local() -> str: + for iface in netifaces.interfaces(): + if netifaces.AF_INET6 in netifaces.ifaddresses(iface): + for netif_inet6 in netifaces.ifaddresses(iface)[netifaces.AF_INET6]: + if 'fe80' in netif_inet6['addr']: + return iface + + log.warning("No necessary IPv6 link-local address was found!") + return "eth0" diff --git a/modules/SerialCommHub/main/serial_communication_hubImpl.cpp b/modules/SerialCommHub/main/serial_communication_hubImpl.cpp index e8aeab696..bf6b4a38d 100644 --- a/modules/SerialCommHub/main/serial_communication_hubImpl.cpp +++ b/modules/SerialCommHub/main/serial_communication_hubImpl.cpp @@ -97,12 +97,11 @@ static std::vector to_little_endian_16(const std::vector& res static std::vector to_little_endian_int(const std::vector& response) { std::vector r; - uint8_t hi, lo; for (auto it = response.begin(); it != response.end();) { - hi = *it++; + uint8_t hi = *it++; if (it == response.end()) break; - lo = *it++; + uint8_t lo = *it++; r.push_back((uint16_t)((hi << 8) | lo)); } return r; @@ -121,6 +120,8 @@ static void add_crc(std::vector& message) { void serial_communication_hubImpl::init() { + last_message_end_time = std::chrono::steady_clock::now(); + everest::connection::SerialDeviceConfiguration cfg(config.serial_port); cfg.set_sensible_defaults(); cfg.set_baud_rate(baudrate_from_int(config.baudrate)); @@ -178,6 +179,7 @@ serial_communication_hubImpl::handle_send_raw(int& target_device_id, bytes_written = serial_device->write(message.data(), message.size()); serial_device->drain(); + last_message_end_time = std::chrono::steady_clock::now(); } // check if all bytes were written @@ -220,6 +222,7 @@ types::serial_comm_hub_requests::ResultRaw serial_communication_hubImpl::handle_ // Receive reply with timeout bytes_received = serial_device->read(receive_buffer, sizeof(receive_buffer)); + last_message_end_time = std::chrono::steady_clock::now(); } // check if timed out @@ -253,15 +256,18 @@ types::serial_comm_hub_requests::ResultRaw serial_communication_hubImpl::handle_ return status; } -types::serial_comm_hub_requests::Result -serial_communication_hubImpl::handle_modbus_read_holding_registers(int& target_device_id, int& first_register_address, - int& num_registers_to_read) { +types::serial_comm_hub_requests::Result serial_communication_hubImpl::handle_modbus_read_holding_registers( + int& target_device_id, int& first_register_address, int& num_registers_to_read, int& pause_between_messages) { std::vector response; types::serial_comm_hub_requests::Result result; result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::ModbusException; { std::scoped_lock lock(serial_mutex); + auto time_since_last_message = std::chrono::steady_clock::now() - last_message_end_time; + auto pause_time = std::chrono::milliseconds(pause_between_messages); + if (time_since_last_message < pause_time) + std::this_thread::sleep_for(pause_time - time_since_last_message); bool done{false}; uint8_t retry_counter{this->num_resends_on_error}; @@ -301,6 +307,8 @@ serial_communication_hubImpl::handle_modbus_read_holding_registers(int& target_d } done = true; } while (!done); + + last_message_end_time = std::chrono::steady_clock::now(); } EVLOG_debug << fmt::format("Process response (size {})", response.size()); @@ -313,7 +321,8 @@ serial_communication_hubImpl::handle_modbus_read_holding_registers(int& target_d } types::serial_comm_hub_requests::StatusCodeEnum serial_communication_hubImpl::handle_modbus_write_multiple_registers( - int& target_device_id, int& first_register_address, types::serial_comm_hub_requests::VectorUint16& data_raw) { + int& target_device_id, int& first_register_address, types::serial_comm_hub_requests::VectorUint16& data_raw, + int& pause_between_messages) { std::vector raw_message; append_array(raw_message, data_raw.data); @@ -326,6 +335,11 @@ types::serial_comm_hub_requests::StatusCodeEnum serial_communication_hubImpl::ha { std::scoped_lock lock(serial_mutex); + auto time_since_last_message = std::chrono::steady_clock::now() - last_message_end_time; + auto pause_time = std::chrono::milliseconds(pause_between_messages); + if (time_since_last_message < pause_time) + std::this_thread::sleep_for(pause_time - time_since_last_message); + bool done{false}; uint8_t retry_counter{this->num_resends_on_error}; do { @@ -334,8 +348,8 @@ types::serial_comm_hub_requests::StatusCodeEnum serial_communication_hubImpl::ha } EVLOG_debug << fmt::format("Try {} Call modbus_client->write_multiple_registers(id {} addr {} len {})", - (int)num_resends_on_error, (uint8_t)target_device_id, - (uint16_t)first_register_address, (uint16_t)payload.size()); + (int)retry_counter, (uint8_t)target_device_id, (uint16_t)first_register_address, + (uint16_t)payload.size()); try { response = @@ -364,6 +378,8 @@ types::serial_comm_hub_requests::StatusCodeEnum serial_communication_hubImpl::ha } done = true; } while (!done); + + last_message_end_time = std::chrono::steady_clock::now(); } EVLOG_debug << fmt::format("Done writing"); diff --git a/modules/SerialCommHub/main/serial_communication_hubImpl.hpp b/modules/SerialCommHub/main/serial_communication_hubImpl.hpp index d4ea8695b..f1970efac 100644 --- a/modules/SerialCommHub/main/serial_communication_hubImpl.hpp +++ b/modules/SerialCommHub/main/serial_communication_hubImpl.hpp @@ -20,6 +20,7 @@ #include #include #include +#include // ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 namespace module { @@ -40,6 +41,7 @@ class serial_communication_hubImpl : public serial_communication_hubImplBase { serial_communication_hubImplBase(ev, "main"), mod(mod), config(config){}; // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + // insert your public definitions here // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 protected: @@ -51,11 +53,12 @@ class serial_communication_hubImpl : public serial_communication_hubImplBase { handle_send_raw_wait_reply(int& target_device_id, types::serial_comm_hub_requests::VectorUint8& data_raw, bool& add_crc16) override; virtual types::serial_comm_hub_requests::Result - handle_modbus_read_holding_registers(int& target_device_id, int& first_register_address, - int& num_registers_to_read) override; + handle_modbus_read_holding_registers(int& target_device_id, int& first_register_address, int& num_registers_to_read, + int& pause_between_messages) override; virtual types::serial_comm_hub_requests::StatusCodeEnum handle_modbus_write_multiple_registers(int& target_device_id, int& first_register_address, - types::serial_comm_hub_requests::VectorUint16& data_raw) override; + types::serial_comm_hub_requests::VectorUint16& data_raw, + int& pause_between_messages) override; // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 // insert your protected definitions here @@ -75,7 +78,7 @@ class serial_communication_hubImpl : public serial_communication_hubImplBase { std::unique_ptr modbus_connection; std::unique_ptr modbus_client; std::mutex serial_mutex; - + std::chrono::time_point last_message_end_time; // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 }; diff --git a/modules/YetiDriver/powermeter/powermeterImpl.cpp b/modules/YetiDriver/powermeter/powermeterImpl.cpp index 580616a49..ae9020d2f 100644 --- a/modules/YetiDriver/powermeter/powermeterImpl.cpp +++ b/modules/YetiDriver/powermeter/powermeterImpl.cpp @@ -1,47 +1,54 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest #include "powermeterImpl.hpp" +#include namespace module { namespace powermeter { -static Everest::json yeti_to_everest(const PowerMeter& p) { - Everest::json j; - - j["timestamp"] = p.time_stamp; - j["meter_id"] = "YETI_POWERMETER"; - j["phase_seq_error"] = p.phaseSeqError; - - j["energy_Wh_import"]["total"] = p.totalWattHr; - j["energy_Wh_import"]["L1"] = p.wattHrL1; - j["energy_Wh_import"]["L2"] = p.wattHrL2; - j["energy_Wh_import"]["L3"] = p.wattHrL3; - - j["power_W"]["total"] = p.wattL1 + p.wattL2 + p.wattL3; - j["power_W"]["L1"] = p.wattL1; - j["power_W"]["L2"] = p.wattL2; - j["power_W"]["L3"] = p.wattL3; - - j["voltage_V"]["L1"] = p.vrmsL1; - j["voltage_V"]["L2"] = p.vrmsL2; - j["voltage_V"]["L3"] = p.vrmsL3; - - j["current_A"]["L1"] = p.irmsL1; - j["current_A"]["L2"] = p.irmsL2; - j["current_A"]["L3"] = p.irmsL3; - j["current_A"]["N"] = p.irmsN; - - j["frequency_Hz"]["L1"] = p.freqL1; - j["frequency_Hz"]["L2"] = p.freqL2; - j["frequency_Hz"]["L3"] = p.freqL3; +static types::powermeter::Powermeter yeti_to_everest(const PowerMeter& p) { + types::powermeter::Powermeter j; + + j.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now()); + j.meter_id = "YETI_POWERMETER"; + j.phase_seq_error = p.phaseSeqError; + + j.energy_Wh_import.total = p.totalWattHr; + j.energy_Wh_import.L1 = p.wattHrL1; + j.energy_Wh_import.L2 = p.wattHrL2; + j.energy_Wh_import.L3 = p.wattHrL3; + + types::units::Power pwr; + pwr.total = p.wattL1 + p.wattL2 + p.wattL3; + pwr.L1 = p.wattL1; + pwr.L2 = p.wattL2; + pwr.L3 = p.wattL3; + j.power_W = pwr; + + types::units::Voltage volt; + volt.L1 = p.vrmsL1; + volt.L2 = p.vrmsL2; + volt.L3 = p.vrmsL3; + j.voltage_V = volt; + + types::units::Current amp; + amp.L1 = p.irmsL1; + amp.L2 = p.irmsL2; + amp.L3 = p.irmsL3; + amp.N = p.irmsN; + j.current_A = amp; + + types::units::Frequency freq; + freq.L1 = p.freqL1; + freq.L2 = p.freqL2; + freq.L3 = p.freqL3; + j.frequency_Hz = freq; return j; } void powermeterImpl::init() { - mod->serial.signalPowerMeter.connect([this](const PowerMeter& p) { - publish_powermeter(yeti_to_everest(p)); - }); + mod->serial.signalPowerMeter.connect([this](const PowerMeter& p) { publish_powermeter(yeti_to_everest(p)); }); } void powermeterImpl::ready() { diff --git a/types/evse_manager.yaml b/types/evse_manager.yaml index 944fbadd1..f04ba1ddd 100644 --- a/types/evse_manager.yaml +++ b/types/evse_manager.yaml @@ -81,13 +81,19 @@ types: Enabled: Signaled when the EVSE is enabled (e.g. after an enable command) Disabled: Signaled when the EVSE is disabled (e.g. after a disable command) SessionStarted: Signaled when a session has been started. A session has been started either when an EV is plugged in or the user has been authorized to start a transaction (e.g. after an authorize command) - TransactionStarted: Signaled when a transaction has been started. Transaction starts at the point that all conditions for charging are met: EV is connected and user has been authorized. AuthRequired: Signaled when an EVSE needs authorization to start a transaction + TransactionStarted: Signaled when a transaction has been started. Transaction starts at the point that all conditions for charging are met: EV is connected and user has been authorized + PrepareCharging: EVSE started to prepare for charging. DC: CableCheck, PreCharge, PowerDelivery. AC: wait for the car to proceed to state C/D + ChargingStarted: DC: CurrentDemand has started. AC: Auth is ok and car requested power (state C/D) ChargingPausedEV: Signaled when charging is paused by the EV ChargingPausedEVSE: Signaled when charging is paused by the EVSE + ChargingResumed: Charging has resumed from a pause + StoppingCharging: EVSE has started to stop the charging process. DC: CurrentDemand has finished, now doing WeldingCheck etc, AC: Wait for car to return to state B or A + ChargingFinished: Charging is finished. Essentially the same as TransactionFinished, but emitted for clarity TransactionFinished: Signaled when the transaction finished. Transaction finishes at the point where one of the preconditions for charging irrevocably becomes false: When a user swipes to stop the transaction and the stop is authorized. - SessionFinished: Signaled when the transaction finished. Session finishes at the point that the EVSE is available again (no cable plugged) + SessionFinished: Session finishes at the point that the EVSE is available again (no cable plugged) Error: Signaled when an error occured + AllErrorsCleared: Signalled when all errors are cleared PermanentFault: Signaled when there is a permanent fault at the EVSE ReservationStart: Signaled when a reservation starts ReservationEnd: Signaled when a reservation ends @@ -98,14 +104,19 @@ types: - Enabled - Disabled - SessionStarted - - TransactionStarted - AuthRequired + - TransactionStarted + - PrepareCharging + - ChargingStarted - ChargingPausedEV - ChargingPausedEVSE - ChargingResumed + - StoppingCharging + - ChargingFinished - TransactionFinished - SessionFinished - Error + - AllErrorsCleared - PermanentFault - ReservationStart - ReservationEnd @@ -262,3 +273,69 @@ types: type: integer minimum: 1 maximum: 3 + EVInfo: + description: Information about the connected EV if available + type: object + additionalProperties: false + properties: + soc: + description: State of charge of the vehicle's battery in percent + type: number + minimum: 0 + maximum: 100 + present_voltage: + description: Current voltage of the vehicles battery [V] + type: number + present_current: + description: Current current of the vehicles battery [A] + type: number + target_voltage: + description: Target voltage that the vehicle requested [V] + type: number + target_current: + description: Target current that the vehicle requested [A] + type: number + maximum_current_limit: + description: Maximum current that the vehicle supports [A] + type: number + minimum_current_limit: + description: Charging below this limit is not energy efficient [A] + type: number + maximum_voltage_limit: + description: Maximum voltage that the vehicle supports [V] + type: number + maximum_power_limit: + description: Maximum power that the vehicle supports [W] + type: number + estimated_time_full: + description: Estimated time when the vehicle is fully charged + type: string + format: date-time + departure_time: + description: Time when the vehicle intends to depart (If set by the user) + type: string + format: date-time + estimated_time_bulk: + description: Estimated time when the vehicle finished bulk charging (e.g. 80%) + type: string + format: date-time + evcc_id: + description: EVCC ID (typically MAC address) + type: string + pattern: ^[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){5}$ + remaining_energy_needed: + description: Remaining energy needed to fulfill charging goal [Wh] + type: number + battery_capacity: + description: Vehicle's battery capacity [Wh] + type: number + battery_full_soc: + description: SoC which the vehicle considers fully charged [%] + type: number + minimum: 0 + maximum: 100 + battery_bulk_soc: + description: SoC which the vehicle considers bulk charging reached [%] + type: number + minimum: 0 + maximum: 100 diff --git a/types/iso15118_charger.yaml b/types/iso15118_charger.yaml new file mode 100644 index 000000000..bd2ea58d7 --- /dev/null +++ b/types/iso15118_charger.yaml @@ -0,0 +1,332 @@ +description: ISO15118 charger types +types: + PaymentOption: + description: Payment options for the SECC and EVCC + type: string + enum: + - Contract + - ExternalPayment + EnergyTransferMode: + description: Possible energy transfer modes + type: string + enum: + - AC_single_phase_core + - AC_three_phase_core + - DC_core + - DC_extended + - DC_combo_core + - DC_unique + DebugMode: + description: The different debug modes + type: string + enum: + - None + - Lite + - Full + IsolationStatus: + description: The different charger isolation status + type: string + enum: + - Invalid + - Valid + - Warning + - Fault + - No_IMD + DC_EVErrorCode: + description: Indicates the DC EV internal status + type: string + enum: + - NO_ERROR + - FAILED_RESSTemperatureInhibit + - FAILED_EVShiftPosition + - FAILED_ChargerConnectorLockFault + - FAILED_EVRESSMalfunction + - FAILED_ChargingCurrentdifferentia + - FAILED_ChargingVoltageOutOfRange + - Reserved_A + - Reserved_B + - Reserved_C + - FAILED_ChargingSystemIncompatibility + - NoData + ChargingSession: + description: + ChargingSession indicates the intention of the EVCC to either pause + or terminate a V2G Communication Session + type: string + enum: + - Terminate + - Pause + V2G_Message_ID: + description: This element contains the id of the v2g message body + type: string + enum: + - SupportedAppProtocolReq + - SupportedAppProtocolRes + - SessionSetupReq + - SessionSetupRes + - ServiceDiscoveryReq + - ServiceDiscoveryRes + - ServiceDetailReq + - ServiceDetailRes + - PaymentServiceSelectionReq + - PaymentServiceSelectionRes + - ServicePaymentSelectionReq + - ServicePaymentSelectionRes + - AuthorizationReq + - AuthorizationRes + - ContractAuthenticationReq + - ContractAuthenticationRes + - ChargeParameterDiscoveryReq + - ChargeParameterDiscoveryRes + - ChargingStatusReq + - ChargingStatusRes + - PowerDeliveryReq + - PowerDeliveryRes + - CableCheckReq + - CableCheckRes + - PreChargeReq + - PreChargeRes + - CurrentDemandReq + - CurrentDemandRes + - WeldingDetectionReq + - WeldingDetectionRes + - SessionStopReq + - SessionStopRes + DC_EVSEPresentVoltage_Current: + description: Set the present voltage and current for the EVSE + type: object + additionalProperties: false + required: + - EVSEPresentVoltage + properties: + EVSEPresentVoltage: + description: "[V] Output voltage of the EVSE as defined in IEC CDV 61851-23" + type: number + minumum: 0 + maximum: 1000 + EVSEPresentCurrent: + description: "[A] Present output current of the EVSE" + type: number + minumum: 0 + maximum: 400 + DC_EVSEMaximumLimits: + description: Maximum Values (current, power and voltage) the EVSE can deliver + type: object + additionalProperties: false + required: + - EVSEMaximumCurrentLimit + - EVSEMaximumPowerLimit + - EVSEMaximumVoltageLimit + properties: + EVSEMaximumCurrentLimit: + description: "[A] Maximum current the EVSE can deliver" + type: number + minimum: 0 + maximum: 400 + EVSEMaximumPowerLimit: + description: "[W] Maximum power the EVSE can deliver" + type: number + minimum: 0 + maximum: 200000 + EVSEMaximumVoltageLimit: + description: "[V] Maximum voltage the EVSE can deliver" + type: number + minimum: 0 + maximum: 1000 + DC_EVSEMinimumLimits: + description: Minimum Values (current and voltage) the EVSE can deliver + type: object + additionalProperties: false + required: + - EVSEMinimumCurrentLimit + - EVSEMinimumVoltageLimit + properties: + EVSEMinimumCurrentLimit: + description: "[A] Minimum current the EVSE can deliver with the expected accuracy" + type: number + minimum: 0 + maximum: 400 + EVSEMinimumVoltageLimit: + description: "[V] Minimum voltage the EVSE can deliver with the expected accuracy" + type: number + minimum: 0 + maximum: 1000 + DC_EVStatusType: + description: Current status of the EV + type: object + additionalProperties: false + required: + - DC_EVReady + - DC_EVErrorCode + - DC_EVRESSSOC + properties: + DC_EVReady: + description: If set to TRUE, the EV is ready to charge + type: boolean + DC_EVErrorCode: + description: Indicates the EV internal status + type: string + $ref: /iso15118_charger#/DC_EVErrorCode + DC_EVRESSSOC: + description: State of charge of the EVs battery (RESS) + type: number + minimum: 0 + maximum: 100 + DC_EVCabinConditioning: + description: >- + 'DIN70121 only: The EV is using energy from the DC supply toheat + or cool the passenger compartment.' + type: boolean + DC_EVRESSConiditioning: + description: >- + 'DIN70121 only: The vehicle is using energy from the DC charger + to condition the RESS to a target temperature.' + type: boolean + DC_EVTargetValues: + description: Target voltage and current requested by the EV + type: object + additionalProperties: false + required: + - DC_EVTargetVoltage + - DC_EVTargetCurrent + properties: + DC_EVTargetVoltage: + description: "[V] Target Voltage requested by EV" + type: number + minimum: -2 + maximum: 1000 + DC_EVTargetCurrent: + description: "[A] Current demanded by EV" + type: number + minimum: -2 + maximum: 800 + DC_EVMaximumLimits: + description: + Maximum Values (current, power and voltage) supported and allowed + by the EV + type: object + additionalProperties: false + properties: + DC_EVMaximumCurrentLimit: + description: "[A] Maximum current supported and allowed by the EV" + type: number + minimum: -2 + maximum: 1000 + DC_EVMaximumPowerLimit: + description: "Optional: [W] Maximum power supported and allowed by the EV" + type: number + minimum: -2 + maximum: 350000 + DC_EVMaximumVoltageLimit: + description: "[V] Maximum voltage supported and allowed by the EV" + type: number + minimum: -2 + maximum: 1000 + DC_EVRemainingTime: + description: Estimated or calculated time until bulk and full charge is complete + type: object + additionalProperties: false + properties: + EV_RemainingTimeToFullSoC: + description: + "[RFC3339 UTC] Estimated or calculated time until full charge + (100% SOC) is complete" + type: string + format: date-time + EV_RemainingTimeToBulkSoC: + description: + "[RFC3339 UTC] Estimated or calculated time until bulk charge + (approx. 80% SOC) is complete" + type: string + format: date-time + AppProtocol: + description: >- + This message element is used by the EVCC for transmitting the list + of supported protocols + type: object + additionalProperties: false + required: + - ProtocolNamespace + - VersionNumberMajor + - VersionNumberMinor + - SchemaID + - Priority + properties: + ProtocolNamespace: + description: >- + This message element is used by the EVCC to uniquely identify + the Namespace URI of a specific protocol supported by the EVCC + type: string + minLength: 1 + maxLength: 100 + VersionNumberMajor: + description: >- + This message element is used by the EVCC to indicate the major + version number of the protocol + type: integer + minimum: 0 + VersionNumberMinor: + description: >- + This message element is used by the EVCC to indicate the minor + version number of the protocol + type: integer + minimum: 0 + SchemaID: + description: >- + This message element is used by the EVCC to indicate the SchemaID + assigned by the EVCC to the protocol + type: integer + minimum: 0 + maximum: 255 + Priority: + description: >- + This message element is used by the EVCC for indicating the protocol + priority of a specific protocol allowing the SECC to select a protocol based + on priorities + type: integer + minimum: 1 + maximum: 20 + AppProtocols: + description: >- + This message element is used by the EVCC for transmitting the list + of supported protocols + type: object + additionalProperties: false + required: + - Protocols + properties: + Protocols: + type: array + items: + type: object + $ref: /iso15118_charger#/AppProtocol + additionalProperties: false + minItems: 1 + maxItems: 20 + V2G_Messages: + description: This element contains all V2G elements + type: object + additionalProperties: false + required: + - V2G_Message_ID + properties: + V2G_Message_ID: + description: This element contains the id of the v2g message body + type: string + $ref: /iso15118_charger#/V2G_Message_ID + V2G_Message_XML: + description: Contains the decoded EXI stream as V2G message XML file + type: string + minLength: 0 + V2G_Message_JSON: + description: Contains the decoded EXI stream as V2G message JSON file + type: string + minLength: 0 + V2G_Message_EXI_Hex: + description: Contains the EXI stream as hex string + type: string + minLength: 0 + V2G_Message_EXI_Base64: + description: Contains the EXI stream as base64 string + type: string + minLength: 0 diff --git a/types/isolation_monitor.yaml b/types/isolation_monitor.yaml new file mode 100644 index 000000000..0944ee9dd --- /dev/null +++ b/types/isolation_monitor.yaml @@ -0,0 +1,19 @@ +description: Isolation monitor types +types: + IsolationMeasurement: + description: Results of an isolation measurement + type: object + additionalProperties: false + required: + - resistance_P_Ohm + - resistance_N_Ohm + properties: + resistance_P_Ohm: + description: Isolation resistance of positive terminal to PE in Ohm + type: number + resistance_N_Ohm: + description: Isolation resistance of negative terminal to PE in Ohm + type: number + voltage_V: + description: DC voltage between positive and negative terminal in Volt + type: number diff --git a/types/power_supply_DC.yaml b/types/power_supply_DC.yaml new file mode 100644 index 000000000..3d1b12331 --- /dev/null +++ b/types/power_supply_DC.yaml @@ -0,0 +1,106 @@ +description: Power supply DC types +types: + Mode: + description: >- + Operation mode of power supply. Power supply delivers power in Export + mode and draws power in Import mode. + type: string + enum: + - "Off" + - Export + - Import + - Fault + VoltageCurrent: + description: Voltage (V) and Current (A) at the input/output of the power supply + type: object + additionalProperties: false + required: + - voltage_V + - current_A + properties: + voltage_V: + description: Voltage in V + type: number + current_A: + description: Current in A + type: number + Capabilities: + description: Capabilities for this power supply. + type: object + additionalProperties: false + required: + - bidirectional + - max_export_voltage_V + - min_export_voltage_V + - max_export_current_A + - min_export_current_A + - max_export_power_W + - current_regulation_tolerance_A + - peak_current_ripple_A + properties: + bidirectional: + description: >- + 'true: support bidirectional energy flow, false: support only + Export mode (output)' + type: boolean + current_regulation_tolerance_A: + description: Absolute magnitude of the regulation tolerance in Ampere + type: number + peak_current_ripple_A: + description: Peak-to-peak magnitude of the current ripple in Ampere + type: number + max_export_voltage_V: + description: Maximum voltage that the power supply can output in Volt + type: number + min_export_voltage_V: + description: Minimum voltage that the power supply can output in Volt + type: number + max_export_current_A: + description: Maximum current that the power supply can output in Ampere + type: number + min_export_current_A: + description: Minimum current limit that the power supply can set in Ampere + type: number + max_import_voltage_V: + description: + Maximum voltage that the power supply supports to import energy + in Volt + type: number + min_import_voltage_V: + description: + Minimum voltage that the power supply requires to import energy + in Volt + type: number + max_import_current_A: + description: Maximum current that the power supply can output in Ampere + type: number + min_import_current_A: + description: Minimum current limit that the power supply can set in Ampere + type: number + max_export_power_W: + description: Maximum export power that the power supply can output in Watt + type: number + max_import_power_W: + description: Maximum import power that the power supply can sink in Watt + type: number + FaultCode: + description: Fault codes + type: string + enum: + - CommunicationFailure + - ConfigurationError + - HardwareFault + - OverTemperature + - UnderTemperature + - UnderVoltageAC + - OverVoltageAC + - UnderVoltageDC + - OverVoltageDC + - UnderVoltageBattery + - OverVoltageBattery + - OverCurrentAC + - OverCurrentBattery + - OverCurrentDC + - WrongCabling + - Other + - NoError diff --git a/types/powermeter.yaml b/types/powermeter.yaml index 7ea8c5ce7..dabb81e9f 100644 --- a/types/powermeter.yaml +++ b/types/powermeter.yaml @@ -1,7 +1,7 @@ description: Powermeter types types: Powermeter: - description: Measured dataset + description: Measured dataset (AC or DC) type: object additionalProperties: false required: @@ -10,15 +10,13 @@ types: properties: timestamp: description: Timestamp of measurement - type: number - timestamp_RFC3339_UTC: - description: Timepoint when reached the everest module. type: string + format: date-time meter_id: description: A (user defined) meter if (e.g. id printed on the case) type: string phase_seq_error: - description: true for 3 phase rotation error (ccw) + description: "AC only: true for 3 phase rotation error (ccw)" type: boolean energy_Wh_import: description: Imported energy in Wh (from grid) @@ -29,7 +27,8 @@ types: type: object $ref: /units#/Energy power_W: - description: Instantaneous power in Watt. Negative values are exported, positive + description: + Instantaneous power in Watt. Negative values are exported, positive values imported Energy. type: object $ref: /units#/Power diff --git a/types/units.yaml b/types/units.yaml index 7b3d09123..24fbe6380 100644 --- a/types/units.yaml +++ b/types/units.yaml @@ -4,55 +4,58 @@ types: description: Current in Ampere type: object additionalProperties: false - required: - - L1 properties: + DC: + description: DC current + type: number L1: - description: L1 value only + description: AC L1 value only type: number L2: - description: L2 value only + description: AC L2 value only type: number L3: - description: L3 value only + description: AC L3 value only type: number N: - description: Neutral value only + description: AC Neutral value only type: number Voltage: description: Voltage in Volt type: object additionalProperties: false - required: - - L1 properties: + DC: + description: DC voltage + type: number L1: - description: L1 value only + description: AC L1 value only type: number L2: - description: L2 value only + description: AC L2 value only type: number L3: - description: L3 value only + description: AC L3 value only type: number Frequency: - description: Frequency in Hertz + description: "AC only: Frequency in Hertz" type: object additionalProperties: false required: - L1 properties: L1: - description: L1 value + description: AC L1 value type: number L2: - description: L2 value + description: AC L2 value type: number L3: - description: L3 value + description: AC L3 value type: number Power: - description: Instantaneous power in Watt. Negative values are exported, positive + description: + Instantaneous power in Watt. Negative values are exported, positive values imported Energy. type: object additionalProperties: false @@ -60,16 +63,16 @@ types: - total properties: total: - description: Sum value + description: DC / AC Sum value type: number L1: - description: L1 value only + description: AC L1 value only type: number L2: - description: L2 value only + description: AC L2 value only type: number L3: - description: L3 value only + description: AC L3 value only type: number Energy: description: Energy in Wh. @@ -79,16 +82,16 @@ types: - total properties: total: - description: Sum value (which is relevant for billing) + description: DC / AC Sum value (which is relevant for billing) type: number L1: - description: L1 value only + description: AC L1 value only type: number L2: - description: L2 value only + description: AC L2 value only type: number L3: - description: L3 value only + description: AC L3 value only type: number ReactivePower: description: Reactive power VAR @@ -100,13 +103,12 @@ types: total: description: VAR total type: number - VARphA: + L1: description: VAR phase A type: number - VARphB: + L2: description: VAR phase B type: number - VARphC: + L3: description: VAR phase C type: number -