Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/service_status/ab#2950 #148

Merged
merged 8 commits into from
Oct 17, 2022
34 changes: 33 additions & 1 deletion iso15118/secc/comm_session_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 100 ms maybe too liberal for this - as servers could potentially be ready in just a few ms - modify this to be 10ms maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.


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
Expand Down
16 changes: 16 additions & 0 deletions iso15118/secc/controller/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -72,6 +73,14 @@ class EVDataContext:
soc: Optional[int] = None # 0-100


class EVSEServiceStatus(str, Enum):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the intention of the enum would be to indicate the status of the entire 15118 service and not just the one EVSE (even though that is what it is being used for at the moment but would change soon), I think it might be better to reflect that - calling it ServiceStatus should do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

READY = "ready"
STARTING = "starting"
STOPPING = "stopping"
ERROR = "error"
BUSY = "busy"


@dataclass
class EVChargeParamsLimits:
ev_max_voltage: Optional[Union[PVEVMaxVoltageLimit, PVEVMaxVoltage]] = None
Expand All @@ -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:
"""
Expand Down
3 changes: 3 additions & 0 deletions iso15118/secc/controller/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
EVChargeParamsLimits,
EVDataContext,
EVSEControllerInterface,
EVSEServiceStatus,
)
from iso15118.shared.exceptions import EncryptionError, PrivateKeyReadError
from iso15118.shared.exi_codec import EXI
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions iso15118/secc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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()
Expand Down
7 changes: 7 additions & 0 deletions iso15118/secc/transport/tcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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()

Copy link
Contributor

@shalinnijel2 shalinnijel2 Oct 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment regarding udp server. Wouldn't it suffice if we pass in the ready_event Event through the start_tls() or start_no_tls() method - as the state is not required after this - so making it a member variable would not be required. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it could be used in the future to notify when a server failed. It's not needed for now.

try:
# Shield the task so we can handle the cancellation
# closing the opening connections
Expand Down
2 changes: 2 additions & 0 deletions iso15118/secc/transport/udp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -99,6 +100,7 @@ async def start(self):
f"{SDP_MULTICAST_GROUP}%{self.iface} "
f"and port {SDP_SERVER_PORT}"
)
self.ready_event.set()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there is much value in making read_event a member variable as the only thing time it is used for is to indicate when the udp server is setup? Wouldn't it suffice to pass it through the start() method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

tasks = [self.rcv_task()]
await wait_for_tasks(tasks)

Expand Down