From 63f1fd9386aa4fba35f9baf5d2226d078594a5c2 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 7 Nov 2022 15:57:46 +0000 Subject: [PATCH 01/10] interface added for pause and terminate --- iso15118/secc/controller/interface.py | 12 ++++++ iso15118/secc/controller/simulator.py | 7 ++++ iso15118/secc/states/iso15118_2_states.py | 10 +++-- iso15118/shared/comm_session.py | 11 ++++-- iso15118/shared/messages/enums.py | 5 +++ tests/secc/states/test_iso15118_2_states.py | 44 ++++++++++++--------- tests/secc/states/test_messages.py | 9 +++++ 7 files changed, 73 insertions(+), 25 deletions(-) diff --git a/iso15118/secc/controller/interface.py b/iso15118/secc/controller/interface.py index 7b5b0eb6..e783283d 100644 --- a/iso15118/secc/controller/interface.py +++ b/iso15118/secc/controller/interface.py @@ -33,6 +33,7 @@ CpState, EnergyTransferModeEnum, Protocol, + SessionStopAction, ) from iso15118.shared.messages.iso15118_2.datatypes import ( ACEVSEChargeParameter, @@ -699,3 +700,14 @@ async def get_15118_ev_certificate( - ISO 15118-20 and ISO 15118-2 """ raise NotImplementedError + + @abstractmethod + async def session_stop(self, action: SessionStopAction) -> None: + """ + Called when EV requires termination or pausing of the charging session. + Args: + action : SessionStopAction + Relevant for: + - ISO 15118-20 and ISO 15118-2 + """ + raise NotImplementedError diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index dab128c0..6763b703 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -58,6 +58,7 @@ Namespace, PriceAlgorithm, Protocol, + SessionStopAction, UnitSymbol, ) from iso15118.shared.messages.iso15118_2.body import Body, CertificateInstallationRes @@ -910,3 +911,9 @@ async def get_15118_ev_certificate( ).decode("utf-8") return base64_encode_cert_install_res + + async def session_stop(self, action: SessionStopAction) -> None: + """ + Overrides EVSEControllerInterface.session_stop(). + """ + pass diff --git a/iso15118/secc/states/iso15118_2_states.py b/iso15118/secc/states/iso15118_2_states.py index c1edfb9b..abf7fe3e 100644 --- a/iso15118/secc/states/iso15118_2_states.py +++ b/iso15118/secc/states/iso15118_2_states.py @@ -84,6 +84,7 @@ CertificateChain, ChargeProgress, ChargeService, + ChargingSession, DHPublicKey, EncryptedPrivateKey, EnergyTransferModeList, @@ -123,7 +124,7 @@ verify_certs, verify_signature, ) -from iso15118.shared.states import Base64, State, Terminate +from iso15118.shared.states import Base64, Pause, State, Terminate logger = logging.getLogger(__name__) @@ -1869,9 +1870,12 @@ async def process_message( f"EV Requested to {session_status} the communication session", self.comm_session.writer.get_extra_info("peername"), ) - + if msg.body.session_stop_req.charging_session == ChargingSession.PAUSE: + next_state = Pause + else: + next_state = Terminate self.create_next_message( - Terminate, + next_state, SessionStopRes(response_code=ResponseCode.OK), Timeouts.V2G_SECC_SEQUENCE_TIMEOUT, Namespace.ISO_V2_MSG_DEF, diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index bdd81f5b..06c49c76 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -35,6 +35,7 @@ ISOV20PayloadTypes, Namespace, Protocol, + SessionStopAction, ) from iso15118.shared.messages.iso15118_2.datatypes import EnergyTransferModeEnum from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 @@ -380,9 +381,9 @@ async def stop(self, reason: str): """ if self.current_state.next_state == Pause: self.save_session_info() - terminate_or_pause = "Pause" + terminate_or_pause = SessionStopAction.PAUSE else: - terminate_or_pause = "Terminate" + terminate_or_pause = SessionStopAction.TERMINATE logger.info( f"The data link will {terminate_or_pause} in 2 seconds and " @@ -391,8 +392,10 @@ async def stop(self, reason: str): logger.info(f"Reason: {reason}") await asyncio.sleep(2) - # TODO Signal data link layer to either terminate or pause the data - # link connection + # Signal data link layer to either terminate or pause the data + # link connection + if hasattr(self.comm_session, "evse_controller"): + await self.comm_session.evse_controller.session_stop(terminate_or_pause) logger.info(f"{terminate_or_pause}d the data link") await asyncio.sleep(3) try: diff --git a/iso15118/shared/messages/enums.py b/iso15118/shared/messages/enums.py index c9b5ec90..7bc95028 100644 --- a/iso15118/shared/messages/enums.py +++ b/iso15118/shared/messages/enums.py @@ -436,3 +436,8 @@ class CpState(str, Enum): E = "E" F = "F" UNKNOWN = "UNKNOWN" + + +class SessionStopAction(str, Enum): + TERMINATE = "terminate" + PAUSE = "pause" diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index e762a341..1e1acd44 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -12,6 +12,7 @@ PaymentDetails, PowerDelivery, ServiceDiscovery, + SessionStop, Terminate, WeldingDetection, ) @@ -24,9 +25,14 @@ CpState, EVSEProcessing, ) -from iso15118.shared.messages.iso15118_2.body import ResponseCode -from iso15118.shared.messages.iso15118_2.datatypes import ACEVSEStatus, CertificateChain +from iso15118.shared.messages.iso15118_2.body import ResponseCode, SessionStopReq +from iso15118.shared.messages.iso15118_2.datatypes import ( + ACEVSEStatus, + CertificateChain, + ChargingSession, +) from iso15118.shared.security import get_random_bytes +from iso15118.shared.states import Pause from tests.secc.states.test_messages import ( get_charge_parameter_discovery_req_message_departure_time_one_hour, get_charge_parameter_discovery_req_message_no_departure_time, @@ -42,6 +48,7 @@ get_power_delivery_req_charging_profile_in_limits, get_power_delivery_req_charging_profile_not_in_limits_span_over_sa, get_power_delivery_req_charging_profile_out_of_boundary, + get_dummy_v2g_session_stop_req, get_v2g_message_power_delivery_req, get_v2g_message_power_delivery_req_charging_profile_in_boundary_valid, ) @@ -55,6 +62,8 @@ def _comm_session(self, comm_secc_session_mock): self.comm_session = comm_secc_session_mock self.comm_session.config = Config() self.comm_session.is_tls = False + self.comm_session.writer = Mock() + self.comm_session.writer.get_extra_info = Mock() async def test_current_demand_to_power_delivery_when_power_delivery_received( self, @@ -104,8 +113,6 @@ async def test_payment_details_next_state_on_payment_details_req_auth( is_authorized_return_value: AuthorizationStatus, expected_next_state: StateSECC, ): - self.comm_session.writer = Mock() - self.comm_session.writer.get_extra_info = Mock() self.comm_session.selected_auth_option = AuthEnum.PNC_V2 mock_is_authorized = AsyncMock(return_value=is_authorized_return_value) @@ -183,8 +190,6 @@ async def test_authorization_next_state_on_authorization_request( expected_response_code: ResponseCode, expected_evse_processing: EVSEProcessing, ): - self.comm_session.writer = Mock() - self.comm_session.writer.get_extra_info = Mock() self.comm_session.selected_auth_option = auth_type mock_is_authorized = AsyncMock(return_value=is_authorized_return_value) @@ -211,8 +216,6 @@ async def test_authorization_next_state_on_authorization_request( ) async def test_authorization_req_gen_challenge_invalid(self): - self.comm_session.writer = Mock() - self.comm_session.writer.get_extra_info = Mock() self.comm_session.selected_auth_option = AuthEnum.PNC_V2 self.comm_session.contract_cert_chain = Mock() self.comm_session.gen_challenge = get_random_bytes(16) @@ -230,8 +233,6 @@ async def test_authorization_req_gen_challenge_invalid(self): ) async def test_authorization_req_gen_challenge_valid(self): - self.comm_session.writer = Mock() - self.comm_session.writer.get_extra_info = Mock() self.comm_session.selected_auth_option = AuthEnum.PNC_V2 self.comm_session.gen_challenge = get_random_bytes(16) id = "aReq" @@ -455,8 +456,6 @@ async def test_power_delivery_state_c( ): power_delivery = PowerDelivery(self.comm_session) - self.comm_session.writer = Mock() - self.comm_session.writer.get_extra_info = Mock() mock_get_cp_state = AsyncMock(return_value=get_state_return_value) self.comm_session.evse_controller.get_cp_state = mock_get_cp_state await power_delivery.process_message( @@ -467,8 +466,6 @@ async def test_power_delivery_state_c( async def test_service_discovery_req_unexpected_state(self): self.comm_session.selected_auth_option = AuthEnum.PNC_V2 self.comm_session.config.free_charging_service = False - self.comm_session.writer = Mock() - self.comm_session.writer.get_extra_info = Mock() service_discovery = ServiceDiscovery(self.comm_session) await service_discovery.process_message( message=get_dummy_v2g_message_service_discovery_req() @@ -484,8 +481,6 @@ async def test_service_discovery_req_unexpected_state(self): async def test_charging_status_evse_status(self): charging_status = ChargingStatus(self.comm_session) - self.comm_session.writer = Mock() - self.comm_session.writer.get_extra_info = Mock() self.comm_session.selected_schedule = 1 await charging_status.process_message(message=get_dummy_charging_status_req()) @@ -498,8 +493,6 @@ async def test_charging_status_evse_status(self): async def test_charging_status_evse_status_altered(self): charging_status = ChargingStatus(self.comm_session) - self.comm_session.writer = Mock() - self.comm_session.writer.get_extra_info = Mock() self.comm_session.selected_schedule = 1 async def get_ac_evse_status_patch(): @@ -513,3 +506,18 @@ async def get_ac_evse_status_patch(): await charging_status.process_message(message=get_dummy_charging_status_req()) charging_status_res = charging_status.message.body.charging_status_res assert charging_status_res.ac_evse_status == await get_ac_evse_status_patch() + + @pytest.mark.parametrize( + "charging_session, expected_next_state", + [ + (ChargingSession.PAUSE, Pause), + (ChargingSession.TERMINATE, Terminate), + ], + ) + async def test_session_stop_req(self, charging_session, expected_next_state): + # V2G2-718 + session_stop = SessionStop(self.comm_session) + await session_stop.process_message( + message=get_dummy_v2g_session_stop_req(charging_session) + ) + assert session_stop.next_state == expected_next_state diff --git a/tests/secc/states/test_messages.py b/tests/secc/states/test_messages.py index 34a10ebf..c1353f4b 100644 --- a/tests/secc/states/test_messages.py +++ b/tests/secc/states/test_messages.py @@ -508,3 +508,12 @@ def get_dummy_charging_status_req() -> V2GMessage: header=MessageHeader(session_id=MOCK_SESSION_ID), body=Body(charging_status_req=charging_status_req), ) + + +def get_dummy_v2g_session_stop_req(charging_session: ChargingSession) -> V2GMessage: + session_stop_req = SessionStopReq(charging_session=charging_session) + + return V2GMessage( + header=MessageHeader(session_id=MOCK_SESSION_ID), + body=Body(session_stop_req=session_stop_req), + ) From dc3282a5f36e6b1dff65af92d0faf07492d7cc5d Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 7 Nov 2022 16:05:00 +0000 Subject: [PATCH 02/10] reformat --- tests/secc/states/test_iso15118_2_states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index 1e1acd44..99e759c3 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -25,7 +25,7 @@ CpState, EVSEProcessing, ) -from iso15118.shared.messages.iso15118_2.body import ResponseCode, SessionStopReq +from iso15118.shared.messages.iso15118_2.body import ResponseCode from iso15118.shared.messages.iso15118_2.datatypes import ( ACEVSEStatus, CertificateChain, From 3276f2600cd6811dd186b060c9b1791958878040 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:02:13 +0000 Subject: [PATCH 03/10] session_setup method name has modified as update_data_link --- iso15118/secc/controller/interface.py | 2 +- iso15118/secc/controller/simulator.py | 4 ++-- iso15118/shared/comm_session.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/iso15118/secc/controller/interface.py b/iso15118/secc/controller/interface.py index e783283d..cba2cb39 100644 --- a/iso15118/secc/controller/interface.py +++ b/iso15118/secc/controller/interface.py @@ -702,7 +702,7 @@ async def get_15118_ev_certificate( raise NotImplementedError @abstractmethod - async def session_stop(self, action: SessionStopAction) -> None: + async def update_data_link(self, action: SessionStopAction) -> None: """ Called when EV requires termination or pausing of the charging session. Args: diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index 6763b703..371a8964 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -912,8 +912,8 @@ async def get_15118_ev_certificate( return base64_encode_cert_install_res - async def session_stop(self, action: SessionStopAction) -> None: + async def update_data_link(self, action: SessionStopAction) -> None: """ - Overrides EVSEControllerInterface.session_stop(). + Overrides EVSEControllerInterface.update_data_link(). """ pass diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index 06c49c76..77dbd6c0 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -395,7 +395,7 @@ async def stop(self, reason: str): # Signal data link layer to either terminate or pause the data # link connection if hasattr(self.comm_session, "evse_controller"): - await self.comm_session.evse_controller.session_stop(terminate_or_pause) + await self.comm_session.evse_controller.update_data_link(terminate_or_pause) logger.info(f"{terminate_or_pause}d the data link") await asyncio.sleep(3) try: From d07b17b2687555643e4b3a5e5328ddbce41e7e1e Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 18 Nov 2022 09:54:31 +0000 Subject: [PATCH 04/10] isort --- tests/secc/states/test_iso15118_2_states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index 99e759c3..2330eeae 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -44,11 +44,11 @@ get_dummy_v2g_message_power_delivery_req_charge_stop, get_dummy_v2g_message_service_discovery_req, get_dummy_v2g_message_welding_detection_req, + get_dummy_v2g_session_stop_req, get_power_delivery_req_charging_profile_in_boundary_invalid, get_power_delivery_req_charging_profile_in_limits, get_power_delivery_req_charging_profile_not_in_limits_span_over_sa, get_power_delivery_req_charging_profile_out_of_boundary, - get_dummy_v2g_session_stop_req, get_v2g_message_power_delivery_req, get_v2g_message_power_delivery_req_charging_profile_in_boundary_valid, ) From cb7eda827b56bee479d88a7b9e75cbd7a20bfd8f Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 18 Nov 2022 16:39:37 +0000 Subject: [PATCH 05/10] udp chahges rolbacked --- iso15118/secc/transport/udp_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iso15118/secc/transport/udp_server.py b/iso15118/secc/transport/udp_server.py index bff11352..e5fa8d3f 100644 --- a/iso15118/secc/transport/udp_server.py +++ b/iso15118/secc/transport/udp_server.py @@ -62,9 +62,9 @@ async def __init__. # Bind the socket to the predefined port for receiving # UDP packets (SDP requests) - full_ipv6_address = await get_link_local_full_addr(SDP_SERVER_PORT, iface) - sock.bind(full_ipv6_address) - + #full_ipv6_address = await get_link_local_full_addr(SDP_SERVER_PORT, iface) + #sock.bind(full_ipv6_address) + sock.bind(("", SDP_SERVER_PORT)) # After the regular socket is created and bound to a port, it can be # added to the multicast group by using setsockopt() to set the # IPV6_JOIN_GROUP option. The option value is the 16-byte packed From c380f6eb3e1587830f16db34ab3eed691ce87c48 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:32:40 +0000 Subject: [PATCH 06/10] new udp tests --- iso15118/secc/transport/udp_server.py | 3 ++- iso15118/shared/network.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/iso15118/secc/transport/udp_server.py b/iso15118/secc/transport/udp_server.py index e5fa8d3f..8860c272 100644 --- a/iso15118/secc/transport/udp_server.py +++ b/iso15118/secc/transport/udp_server.py @@ -62,7 +62,8 @@ async def __init__. # Bind the socket to the predefined port for receiving # UDP packets (SDP requests) - #full_ipv6_address = await get_link_local_full_addr(SDP_SERVER_PORT, iface) + full_ipv6_address = await get_link_local_full_addr(SDP_SERVER_PORT, iface) + logger.info(full_ipv6_address) #sock.bind(full_ipv6_address) sock.bind(("", SDP_SERVER_PORT)) # After the regular socket is created and bound to a port, it can be diff --git a/iso15118/shared/network.py b/iso15118/shared/network.py index 6630fb86..e92e48c5 100644 --- a/iso15118/shared/network.py +++ b/iso15118/shared/network.py @@ -169,6 +169,8 @@ def get_tcp_port() -> int: A port number in the range of Dynamic Ports (49152-65535) as defined in IETF RFC 6335 are allowed for TCP. """ + logger.info("Shalinnn") + logger.info(psutil.net_if_addrs()) return randint(49152, 65535) From 307fb93a3c7713cf2465f8c27efaa06962824f95 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:42:05 +0000 Subject: [PATCH 07/10] new udp tests --- iso15118/secc/transport/udp_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iso15118/secc/transport/udp_server.py b/iso15118/secc/transport/udp_server.py index 8860c272..63e165af 100644 --- a/iso15118/secc/transport/udp_server.py +++ b/iso15118/secc/transport/udp_server.py @@ -65,7 +65,7 @@ async def __init__. full_ipv6_address = await get_link_local_full_addr(SDP_SERVER_PORT, iface) logger.info(full_ipv6_address) #sock.bind(full_ipv6_address) - sock.bind(("", SDP_SERVER_PORT)) + sock.bind(('fe80::201:87ff:fe0c:72a0', SDP_SERVER_PORT)) # After the regular socket is created and bound to a port, it can be # added to the multicast group by using setsockopt() to set the # IPV6_JOIN_GROUP option. The option value is the 16-byte packed From 1795ca82505278706ed9f4d750ff4673d7eb52b7 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:08:16 +0000 Subject: [PATCH 08/10] new udp tests --- iso15118/secc/transport/udp_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iso15118/secc/transport/udp_server.py b/iso15118/secc/transport/udp_server.py index 63e165af..8860c272 100644 --- a/iso15118/secc/transport/udp_server.py +++ b/iso15118/secc/transport/udp_server.py @@ -65,7 +65,7 @@ async def __init__. full_ipv6_address = await get_link_local_full_addr(SDP_SERVER_PORT, iface) logger.info(full_ipv6_address) #sock.bind(full_ipv6_address) - sock.bind(('fe80::201:87ff:fe0c:72a0', SDP_SERVER_PORT)) + sock.bind(("", SDP_SERVER_PORT)) # After the regular socket is created and bound to a port, it can be # added to the multicast group by using setsockopt() to set the # IPV6_JOIN_GROUP option. The option value is the 16-byte packed From 8228f7c35b6ceaa9a11b3237134c1385b3e4fe99 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 21 Nov 2022 09:17:29 +0000 Subject: [PATCH 09/10] rollback unrelated changes --- iso15118/secc/transport/udp_server.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/iso15118/secc/transport/udp_server.py b/iso15118/secc/transport/udp_server.py index 8860c272..bff11352 100644 --- a/iso15118/secc/transport/udp_server.py +++ b/iso15118/secc/transport/udp_server.py @@ -63,9 +63,8 @@ async def __init__. # Bind the socket to the predefined port for receiving # UDP packets (SDP requests) full_ipv6_address = await get_link_local_full_addr(SDP_SERVER_PORT, iface) - logger.info(full_ipv6_address) - #sock.bind(full_ipv6_address) - sock.bind(("", SDP_SERVER_PORT)) + sock.bind(full_ipv6_address) + # After the regular socket is created and bound to a port, it can be # added to the multicast group by using setsockopt() to set the # IPV6_JOIN_GROUP option. The option value is the 16-byte packed From 879d125e7982c2a9b65eada089b8a0d3e3740755 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 21 Nov 2022 09:33:46 +0000 Subject: [PATCH 10/10] rollback unrelated changes --- iso15118/shared/network.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/iso15118/shared/network.py b/iso15118/shared/network.py index e92e48c5..6630fb86 100644 --- a/iso15118/shared/network.py +++ b/iso15118/shared/network.py @@ -169,8 +169,6 @@ def get_tcp_port() -> int: A port number in the range of Dynamic Ports (49152-65535) as defined in IETF RFC 6335 are allowed for TCP. """ - logger.info("Shalinnn") - logger.info(psutil.net_if_addrs()) return randint(49152, 65535)