diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 2de292fe..c95e4e96 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -28,9 +28,8 @@ jobs: - name: Install dependencies run: make install-local -# TODO: Fix MyPy issues https://github.com/SwitchEV/iso15118/issues/93 -# - name: Mypy -# run: make mypy + - name: Mypy + run: make mypy - name: Black run: make black diff --git a/Makefile b/Makefile index 1c3fc7b7..e653feb1 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,7 @@ test: # Run mypy checks mypy: - poetry run mypy --config-file mypy.ini iso15118 tests + poetry run mypy --config-file mypy.ini --check-untyped-defs iso15118 tests # Reformat with isort and black reformat: diff --git a/iso15118/evcc/comm_session_handler.py b/iso15118/evcc/comm_session_handler.py index 649fcc98..0ae655d1 100644 --- a/iso15118/evcc/comm_session_handler.py +++ b/iso15118/evcc/comm_session_handler.py @@ -11,7 +11,7 @@ import logging from asyncio.streams import StreamReader, StreamWriter from ipaddress import IPv6Address -from typing import List, Optional, Tuple, Union +from typing import Coroutine, List, Optional, Tuple, Union from pydantic.error_wrappers import ValidationError @@ -106,7 +106,7 @@ def __init__( self.service_details_to_request: List[int] = [] # Protocols supported by the EVCC as sent to the SECC via # the SupportedAppProtocolReq message - self.supported_protocols: List[Protocol] = [] + self.supported_app_protocols: List[AppProtocol] = [] # The Ongoing timer (given in seconds) starts running once the EVCC # receives a response with the field EVSEProcessing set to 'Ongoing'. # Once the timer is up, the EV will terminate the communication session. @@ -197,8 +197,8 @@ def create_sap(self) -> Union[SupportedAppProtocolReq, None]: ) app_protocols.append(app_protocol_entry) - self.supported_protocols = app_protocols - sap_req = SupportedAppProtocolReq(app_protocol=self.supported_protocols) + self.supported_app_protocols = app_protocols + sap_req = SupportedAppProtocolReq(app_protocol=self.supported_app_protocols) return sap_req @@ -269,13 +269,13 @@ def __init__( codec: IEXICodec, ev_controller: EVControllerInterface, ): - self.list_of_tasks = [] - self.udp_client = None - self.tcp_client = None - self.tls_client = None - self.config = config - self.iface = iface - self.ev_controller = ev_controller + self.list_of_tasks: List[Coroutine] = [] + self.udp_client: UDPClient = None + self.tcp_client: TCPClient = None + self.tls_client: bool = None + self.config: EVCCConfig = config + self.iface: str = iface + self.ev_controller: EVControllerInterface = ev_controller self.sdp_retries_number = SDP_MAX_REQUEST_COUNTER self._sdp_retry_cycles = self.config.sdp_retry_cycles @@ -283,7 +283,7 @@ def __init__( EXI().set_exi_codec(codec) # Receiving queue for UDP client to notify about incoming datagrams - self._rcv_queue = asyncio.Queue(0) + self._rcv_queue: asyncio.Queue = asyncio.Queue(0) # The communication session is a tuple containing the session itself # and the associated task, so we can cancel the task when needed diff --git a/iso15118/evcc/controller/simulator.py b/iso15118/evcc/controller/simulator.py index cd23ffd7..24ff21eb 100644 --- a/iso15118/evcc/controller/simulator.py +++ b/iso15118/evcc/controller/simulator.py @@ -194,13 +194,13 @@ async def select_vas_services_v20( matched_vas_services = [ service for service in services if not service.is_energy_service ] - selected_vas_services: List[MatchedService] = [] + selected_vas_services: List[SelectedVAS] = [] for vas_service in matched_vas_services: selected_vas_services.append( SelectedVAS( - service=vas_service, + service=vas_service.service, is_free=vas_service.is_free, - parameter_set=selected_vas_services.parameter_sets[0], + parameter_set=vas_service.parameter_sets[0], ) ) return selected_vas_services diff --git a/iso15118/evcc/evcc_config.py b/iso15118/evcc/evcc_config.py index aef56d54..b72c6a34 100644 --- a/iso15118/evcc/evcc_config.py +++ b/iso15118/evcc/evcc_config.py @@ -30,7 +30,7 @@ class EVCCConfig(BaseModel): raw_supported_energy_services: List[str] = Field( _default_supported_energy_services, max_items=4, alias="supportedEnergyServices" ) - supported_energy_services: Optional[List[ServiceV20]] = None + supported_energy_services: List[ServiceV20] = None is_cert_install_needed: bool = Field(False, alias="isCertInstallNeeded") # Indicates the security level (either TCP (unencrypted) or TLS (encrypted)) # the EVCC shall send in the SDP request @@ -119,3 +119,4 @@ async def load_from_file(file_name: str) -> EVCCConfig: return ev_config except Exception as err: logger.debug(f"Error on loading evcc config file:{err}") + return EVCCConfig() diff --git a/iso15118/evcc/states/evcc_state.py b/iso15118/evcc/states/evcc_state.py index ee9d1cd5..537f130c 100644 --- a/iso15118/evcc/states/evcc_state.py +++ b/iso15118/evcc/states/evcc_state.py @@ -12,14 +12,12 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) -from iso15118.shared.messages.din_spec.body import BodyBase as BodyBaseDINSPEC from iso15118.shared.messages.din_spec.body import Response as ResponseDINSPEC from iso15118.shared.messages.din_spec.body import ( 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, @@ -132,9 +130,14 @@ def check_msg_v20( V2GMessageV20, V2GMessageDINSPEC, ], - expected_msg_type: Type[T], - ) -> Optional[T]: - return self.check_msg(message, expected_msg_type, expected_msg_type) + expected_msg_type: Union[ + Type[SupportedAppProtocolRes], + Type[ResponseV2], + Type[V2GResponseV20], + Type[ResponseDINSPEC], + ], + ) -> Optional[V2GMessageV20]: + return self.check_msg(message, V2GMessageV20, expected_msg_type) def check_msg( self, @@ -184,12 +187,9 @@ def check_msg( ) return None - msg_body: Union[ - SupportedAppProtocolRes, BodyBaseV2, V2GResponseV20, BodyBaseDINSPEC - ] if isinstance(message, V2GMessageV2) or isinstance(message, V2GMessageDINSPEC): # ISO 15118-2 or DIN SPEC 72101 - msg_body = message.body.get_message() + msg_body = message.body.get_message() # type: ignore else: # SupportedAppProtocolReq, V2GRequest (ISO 15118-20) msg_body = message @@ -208,7 +208,8 @@ def check_msg( return None if ( - not isinstance( + message is not None + and not isinstance( msg_body, ( SupportedAppProtocolRes, diff --git a/iso15118/evcc/states/iso15118_20_states.py b/iso15118/evcc/states/iso15118_20_states.py index 66c0a82f..633e1ac0 100644 --- a/iso15118/evcc/states/iso15118_20_states.py +++ b/iso15118/evcc/states/iso15118_20_states.py @@ -6,7 +6,7 @@ import logging import time -from typing import Any, List, Union +from typing import Any, List, Union, cast from iso15118.evcc.comm_session_handler import EVCCCommunicationSession from iso15118.evcc.states.evcc_state import StateEVCC @@ -69,7 +69,10 @@ from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) +from iso15118.shared.messages.iso15118_20.common_types import V2GRequest from iso15118.shared.messages.iso15118_20.dc import ( + BPTDynamicDCChargeLoopReqParams, + BPTScheduledDCChargeLoopReqParams, DCCableCheckReq, DCCableCheckRes, DCChargeLoopReq, @@ -79,6 +82,8 @@ DCPreChargeReq, DCPreChargeRes, DCWeldingDetectionReq, + DynamicDCChargeLoopReqParams, + ScheduledDCChargeLoopReqParams, ) from iso15118.shared.messages.iso15118_20.timeouts import Timeouts from iso15118.shared.messages.timeouts import Timeouts as TimeoutsShared @@ -126,11 +131,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, SessionSetupRes) + msg: V2GMessageV20 = self.check_msg_v20(message, SessionSetupRes) if not msg: return - session_setup_res: SessionSetupRes = msg + session_setup_res: SessionSetupRes = cast(SessionSetupRes, msg) self.comm_session.session_id = msg.header.session_id self.comm_session.evse_id = session_setup_res.evse_id @@ -170,11 +175,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, AuthorizationSetupRes) + msg: V2GMessageV20 = self.check_msg_v20(message, AuthorizationSetupRes) if not msg: return - auth_setup_res: AuthorizationSetupRes = msg + auth_setup_res: AuthorizationSetupRes = cast(AuthorizationSetupRes, msg) signature = None if ( @@ -199,7 +204,7 @@ async def process_message( signature = create_signature( [ ( - oem_prov_cert_chain.id, + "id1", EXI().to_exi( oem_prov_cert_chain, Namespace.ISO_V20_COMMON_MSG ), @@ -352,11 +357,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, AuthorizationRes) + msg: V2GMessageV20 = self.check_msg_v20(message, AuthorizationRes) if not msg: return - auth_res: AuthorizationRes = msg # noqa: F841 + auth_res: AuthorizationRes = cast(AuthorizationRes, msg) # TODO Act upon the response codes and evse_processing value of auth_res # (and delete the # noqa: F841) # TODO: V2G20-2221 demands to send CertificateInstallationReq if necessary @@ -440,11 +445,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, ServiceDiscoveryRes) + msg: V2GMessageV20 = self.check_msg_v20(message, ServiceDiscoveryRes) if not msg: return - service_discovery_res: ServiceDiscoveryRes = msg + service_discovery_res: ServiceDiscoveryRes = cast(ServiceDiscoveryRes, msg) self.comm_session.service_renegotiation_supported = ( service_discovery_res.service_renegotiation_supported @@ -549,11 +554,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, ServiceDetailRes) + msg: V2GMessageV20 = self.check_msg_v20(message, ServiceDetailRes) if not msg: return - service_detail_res: ServiceDetailRes = msg + service_detail_res: ServiceDetailRes = cast(ServiceDetailRes, msg) self.store_parameter_sets(service_detail_res) @@ -696,13 +701,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, ServiceSelectionRes) + msg: V2GMessageV20 = self.check_msg_v20(message, ServiceSelectionRes) if not msg: return - service_selection_res: ServiceSelectionRes = msg # noqa: F841 # TODO Act upon the possible negative response codes in service_selection_res - # (and delete the # noqa: F841) next_req: Any = None if self.comm_session.selected_energy_service.service in ( @@ -800,11 +803,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, ScheduleExchangeRes) + msg: V2GMessageV20 = self.check_msg_v20(message, ScheduleExchangeRes) if not msg: return - schedule_exchange_res: ScheduleExchangeRes = msg + schedule_exchange_res: ScheduleExchangeRes = cast(ScheduleExchangeRes, msg) if schedule_exchange_res.evse_processing == Processing.ONGOING: self.create_next_message( @@ -906,12 +909,10 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, PowerDeliveryRes) + msg: V2GMessageV20 = self.check_msg_v20(message, PowerDeliveryRes) if not msg: return - power_delivery_res: PowerDeliveryRes = msg # noqa - if self.comm_session.ev_processing == Processing.ONGOING: await self.create_new_power_delivery_req( self.comm_session.schedule_exchange_res @@ -958,8 +959,10 @@ async def process_message( return - scheduled_params, dynamic_params = None, None - bpt_scheduled_params, bpt_dynamic_params = None, None + scheduled_params: ScheduledDCChargeLoopReqParams = None + dynamic_params: DynamicDCChargeLoopReqParams = None + bpt_scheduled_params: BPTScheduledDCChargeLoopReqParams = None + bpt_dynamic_params: BPTDynamicDCChargeLoopReqParams = None selected_energy_service = self.comm_session.selected_energy_service control_mode = self.comm_session.control_mode ev_controller = self.comm_session.ev_controller @@ -970,17 +973,25 @@ async def process_message( ) if selected_energy_service.service == ServiceV20.AC: if control_mode == ControlMode.SCHEDULED: - scheduled_params = charging_loop_params + scheduled_params = cast( + ScheduledDCChargeLoopReqParams, charging_loop_params + ) else: # Dynamic - dynamic_params = charging_loop_params + dynamic_params = cast( + DynamicDCChargeLoopReqParams, charging_loop_params + ) else: # AC_BPT if control_mode == ControlMode.SCHEDULED: - bpt_scheduled_params = charging_loop_params + bpt_scheduled_params = cast( + BPTScheduledDCChargeLoopReqParams, charging_loop_params + ) else: # Dynamic - bpt_dynamic_params = charging_loop_params + bpt_dynamic_params = cast( + BPTDynamicDCChargeLoopReqParams, charging_loop_params + ) ac_charge_loop_req = ACChargeLoopReq( header=MessageHeader( @@ -1123,7 +1134,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, SessionStopRes) + msg: V2GMessageV20 = self.check_msg_v20(message, SessionStopRes) if not msg: return @@ -1176,13 +1187,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, ACChargeParameterDiscoveryRes) + msg: V2GMessageV20 = self.check_msg_v20(message, ACChargeParameterDiscoveryRes) if not msg: return - ac_cpd_res: ACChargeParameterDiscoveryRes = msg # noqa: F841 # TODO Act upon the possible negative response codes in ac_cpd_res - # (and delete the # noqa: F841) self.comm_session.ongoing_schedule_exchange_req = ( await self.build_schedule_exchange_request() @@ -1243,11 +1252,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, ACChargeLoopRes) + msg: V2GMessageV20 = self.check_msg_v20(message, ACChargeLoopRes) if not msg: return - ac_charge_loop_res: ACChargeLoopRes = msg + ac_charge_loop_res: ACChargeLoopRes = cast(ACChargeLoopRes, msg) # Before checking if we should continue charging, # check if SECC requested a renegotiation. @@ -1354,13 +1363,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, DCChargeParameterDiscoveryRes) + msg: V2GMessageV20 = self.check_msg_v20(message, DCChargeParameterDiscoveryRes) if not msg: return - dc_cpd_res: DCChargeParameterDiscoveryRes = msg # noqa: F841 # TODO Act upon the possible negative response codes in dc_cpd_res - # (and delete the # noqa: F841) self.comm_session.ongoing_schedule_exchange_req = ( await self.build_schedule_exchange_request() @@ -1421,11 +1428,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, DCCableCheckRes) + msg: V2GMessageV20 = self.check_msg_v20(message, DCCableCheckRes) if not msg: return - cable_check_res: DCCableCheckRes = msg # noqa + cable_check_res: DCCableCheckRes = cast(DCCableCheckRes, msg) if cable_check_res.evse_processing == Processing.FINISHED: # Reset the Ongoing timer @@ -1498,11 +1505,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, DCPreChargeRes) + msg: V2GMessageV20 = self.check_msg_v20(message, DCPreChargeRes) if not msg: return - precharge_res: DCPreChargeRes = msg + precharge_res: DCPreChargeRes = cast(DCPreChargeRes, msg) next_state = None if ( await self.comm_session.ev_controller.is_precharged( @@ -1620,11 +1627,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, DCChargeLoopRes) + msg: V2GMessageV20 = self.check_msg_v20(message, DCChargeLoopRes) if not msg: return - charge_loop_res: DCChargeLoopRes = msg # noqa + charge_loop_res: DCChargeLoopRes = cast(DCChargeLoopRes, msg) # if charge_loop_res.evse_power_limit_achieved: # self.stop_v20_charging(False) @@ -1738,7 +1745,7 @@ async def process_message( charging_session=ChargingSession.TERMINATE, ) next_state = SessionStop - next_request = session_stop_req + next_request: V2GRequest = session_stop_req next_timeout = Timeouts.SESSION_STOP_REQ namespace = Namespace.ISO_V20_COMMON_MSG next_payload_type = ISOV20PayloadTypes.MAINSTREAM @@ -1747,7 +1754,7 @@ async def process_message( if await self.comm_session.ev_controller.welding_detection_has_finished(): processing = Processing.FINISHED self.welding_detection_complete = True - next_request: Any = DCWeldingDetectionReq( + next_request = DCWeldingDetectionReq( header=MessageHeader( session_id=self.comm_session.session_id, timestamp=time.time(), diff --git a/iso15118/evcc/states/iso15118_2_states.py b/iso15118/evcc/states/iso15118_2_states.py index 2e496b9a..de51dd2a 100644 --- a/iso15118/evcc/states/iso15118_2_states.py +++ b/iso15118/evcc/states/iso15118_2_states.py @@ -1016,7 +1016,7 @@ async def process_message( else: self.create_next_message( CurrentDemand, - self.build_current_demand_req(), + await self.build_current_demand_req(), Timeouts.CHARGING_STATUS_REQ, Namespace.ISO_V2_MSG_DEF, ) diff --git a/iso15118/evcc/states/sap_states.py b/iso15118/evcc/states/sap_states.py index 3f73b2d7..4fd12c1e 100644 --- a/iso15118/evcc/states/sap_states.py +++ b/iso15118/evcc/states/sap_states.py @@ -76,7 +76,9 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg(message, SupportedAppProtocolRes, SupportedAppProtocolRes) + msg: SupportedAppProtocolRes = self.check_msg( + message, SupportedAppProtocolRes, SupportedAppProtocolRes + ) if not msg: return @@ -100,7 +102,7 @@ async def process_message( ] = ISOV2PayloadTypes.EXI_ENCODED match = False - for protocol in self.comm_session.supported_protocols: + for protocol in self.comm_session.supported_app_protocols: if protocol.schema_id == sap_res.schema_id: match = True if protocol.protocol_ns == Protocol.ISO_15118_2.ns.value: diff --git a/iso15118/evcc/transport/tcp_client.py b/iso15118/evcc/transport/tcp_client.py index ce20ec83..7f67a038 100644 --- a/iso15118/evcc/transport/tcp_client.py +++ b/iso15118/evcc/transport/tcp_client.py @@ -18,7 +18,7 @@ def __init__(self, session_handler_queue, port, is_tls): self.writer = None self.port = port self._session_handler_queue = session_handler_queue - self._rcv_queue = asyncio.Queue() + self._rcv_queue: asyncio.Queue = asyncio.Queue() self._last_message_sent = None self.ssl_context = None if is_tls: diff --git a/iso15118/evcc/transport/udp_client.py b/iso15118/evcc/transport/udp_client.py index c5ccb7de..4bec2453 100644 --- a/iso15118/evcc/transport/udp_client.py +++ b/iso15118/evcc/transport/udp_client.py @@ -41,7 +41,7 @@ def __init__(self, session_handler_queue: asyncio.Queue, iface: str): self.iface = iface @staticmethod - def _create_socket(iface: str) -> "socket": + def _create_socket(iface: str) -> socket.socket: """ This method creates an IPv6 socket configured to send multicast datagrams """ diff --git a/iso15118/py.typed b/iso15118/py.typed new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/iso15118/py.typed @@ -0,0 +1 @@ + diff --git a/iso15118/secc/comm_session_handler.py b/iso15118/secc/comm_session_handler.py index f893f33a..3c16ac7a 100644 --- a/iso15118/secc/comm_session_handler.py +++ b/iso15118/secc/comm_session_handler.py @@ -14,7 +14,7 @@ import logging import socket from asyncio.streams import StreamReader, StreamWriter -from typing import Dict, List, Optional, Tuple, Union +from typing import Any, Coroutine, Dict, List, Optional, Tuple, Union from iso15118.secc.controller.interface import ( EVSEControllerInterface, @@ -176,13 +176,13 @@ class CommunicationSessionHandler: def __init__( self, config: Config, codec: IEXICodec, evse_controller: EVSEControllerInterface ): - self.list_of_tasks = [] - self.udp_server = None - self.tcp_server = None - self.tcp_server_handler = None - self.config = config - self.evse_controller = evse_controller - self.udp_processor_lock = asyncio.Lock() + self.list_of_tasks: List[Coroutine] = [] + self.udp_server: Optional[UDPServer] = None + self.tcp_server: Optional[TCPServer] = None + self.tcp_server_handler: Optional[asyncio.Task[Any]] = None + self.config: Config = config + self.evse_controller: EVSEControllerInterface = evse_controller + self.udp_processor_lock: asyncio.Lock = asyncio.Lock() # List of server status events self.status_event_list: List[asyncio.Event] = [] @@ -192,12 +192,14 @@ def __init__( # Receiving queue for UDP or TCP packets and session # triggers (e.g. pause/terminate) - self._rcv_queue = asyncio.Queue() + self._rcv_queue: asyncio.Queue = asyncio.Queue() # The comm_sessions dict keys are of type str (the IPv6 address), the # values are a tuple containing the SECCCommunicationSession and the # associated ayncio.Task object (so we can cancel the task when needed) - self.comm_sessions: Dict[str, (SECCCommunicationSession, asyncio.Task)] = {} + self.comm_sessions: Dict[ + str, Tuple[SECCCommunicationSession, asyncio.Task] + ] = {} async def start_session_handler( self, iface: str, start_udp_server: Optional[bool] = True @@ -286,7 +288,9 @@ async def get_from_rcv_queue(self, queue: asyncio.Queue): except (KeyError, ConnectionResetError) as e: if isinstance(e, ConnectionResetError): logger.info("Can't resume session. End and start new one.") - await self.end_current_session(notification.ip_address) + await self.end_current_session( + notification.ip_address, SessionStopAction.TERMINATE + ) comm_session = SECCCommunicationSession( notification.transport, self._rcv_queue, diff --git a/iso15118/secc/controller/interface.py b/iso15118/secc/controller/interface.py index 3f254ecc..fe4297fd 100644 --- a/iso15118/secc/controller/interface.py +++ b/iso15118/secc/controller/interface.py @@ -68,7 +68,9 @@ ServiceList, ServiceParameterList, ) -from iso15118.shared.messages.iso15118_20.common_types import EVSEStatus, RationalNumber +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 from iso15118.shared.messages.iso15118_20.common_types import ( ResponseCode as ResponseCodeV20, ) @@ -98,8 +100,8 @@ class EVDataContext: ac_current: Optional[dict] = None # {"l1": 10, "l2": 10, "l3": 10} ac_voltage: Optional[dict] = None # {"l1": 230, "l2": 230, "l3": 230} soc: Optional[int] = None # 0-100 - remaining_time_to_full_soc_s: Optional[int] = None - remaining_time_to_bulk_soc_s: Optional[int] = None + remaining_time_to_full_soc_s: Optional[float] = None + remaining_time_to_bulk_soc_s: Optional[float] = None evcc_id: Optional[str] = None # from ISO 15118-20 AC @@ -175,7 +177,7 @@ class EVSEControllerInterface(ABC): def __init__(self): self.ev_data_context = EVDataContext() self.ev_charge_params_limits = EVChargeParamsLimits() - self._selected_protocol = Optional[Protocol] + self._selected_protocol: Optional[Protocol] = None def reset_ev_data_context(self): self.ev_data_context = EVDataContext() @@ -394,7 +396,7 @@ async def get_meter_info_v2(self) -> MeterInfoV2: raise NotImplementedError @abstractmethod - async def get_meter_info_v20(self) -> MeterInfoV2: + async def get_meter_info_v20(self) -> MeterInfoV20: """ Provides the MeterInfo from the EVSE's smart meter @@ -523,7 +525,7 @@ def set_selected_protocol(self, protocol: Protocol) -> None: """ self._selected_protocol = protocol - def get_selected_protocol(self) -> Protocol: + def get_selected_protocol(self) -> Optional[Protocol]: """Get the selected Protocol.""" return self._selected_protocol @@ -669,7 +671,9 @@ async def get_evse_present_current( @abstractmethod async def set_precharge( - self, voltage: PVEVTargetVoltage, current: PVEVTargetCurrent + self, + voltage: Union[PVEVTargetVoltage, RationalNumber], + current: Union[PVEVTargetCurrent, RationalNumber], ): """ Sets the precharge information coming from the EV. diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index 5103029f..884db365 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -66,7 +66,11 @@ SessionStopAction, UnitSymbol, ) -from iso15118.shared.messages.iso15118_2.body import Body, CertificateInstallationRes +from iso15118.shared.messages.iso15118_2.body import ( + Body, + CertificateInstallationReq, + CertificateInstallationRes, +) from iso15118.shared.messages.iso15118_2.datatypes import ( EMAID, ACEVSEChargeParameter, @@ -194,6 +198,8 @@ class SimEVSEController(EVSEControllerInterface): A simulated version of an EVSE controller """ + v20_service_id_parameter_mapping: Optional[Dict[int, ServiceParameterList]] = None + @classmethod async def create(cls): self = SimEVSEController() @@ -371,6 +377,8 @@ async def get_service_parameter_list( self, service_id: int ) -> Optional[ServiceParameterList]: """Overrides EVSEControllerInterface.get_service_parameter_list().""" + if self.v20_service_id_parameter_mapping is None: + return None if service_id in self.v20_service_id_parameter_mapping.keys(): service_parameter_list = self.v20_service_id_parameter_mapping[service_id] else: @@ -445,11 +453,15 @@ async def is_authorized( ) -> AuthorizationResponse: """Overrides EVSEControllerInterface.is_authorized().""" protocol = self.get_selected_protocol() - response_code = ResponseCodeV2.OK + response_code: Optional[ + Union[ResponseCodeDINSPEC, ResponseCodeV2, ResponseCodeV20] + ] = None if protocol == Protocol.DIN_SPEC_70121: response_code = ResponseCodeDINSPEC.OK elif protocol == Protocol.ISO_15118_20_COMMON_MESSAGES: response_code = ResponseCodeV20.OK + else: + response_code = ResponseCodeV2.OK return AuthorizationResponse( authorization_status=AuthorizationStatus.ACCEPTED, @@ -651,6 +663,13 @@ async def send_charging_power_limits( """ if protocol == Protocol.ISO_15118_20_AC: + charge_parameters: Optional[ + Union[ + ACChargeParameterDiscoveryResParams, + BPTACChargeParameterDiscoveryResParams, + DCChargeParameterDiscoveryResParams, + ] + ] if selected_energy_service in [ServiceV20.AC, ServiceV20.AC_BPT]: charge_parameters = await self.get_ac_charge_params_v20( selected_energy_service @@ -662,22 +681,30 @@ async def send_charging_power_limits( ev_data_context = self.get_ev_data_context() logger.info(f"EV data context: {ev_data_context}") + if isinstance( + charge_parameters, ACChargeParameterDiscoveryResParams + ) or isinstance(charge_parameters, DCChargeParameterDiscoveryResParams): + max_discharge_power = ev_data_context.ev_max_discharge_power + min_discharge_power = ev_data_context.ev_min_discharge_power + else: + max_discharge_power = min( + ev_data_context.ev_max_discharge_power, + charge_parameters.evse_max_discharge_power.get_decimal_value(), + ) + min_discharge_power = max( + ev_data_context.ev_min_discharge_power, + charge_parameters.evse_min_discharge_power.get_decimal_value(), + ) max_charge_power = min( ev_data_context.ev_max_charge_power, charge_parameters.evse_max_charge_power.get_decimal_value(), ) - max_discharge_power = min( - ev_data_context.ev_max_discharge_power, - charge_parameters.evse_max_discharge_power.get_decimal_value(), - ) + min_charge_power = max( ev_data_context.ev_min_charge_power, charge_parameters.evse_min_charge_power.get_decimal_value(), ) - min_discharge_power = max( - ev_data_context.ev_min_discharge_power, - charge_parameters.evse_min_discharge_power.get_decimal_value(), - ) + logger.debug( f"\n\r --- EV-EVSE System Power Limits --- \n" f"max_charge_power [W]: {max_charge_power}\n" @@ -717,8 +744,10 @@ async def get_ac_charge_params_v2(self) -> ACEVSEChargeParameter: async def get_ac_charge_params_v20( self, selected_service: ServiceV20 - ) -> Union[ - ACChargeParameterDiscoveryResParams, BPTACChargeParameterDiscoveryResParams + ) -> Optional[ + Union[ + ACChargeParameterDiscoveryResParams, BPTACChargeParameterDiscoveryResParams + ] ]: """Overrides EVSEControllerInterface.get_ac_charge_params_v20().""" ac_charge_parameter_discovery_res_params = ACChargeParameterDiscoveryResParams( @@ -747,6 +776,7 @@ async def get_ac_charge_params_v20( evse_min_discharge_power_l2=RationalNumber(exponent=0, value=300), evse_min_discharge_power_l3=RationalNumber(exponent=0, value=300), ) + return None async def get_ac_charge_loop_params_v20( self, control_mode: ControlMode, selected_service: ServiceV20 @@ -856,7 +886,9 @@ async def get_cable_check_status(self) -> Union[IsolationLevel, None]: return IsolationLevel.VALID async def set_precharge( - self, voltage: PVEVTargetVoltage, current: PVEVTargetCurrent + self, + voltage: Union[PVEVTargetVoltage, RationalNumber], + current: Union[PVEVTargetCurrent, RationalNumber], ): pass @@ -885,8 +917,10 @@ async def get_evse_max_power_limit(self) -> PVEVSEMaxPowerLimit: async def get_dc_charge_params_v20( self, selected_service: ServiceV20 - ) -> Union[ - DCChargeParameterDiscoveryResParams, BPTDCChargeParameterDiscoveryResParams + ) -> Optional[ + Union[ + DCChargeParameterDiscoveryResParams, BPTDCChargeParameterDiscoveryResParams + ] ]: """Override EVSEControllerInterface.get_dc_charge_params_v20().""" dc_charge_parameter_discovery_res = DCChargeParameterDiscoveryResParams( @@ -908,14 +942,17 @@ async def get_dc_charge_params_v20( evse_max_discharge_current=RationalNumber(exponent=0, value=11), evse_min_discharge_current=RationalNumber(exponent=0, value=0), ) + return None async def get_dc_charge_loop_params_v20( self, control_mode: ControlMode, selected_service: ServiceV20 - ) -> Union[ - ScheduledDCChargeLoopResParams, - BPTScheduledDCChargeLoopResParams, - DynamicDCChargeLoopRes, - BPTDynamicDCChargeLoopRes, + ) -> Optional[ + Union[ + ScheduledDCChargeLoopResParams, + BPTScheduledDCChargeLoopResParams, + DynamicDCChargeLoopRes, + BPTDynamicDCChargeLoopRes, + ] ]: """Overrides EVSEControllerInterface.get_dc_charge_loop_params().""" if selected_service == ServiceV20.DC: @@ -932,6 +969,7 @@ async def get_dc_charge_loop_params_v20( evse_maximum_voltage=RationalNumber(exponent=1, value=600), ) return dynamic_params + return None elif selected_service == ServiceV20.DC_BPT: if control_mode == ControlMode.SCHEDULED: bpt_scheduled_params = BPTScheduledDCChargeLoopResParams( @@ -951,8 +989,8 @@ async def get_dc_charge_loop_params_v20( ) return bpt_dynamic_params else: - logger.error(f"Energy service {selected_service.service} not yet supported") - return + logger.error(f"Energy service {selected_service.name} not yet supported") + return None async def get_15118_ev_certificate( self, base64_encoded_cert_installation_req: str, namespace: str @@ -1069,24 +1107,31 @@ async def get_15118_ev_certificate( except Exception as exc: raise Exception(f"Error creating signature {exc}") - header = MessageHeaderV2( - session_id=cert_install_req.header.session_id, - signature=signature, - ) - body = Body.parse_obj({"CertificateInstallationRes": cert_install_res.dict()}) - to_be_exi_encoded = V2GMessageV2(header=header, body=body) - exi_encoded_cert_installation_res = EXI().to_exi( - to_be_exi_encoded, Namespace.ISO_V2_MSG_DEF - ) + if isinstance(cert_install_req, CertificateInstallationReq): + header = MessageHeaderV2( + session_id=cert_install_req.header.session_id, + signature=signature, + ) + body = Body.parse_obj( + {"CertificateInstallationRes": cert_install_res.dict()} + ) + to_be_exi_encoded = V2GMessageV2(header=header, body=body) + exi_encoded_cert_installation_res = EXI().to_exi( + to_be_exi_encoded, Namespace.ISO_V2_MSG_DEF + ) - # base64.b64encode in Python is a binary transform so the return value is byte[] - # But the CPO expects exi_encoded_cert_installation_res as a string, hence the - # added .decode("utf-8") - base64_encode_cert_install_res = base64.b64encode( - exi_encoded_cert_installation_res - ).decode("utf-8") + # base64.b64encode in Python is a binary transform + # so the return value is byte[] + # But the CPO expects exi_encoded_cert_installation_res + # as a string, hence the added .decode("utf-8") + base64_encode_cert_install_res = base64.b64encode( + exi_encoded_cert_installation_res + ).decode("utf-8") - return base64_encode_cert_install_res + return base64_encode_cert_install_res + else: + logger.info(f"Ignoring EXI decoding of a {type(cert_install_req)} message.") + return "" async def update_data_link(self, action: SessionStopAction) -> None: """ diff --git a/iso15118/secc/states/din_spec_states.py b/iso15118/secc/states/din_spec_states.py index dedae42f..632f2ab4 100644 --- a/iso15118/secc/states/din_spec_states.py +++ b/iso15118/secc/states/din_spec_states.py @@ -15,6 +15,7 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) +from iso15118.shared.messages.datatypes import PVEVSEPresentCurrent from iso15118.shared.messages.din_spec.body import ( CableCheckReq, CableCheckRes, @@ -597,9 +598,16 @@ async def process_message( Protocol.DIN_SPEC_70121 ) ) - present_current_in_a = present_current.value * 10**present_current.multiplier - target_current = precharge_req.ev_target_current - target_current_in_a = target_current.value * 10**target_current.multiplier + if isinstance(present_current, PVEVSEPresentCurrent): + present_current_in_a = ( + present_current.value * 10**present_current.multiplier + ) + target_current = precharge_req.ev_target_current + target_current_in_a = target_current.value * 10**target_current.multiplier + else: + present_current_in_a = present_current.value + target_current = precharge_req.ev_target_current + target_current_in_a = precharge_req.ev_target_current.value if present_current_in_a > 2 or target_current_in_a > 2: self.stop_state_machine( diff --git a/iso15118/secc/states/iso15118_20_states.py b/iso15118/secc/states/iso15118_20_states.py index 6b88da2c..ffb9e96c 100644 --- a/iso15118/secc/states/iso15118_20_states.py +++ b/iso15118/secc/states/iso15118_20_states.py @@ -6,7 +6,7 @@ import asyncio import logging import time -from typing import List, Optional, Tuple, Type, Union +from typing import List, Optional, Tuple, Type, Union, cast from iso15118.secc.comm_session_handler import SECCCommunicationSession from iso15118.secc.states.secc_state import StateSECC @@ -15,6 +15,9 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) +from iso15118.shared.messages.din_spec.datatypes import ( + ResponseCode as ResponseCodeDINSPEC, +) from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC from iso15118.shared.messages.enums import ( AuthEnum, @@ -30,6 +33,7 @@ ServiceV20, SessionStopAction, ) +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 from iso15118.shared.messages.iso15118_20.ac import ( ACChargeLoopReq, @@ -76,7 +80,10 @@ EVSEStatus, MessageHeader, Processing, - ResponseCode, +) +from iso15118.shared.messages.iso15118_20.common_types import ResponseCode +from iso15118.shared.messages.iso15118_20.common_types import ( + ResponseCode as ResponseCodeV20, ) from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, @@ -130,11 +137,11 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, [SessionSetupReq]) + msg: V2GMessageV20 = self.check_msg_v20(message, [SessionSetupReq]) if not msg: return - session_setup_req: SessionSetupReq = msg + session_setup_req: SessionSetupReq = cast(SessionSetupReq, msg) # Check session ID. Most likely, we need to create a new one session_id: str = get_random_bytes(8).hex().upper() @@ -211,7 +218,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [ AuthorizationSetupReq, @@ -349,7 +356,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [ AuthorizationReq, @@ -378,9 +385,10 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - auth_req: AuthorizationReq = msg - response_code: ResponseCode = ResponseCode.OK - + auth_req: AuthorizationReq = cast(AuthorizationReq, msg) + response_code: Optional[ + Union[ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC] + ] = ResponseCode.OK self.comm_session.selected_auth_option = AuthEnum( auth_req.selected_auth_service.value ) @@ -410,7 +418,7 @@ async def process_message( await self.comm_session.evse_controller.is_authorized() ) evse_processing = Processing.ONGOING - response_code = ResponseCode.OK + if resp_status := current_authorization_status.certificate_response_status: # Based on table 224 in ISO 15118-20 the response code should be # one of the following: @@ -532,7 +540,7 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - service_discovery_req: ServiceDiscoveryReq = msg + service_discovery_req: ServiceDiscoveryReq = cast(ServiceDiscoveryReq, msg) # TODO: Filter services based on # SupportedServiceIDs field in ServiceDiscoveryReq offered_energy_services = ( @@ -655,7 +663,7 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - service_detail_req: ServiceDetailReq = msg + service_detail_req: ServiceDetailReq = cast(ServiceDetailReq, msg) service_parameter_list = ( await self.comm_session.evse_controller.get_service_parameter_list( @@ -726,7 +734,7 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - service_selection_req: ServiceSelectionReq = msg + service_selection_req: ServiceSelectionReq = cast(ServiceSelectionReq, msg) valid, reason, res_code = self.check_selected_services(service_selection_req) if not valid: @@ -734,7 +742,7 @@ async def process_message( return energy_service_id = service_selection_req.selected_energy_service.service_id - + next_state: Type[State] = None if energy_service_id in (ServiceV20.AC.id, ServiceV20.AC_BPT.id): next_state = ACChargeParameterDiscovery elif energy_service_id in (ServiceV20.DC.id, ServiceV20.DC_BPT.id): @@ -895,7 +903,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [ScheduleExchangeReq, DCCableCheckReq, PowerDeliveryReq, SessionStopReq], False, @@ -915,7 +923,7 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - schedule_exchange_req: ScheduleExchangeReq = msg + schedule_exchange_req: ScheduleExchangeReq = cast(ScheduleExchangeReq, msg) scheduled_params, dynamic_params = None, None evse_processing = Processing.ONGOING @@ -992,7 +1000,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [PowerDeliveryReq, DCWeldingDetectionReq, SessionStopReq], False ) if not msg: @@ -1008,7 +1016,7 @@ async def process_message( ) return - power_delivery_req: PowerDeliveryReq = msg + power_delivery_req: PowerDeliveryReq = cast(PowerDeliveryReq, msg) next_state: Optional[Type[State]] = None header = MessageHeader( @@ -1204,21 +1212,21 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, [SessionStopReq], False) + msg: V2GMessageV20 = self.check_msg_v20(message, [SessionStopReq], False) if not msg: return - session_stop_req: SessionStopReq = msg + session_stop_req: SessionStopReq = cast(SessionStopReq, msg) evse_controller = self.comm_session.evse_controller # [V2G20-1477] : If EVSE supports ServiceRegotiation and EVCC requests # it in the SessionStopReq, the next state should be set to ServiceDiscoveryReq - next_state = Terminate + next_state: Type[State] = Terminate if ( session_stop_req.charging_session == ChargingSession.SERVICE_RENEGOTIATION and await evse_controller.service_renegotiation_supported() ): - next_state = ServiceDiscoveryReq + next_state = ServiceDiscovery session_stop_state = SessionStopAction.PAUSE elif session_stop_req.charging_session == ChargingSession.TERMINATE: session_stop_state = SessionStopAction.TERMINATE @@ -1285,7 +1293,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [ACChargeParameterDiscoveryReq, SessionStopReq], False ) if not msg: @@ -1295,7 +1303,9 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - ac_cpd_req: ACChargeParameterDiscoveryReq = msg + ac_cpd_req: ACChargeParameterDiscoveryReq = cast( + ACChargeParameterDiscoveryReq, msg + ) energy_service = self.comm_session.selected_energy_service.service ac_params, bpt_ac_params = None, None @@ -1371,7 +1381,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( # TODO A MeteringConfirmationReq can come in using the multiplexed side # stream. Need to figure out how to enable multiplexed communication message, @@ -1389,7 +1399,7 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - ac_charge_loop_req: ACChargeLoopReq = msg + ac_charge_loop_req: ACChargeLoopReq = cast(ACChargeLoopReq, msg) scheduled_params, dynamic_params = None, None bpt_scheduled_params, bpt_dynamic_params = None, None @@ -1501,7 +1511,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [DCChargeParameterDiscoveryReq, SessionStopReq], False ) if not msg: @@ -1511,7 +1521,9 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - dc_cpd_req: DCChargeParameterDiscoveryReq = msg + dc_cpd_req: DCChargeParameterDiscoveryReq = cast( + DCChargeParameterDiscoveryReq, msg + ) energy_service = self.comm_session.selected_energy_service.service dc_params, bpt_dc_params = None, None @@ -1588,7 +1600,9 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20(message, [DCCableCheckReq, SessionStopReq], False) + msg: V2GMessageV20 = self.check_msg_v20( + message, [DCCableCheckReq, SessionStopReq], False + ) if not msg: return @@ -1596,8 +1610,6 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - dc_cable_check_req: DCCableCheckReq = msg # noqa - if not self.cable_check_req_was_received: # First DCCableCheckReq received. Start cable check. await self.comm_session.evse_controller.start_cable_check() @@ -1674,7 +1686,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [DCPreChargeReq, PowerDeliveryReq], self.expecting_precharge_req, @@ -1686,10 +1698,10 @@ async def process_message( await PowerDelivery(self.comm_session).process_message(message, message_exi) return - precharge_req: DCPreChargeReq = msg + precharge_req: DCPreChargeReq = cast(DCPreChargeReq, msg) self.expecting_precharge_req = False - next_state = None + next_state: Type[StateSECC] = None if precharge_req.ev_processing == Processing.FINISHED: next_state = PowerDelivery else: @@ -1736,7 +1748,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [DCChargeLoopReq, PowerDeliveryReq], self.expecting_charge_loop_req ) if not msg: @@ -1746,7 +1758,6 @@ async def process_message( await PowerDelivery(self.comm_session).process_message(message, message_exi) return - dc_charge_loop_req: DCChargeLoopReq = msg # noqa self.expecting_charge_loop_req = False dc_charge_loop_res = await self.build_dc_charge_loop_res() @@ -1836,7 +1847,7 @@ async def process_message( ], message_exi: bytes = None, ): - msg = self.check_msg_v20( + msg: V2GMessageV20 = self.check_msg_v20( message, [DCWeldingDetectionReq, SessionStopReq], self.expecting_welding_detection_req, @@ -1848,7 +1859,6 @@ async def process_message( await SessionStop(self.comm_session).process_message(message, message_exi) return - welding_detection_req: DCWeldingDetectionReq = msg # noqa self.expecting_welding_detection_req = False welding_detection_res = DCWeldingDetectionRes( header=MessageHeader( diff --git a/iso15118/secc/states/iso15118_2_states.py b/iso15118/secc/states/iso15118_2_states.py index e59754ba..1043c916 100644 --- a/iso15118/secc/states/iso15118_2_states.py +++ b/iso15118/secc/states/iso15118_2_states.py @@ -7,7 +7,7 @@ import base64 import logging import time -from typing import List, Optional, Type, Union +from typing import List, Optional, Tuple, Type, Union from iso15118.secc.comm_session_handler import SECCCommunicationSession from iso15118.secc.controller.interface import ( @@ -30,7 +30,18 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) -from iso15118.shared.messages.datatypes import DCEVSEChargeParameter, DCEVSEStatus +from iso15118.shared.messages.datatypes import ( + DCEVSEChargeParameter, + DCEVSEStatus, + PVEVMaxCurrent, + PVEVMaxCurrentLimit, + PVEVMaxVoltage, + PVEVMaxVoltageLimit, + PVEVSEPresentCurrent, +) +from iso15118.shared.messages.din_spec.datatypes import ( + ResponseCode as ResponseCodeDINSPEC, +) from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC from iso15118.shared.messages.enums import ( AuthEnum, @@ -94,6 +105,9 @@ EnergyTransferModeList, Parameter, ParameterSet, +) +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 +from iso15118.shared.messages.iso15118_2.datatypes import ( SAScheduleList, SAScheduleTuple, ServiceCategory, @@ -105,6 +119,9 @@ SubCertificates, ) from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 +from iso15118.shared.messages.iso15118_20.common_types import ( + ResponseCode as ResponseCodeV20, +) from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) @@ -617,7 +634,7 @@ async def process_message( self.comm_session.selected_auth_option = AuthEnum( service_selection_req.selected_auth_option.value ) - self.comm_session.ev_session_context.auth_options: List[AuthEnum] = [ + self.comm_session.ev_session_context.auth_options = [ self.comm_session.selected_auth_option ] @@ -699,7 +716,9 @@ async def process_message( base64_certificate_install_req, Namespace.ISO_V2_MSG_DEF ) ) - certificate_installation_res: Base64 = Base64( + certificate_installation_res: Union[ + CertificateInstallationRes, Base64 + ] = Base64( message=base64_certificate_installation_res, message_name=CertificateInstallationRes.__name__, namespace=Namespace.ISO_V2_MSG_DEF, @@ -709,6 +728,7 @@ async def process_message( certificate_installation_res, signature, ) = self.generate_certificate_installation_res() + except Exception as e: error = f"Error building CertificateInstallationRes: {e}" logger.error(error) @@ -767,7 +787,7 @@ def validate_message_signature(self, message: V2GMessageV2) -> bool: def generate_certificate_installation_res( self, - ) -> (CertificateInstallationRes, Signature): + ) -> Tuple[CertificateInstallationRes, Signature]: # Here we create the CertificateInstallationRes message ourselves as we # have access to all certificates and private keys needed. # This is however not the real production case. @@ -964,12 +984,13 @@ async def process_message( await self.comm_session.evse_controller.is_authorized( id_token=payment_details_req.emaid, id_token_type=AuthorizationTokenType.EMAID, - certificate_chain=pem_certificate_chain, + certificate_chain=bytes(pem_certificate_chain, "utf-8"), hash_data=hash_data, ) ) - - response_code = ResponseCode.OK + response_code: Optional[ + Union[ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC] + ] = ResponseCode.OK if resp_status := current_authorization_status.certificate_response_status: # according to table 112 of ISO 15118-2, the Response code # for this message can only be one of the following: @@ -1167,7 +1188,9 @@ async def process_message( ) ) - response_code = ResponseCode.OK + response_code: Optional[ + Union[ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC] + ] = ResponseCode.OK if resp_status := current_authorization_status.certificate_response_status: # according to table 112 of ISO 15118-2, the Response code # for this message can only be one of the following: @@ -1317,8 +1340,12 @@ async def process_message( ac_evse_charge_params = ( await self.comm_session.evse_controller.get_ac_charge_params_v2() ) - ev_max_voltage = charge_params_req.ac_ev_charge_parameter.ev_max_voltage - ev_max_current = charge_params_req.ac_ev_charge_parameter.ev_max_current + ev_max_voltage: Union[ + PVEVMaxVoltageLimit, PVEVMaxVoltage + ] = charge_params_req.ac_ev_charge_parameter.ev_max_voltage + ev_max_current: Union[ + PVEVMaxCurrentLimit, PVEVMaxCurrent + ] = charge_params_req.ac_ev_charge_parameter.ev_max_current e_amount = charge_params_req.ac_ev_charge_parameter.e_amount ev_charge_params_limits = EVChargeParamsLimits( ev_max_voltage=ev_max_voltage, @@ -1401,7 +1428,7 @@ async def process_message( ) signature = None - next_state = None + next_state: Type[State] = None if sa_schedule_list: self.comm_session.offered_schedules = sa_schedule_list if charge_params_req.ac_ev_charge_parameter: @@ -1638,7 +1665,7 @@ async def process_message( logger.debug(f"ChargeProgress set to {power_delivery_req.charge_progress}") - next_state: Type[State] + next_state: Type[State] = None if power_delivery_req.charge_progress == ChargeProgress.START: # According to section 8.7.4 in ISO 15118-2, the EV enters into HLC-C # (High Level Controlled Charging) once PowerDeliveryRes(ResponseCode=OK) @@ -2008,7 +2035,7 @@ async def process_message( msg = self.check_msg_v2(message, [SessionStopReq]) if not msg: return - + next_state: Type[State] = None if msg.body.session_stop_req.charging_session == ChargingSession.PAUSE: next_state = Pause session_stop_state = SessionStopAction.PAUSE @@ -2301,9 +2328,16 @@ async def process_message( Protocol.ISO_15118_2 ) ) - present_current_in_a = present_current.value * 10**present_current.multiplier - target_current = precharge_req.ev_target_current - target_current_in_a = target_current.value * 10**target_current.multiplier + if isinstance(present_current, PVEVSEPresentCurrent): + present_current_in_a = ( + present_current.value * 10**present_current.multiplier + ) + target_current = precharge_req.ev_target_current + target_current_in_a = target_current.value * 10**target_current.multiplier + else: + present_current_in_a = present_current.value + target_current = precharge_req.ev_target_current + target_current_in_a = target_current.value if present_current_in_a > 2 or target_current_in_a > 2: self.stop_state_machine( diff --git a/iso15118/secc/states/secc_state.py b/iso15118/secc/states/secc_state.py index 3b982814..159cd7be 100644 --- a/iso15118/secc/states/secc_state.py +++ b/iso15118/secc/states/secc_state.py @@ -36,6 +36,7 @@ from iso15118.shared.messages.iso15118_20.common_types import ( ResponseCode as ResponseCodeV20, ) +from iso15118.shared.messages.iso15118_20.common_types import V2GMessage from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) @@ -235,7 +236,7 @@ def check_msg( ] if isinstance(message, V2GMessageV2) or isinstance(message, V2GMessageDINSPEC): # ISO 15118-2 - msg_body = message.body.get_message() + msg_body = message.body.get_message() # type: ignore else: # SupportedAppProtocolReq, V2GRequestV20 (ISO 15118-20) msg_body = message @@ -317,7 +318,12 @@ def stop_state_machine( self.comm_session.stop_reason = StopNotification( False, reason, self.comm_session.writer.get_extra_info("peername") ) - + msg_type: Optional[ + Union[ + Type[Union[BodyBaseDINSPEC, BodyBaseV2, V2GMessage]], + SupportedAppProtocolReq, + ] + ] = None if isinstance(faulty_request, V2GMessageV2): msg_type = get_msg_type(str(faulty_request)) msg_namespace = Namespace.ISO_V2_MSG_DEF @@ -328,8 +334,8 @@ def stop_state_machine( msg_type = type(faulty_request) msg_namespace = Namespace.ISO_V20_BASE elif isinstance(faulty_request, SupportedAppProtocolReq): - msg_namespace = Namespace.SAP msg_type = faulty_request + msg_namespace = Namespace.SAP else: msg_type = message_body_type msg_namespace = namespace diff --git a/iso15118/secc/transport/tcp_server.py b/iso15118/secc/transport/tcp_server.py index cfa4e139..0ba81c59 100644 --- a/iso15118/secc/transport/tcp_server.py +++ b/iso15118/secc/transport/tcp_server.py @@ -1,7 +1,7 @@ import asyncio import logging import socket -from typing import Tuple +from typing import Optional, Tuple from iso15118.shared.network import get_link_local_full_addr, get_tcp_port from iso15118.shared.notifications import TCPClientNotification @@ -22,12 +22,12 @@ class TCPServer(asyncio.Protocol): ipv6_address_host: str def __init__(self, session_handler_queue: asyncio.Queue, iface: str) -> None: - self._session_handler_queue = session_handler_queue + self._session_handler_queue: asyncio.Queue = session_handler_queue # The dynamic TCP port number in the range of (49152-65535) - self.port = get_tcp_port() - self.iface = iface - self.server = None - self.is_tls_enabled = False + self.port: int = get_tcp_port() + self.iface: str = iface + self.server: Optional[asyncio.Server] = None + self.is_tls_enabled: bool = False async def start_tls(self, ready_event: asyncio.Event): """ diff --git a/iso15118/secc/transport/udp_server.py b/iso15118/secc/transport/udp_server.py index 263065e8..10d338eb 100644 --- a/iso15118/secc/transport/udp_server.py +++ b/iso15118/secc/transport/udp_server.py @@ -47,7 +47,7 @@ def __init__(self, session_handler_queue: asyncio.Queue, iface: str): self.pause_server: bool = False @staticmethod - async def _create_socket(iface: str) -> "socket": + async def _create_socket(iface: str) -> socket.socket: """ This method is necessary because Python does not allow async def __init__. @@ -112,7 +112,9 @@ async def start(self, ready_event: asyncio.Event): # (see loop.create_datagram_endpoint()) loop = asyncio.get_running_loop() # One protocol instance will be created to serve all client requests - self._transport, _ = await loop.create_datagram_endpoint( + self._transport, _ = await loop.create_datagram_endpoint( # type: ignore + # DatagramTransport is a subclass of BaseTransport, + # which is not recognized by mypy lambda: self, sock=await self._create_socket(self.iface), ) diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index db0665be..c424e37f 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -140,11 +140,11 @@ def get_exi_ns( elif self.comm_session.protocol == Protocol.DIN_SPEC_70121: return Namespace.DIN_MSG_DEF elif self.comm_session.protocol.ns.startswith(Namespace.ISO_V20_BASE): - return self.v20_payload_type_to_namespace.get( - payload_type.value, Namespace.ISO_V20_COMMON_MSG - ) - else: - return Namespace.ISO_V20_COMMON_MSG + if isinstance(payload_type, ISOV20PayloadTypes): + return self.v20_payload_type_to_namespace.get( + payload_type, Namespace.ISO_V20_COMMON_MSG + ) + return Namespace.ISO_V20_COMMON_MSG async def process_message(self, message: bytes): """ @@ -203,25 +203,18 @@ async def process_message(self, message: bytes): ) if hasattr(self.comm_session, "evse_id"): - logger.trace( + logger.trace( # type: ignore[attr-defined] f"{self.comm_session.evse_id}:::" f"{v2gtp_msg.payload.hex()}:::" - f"{self.get_exi_ns(v2gtp_msg.payload_type).value}" + f"{self.get_exi_ns(v2gtp_msg.payload_type)}" ) except V2GMessageValidationError as exc: - self.comm_session.current_state.stop_state_machine( - exc.reason, - None, - exc.response_code, - exc.message, - self.get_exi_ns(v2gtp_msg.payload_type), - ) logger.error( f"EXI message (ns={self.get_exi_ns(v2gtp_msg.payload_type)}) " f"where validation failed: {v2gtp_msg.payload.hex()}" ) - return + raise exc except EXIDecodingError as exc: logger.exception(f"{exc}") logger.error( @@ -377,7 +370,8 @@ def save_session_info(self): async def _update_state_info(self, state: State): if hasattr(self.comm_session, "evse_controller"): - await self.comm_session.evse_controller.set_present_protocol_state(state) + evse_controller = self.comm_session.evse_controller # type: ignore + await evse_controller.set_present_protocol_state(state) async def stop(self, reason: str): """ @@ -411,7 +405,8 @@ 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.update_data_link(terminate_or_pause) + evse_controller = self.comm_session.evse_controller # type: ignore + await evse_controller.update_data_link(terminate_or_pause) logger.info(f"{terminate_or_pause}d the data link") await asyncio.sleep(3) try: @@ -529,7 +524,7 @@ async def rcv_loop(self, timeout: float): if isinstance(exc, InvalidV2GTPMessageError): additional_info = f": {exc}" - stop_reason: str = ( + stop_reason = ( f"{exc.__class__.__name__} occurred while processing message " f"{message_name} in state {str(self.current_state)}" f":{additional_info}" @@ -545,7 +540,7 @@ async def rcv_loop(self, timeout: float): self.session_handler_queue.put_nowait(self.stop_reason) return except (AttributeError, ValueError) as exc: - stop_reason: str = ( + stop_reason = ( f"{exc.__class__.__name__} occurred while processing message in " f"state {str(self.current_state)}: {exc}" ) diff --git a/iso15118/shared/exceptions.py b/iso15118/shared/exceptions.py index 326b8797..e1eb42f7 100644 --- a/iso15118/shared/exceptions.py +++ b/iso15118/shared/exceptions.py @@ -1,6 +1,10 @@ -from typing import Any +from typing import Any, Union +from iso15118.shared.messages.din_spec.datatypes import ResponseCode as ResponseCodeDIN from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode +from iso15118.shared.messages.iso15118_20.common_types import ( + ResponseCode as ResponseCodeV20, +) class InvalidInterfaceError(Exception): @@ -246,7 +250,12 @@ def __init__(self): class V2GMessageValidationError(Exception): """Is thrown if message validation is failed""" - def __init__(self, reason: str, response_code: ResponseCode, message: Any): + def __init__( + self, + reason: str, + response_code: Union[ResponseCode, ResponseCodeV20, ResponseCodeDIN], + message: Any, + ): Exception.__init__(self) self.reason = reason self.response_code = response_code diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 69763ad5..3e0de975 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -1,7 +1,7 @@ import json import logging from base64 import b64decode, b64encode -from typing import Union +from typing import Optional, Type, Union from pydantic import ValidationError @@ -17,9 +17,11 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) +from iso15118.shared.messages.din_spec.body import BodyBase as BodyBaseDINSPEC from iso15118.shared.messages.din_spec.body import get_msg_type as get_msg_type_dinspec from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC from iso15118.shared.messages.enums import Namespace +from iso15118.shared.messages.iso15118_2.body import BodyBase as BodyBaseV2 from iso15118.shared.messages.iso15118_2.body import get_msg_type from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 @@ -53,6 +55,7 @@ SessionStopReq, SessionStopRes, ) +from iso15118.shared.messages.iso15118_20.common_types import V2GMessage from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) @@ -323,7 +326,7 @@ def from_exi( # When parsing the dict, we need to remove the first key, which is # the message name itself (e.g. SessionSetupReq) msg_dict = decoded_dict[msg_name] - msg_classes_dict = { + msg_classes_dict: dict[str, Type[V2GMessage]] = { "SessionSetupReq": SessionSetupReq, "SessionSetupRes": SessionSetupRes, "AuthorizationSetupReq": AuthorizationSetupReq, @@ -360,7 +363,7 @@ def from_exi( "SessionStopRes": SessionStopRes, # TODO add all the other message types and states } - msg_class = msg_classes_dict.get(msg_name) + msg_class: Type[V2GMessage] = msg_classes_dict.get(msg_name) if not msg_class: logger.error( "Unable to identify message to parse given the message " @@ -372,6 +375,17 @@ def from_exi( raise EXIDecodingError("Can't identify protocol to use for decoding") except ValidationError as exc: + msg_type: Optional[ + Type[ + Union[ + BodyBaseDINSPEC, + BodyBaseV2, + V2GMessage, + SupportedAppProtocolReq, + SupportedAppProtocolRes, + ] + ], + ] = None if namespace == Namespace.ISO_V2_MSG_DEF: msg_name = next(iter(decoded_dict["V2G_Message"]["Body"])) msg_type = get_msg_type(msg_name) diff --git a/iso15118/shared/messages/datatypes.py b/iso15118/shared/messages/datatypes.py index adfb62ce..f5789f21 100644 --- a/iso15118/shared/messages/datatypes.py +++ b/iso15118/shared/messages/datatypes.py @@ -47,7 +47,8 @@ def validate_value_range(cls, values): calculated_value = value * 10**multiplier if calculated_value > cls._max_limit or calculated_value < cls._min_limit: raise ValueError( - f"{cls.__name__[2:]} value limit exceeded: {calculated_value} \n" + f"{cls.__name__[2:] }" # type: ignore[attr-defined] + f"value limit exceeded: {calculated_value} \n" f"Max: {cls._max_limit} \n" f"Min: 0" ) diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 1aa933f6..654107f0 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -561,7 +561,7 @@ def get_msg_type(msg_name: str) -> Optional[Type[BodyBase]]: Returns: The message type corresponding to the given message name """ - msg_dict = { + msg_dict: dict[str, Type[BodyBase]] = { "SessionSetupReq": SessionSetupReq, "SessionSetupRes": SessionSetupRes, "ServiceDiscoveryReq": ServiceDiscoveryReq, diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 92996dcc..0246738f 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -734,7 +734,7 @@ def get_msg_type(msg_name: str) -> Optional[Type[BodyBase]]: Returns: The message type corresponding to the given message name """ - msg_dict = { + msg_dict: dict[str, Type[BodyBase]] = { "SessionSetupReq": SessionSetupReq, "SessionSetupRes": SessionSetupRes, "ServiceDiscoveryReq": ServiceDiscoveryReq, diff --git a/iso15118/shared/messages/iso15118_2/datatypes.py b/iso15118/shared/messages/iso15118_2/datatypes.py index 34645da8..a36132ce 100644 --- a/iso15118/shared/messages/iso15118_2/datatypes.py +++ b/iso15118/shared/messages/iso15118_2/datatypes.py @@ -15,6 +15,7 @@ from typing import List from pydantic import Field, conbytes, constr, root_validator, validator +from typing_extensions import TypeAlias from iso15118.shared.messages import BaseModel from iso15118.shared.messages.datatypes import ( @@ -50,9 +51,9 @@ # https://pydantic-docs.helpmanual.io/usage/types/#constrained-types # constrained types # Check Annex C.6 or the certificateType in V2G_CI_MsgDataTypes.xsd -Certificate = conbytes(max_length=800) +Certificate: TypeAlias = conbytes(max_length=800) # type: ignore # Check Annex C.6 or the eMAIDType in V2G_CI_MsgDataTypes.xsd -eMAID = constr(min_length=14, max_length=15) +eMAID: TypeAlias = constr(min_length=14, max_length=15) # type: ignore class EVChargeParameter(BaseModel): diff --git a/iso15118/shared/messages/iso15118_20/common_messages.py b/iso15118/shared/messages/iso15118_20/common_messages.py index 1ff73222..8805c615 100644 --- a/iso15118/shared/messages/iso15118_20/common_messages.py +++ b/iso15118/shared/messages/iso15118_20/common_messages.py @@ -12,7 +12,7 @@ """ from dataclasses import dataclass from enum import Enum -from typing import List +from typing import List, Tuple from pydantic import Field, root_validator, validator @@ -1295,8 +1295,8 @@ class MatchedService: is_free: bool parameter_sets: List[ParameterSet] - def service_parameter_set_ids(self) -> [(int, int)]: - service_param_set_ids: List[(int, int)] = [] + def service_parameter_set_ids(self) -> List[Tuple[int, int]]: + service_param_set_ids: List[Tuple[int, int]] = [] for parameter_set in self.parameter_sets: service_param_set_ids.append((self.service.id, parameter_set.id)) return service_param_set_ids diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index 684b704b..b8f33d66 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -15,6 +15,7 @@ from typing import List from pydantic import Field, conbytes, conint, constr, validator +from typing_extensions import TypeAlias from iso15118.shared.messages import BaseModel from iso15118.shared.messages.enums import ( @@ -29,15 +30,15 @@ # https://pydantic-docs.helpmanual.io/usage/types/#constrained-types # Check Annex C.1 or V2G_CI_CommonTypes.xsd # certificateType (a DER encoded X.509 certificate) -Certificate = conbytes(max_length=1600) +Certificate: TypeAlias = conbytes(max_length=1600) # type: ignore # identifierType -Identifier = constr(max_length=255) +Identifier: TypeAlias = constr(max_length=255) # type: ignore # numericIDType -NumericID = conint(ge=1, le=UINT_32_MAX) +NumericID: TypeAlias = conint(ge=1, le=UINT_32_MAX) # type: ignore # nameType -Name = constr(max_length=80) +Name: TypeAlias = constr(max_length=80) # type: ignore # descriptionType -Description = constr(max_length=160) +Description: TypeAlias = constr(max_length=160) # type: ignore class MessageHeader(BaseModel): diff --git a/iso15118/shared/messages/sdp.py b/iso15118/shared/messages/sdp.py index 6a7d32bd..6eca522f 100644 --- a/iso15118/shared/messages/sdp.py +++ b/iso15118/shared/messages/sdp.py @@ -4,6 +4,11 @@ from typing import Union from iso15118.shared.exceptions import InvalidSDPRequestError, InvalidSDPResponseError +from iso15118.shared.messages.enums import ( + DINPayloadTypes, + ISOV2PayloadTypes, + ISOV20PayloadTypes, +) logger = logging.getLogger(__name__) @@ -94,7 +99,9 @@ def __init__(self, security: Security, transport_protocol: Transport): self.security = security self.transport_protocol = transport_protocol # SDPRequest has the same payload type in -2 and -20 - self.payload_type = 0x9000 + self.payload_type: Union[ + DINPayloadTypes, ISOV2PayloadTypes, ISOV20PayloadTypes + ] = ISOV2PayloadTypes.SDP_REQUEST def to_payload(self) -> bytes: message = self.security.to_bytes(1, "big") + self.transport_protocol.to_bytes( diff --git a/iso15118/shared/network.py b/iso15118/shared/network.py index 5f78f63f..9d7fc6e6 100644 --- a/iso15118/shared/network.py +++ b/iso15118/shared/network.py @@ -101,7 +101,7 @@ async def _get_full_ipv6_address(host: str, port: int) -> Tuple[str, int, int, i ) # We only need the socket_address here _, _, _, _, socket_address = addr_info_list[0] - return socket_address + return socket_address # type: ignore[return-value] def validate_nic(nic: str) -> None: diff --git a/iso15118/shared/security.py b/iso15118/shared/security.py index 2608a82b..a9264b97 100644 --- a/iso15118/shared/security.py +++ b/iso15118/shared/security.py @@ -6,7 +6,7 @@ from datetime import datetime from enum import Enum, auto from ssl import DER_cert_to_PEM_cert, SSLContext, SSLError, VerifyMode -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union, cast from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.hazmat.backends.openssl.backend import Backend @@ -35,6 +35,7 @@ ExtensionNotFound, ExtensionOID, NameOID, + extensions, load_der_x509_certificate, ) from cryptography.x509.ocsp import OCSPRequestBuilder @@ -477,8 +478,8 @@ def log_certs_details(certs: List[bytes]): def verify_certs( leaf_cert_bytes: bytes, - sub_ca_certs: List[bytes], - root_ca_cert: bytes, + sub_ca_certs_bytes: List[bytes], + root_ca_cert_bytes: bytes, private_environment: bool = False, ): """ @@ -515,10 +516,10 @@ def verify_certs( leaf_cert = load_der_x509_certificate(leaf_cert_bytes) sub_ca2_cert = None sub_ca1_cert = None - root_ca_cert = load_der_x509_certificate(root_ca_cert) + root_ca_cert = load_der_x509_certificate(root_ca_cert_bytes) sub_ca_der_certs: List[Certificate] = [ - load_der_x509_certificate(cert) for cert in sub_ca_certs + load_der_x509_certificate(cert) for cert in sub_ca_certs_bytes ] # Step 1.a: Categorize the sub-CA certificates into sub-CA 1 and sub-CA 2. @@ -532,9 +533,17 @@ def verify_certs( # TODO We also need to check each certificate's attributes for # compliance with the corresponding certificate profile for cert in sub_ca_der_certs: - path_len = cert.extensions.get_extension_for_oid( - ExtensionOID.BASIC_CONSTRAINTS - ).value.path_length + try: + basic_contrains = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ).value + path_len = 0 + if isinstance(basic_contrains, extensions.BasicConstraints): + path_len = basic_contrains.path_length + except ExtensionNotFound: + raise CertAttributeError( + subject=cert.subject.__str__(), attr="PathLength", invalid_value="None" + ) if path_len == 0: if sub_ca2_cert: logger.error( @@ -731,7 +740,9 @@ def get_cert_cn(der_cert: bytes) -> str: """ cert = load_der_x509_certificate(der_cert) cn = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME).pop() - return cn.value + if isinstance(cn.value, str): + return cn.value + return cn.value.decode("utf-8") def get_cert_issuer_serial(cert_path: str) -> Tuple[str, int]: @@ -1219,26 +1230,26 @@ def derive_certificate_hash_data( Only SHA256, SHA384, and SHA512 are allowed. (3.42 HashAlgorithmEnumType, p. 403, OCPP 2.0.1 Part 2) """ - certificate: Certificate = load_der_x509_certificate(certificate) - issuer_certificate = load_der_x509_certificate(issuer_certificate) + cert: Certificate = load_der_x509_certificate(certificate) + issuer_cert = load_der_x509_certificate(issuer_certificate) builder = OCSPRequestBuilder().add_certificate( - certificate, issuer_certificate, certificate.signature_hash_algorithm + cert, issuer_cert, cert.signature_hash_algorithm ) ocsp_request = builder.build() # For the hash algorithm, convert to the naming used in OCPP. # Only SHA256, SHA384, and SHA512 are allowed in OCPP 2.0.1. - hash_algorithm_for_ocpp = certificate.signature_hash_algorithm.name.upper() + hash_algorithm_for_ocpp = cert.signature_hash_algorithm.name.upper() if hash_algorithm_for_ocpp not in {"SHA256", "SHA384", "SHA512"}: raise CertAttributeError( - subject=certificate.subject.__str__(), + subject=cert.subject.__str__(), attr="HashAlgorithm", invalid_value=hash_algorithm_for_ocpp, ) try: - responder_url = get_ocsp_url_for_certificate(certificate) + responder_url = get_ocsp_url_for_certificate(cert) except (ExtensionNotFound, OCSPServerNotFoundError) as e: raise e @@ -1302,9 +1313,12 @@ def get_ocsp_url_for_certificate(certificate: Certificate) -> str: OCSPServerNotFoundError: if OCSP server entry is not found """ try: - auth_inf_access = certificate.extensions.get_extension_for_oid( - ExtensionOID.AUTHORITY_INFORMATION_ACCESS - ).value + auth_inf_access = cast( + extensions.AuthorityInformationAccess, + certificate.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ).value, + ) except ExtensionNotFound: logger.debug( f"Authority Information Access extension not " diff --git a/iso15118/shared/states.py b/iso15118/shared/states.py index 469c6195..3a99277a 100644 --- a/iso15118/shared/states.py +++ b/iso15118/shared/states.py @@ -262,7 +262,7 @@ def create_next_message( V2GMessageDINSPEC, ] = None if isinstance(next_msg, BodyBaseDINSPEC): - note: Union[NotificationDINSPEC, None] = None + note_dinspec: Union[NotificationDINSPEC, None] = None if ( self.comm_session.stop_reason and not self.comm_session.stop_reason.successful @@ -273,17 +273,21 @@ def create_next_message( fault_msg = self.comm_session.stop_reason.reason[:62] + ".." else: fault_msg = self.comm_session.stop_reason.reason - note = NotificationDINSPEC( + note_dinspec = NotificationDINSPEC( fault_code=FaultCodeDINSPEC.PARSING_ERROR, fault_msg=fault_msg ) - header = MessageHeaderDINSPEC( + header_dinspec: MessageHeaderDINSPEC = MessageHeaderDINSPEC( session_id=self.comm_session.session_id, signature=signature, - notification=note, + notification=note_dinspec, + ) + body_dinspec: BodyDINSPEC = BodyDINSPEC.parse_obj( + {str(next_msg): next_msg.dict()} ) - body = BodyDINSPEC.parse_obj({str(next_msg): next_msg.dict()}) try: - to_be_exi_encoded = V2GMessageDINSPEC(header=header, body=body) + to_be_exi_encoded = V2GMessageDINSPEC( + header=header_dinspec, body=body_dinspec + ) except ValidationError as exc: logger.exception(exc) raise exc @@ -303,12 +307,12 @@ def create_next_message( note = Notification( fault_code=FaultCode.PARSING_ERROR, fault_msg=fault_msg ) - header = MessageHeaderV2( + header: MessageHeaderV2 = MessageHeaderV2( session_id=self.comm_session.session_id, signature=signature, notification=note, ) - body = Body.parse_obj({str(next_msg): next_msg.dict()}) + body: Body = Body.parse_obj({str(next_msg): next_msg.dict()}) try: to_be_exi_encoded = V2GMessageV2(header=header, body=body) except ValidationError as exc: @@ -338,7 +342,7 @@ def create_next_message( exi_payload = EXI().to_exi(to_be_exi_encoded, namespace) if hasattr(self.comm_session, "evse_id"): - logger.trace( + logger.trace( # type: ignore[attr-defined] f"{self.comm_session.evse_id}:::" f"{exi_payload.hex()}:::" f"{namespace.value}" diff --git a/iso15118/shared/utils.py b/iso15118/shared/utils.py index bc050ff7..19536bc7 100644 --- a/iso15118/shared/utils.py +++ b/iso15118/shared/utils.py @@ -34,8 +34,7 @@ def load_requested_protocols(read_protocols: Optional[List[str]]) -> List[Protoc f"No supported protocols configured. Supported protocols are " f"{supported_protocols} and could be configured in evcc_config.json" ) - supported_protocols = [Protocol[x] for x in valid_protocols] - return supported_protocols + return [Protocol[name] for name in valid_protocols if name in Protocol.__members__] def load_requested_energy_services( @@ -60,8 +59,9 @@ def load_requested_energy_services( f"No supported energy services configured. Supported energy services are " f"{supported_services} and could be configured in evcc_config.json" ) - supported_services = [ServiceV20[x] for x in valid_services] - return supported_services + return [ + ServiceV20[name] for name in valid_services if name in ServiceV20.__members__ + ] def load_requested_auth_modes(read_auth_modes: Optional[List[str]]) -> List[AuthEnum]: @@ -111,16 +111,18 @@ async def wait_for_tasks( for task in await_tasks: if not isinstance(task, asyncio.Task): - task = asyncio.create_task(task) - tasks.append(task) + new_task = asyncio.create_task(task) + tasks.append(new_task) + else: + tasks.append(task) done, pending = await asyncio.wait(tasks, return_when=return_when) - for task in pending: - await cancel_task(task) + for pending_task in pending: + await cancel_task(pending_task) - for task in done: + for done_task in done: try: - task.result() + done_task.result() except Exception as e: logger.exception(e) diff --git a/poetry.lock b/poetry.lock index 614a1eaa..f080bad1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiofile" -version = "3.8.7" +version = "3.8.8" description = "Asynchronous file operations." optional = false python-versions = ">=3.7, <4" files = [ - {file = "aiofile-3.8.7-py3-none-any.whl", hash = "sha256:4c38991b1227e221296fa05bbc95bffba9c203fef1ce09ad3076cfe7b61842c7"}, - {file = "aiofile-3.8.7.tar.gz", hash = "sha256:a8f9dec17282b3583337c4ef2d1a67f33072ab80dd03608041ed9e71b88dc521"}, + {file = "aiofile-3.8.8-py3-none-any.whl", hash = "sha256:41e8845cce055779cd77713d949a339deb012eab605b857765e8f8e52a5ed811"}, + {file = "aiofile-3.8.8.tar.gz", hash = "sha256:41f3dc40bd730459d58610476e82e5efb2f84ae6e9fa088a9545385d838b8a43"}, ] [package.dependencies] @@ -76,32 +76,27 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "caio" -version = "0.9.12" +version = "0.9.13" description = "Asynchronous file IO for Linux MacOS or Windows." optional = false python-versions = ">=3.7, <4" files = [ - {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"}, + {file = "caio-0.9.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a921c0514ea2bb161d9d9c4940d409a2431cc6bab89abbf20fb16fdc6964f650"}, + {file = "caio-0.9.13-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:0a04bcac15b0c18057ec57dd9666c4d5c1058958a8db5191b46a1e54931c4a6e"}, + {file = "caio-0.9.13-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18307046421f4a8cac85c3d84aa941ab283fbbf0e57f0e5f81a306a057668f43"}, + {file = "caio-0.9.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:789f8b55f4a2b46be14361df3ac8d14b6c8f0a3730badd70cb1b7778fcdc7039"}, + {file = "caio-0.9.13-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a914684bad2a757cf013ae88d785d81659a3add1885bad60cd20bfbd3068bd5a"}, + {file = "caio-0.9.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6577cec932011db441a87bb35051a0ae8c6f970ea1af165cef3106746ae9a52"}, + {file = "caio-0.9.13-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:e13c1b882f1c16bc15441d4cfe3e7e6b58b66cb5cf07a0ce46103ff9d0c1d16b"}, + {file = "caio-0.9.13-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:114d6c158dc592849532fd0992cbfb85430f4f6e48323a3990c39e4741f81a25"}, + {file = "caio-0.9.13-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9c5f115c6b5e032f00f35d5da69f27322272b1cb8d51a21da1c2444bd1926beb"}, + {file = "caio-0.9.13-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:48f2c32f67923c728f4b79e03e4a0fc8dc175aa079a1e301773ab7d6baaa571f"}, + {file = "caio-0.9.13-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7b6eafa11a446dced3cd10342140e618acb548b513aaf3a1fa4c86451d3856"}, + {file = "caio-0.9.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b1e4252b783ad25f15eeff6e412f7e9ec9409e7b4a81fd2893dc1ce1819385ff"}, + {file = "caio-0.9.13-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:a07c9933543d18f98525f34a786700134403f13050a5bd22a642629e2ab410d4"}, + {file = "caio-0.9.13-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a090ab82f21b310b56fa6afe04b3be92a0d1577a2e57127b61426c9dd6df34be"}, + {file = "caio-0.9.13-py3-none-any.whl", hash = "sha256:582cbfc6e203d1dedf662ba972a94db6e744fe0b6bb9e02922b0f86803006fc9"}, + {file = "caio-0.9.13.tar.gz", hash = "sha256:26f1e08a442bef4526a66142ea4e325e22dca8f040800aecb3caf8fae0589e98"}, ] [package.extras] @@ -185,13 +180,13 @@ pycparser = "*" [[package]] name = "click" -version = "8.1.6" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -210,63 +205,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.0" +version = "7.3.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, - {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, - {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, - {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, - {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, - {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, - {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, - {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, - {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, - {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, - {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, - {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, - {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, - {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, ] [package.dependencies] @@ -531,13 +526,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -668,13 +663,13 @@ files = [ [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.1-py3-none-any.whl", hash = "sha256:460c9a59b14e27c602eb5ece2e47bec99dc5fc5f6513cf924a7d03a578991b1f"}, + {file = "pytest-7.4.1.tar.gz", hash = "sha256:2f2301e797521b23e4d2585a0a3d7b5e50fdddaaf7e7d6773ea26ddb17c213ab"}, ] [package.dependencies] @@ -785,6 +780,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "types-toml" +version = "0.10.8.7" +description = "Typing stubs for toml" +optional = false +python-versions = "*" +files = [ + {file = "types-toml-0.10.8.7.tar.gz", hash = "sha256:58b0781c681e671ff0b5c0319309910689f4ab40e8a2431e205d70c94bb6efb1"}, + {file = "types_toml-0.10.8.7-py3-none-any.whl", hash = "sha256:61951da6ad410794c97bec035d59376ce1cbf4453dc9b6f90477e81e4442d631"}, +] + [[package]] name = "typing-extensions" version = "4.7.1" @@ -799,4 +805,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "3a699d4a9db0afa00f4c708ed7bd22182a1996a189ea16bfd096466a64859bb0" +content-hash = "d54ccf62c594aebd7e827dbeb6bfbc4b7ea6872aeae42ab91fdc5d1d1744e99e" diff --git a/pyproject.toml b/pyproject.toml index f160f22f..25d76db8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,8 @@ cryptography = "41.0.1" pydantic = "^1.9.0" psutil = "^5.9.0" py4j = "^0.10.9" +types-toml = "^0.10.8.7" +mypy-extensions = "^1.0.0" [tool.poetry.group.dev.dependencies] pytest = "^7.1.1" diff --git a/tests/dinspec/evcc/test_dinspec_evcc_states.py b/tests/dinspec/evcc/test_dinspec_evcc_states.py index 8b67dd11..df520b90 100644 --- a/tests/dinspec/evcc/test_dinspec_evcc_states.py +++ b/tests/dinspec/evcc/test_dinspec_evcc_states.py @@ -61,7 +61,7 @@ def _comm_session(self, comm_evcc_session_mock): self.comm_session_mock.selected_energy_mode = EnergyTransferModeEnum.DC_CORE self.comm_session_mock.selected_auth_option = AuthEnum.EIM_V2 self.comm_session_mock.writer = MockWriter() - self.comm_session_mock.ongoing_timer: float = -1 + self.comm_session_mock.ongoing_timer = -1 @pytest.fixture(autouse=True) def _is_welding_detection_complete(self): diff --git a/tests/iso15118_2/secc/messages/15118_2_invalid_messages.py b/tests/iso15118_2/secc/messages/15118_2_invalid_messages.py index 1b6bf853..8f528c6d 100644 --- a/tests/iso15118_2/secc/messages/15118_2_invalid_messages.py +++ b/tests/iso15118_2/secc/messages/15118_2_invalid_messages.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from iso15118.shared.exceptions import V2GMessageValidationError -from iso15118.shared.messages.iso15118_2.body import Body, ChargeParameterDiscoveryReq +from iso15118.shared.messages.iso15118_2.body import Body from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage @@ -32,7 +32,7 @@ class InvalidV2GMessage: '"Wh","Value":200},"EVEnergyRequest":{"Multiplier":3,"Unit":"Wh",' '"Value":160},"FullSOC":99,"BulkSOC":80}}}}}' ), - ChargeParameterDiscoveryReq, + Body(), ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, ) ), diff --git a/tests/iso15118_2/secc/states/test_iso15118_2_states.py b/tests/iso15118_2/secc/states/test_iso15118_2_states.py index 77e7da53..2cfea9d9 100644 --- a/tests/iso15118_2/secc/states/test_iso15118_2_states.py +++ b/tests/iso15118_2/secc/states/test_iso15118_2_states.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import List +from typing import List, cast from unittest.mock import AsyncMock, Mock, patch import pytest @@ -45,6 +45,7 @@ ServiceID, ServiceName, ) +from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 from iso15118.shared.security import get_random_bytes from iso15118.shared.states import Pause from tests.iso15118_2.secc.states.test_messages import ( @@ -306,6 +307,8 @@ async def test_authorization_next_state_on_authorization_request( message=get_dummy_v2g_message_authorization_req() ) assert authorization.next_state == expected_next_state + authorization.message = cast(V2GMessageV2, authorization.message) + assert ( authorization.message.body.authorization_res.response_code == expected_response_code @@ -327,6 +330,7 @@ async def test_authorization_req_gen_challenge_invalid(self): message=get_dummy_v2g_message_authorization_req(id, gen_challenge) ) assert authorization.next_state == Terminate + assert isinstance(authorization.message, V2GMessageV2) assert ( authorization.message.body.authorization_res.response_code == ResponseCode.FAILED_CHALLENGE_INVALID @@ -361,6 +365,7 @@ async def test_charge_parameter_discovery_res_v2g2_303(self): charging_duration = ( charge_parameter_discovery_req_departure_time_set.body.charge_parameter_discovery_req.ac_ev_charge_parameter.departure_time # noqa ) + assert isinstance(charge_parameter_discovery.message, V2GMessageV2) assert ( charge_parameter_discovery.message.body.charge_parameter_discovery_res @@ -404,6 +409,8 @@ async def test_charge_parameter_discovery_res_v2g2_304(self): await charge_parameter_discovery.process_message( message=get_charge_parameter_discovery_req_message_no_departure_time() ) + assert isinstance(charge_parameter_discovery.message, V2GMessageV2) + assert ( charge_parameter_discovery.message.body.charge_parameter_discovery_res is not None @@ -444,10 +451,12 @@ async def test_charge_parameter_discovery_res_v2g2_761(self): await charge_parameter_discovery.process_message( message=get_charge_parameter_discovery_req_message_no_departure_time() ) + assert isinstance(charge_parameter_discovery.message, V2GMessageV2) assert ( charge_parameter_discovery.message.body.charge_parameter_discovery_res is not None ) + charge_parameter_discovery_res = ( charge_parameter_discovery.message.body.charge_parameter_discovery_res ) @@ -514,6 +523,8 @@ async def test_charge_parameter_discovery_req_v2g2_225( await power_delivery.process_message(message=power_delivery_message) assert power_delivery.next_state is expected_state + assert isinstance(power_delivery.message, V2GMessageV2) + assert ( power_delivery.message.body.power_delivery_res.response_code is expected_response_code @@ -550,6 +561,7 @@ async def test_service_discovery_req_unexpected_state(self): message=get_dummy_v2g_message_service_discovery_req() ) assert service_discovery.next_state is Terminate + assert isinstance(service_discovery.message, V2GMessageV2) assert ( service_discovery.message.body.service_discovery_res.response_code is ResponseCode.FAILED_SEQUENCE_ERROR @@ -559,6 +571,7 @@ async def test_charging_status_evse_status(self): charging_status = ChargingStatus(self.comm_session) self.comm_session.selected_schedule = 1 await charging_status.process_message(message=get_dummy_charging_status_req()) + assert isinstance(charging_status.message, V2GMessageV2) charging_status_res = charging_status.message.body.charging_status_res assert charging_status_res.ac_evse_status == ACEVSEStatus( @@ -580,6 +593,7 @@ async def get_ac_evse_status_patch(): self.comm_session.evse_controller.get_ac_evse_status = get_ac_evse_status_patch await charging_status.process_message(message=get_dummy_charging_status_req()) + assert isinstance(charging_status.message, V2GMessageV2) charging_status_res = charging_status.message.body.charging_status_res assert charging_status_res.ac_evse_status == await get_ac_evse_status_patch() @@ -612,6 +626,7 @@ async def test_service_detail_service_id_is_in_offered_list( message=get_v2g_message_service_detail_req(service_id=service_id) ) assert isinstance(self.comm_session.current_state, ServiceDetail) + assert isinstance(service_details.message, V2GMessageV2) assert ( service_details.message.body.service_detail_res.response_code is response_code @@ -705,6 +720,8 @@ async def test_resumed_session_auth_options_charge_service( await service_discovery.process_message( message=get_v2g_message_service_discovery_req() ) + service_discovery.message = cast(V2GMessageV2, service_discovery.message) + assert ( service_discovery.message.body.service_discovery_res.charge_service == charge_service @@ -752,6 +769,10 @@ async def test_resumed_session_sa_schedule_tuple( energy_transfer_modes[0] ) ) + + charge_parameter_discovery.message = cast( + V2GMessageV2, charge_parameter_discovery.message + ) sa_schedule_list = ( charge_parameter_discovery.message.body.charge_parameter_discovery_res.sa_schedule_list.schedule_tuples # noqa ) @@ -789,6 +810,7 @@ async def test_sales_tariff_in_free_charging_schedules(self, free_charging_servi energy_transfer_modes[0] ) ) + assert isinstance(charge_parameter_discovery.message, V2GMessageV2) for ( schedule_tuple ) in ( diff --git a/tests/iso15118_2/secc/states/test_messages.py b/tests/iso15118_2/secc/states/test_messages.py index c29c2dc1..1720b15c 100644 --- a/tests/iso15118_2/secc/states/test_messages.py +++ b/tests/iso15118_2/secc/states/test_messages.py @@ -524,7 +524,7 @@ def get_v2g_message_service_detail_req(service_id) -> V2GMessage: def get_v2g_message_session_stop_with_pause() -> V2GMessage: - session_stop_pause_req = SessionStopReq(ChargingSession=ChargingSession.PAUSE) + session_stop_pause_req = SessionStopReq(charging_session=ChargingSession.PAUSE) return V2GMessage( header=MessageHeader(session_id=MOCK_SESSION_ID), body=Body(session_stop_req=session_stop_pause_req), diff --git a/tests/iso15118_20/secc/test_iso15118_20_ac_states.py b/tests/iso15118_20/secc/test_iso15118_20_ac_states.py index 95a1673e..20e4c361 100644 --- a/tests/iso15118_20/secc/test_iso15118_20_ac_states.py +++ b/tests/iso15118_20/secc/test_iso15118_20_ac_states.py @@ -26,6 +26,7 @@ MatchedService, SelectedEnergyService, Service, + ServiceDetailRes, ServiceList, ) from iso15118.shared.messages.iso15118_20.common_types import Processing, ResponseCode @@ -96,6 +97,7 @@ async def test_service_detail_service_id_is_in_offered_list( await service_details.process_message( message=get_v2g_message_service_detail_req(service_id_input) ) + assert isinstance(service_details.message, ServiceDetailRes) assert service_details.message.response_code is response_code assert isinstance(self.comm_session.current_state, ServiceDetail) diff --git a/tests/shared/messages/test_din_spec.py b/tests/shared/messages/test_din_spec.py index a60b043c..2a859d8a 100644 --- a/tests/shared/messages/test_din_spec.py +++ b/tests/shared/messages/test_din_spec.py @@ -251,5 +251,5 @@ def test_common_v2g_messages_can_be_parsed_and_created( ): decoded_dict = json.loads(message.json_str, cls=CustomJSONDecoder) - message = V2GMessageDINSPEC.parse_obj(decoded_dict["V2G_Message"]) - assert isinstance(message, V2GMessageDINSPEC) + dinspec_message = V2GMessageDINSPEC.parse_obj(decoded_dict["V2G_Message"]) + assert isinstance(dinspec_message, V2GMessageDINSPEC) diff --git a/tests/shared/messages/test_iso15118_2.py b/tests/shared/messages/test_iso15118_2.py index 1a870cfc..c77ad6eb 100644 --- a/tests/shared/messages/test_iso15118_2.py +++ b/tests/shared/messages/test_iso15118_2.py @@ -204,5 +204,5 @@ def test_common_v2g_messages_can_be_parsed_and_created( ): decoded_dict = json.loads(message.json_str, cls=CustomJSONDecoder) - message = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) - assert isinstance(message, V2GMessage) + v2g_message = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) + assert isinstance(v2g_message, V2GMessage)