From 47ba810ea8a990676cc8594e54afe4ccca42047b Mon Sep 17 00:00:00 2001 From: tropxy Date: Wed, 8 Mar 2023 16:33:25 +0000 Subject: [PATCH 1/4] Added report of the evse status during the charging loop of both AC and DC in -20 --- iso15118/secc/controller/interface.py | 2 +- iso15118/secc/controller/simulator.py | 27 ++++++---- iso15118/secc/states/iso15118_20_states.py | 12 ++++- poetry.lock | 57 ++++++++++------------ 4 files changed, 54 insertions(+), 44 deletions(-) diff --git a/iso15118/secc/controller/interface.py b/iso15118/secc/controller/interface.py index 2be9a7a3..14a2ab03 100644 --- a/iso15118/secc/controller/interface.py +++ b/iso15118/secc/controller/interface.py @@ -445,7 +445,7 @@ async def is_contactor_closed(self) -> bool: raise NotImplementedError @abstractmethod - async def get_evse_status(self) -> EVSEStatus: + async def get_evse_status(self) -> Optional[EVSEStatus]: """ Gets the status of the EVSE diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index 15146905..7a95c844 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -569,11 +569,18 @@ async def is_contactor_opened(self) -> bool: """Overrides EVSEControllerInterface.is_contactor_opened().""" return True - async def get_evse_status(self) -> EVSEStatus: + async def get_evse_status(self) -> Optional[EVSEStatus]: """Overrides EVSEControllerInterface.get_evse_status().""" - return EVSEStatus( - notification_max_delay=0, evse_notification=EVSENotificationV20.TERMINATE - ) + # TODO: this function can be generic to all protocols. + # We can make use of the method `get_evse_id` + # or other way to get the evse_id to request + # status of a specific evse_id. We can also use the + # `self.comm_session.protocol` obtained during SAP, + # and inject its value into the `get_evse_status` + # to decide on providing the -2ß EVSEStatus or the + # -2 AC or DC one and the `selected_charging_type_is_ac` in -2 + # to decide on returning the ACEVSEStatus or the DCEVSEStatus + return None async def set_present_protocol_state(self, state_name: str): pass @@ -623,12 +630,12 @@ async def send_charging_power_limits( ev_data_context.ev_min_discharge_power, charge_parameters.evse_min_discharge_power.get_decimal_value(), ) - logger.info( - f"\n Power limits \n" - f"max_charge_power: {max_charge_power}\n" - f"min_charge_power: {min_charge_power}\n" - f"max_discharge_power: {max_discharge_power}\n" - f"min_discharge_power: {min_discharge_power}\n" + logger.debug( + f"\n\r --- EV-EVSE System Power Limits --- \n" + f"max_charge_power [W]: {max_charge_power}\n" + f"min_charge_power [W]: {min_charge_power}\n" + f"max_discharge_power [W]: {max_discharge_power}\n" + f"min_discharge_power [W]: {min_discharge_power}\n" ) # NOTE: Currently reactive limits are not available # https://iso15118.elaad.io/pt2/15118-20/user-group/-/issues/65 diff --git a/iso15118/secc/states/iso15118_20_states.py b/iso15118/secc/states/iso15118_20_states.py index 2ffee34c..31346432 100644 --- a/iso15118/secc/states/iso15118_20_states.py +++ b/iso15118/secc/states/iso15118_20_states.py @@ -71,6 +71,7 @@ SessionStopRes, ) from iso15118.shared.messages.iso15118_20.common_types import ( + EVSEStatus, MessageHeader, Processing, ResponseCode, @@ -1357,11 +1358,14 @@ async def process_message( if ac_charge_loop_req.meter_info_requested: meter_info = await self.comm_session.evse_controller.get_meter_info_v20() + evse_status: Optional[EVSEStatus] = await self.comm_session.evse_controller.get_evse_status() + ac_charge_loop_res = ACChargeLoopRes( header=MessageHeader( session_id=self.comm_session.session_id, timestamp=time.time(), ), + evse_status=evse_status, # TODO Check for other failed or warning response codes response_code=ResponseCode.OK, scheduled_params=scheduled_params, @@ -1671,6 +1675,7 @@ async def build_dc_charge_loop_res(self) -> DCChargeLoopRes: bpt_scheduled_params, bpt_dynamic_params = None, None selected_energy_service = self.comm_session.selected_energy_service control_mode = self.comm_session.control_mode + response_code = ResponseCode.OK if selected_energy_service.service == ServiceV20.DC: if control_mode == ControlMode.SCHEDULED: scheduled_params = await self.comm_session.evse_controller.get_dc_charge_loop_params_v20( # noqa @@ -1693,13 +1698,16 @@ async def build_dc_charge_loop_res(self) -> DCChargeLoopRes: logger.error( f"Energy service {selected_energy_service.service} not yet supported" ) - return + response_code = ResponseCode.FAILED_SERVICE_SELECTION_INVALID + + evse_status: Optional[EVSEStatus] = await self.comm_session.evse_controller.get_evse_status() dc_charge_loop_res = DCChargeLoopRes( header=MessageHeader( session_id=self.comm_session.session_id, timestamp=time.time() ), - response_code=ResponseCode.OK, + evse_status=evse_status, + response_code=response_code, evse_present_current=await self.comm_session.evse_controller.get_evse_present_current( # noqa Protocol.ISO_15118_20_DC ), # noqa diff --git a/poetry.lock b/poetry.lock index f8e4e2ac..9623a019 100644 --- a/poetry.lock +++ b/poetry.lock @@ -97,42 +97,37 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "caio" -version = "0.9.11" +version = "0.9.12" description = "Asynchronous file IO for Linux MacOS or Windows." category = "dev" optional = false python-versions = ">=3.7, <4" files = [ - {file = "caio-0.9.11-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:3b9b2f1d94876ee3544b78d8a33e0c0091b79fe4bdc8cc00b64d4d9c59385195"}, - {file = "caio-0.9.11-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3adc061b2005e3e3e83066b5e9ac9966634ff65db5b60abe3bf1ec7f2c7903"}, - {file = "caio-0.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b90cc66230e78fd85d94e28fd6bd73032319286f0808f60118a2e63dbf4cde5"}, - {file = "caio-0.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a5c0d2d7d437c13ef72061acf0f4f878ae17642b73bd1d8a5594b9c7398f98e"}, - {file = "caio-0.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b9856bcb2f523e9ac2e00e75f3bd110a927b9ed0569a9d13c029d4b3aa8a79e4"}, - {file = "caio-0.9.11-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58e184d33e94962acbc9f4e8ff9b993ca6f78794de9018a2c3060cb2c190398e"}, - {file = "caio-0.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19f8da7314bd80a8620e8e1fc65e971bbb97eaa8ead692e81d91561cc69d0a0b"}, - {file = "caio-0.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19255def9e395768573f002e9d01c3eaabbf6f4dda68a1c56a0862403da1faa6"}, - {file = "caio-0.9.11-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cd1c6d433830a7e1c2d5ff854956ea3a0a46ac6e72196824a55c00111a32d21c"}, - {file = "caio-0.9.11-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:8157587b6ee340d7510c7e37b6f5f1223ec363154b38b43b718c7cc6b77d58c2"}, - {file = "caio-0.9.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c263011c8993d4330ae93ade6973026777b23fc41dd94cbd018b0b0f4eef8a"}, - {file = "caio-0.9.11-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e81dd50f9b53db5214469f3b953bb16cfb96c05f08f334c7aaf0d9096c37689"}, - {file = "caio-0.9.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99eb9ef0543e85e49a7883b35152e216af27158ba4f5e610e784c090d550ee4c"}, - {file = "caio-0.9.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7dcd1f63e50b6a175750bf1fd92734b0a9a53108ee14afa72971b64da1d701e5"}, - {file = "caio-0.9.11-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:e02e0c241b6dc19be0f85ece23ff6861907ef1cc6a8923a81345887a79f29300"}, - {file = "caio-0.9.11-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b60914721ebbc87d7098b55266d26ff52e7f59aa9b902eadfb185e5ca3bf913e"}, - {file = "caio-0.9.11-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22ad87a94b2062151ef78c613711c97ddcebba7399ece39e4fd88728853137d2"}, - {file = "caio-0.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e262c613d47c443c4daea03eb7c15001992136af71d4b6a344c20f0f8307f25b"}, - {file = "caio-0.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf65276487232bca415f62699298601537b558d8fd11b038633e555c45d0e82f"}, - {file = "caio-0.9.11-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:2086d65b157d39b0f070a3e6dfbca970c69d15547dfa9ef346506f4da968e960"}, - {file = "caio-0.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:43ed0962d712db7a7081cb300a6cf8e7924efd6508006db679e16557e60a394c"}, - {file = "caio-0.9.11-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:510f01f71361d31ce8938f691e96a7a1306c29e0475aa59a77300a61b6f0732a"}, - {file = "caio-0.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a62a1706f11661f0941bf220f56ee91279d44e4fbfdbc21f43ca2b5d7a52531"}, - {file = "caio-0.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e35124be09f9f26a420c279bbabb5ef0626ce0edff3e5b445e6102855453d63"}, - {file = "caio-0.9.11-py3-none-any.whl", hash = "sha256:287f7a598d8497588f0f0578eb90343b0c9ff3803b57c24e58f54793333132bf"}, - {file = "caio-0.9.11.tar.gz", hash = "sha256:33ca49789bf2d55bc3f5def36784c4624fbf16b78e9c299a2c7f6f22ac084aa6"}, + {file = "caio-0.9.12-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df395e7e1c2025b3f32dbeff20a8b6491959fac8fbd5a9c2d452bf1f5c0ca2bf"}, + {file = "caio-0.9.12-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:acd31b7828c6683bc46e467a32359f08c686d4c25a7e645c029a07c36685fea7"}, + {file = "caio-0.9.12-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0b7ffb561ca5c24e7f080aaa73ebb143ae659bd69645a748b332762c389349f"}, + {file = "caio-0.9.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6285d772ae3a55e758b1bd3bc34f095757e4af45dcc30a183becf9bbdead8ced"}, + {file = "caio-0.9.12-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18df0caecfaa90ab9ac84ff71975fcf2342554b6f65ef69049337204b8b5af42"}, + {file = "caio-0.9.12-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e569b83e9b41d12e094190d0e1a546610829a65609f429a1845e3250d4c5804"}, + {file = "caio-0.9.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7833f81f58e76a3585ff59813e63fa278731c8d26fefe52ae12e783d38c8faea"}, + {file = "caio-0.9.12-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:aa1dab77aca0b2672b9a364f14a03e764c5d811c0ae1395f661a2b8f6723f958"}, + {file = "caio-0.9.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f366c595eda130a184f372d458d647b0ac879a46e872757648d4e29b6cea12ad"}, + {file = "caio-0.9.12-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2759fe1957d0effb3bc38b3508b20fa37610bff9005f3926f570e6c06e901567"}, + {file = "caio-0.9.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e65060717e27c702cd76a90be33250cae46be32a3009781ce382c8675fa7551"}, + {file = "caio-0.9.12-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:a8204c6a7ea3c96057abba3da1690190b3cae0c3f03d81e9b5e3c99978b5bb6e"}, + {file = "caio-0.9.12-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:820bb3ef23ce0d4096f822a8fea97ff2c239dd0351fa588801d2de627df9ab98"}, + {file = "caio-0.9.12-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9a27973a03c777934decd577be577a61a188bee72272b3dc37a7cbc5eedf91"}, + {file = "caio-0.9.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d6b424644ac37ce84f9dd99757d29e7cff01018d3b31f8ec0a38f2d5216165c"}, + {file = "caio-0.9.12-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:15192a28e054cd591489af82f4b1093f15338eb201a2399a337461ff0bbd9fc9"}, + {file = "caio-0.9.12-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:55317d2c90479a58108cfbd6816b85e584e61c48b42269c55f69cf4443857068"}, + {file = "caio-0.9.12-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18318d04cad7ef985fc2fb86d43f866ba34c1ee3445385f2370d6f843c05c69"}, + {file = "caio-0.9.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73c20d8fc7dfb140b7d57e69e6f17e1637d2ac4a9ebe0f5f8c94b56f87c5c087"}, + {file = "caio-0.9.12-py3-none-any.whl", hash = "sha256:b81b3271478e91f18e7ac1f3e4f914ba0924364857ba8e27f03b9d0aea915ca8"}, + {file = "caio-0.9.12.tar.gz", hash = "sha256:d2be553738dd793f8a01a60316f2c5284fbf152219241c0c67ca05f650a37a37"}, ] [package.extras] -develop = ["aiomisc", "pytest", "pytest-cov"] +develop = ["aiomisc-pytest", "pytest", "pytest-cov"] [[package]] name = "cffi" @@ -723,14 +718,14 @@ files = [ [[package]] name = "pytest" -version = "7.2.1" +version = "7.2.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, - {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, + {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, + {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, ] [package.dependencies] From 8650de55e739e2886b30c95e50347a3e3751e23b Mon Sep 17 00:00:00 2001 From: tropxy Date: Wed, 8 Mar 2023 16:39:03 +0000 Subject: [PATCH 2/4] reformatted the code --- iso15118/secc/controller/simulator.py | 9 ++++++--- iso15118/secc/states/iso15118_20_states.py | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index 7a95c844..1411faa8 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -124,9 +124,6 @@ TaxRule, TaxRuleList, ) -from iso15118.shared.messages.iso15118_20.common_types import ( - EVSENotification as EVSENotificationV20, -) from iso15118.shared.messages.iso15118_20.common_types import EVSEStatus from iso15118.shared.messages.iso15118_20.common_types import MeterInfo as MeterInfoV20 from iso15118.shared.messages.iso15118_20.common_types import RationalNumber @@ -580,6 +577,12 @@ async def get_evse_status(self) -> Optional[EVSEStatus]: # to decide on providing the -2ß EVSEStatus or the # -2 AC or DC one and the `selected_charging_type_is_ac` in -2 # to decide on returning the ACEVSEStatus or the DCEVSEStatus + # + # Just as an example, here is how the return could look like + # return EVSEStatus( + # notification_max_delay=0, + # evse_notification=EVSENotificationV20.TERMINATE + # ) return None async def set_present_protocol_state(self, state_name: str): diff --git a/iso15118/secc/states/iso15118_20_states.py b/iso15118/secc/states/iso15118_20_states.py index 31346432..07cf593f 100644 --- a/iso15118/secc/states/iso15118_20_states.py +++ b/iso15118/secc/states/iso15118_20_states.py @@ -1358,7 +1358,9 @@ async def process_message( if ac_charge_loop_req.meter_info_requested: meter_info = await self.comm_session.evse_controller.get_meter_info_v20() - evse_status: Optional[EVSEStatus] = await self.comm_session.evse_controller.get_evse_status() + evse_status: Optional[ + EVSEStatus + ] = await self.comm_session.evse_controller.get_evse_status() ac_charge_loop_res = ACChargeLoopRes( header=MessageHeader( @@ -1700,7 +1702,9 @@ async def build_dc_charge_loop_res(self) -> DCChargeLoopRes: ) response_code = ResponseCode.FAILED_SERVICE_SELECTION_INVALID - evse_status: Optional[EVSEStatus] = await self.comm_session.evse_controller.get_evse_status() + evse_status: Optional[ + EVSEStatus + ] = await self.comm_session.evse_controller.get_evse_status() dc_charge_loop_res = DCChargeLoopRes( header=MessageHeader( From 0b9a15481f8ab1e1443ce83695bc36d9abdd850d Mon Sep 17 00:00:00 2001 From: tropxy Date: Wed, 8 Mar 2023 18:37:00 +0000 Subject: [PATCH 3/4] refactored stop_charging method in EVCC and allowed stopping the session when evse notification is set to terminate --- iso15118/evcc/states/evcc_state.py | 52 ++++++++++ iso15118/evcc/states/iso15118_20_states.py | 111 ++++++++------------- iso15118/secc/controller/simulator.py | 3 + 3 files changed, 94 insertions(+), 72 deletions(-) diff --git a/iso15118/evcc/states/evcc_state.py b/iso15118/evcc/states/evcc_state.py index e8ddc9f0..931ac9ae 100644 --- a/iso15118/evcc/states/evcc_state.py +++ b/iso15118/evcc/states/evcc_state.py @@ -2,6 +2,8 @@ This module contains the abstract class for an EVCC-specific state, which extends the state shared between the EVCC and SECC. """ +import logging +import time from abc import ABC from typing import Optional, Type, TypeVar, Union @@ -16,24 +18,34 @@ SessionSetupRes as SessionSetupResDINSPEC, ) from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC +from iso15118.shared.messages.enums import ISOV20PayloadTypes, Namespace from iso15118.shared.messages.iso15118_2.body import BodyBase as BodyBaseV2 from iso15118.shared.messages.iso15118_2.body import Response as ResponseV2 from iso15118.shared.messages.iso15118_2.body import ( SessionSetupRes as SessionSetupResV2, ) from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 +from iso15118.shared.messages.iso15118_20.common_messages import ( + ChargeProgress, + ChargingSession, + PowerDeliveryReq, +) from iso15118.shared.messages.iso15118_20.common_messages import ( SessionSetupRes as SessionSetupResV20, ) +from iso15118.shared.messages.iso15118_20.common_types import MessageHeader, Processing from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) from iso15118.shared.messages.iso15118_20.common_types import ( V2GResponse as V2GResponseV20, ) +from iso15118.shared.messages.iso15118_20.timeouts import Timeouts from iso15118.shared.notifications import StopNotification from iso15118.shared.states import State, Terminate +logger = logging.getLogger(__name__) + class StateEVCC(State, ABC): """ @@ -230,3 +242,43 @@ def stop_state_machine(self, reason: str): ) self.next_state = Terminate + + def stop_charging( + self, next_state: Type["State"], renegotiate_requested: bool = False + ): + power_delivery_req = PowerDeliveryReq( + header=MessageHeader( + session_id=self.comm_session.session_id, + timestamp=time.time(), + ), + ev_processing=Processing.FINISHED, + charge_progress=ChargeProgress.STOP, + ) + + if next_state.__name__ != "PowerDelivery": + raise ValueError( + f"Attempt to stop charging by going to " + f"state {next_state.__name__} when " + f" 'PowerDelivery' was expected" + ) + + self.create_next_message( + next_state, + power_delivery_req, + Timeouts.POWER_DELIVERY_REQ, + Namespace.ISO_V20_COMMON_MSG, + ISOV20PayloadTypes.MAINSTREAM, + ) + + if renegotiate_requested: + self.comm_session.renegotiation_requested = True + self.comm_session.charging_session_stop_v20 = ( + ChargingSession.SERVICE_RENEGOTIATION + ) + logger.debug( + f"ChargeProgress is set to {ChargeProgress.SCHEDULE_RENEGOTIATION}" + ) + else: + self.comm_session.charging_session_stop_v20 = ChargingSession.TERMINATE + # TODO Implement also a mechanism for pausing + logger.debug(f"ChargeProgress is set to {ChargeProgress.STOP}") diff --git a/iso15118/evcc/states/iso15118_20_states.py b/iso15118/evcc/states/iso15118_20_states.py index 64bf89d1..c1a53c87 100644 --- a/iso15118/evcc/states/iso15118_20_states.py +++ b/iso15118/evcc/states/iso15118_20_states.py @@ -39,7 +39,6 @@ AuthorizationSetupRes, CertificateInstallationReq, ChannelSelection, - ChargeProgress, ChargingSession, EIMAuthReqParams, MatchedService, @@ -1246,12 +1245,23 @@ async def process_message( # check if SECC requested a renegotiation. # evse_status field in ACChargeLoopRes is optional if ac_charge_loop_res.evse_status: - if ( - ac_charge_loop_res.evse_status.evse_notification - == EVSENotification.SERVICE_RENEGOTIATION - ): - self.comm_session.renegotiation_requested = True - self.stop_charging(True) + renegotiation = False + evse_notification = ac_charge_loop_res.evse_status.evse_notification + if evse_notification not in [ + EVSENotification.SERVICE_RENEGOTIATION, + EVSENotification.TERMINATE, + ]: + raise NotImplementedError( + f"Processing for EVSE Notification " + f"{evse_notification} is not " + f"supported at the moment" + ) + if evse_notification == EVSENotification.SERVICE_RENEGOTIATION: + renegotiation = True + self.stop_charging( + next_state=PowerDelivery, renegotiate_requested=renegotiation + ) + elif await self.comm_session.ev_controller.continue_charging(): scheduled_params, dynamic_params = None, None bpt_scheduled_params, bpt_dynamic_params = None, None @@ -1308,38 +1318,7 @@ async def process_message( ISOV20PayloadTypes.AC_MAINSTREAM, ) else: - self.stop_charging(False) - return - - def stop_charging(self, renegotiate_requested: bool): - power_delivery_req = PowerDeliveryReq( - header=MessageHeader( - session_id=self.comm_session.session_id, - timestamp=time.time(), - ), - ev_processing=Processing.FINISHED, - charge_progress=ChargeProgress.STOP, - ) - - self.create_next_message( - PowerDelivery, - power_delivery_req, - Timeouts.POWER_DELIVERY_REQ, - Namespace.ISO_V20_COMMON_MSG, - ISOV20PayloadTypes.MAINSTREAM, - ) - - if renegotiate_requested: - self.comm_session.charging_session_stop_v20 = ( - ChargingSession.SERVICE_RENEGOTIATION - ) - logger.debug( - f"ChargeProgress is set to {ChargeProgress.SCHEDULE_RENEGOTIATION}" - ) - else: - self.comm_session.charging_session_stop_v20 = ChargingSession.TERMINATE - # TODO Implement also a mechanism for pausing - logger.debug(f"ChargeProgress is set to {ChargeProgress.STOP}") + self.stop_charging(next_state=PowerDelivery) # ============================================================================ @@ -1638,9 +1617,27 @@ async def process_message( charge_loop_res: DCChargeLoopRes = msg # noqa # if charge_loop_res.evse_power_limit_achieved: - # await self.stop_charging(False) + # self.stop_charging(False) + + if charge_loop_res.evse_status: + renegotiation = False + evse_notification = charge_loop_res.evse_status.evse_notification + if evse_notification not in [ + EVSENotification.SERVICE_RENEGOTIATION, + EVSENotification.TERMINATE, + ]: + raise NotImplementedError( + f"Processing for EVSE Notification " + f"{evse_notification} is not " + f"supported at the moment" + ) + if evse_notification == EVSENotification.SERVICE_RENEGOTIATION: + renegotiation = True + self.stop_charging( + next_state=PowerDelivery, renegotiate_requested=renegotiation + ) - if await self.comm_session.ev_controller.continue_charging(): + elif await self.comm_session.ev_controller.continue_charging(): current_demand_req = await self.build_current_demand_data() self.create_next_message( @@ -1651,7 +1648,7 @@ async def process_message( ISOV20PayloadTypes.DC_MAINSTREAM, ) else: - await self.stop_charging(False) + self.stop_charging(next_state=PowerDelivery) async def build_current_demand_data(self): scheduled_params, dynamic_params = None, None @@ -1689,36 +1686,6 @@ async def build_current_demand_data(self): ) return dc_charge_loop_req - async def stop_charging(self, renegotiate_requested: bool): - power_delivery_req = PowerDeliveryReq( - header=MessageHeader( - session_id=self.comm_session.session_id, - timestamp=time.time(), - ), - ev_processing=Processing.FINISHED, - charge_progress=ChargeProgress.STOP, - ) - - self.create_next_message( - PowerDelivery, - power_delivery_req, - Timeouts.POWER_DELIVERY_REQ, - Namespace.ISO_V20_COMMON_MSG, - ISOV20PayloadTypes.MAINSTREAM, - ) - - if renegotiate_requested: - self.comm_session.charging_session_stop_v20 = ( - ChargingSession.SERVICE_RENEGOTIATION - ) - logger.debug( - f"ChargeProgress is set to {ChargeProgress.SCHEDULE_RENEGOTIATION}" - ) - else: - self.comm_session.charging_session_stop_v20 = ChargingSession.TERMINATE - # TODO Implement also a mechanism for pausing - logger.debug(f"ChargeProgress is set to {ChargeProgress.STOP}") - class DCWeldingDetection(StateEVCC): """ diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index 1411faa8..edab1899 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -579,6 +579,9 @@ async def get_evse_status(self) -> Optional[EVSEStatus]: # to decide on returning the ACEVSEStatus or the DCEVSEStatus # # Just as an example, here is how the return could look like + # from iso15118.shared.messages.iso15118_20.common_types import ( + # EVSENotification as EVSENotificationV20, + # ) # return EVSEStatus( # notification_max_delay=0, # evse_notification=EVSENotificationV20.TERMINATE From 70b61c5f1d537ed242bf921b3f1bf4b32a409d22 Mon Sep 17 00:00:00 2001 From: tropxy Date: Thu, 9 Mar 2023 09:01:57 +0000 Subject: [PATCH 4/4] renamed stop_charging to stop_v20_charging, so that it is not confused with the stop_charging used in -2 and refactored the code --- iso15118/evcc/controller/simulator.py | 3 ++- iso15118/evcc/states/evcc_state.py | 2 +- iso15118/evcc/states/iso15118_20_states.py | 10 +++++----- iso15118/secc/controller/interface.py | 6 ++++++ iso15118/secc/states/iso15118_20_states.py | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/iso15118/evcc/controller/simulator.py b/iso15118/evcc/controller/simulator.py index 3ed6da18..fb76a93b 100644 --- a/iso15118/evcc/controller/simulator.py +++ b/iso15118/evcc/controller/simulator.py @@ -614,11 +614,12 @@ async def get_ac_charge_loop_params_v20( else: # Dynamic Mode dynamic_params = DynamicACChargeLoopReqParams( + departure_time=2000, ev_target_energy_request=RationalNumber(exponent=3, value=40), ev_max_energy_request=RationalNumber(exponent=3, value=60), ev_min_energy_request=RationalNumber(exponent=3, value=-20), ev_max_charge_power=RationalNumber(exponent=3, value=300), - ev_min_charge_power=RationalNumber(exponent=0, value=-100), + ev_min_charge_power=RationalNumber(exponent=0, value=100), ev_present_active_power=RationalNumber(exponent=3, value=200), ev_present_reactive_power=RationalNumber(exponent=3, value=20), # Add more optional fields if wanted diff --git a/iso15118/evcc/states/evcc_state.py b/iso15118/evcc/states/evcc_state.py index 931ac9ae..ee9d1cd5 100644 --- a/iso15118/evcc/states/evcc_state.py +++ b/iso15118/evcc/states/evcc_state.py @@ -243,7 +243,7 @@ def stop_state_machine(self, reason: str): self.next_state = Terminate - def stop_charging( + def stop_v20_charging( self, next_state: Type["State"], renegotiate_requested: bool = False ): power_delivery_req = PowerDeliveryReq( diff --git a/iso15118/evcc/states/iso15118_20_states.py b/iso15118/evcc/states/iso15118_20_states.py index c1a53c87..40a41d70 100644 --- a/iso15118/evcc/states/iso15118_20_states.py +++ b/iso15118/evcc/states/iso15118_20_states.py @@ -1258,7 +1258,7 @@ async def process_message( ) if evse_notification == EVSENotification.SERVICE_RENEGOTIATION: renegotiation = True - self.stop_charging( + self.stop_v20_charging( next_state=PowerDelivery, renegotiate_requested=renegotiation ) @@ -1318,7 +1318,7 @@ async def process_message( ISOV20PayloadTypes.AC_MAINSTREAM, ) else: - self.stop_charging(next_state=PowerDelivery) + self.stop_v20_charging(next_state=PowerDelivery) # ============================================================================ @@ -1617,7 +1617,7 @@ async def process_message( charge_loop_res: DCChargeLoopRes = msg # noqa # if charge_loop_res.evse_power_limit_achieved: - # self.stop_charging(False) + # self.stop_v20_charging(False) if charge_loop_res.evse_status: renegotiation = False @@ -1633,7 +1633,7 @@ async def process_message( ) if evse_notification == EVSENotification.SERVICE_RENEGOTIATION: renegotiation = True - self.stop_charging( + self.stop_v20_charging( next_state=PowerDelivery, renegotiate_requested=renegotiation ) @@ -1648,7 +1648,7 @@ async def process_message( ISOV20PayloadTypes.DC_MAINSTREAM, ) else: - self.stop_charging(next_state=PowerDelivery) + self.stop_v20_charging(next_state=PowerDelivery) async def build_current_demand_data(self): scheduled_params, dynamic_params = None, None diff --git a/iso15118/secc/controller/interface.py b/iso15118/secc/controller/interface.py index 14a2ab03..4c75e517 100644 --- a/iso15118/secc/controller/interface.py +++ b/iso15118/secc/controller/interface.py @@ -81,6 +81,11 @@ class EVDataContext: soc: Optional[int] = None # 0-100 # from ISO 15118-20 AC + departure_time: Optional[int] = None + ev_target_energy_request: float = 0.0 + ev_max_energy_request: float = 0.0 + ev_min_energy_request: float = 0.0 + ev_max_charge_power: float = 0.0 ev_max_charge_power_l2: Optional[float] = None ev_max_charge_power_l3: Optional[float] = None @@ -94,6 +99,7 @@ class EVDataContext: ev_present_reactive_power_l2: Optional[float] = None ev_present_reactive_power_l3: Optional[float] = None + # BPT values ev_max_discharge_power: float = 0.0 ev_max_discharge_power_l2: Optional[float] = None ev_max_discharge_power_l3: Optional[float] = None diff --git a/iso15118/secc/states/iso15118_20_states.py b/iso15118/secc/states/iso15118_20_states.py index 07cf593f..e825b253 100644 --- a/iso15118/secc/states/iso15118_20_states.py +++ b/iso15118/secc/states/iso15118_20_states.py @@ -1335,7 +1335,7 @@ async def process_message( # representation ev_power_limits = {} for k, v in ev_bpt_charge_parameters.items(): - if v: + if type(v) is dict: ev_power_limits.update({k: v["value"] * 10 ** v["exponent"]}) # update the dict with the decimal values ev_bpt_charge_parameters.update(ev_power_limits)