From d49649ddc7a863d41e399817389eeb795e6fda0e Mon Sep 17 00:00:00 2001 From: Santiago Salamandri Date: Mon, 17 Oct 2022 12:07:38 +0100 Subject: [PATCH 1/8] feat: add evse status enum and set_status abstract method --- iso15118/secc/controller/interface.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/iso15118/secc/controller/interface.py b/iso15118/secc/controller/interface.py index c7577102..850ee3ca 100644 --- a/iso15118/secc/controller/interface.py +++ b/iso15118/secc/controller/interface.py @@ -4,6 +4,7 @@ """ from abc import ABC, abstractmethod from dataclasses import dataclass +from enum import Enum from typing import Dict, List, Optional, Union from iso15118.shared.messages.datatypes import ( @@ -72,6 +73,14 @@ class EVDataContext: soc: Optional[int] = None # 0-100 +class EVSEServiceStatus(str, Enum): + READY = "ready" + STARTING = "starting" + STOPPING = "stopping" + ERROR = "error" + BUSY = "busy" + + @dataclass class EVChargeParamsLimits: ev_max_voltage: Optional[Union[PVEVMaxVoltageLimit, PVEVMaxVoltage]] = None @@ -91,6 +100,13 @@ def reset_ev_data_context(self): # | COMMON FUNCTIONS (FOR ALL ENERGY TRANSFER MODES) | # ============================================================================ + @abstractmethod + async def set_status(self, status: EVSEServiceStatus) -> None: + """ + Sets the new status for the EVSE Controller + """ + raise NotImplementedError + @abstractmethod async def get_evse_id(self, protocol: Protocol) -> str: """ From 26eb08ff0b797f6d0862cb2cae8bb182c7fc7c28 Mon Sep 17 00:00:00 2001 From: Santiago Salamandri Date: Mon, 17 Oct 2022 12:08:21 +0100 Subject: [PATCH 2/8] feat: Implemented set_status method --- iso15118/secc/controller/simulator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index 0b8a50f7..a599af00 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -15,6 +15,7 @@ EVChargeParamsLimits, EVDataContext, EVSEControllerInterface, + EVSEServiceStatus, ) from iso15118.shared.exceptions import EncryptionError, PrivateKeyReadError from iso15118.shared.exi_codec import EXI @@ -195,6 +196,8 @@ def reset_ev_data_context(self): # ============================================================================ # | COMMON FUNCTIONS (FOR ALL ENERGY TRANSFER MODES) | # ============================================================================ + async def set_status(self, status: EVSEServiceStatus) -> None: + logger.debug(f"New Status: {status}") async def get_evse_id(self, protocol: Protocol) -> str: if protocol == Protocol.DIN_SPEC_70121: From 59cf518558b716cc2737a40ee40e467dc7f76f4d Mon Sep 17 00:00:00 2001 From: Santiago Salamandri Date: Mon, 17 Oct 2022 12:09:30 +0100 Subject: [PATCH 3/8] feat: Add ready status events to servers --- iso15118/secc/transport/tcp_server.py | 7 +++++++ iso15118/secc/transport/udp_server.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/iso15118/secc/transport/tcp_server.py b/iso15118/secc/transport/tcp_server.py index 9b6e659c..cd259b19 100644 --- a/iso15118/secc/transport/tcp_server.py +++ b/iso15118/secc/transport/tcp_server.py @@ -27,6 +27,8 @@ def __init__(self, session_handler_queue: asyncio.Queue, iface: str) -> None: self.port_no_tls = get_tcp_port() self.port_tls = get_tcp_port() self.iface = iface + self.tls_ready_event: asyncio.Event = asyncio.Event() + self.tcp_ready_event: asyncio.Event = asyncio.Event() # Making sure the TCP and TLS port are definitely different while self.port_no_tls == self.port_tls: @@ -112,6 +114,11 @@ async def server_factory(self, tls: bool) -> None: f"port {port}" ) + if tls: + self.tls_ready_event.set() + else: + self.tcp_ready_event.set() + try: # Shield the task so we can handle the cancellation # closing the opening connections diff --git a/iso15118/secc/transport/udp_server.py b/iso15118/secc/transport/udp_server.py index c7b002db..a6764efc 100644 --- a/iso15118/secc/transport/udp_server.py +++ b/iso15118/secc/transport/udp_server.py @@ -36,6 +36,7 @@ class UDPServer(asyncio.DatagramProtocol): def __init__(self, session_handler_queue: asyncio.Queue, iface: str): self.started: bool = False self.iface = iface + self.ready_event: asyncio.Event = asyncio.Event() self._session_handler_queue: asyncio.Queue = session_handler_queue self._rcv_queue: asyncio.Queue = asyncio.Queue() self._transport: Optional[DatagramTransport] = None @@ -99,6 +100,7 @@ async def start(self): f"{SDP_MULTICAST_GROUP}%{self.iface} " f"and port {SDP_SERVER_PORT}" ) + self.ready_event.set() tasks = [self.rcv_task()] await wait_for_tasks(tasks) From d5e9fc625cc658efe21fc58ab7aadc371f4b6125 Mon Sep 17 00:00:00 2001 From: Santiago Salamandri Date: Mon, 17 Oct 2022 12:11:11 +0100 Subject: [PATCH 4/8] feat: Add a task to check the servers status --- iso15118/secc/comm_session_handler.py | 34 ++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/iso15118/secc/comm_session_handler.py b/iso15118/secc/comm_session_handler.py index b0e57fb6..7157f0ab 100644 --- a/iso15118/secc/comm_session_handler.py +++ b/iso15118/secc/comm_session_handler.py @@ -16,7 +16,10 @@ from asyncio.streams import StreamReader, StreamWriter from typing import Dict, List, Optional, Tuple, Union -from iso15118.secc.controller.interface import EVSEControllerInterface +from iso15118.secc.controller.interface import ( + EVSEControllerInterface, + EVSEServiceStatus, +) from iso15118.secc.failed_responses import ( init_failed_responses_din_spec_70121, init_failed_responses_iso_v2, @@ -168,6 +171,9 @@ def __init__( self.config = config self.evse_controller = evse_controller + # List of server status events + self.status_event_list: List[asyncio.Event] = [] + # Set the selected EXI codec implementation EXI().set_exi_codec(codec) @@ -189,21 +195,47 @@ async def __init__. """ self.udp_server = UDPServer(self._rcv_queue, self.config.iface) + self.status_event_list.append(self.udp_server.ready_event) + self.tcp_server = TCPServer(self._rcv_queue, self.config.iface) + self.status_event_list.append(self.tcp_server.tls_ready_event) self.list_of_tasks = [ self.get_from_rcv_queue(self._rcv_queue), self.udp_server.start(), self.tcp_server.start_tls(), + self.check_status_task(), ] if not self.config.enforce_tls: self.list_of_tasks.append(self.tcp_server.start_no_tls()) + self.status_event_list.append(self.tcp_server.tcp_ready_event) logger.info("Communication session handler started") await wait_for_tasks(self.list_of_tasks) + def check_events(self) -> bool: + result: bool = True + for event in self.status_event_list: + if event.is_set() is False: + result = False + break + return result + + async def check_ready_status(self) -> None: + # Wait until all flags are set + while self.check_events() is False: + await asyncio.sleep(0.1) + + async def check_status_task(self) -> None: + try: + await asyncio.wait_for(self.check_ready_status(), timeout=10) + await self.evse_controller.set_status(EVSEServiceStatus.READY) + except asyncio.TimeoutError: + logger.error("Timeout: Servers failed to startup") + await self.evse_controller.set_status(EVSEServiceStatus.ERROR) + async def get_from_rcv_queue(self, queue: asyncio.Queue): """ Waits for an incoming message from the transport layer From 16f9b0ed76f4fb74494bff9c10f4278d71c787d1 Mon Sep 17 00:00:00 2001 From: Santiago Salamandri Date: Mon, 17 Oct 2022 12:11:47 +0100 Subject: [PATCH 5/8] feat: Add starting status --- iso15118/secc/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iso15118/secc/main.py b/iso15118/secc/main.py index 32878a57..499a7bb4 100644 --- a/iso15118/secc/main.py +++ b/iso15118/secc/main.py @@ -2,6 +2,7 @@ import logging from iso15118.secc import SECCHandler +from iso15118.secc.controller.interface import EVSEServiceStatus from iso15118.secc.controller.simulator import SimEVSEController from iso15118.shared.exificient_exi_codec import ExificientEXICodec @@ -14,6 +15,7 @@ async def main(): the SECC (Supply Equipment Communication Controller) """ sim_evse_controller = await SimEVSEController.create() + await sim_evse_controller.set_status(EVSEServiceStatus.STARTING) await SECCHandler( exi_codec=ExificientEXICodec(), evse_controller=sim_evse_controller ).start() From b2f89700e7308c6d20247db8879d0ac98e5fb158 Mon Sep 17 00:00:00 2001 From: Santiago Salamandri Date: Mon, 17 Oct 2022 13:55:04 +0100 Subject: [PATCH 6/8] chore: rename EVSEServiceStatus to ServiceStatus --- iso15118/secc/controller/interface.py | 4 ++-- iso15118/secc/controller/simulator.py | 4 ++-- iso15118/secc/main.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/iso15118/secc/controller/interface.py b/iso15118/secc/controller/interface.py index 850ee3ca..1ffcbfcb 100644 --- a/iso15118/secc/controller/interface.py +++ b/iso15118/secc/controller/interface.py @@ -73,7 +73,7 @@ class EVDataContext: soc: Optional[int] = None # 0-100 -class EVSEServiceStatus(str, Enum): +class ServiceStatus(str, Enum): READY = "ready" STARTING = "starting" STOPPING = "stopping" @@ -101,7 +101,7 @@ def reset_ev_data_context(self): # ============================================================================ @abstractmethod - async def set_status(self, status: EVSEServiceStatus) -> None: + async def set_status(self, status: ServiceStatus) -> None: """ Sets the new status for the EVSE Controller """ diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index a599af00..a6356202 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -15,7 +15,7 @@ EVChargeParamsLimits, EVDataContext, EVSEControllerInterface, - EVSEServiceStatus, + ServiceStatus, ) from iso15118.shared.exceptions import EncryptionError, PrivateKeyReadError from iso15118.shared.exi_codec import EXI @@ -196,7 +196,7 @@ def reset_ev_data_context(self): # ============================================================================ # | COMMON FUNCTIONS (FOR ALL ENERGY TRANSFER MODES) | # ============================================================================ - async def set_status(self, status: EVSEServiceStatus) -> None: + async def set_status(self, status: ServiceStatus) -> None: logger.debug(f"New Status: {status}") async def get_evse_id(self, protocol: Protocol) -> str: diff --git a/iso15118/secc/main.py b/iso15118/secc/main.py index 499a7bb4..3d9fa00f 100644 --- a/iso15118/secc/main.py +++ b/iso15118/secc/main.py @@ -2,7 +2,7 @@ import logging from iso15118.secc import SECCHandler -from iso15118.secc.controller.interface import EVSEServiceStatus +from iso15118.secc.controller.interface import ServiceStatus from iso15118.secc.controller.simulator import SimEVSEController from iso15118.shared.exificient_exi_codec import ExificientEXICodec @@ -15,7 +15,7 @@ async def main(): the SECC (Supply Equipment Communication Controller) """ sim_evse_controller = await SimEVSEController.create() - await sim_evse_controller.set_status(EVSEServiceStatus.STARTING) + await sim_evse_controller.set_status(ServiceStatus.STARTING) await SECCHandler( exi_codec=ExificientEXICodec(), evse_controller=sim_evse_controller ).start() From d19bdbfe3916b2d877acebb402932e626e04dbd7 Mon Sep 17 00:00:00 2001 From: Santiago Salamandri Date: Mon, 17 Oct 2022 13:56:38 +0100 Subject: [PATCH 7/8] feat: Remove event atribute and pass it as a parameter --- iso15118/secc/transport/tcp_server.py | 17 ++++++----------- iso15118/secc/transport/udp_server.py | 5 ++--- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/iso15118/secc/transport/tcp_server.py b/iso15118/secc/transport/tcp_server.py index cd259b19..291d7907 100644 --- a/iso15118/secc/transport/tcp_server.py +++ b/iso15118/secc/transport/tcp_server.py @@ -27,26 +27,24 @@ def __init__(self, session_handler_queue: asyncio.Queue, iface: str) -> None: self.port_no_tls = get_tcp_port() self.port_tls = get_tcp_port() self.iface = iface - self.tls_ready_event: asyncio.Event = asyncio.Event() - self.tcp_ready_event: asyncio.Event = asyncio.Event() # Making sure the TCP and TLS port are definitely different while self.port_no_tls == self.port_tls: self.port_tls = get_tcp_port() - async def start_tls(self): + async def start_tls(self, ready_event: asyncio.Event): """ Uses the `server_factory` to start a TLS based server """ - await self.server_factory(tls=True) + await self.server_factory(ready_event, tls=True) - async def start_no_tls(self): + async def start_no_tls(self, ready_event: asyncio.Event): """ Uses the `server_factory` to start a regular TCO based server (No TLS) """ - await self.server_factory(tls=False) + await self.server_factory(ready_event, tls=False) - async def server_factory(self, tls: bool) -> None: + async def server_factory(self, ready_event: asyncio.Event, tls: bool) -> None: """ Factory method to spawn a new server. @@ -114,10 +112,7 @@ async def server_factory(self, tls: bool) -> None: f"port {port}" ) - if tls: - self.tls_ready_event.set() - else: - self.tcp_ready_event.set() + ready_event.set() try: # Shield the task so we can handle the cancellation diff --git a/iso15118/secc/transport/udp_server.py b/iso15118/secc/transport/udp_server.py index a6764efc..323afec0 100644 --- a/iso15118/secc/transport/udp_server.py +++ b/iso15118/secc/transport/udp_server.py @@ -36,7 +36,6 @@ class UDPServer(asyncio.DatagramProtocol): def __init__(self, session_handler_queue: asyncio.Queue, iface: str): self.started: bool = False self.iface = iface - self.ready_event: asyncio.Event = asyncio.Event() self._session_handler_queue: asyncio.Queue = session_handler_queue self._rcv_queue: asyncio.Queue = asyncio.Queue() self._transport: Optional[DatagramTransport] = None @@ -83,7 +82,7 @@ async def __init__. return sock - async def start(self): + async def start(self, ready_event: asyncio.Event): """UDP server tasks to start""" # Get a reference to the event loop as we plan to use a low-level API # (see loop.create_datagram_endpoint()) @@ -100,7 +99,7 @@ async def start(self): f"{SDP_MULTICAST_GROUP}%{self.iface} " f"and port {SDP_SERVER_PORT}" ) - self.ready_event.set() + ready_event.set() tasks = [self.rcv_task()] await wait_for_tasks(tasks) From 5a0b981abf960c40ca017597e6ce8f105a5f3d9c Mon Sep 17 00:00:00 2001 From: Santiago Salamandri Date: Mon, 17 Oct 2022 13:59:40 +0100 Subject: [PATCH 8/8] feat: Pass status event as a parameter to start servers. Reduce check status delay to 10 mS --- iso15118/secc/comm_session_handler.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/iso15118/secc/comm_session_handler.py b/iso15118/secc/comm_session_handler.py index 7157f0ab..6fa9304a 100644 --- a/iso15118/secc/comm_session_handler.py +++ b/iso15118/secc/comm_session_handler.py @@ -16,10 +16,7 @@ from asyncio.streams import StreamReader, StreamWriter from typing import Dict, List, Optional, Tuple, Union -from iso15118.secc.controller.interface import ( - EVSEControllerInterface, - EVSEServiceStatus, -) +from iso15118.secc.controller.interface import EVSEControllerInterface, ServiceStatus from iso15118.secc.failed_responses import ( init_failed_responses_din_spec_70121, init_failed_responses_iso_v2, @@ -195,21 +192,24 @@ async def __init__. """ self.udp_server = UDPServer(self._rcv_queue, self.config.iface) - self.status_event_list.append(self.udp_server.ready_event) + udp_ready_event: asyncio.Event = asyncio.Event() + self.status_event_list.append(udp_ready_event) self.tcp_server = TCPServer(self._rcv_queue, self.config.iface) - self.status_event_list.append(self.tcp_server.tls_ready_event) + tls_ready_event: asyncio.Event = asyncio.Event() + self.status_event_list.append(tls_ready_event) self.list_of_tasks = [ self.get_from_rcv_queue(self._rcv_queue), - self.udp_server.start(), - self.tcp_server.start_tls(), + self.udp_server.start(udp_ready_event), + self.tcp_server.start_tls(tls_ready_event), self.check_status_task(), ] if not self.config.enforce_tls: - self.list_of_tasks.append(self.tcp_server.start_no_tls()) - self.status_event_list.append(self.tcp_server.tcp_ready_event) + tcp_ready_event: asyncio.Event = asyncio.Event() + self.status_event_list.append(tcp_ready_event) + self.list_of_tasks.append(self.tcp_server.start_no_tls(tcp_ready_event)) logger.info("Communication session handler started") @@ -226,15 +226,15 @@ def check_events(self) -> bool: async def check_ready_status(self) -> None: # Wait until all flags are set while self.check_events() is False: - await asyncio.sleep(0.1) + await asyncio.sleep(0.01) async def check_status_task(self) -> None: try: await asyncio.wait_for(self.check_ready_status(), timeout=10) - await self.evse_controller.set_status(EVSEServiceStatus.READY) + await self.evse_controller.set_status(ServiceStatus.READY) except asyncio.TimeoutError: logger.error("Timeout: Servers failed to startup") - await self.evse_controller.set_status(EVSEServiceStatus.ERROR) + await self.evse_controller.set_status(ServiceStatus.ERROR) async def get_from_rcv_queue(self, queue: asyncio.Queue): """