From e8011df7085d67be8411d6d9983f656d0f4d921e Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:26:12 +0100 Subject: [PATCH 01/28] wrong message parameters will return FAILED_WRONG_CHARGE_PARAMETER --- iso15118/evcc/states/evcc_state.py | 24 ++++++++++-- iso15118/secc/states/secc_state.py | 2 +- iso15118/shared/comm_session.py | 5 ++- iso15118/shared/exceptions.py | 10 +++++ iso15118/shared/exi_codec.py | 15 +++++--- iso15118/shared/messages/din_spec/body.py | 13 ++++--- iso15118/shared/messages/din_spec/msgdef.py | 17 +++++++++ iso15118/shared/messages/iso15118_2/body.py | 9 +++-- iso15118/shared/messages/iso15118_2/msgdef.py | 17 +++++++++ iso15118/shared/messages/iso15118_20/ac.py | 29 +++++++++------ .../messages/iso15118_20/common_types.py | 6 ++- iso15118/shared/states.py | 31 +++++++++++++++- tests/secc/states/test_iso15118_2_states.py | 37 ++++++++++++++++++- 13 files changed, 178 insertions(+), 37 deletions(-) diff --git a/iso15118/evcc/states/evcc_state.py b/iso15118/evcc/states/evcc_state.py index e8ddc9f08..74cc7f472 100644 --- a/iso15118/evcc/states/evcc_state.py +++ b/iso15118/evcc/states/evcc_state.py @@ -8,7 +8,7 @@ from iso15118.evcc.comm_session_handler import EVCCCommunicationSession from iso15118.shared.messages.app_protocol import ( SupportedAppProtocolReq, - SupportedAppProtocolRes, + SupportedAppProtocolRes, ResponseCodeSAP, ) from iso15118.shared.messages.din_spec.body import BodyBase as BodyBaseDINSPEC from iso15118.shared.messages.din_spec.body import Response as ResponseDINSPEC @@ -33,7 +33,12 @@ ) from iso15118.shared.notifications import StopNotification from iso15118.shared.states import State, Terminate - +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 +from iso15118.shared.messages.iso15118_20.common_types import ( + ResponseCode as ResponseCodeV20,) +from iso15118.shared.messages.din_spec.datatypes import ( + ResponseCode as ResponseCodeDINSPEC, +) class StateEVCC(State, ABC): """ @@ -216,7 +221,18 @@ def check_msg( return message - def stop_state_machine(self, reason: str): + def stop_state_machine(self, reason: str, + faulty_request: Optional[Union[ + SupportedAppProtocolReq, + SupportedAppProtocolRes, + V2GMessageV2, + V2GMessageV20, + V2GMessageDINSPEC, + ]] = None, + response_code: Optional[Union[ + ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC + ]] = None, + ): """ Prepares the stop of the state machine by setting the next_state to Terminate and providing a reason for logging purposes. @@ -224,6 +240,8 @@ def stop_state_machine(self, reason: str): Args: reason: Additional information as to why the communication session is about to be terminated. Helpful for further debugging. + faulty_request: None for evcc implemantation + response_code: None for evcc implemantation """ self.comm_session.stop_reason = StopNotification( False, reason, self.comm_session.writer.get_extra_info("peername") diff --git a/iso15118/secc/states/secc_state.py b/iso15118/secc/states/secc_state.py index 15645b038..a67835475 100644 --- a/iso15118/secc/states/secc_state.py +++ b/iso15118/secc/states/secc_state.py @@ -335,7 +335,7 @@ def stop_state_machine( payload_type, ) = self.comm_session.failed_responses_isov20.get(type(faulty_request)) # As the Header in the case of -20 is part of the -20 message payload, - # we need to set the session id of the the current session to it + # we need to set the session id of the current session to it error_res.header.session_id = self.comm_session.session_id error_res.response_code = response_code self.create_next_message(Terminate, error_res, 0, namespace, payload_type) diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index 4e8e2e98b..9343e091e 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -18,7 +18,7 @@ EXIDecodingError, FaultyStateImplementationError, InvalidV2GTPMessageError, - MessageProcessingError, + MessageProcessingError, V2GMessageValidationError, ) from iso15118.shared.exi_codec import EXI from iso15118.shared.messages.app_protocol import ( @@ -204,6 +204,9 @@ async def process_message(self, message: bytes): f"{self.get_exi_ns(v2gtp_msg.payload_type).value}" ) + except V2GMessageValidationError as exc: + self.comm_session.current_state.stop_state_machine(exc.reason, exc.message, exc.response_code) + return except EXIDecodingError as exc: logger.exception(f"{exc}") raise exc diff --git a/iso15118/shared/exceptions.py b/iso15118/shared/exceptions.py index 1cc9f64e8..9df072c43 100644 --- a/iso15118/shared/exceptions.py +++ b/iso15118/shared/exceptions.py @@ -1,5 +1,7 @@ from typing import Any +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode + class InvalidInterfaceError(Exception): """ @@ -235,3 +237,11 @@ def __init__(self): self, "No OCSP server entry in Authority Information Access extension field.", ) +class V2GMessageValidationError(Exception): + """Is thrown if message validation is failed""" + + def __init__(self, reason: str,response_code: ResponseCode, message: Any): + Exception.__init__(self) + self.reason = reason + self.response_code = response_code + self.message = message diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 40ac8e671..7a6240961 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -6,7 +6,7 @@ from pydantic import ValidationError -from iso15118.shared.exceptions import EXIDecodingError, EXIEncodingError +from iso15118.shared.exceptions import EXIDecodingError, EXIEncodingError, V2GMessageValidationError from iso15118.shared.exificient_exi_codec import ExificientEXICodec from iso15118.shared.iexi_codec import IEXICodec from iso15118.shared.messages import BaseModel @@ -378,11 +378,14 @@ def from_exi( return msg_class.parse_obj(msg_dict) raise EXIDecodingError("Can't identify protocol to use for decoding") - except ValidationError as exc: - raise EXIDecodingError( - f"Error parsing the decoded EXI into a Pydantic class: {exc}. " - f"\n\nDecoded dict: {decoded_dict}" - ) from exc + except V2GMessageValidationError as exc: + if namespace == Namespace.ISO_V2_MSG_DEF: + exc.message = V2GMessageV2.construct_skip_validation(**decoded_dict["V2G_Message"]) + elif namespace == Namespace.DIN_MSG_DEF: + exc.message = V2GMessageDINSPEC.construct_skip_validation(**decoded_dict["V2G_Message"]) + elif namespace.startswith(Namespace.ISO_V20_BASE): + exc.message = msg_class.construct(**msg_dict) + raise exc except EXIDecodingError as exc: raise EXIDecodingError( f"EXI decoding error: {exc}. \n\nDecoded dict: " f"{decoded_dict}" diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 245f33ba1..81f757db7 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -4,6 +4,7 @@ from pydantic import Field, root_validator, validator +from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.datatypes import ( DCEVSEChargeParameter, @@ -234,7 +235,7 @@ def validate_requested_energy_mode(cls, values): Pydantic validators are "class methods", see https://pydantic-docs.helpmanual.io/usage/validators/ - TODO We need to actually send FAILED_WrongChargeParameter or + We need to actually send FAILED_WrongChargeParameter or FAILED_WrongEnergyTransferMode if the wrong parameter set is provided, one or multiple parameters can not be interpreted (see [V2G2-477]). Need to check how to not just bury @@ -249,15 +250,17 @@ def validate_requested_energy_mode(cls, values): values.get("dc_ev_charge_parameter"), ) if requested_energy_mode not in ("DC_extended", "DC_core"): - raise ValueError( - f"Wrong energy transfer mode transfer mode {requested_energy_mode}" + raise V2GMessageValidationError( + f"Wrong energy transfer mode transfer mode {requested_energy_mode}", + ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, None ) if ("AC_" in requested_energy_mode and dc_params) or ( "DC_" in requested_energy_mode and ac_params ): - raise ValueError( + raise V2GMessageValidationError( "Wrong charge parameters for requested energy " - f"transfer mode {requested_energy_mode}" + f"transfer mode {requested_energy_mode}", + ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, None ) return values diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index 0705fdc57..9e4bd4448 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -12,6 +12,8 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ +from pydantic.main import object_setattr +from typing import Type, Any from pydantic import Field @@ -28,3 +30,18 @@ class V2GMessage(BaseModel): def __str__(self): return str(self.body.get_message_name()) + + @classmethod + def construct_skip_validation(cls: Type['Model'], **values: Any) -> 'Model': + """ + Creates a new model when the validation fails to get V2G message. + """ + m = cls.__new__(cls) + _fields_set = {'header', 'body'} + try: + object_setattr(m, '__fields_set__', _fields_set) + object_setattr(m, 'header', MessageHeader.construct(**values["Header"])) + object_setattr(m, 'body', Body.construct(**values["Body"])) + except Exception as e: + raise ValueError(f"Error on creating {cls} without validation: {e}") + return m \ No newline at end of file diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index e775855b6..a66900ae4 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -15,6 +15,7 @@ from pydantic import Field, root_validator, validator +from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.datatypes import ( DCEVSEChargeParameter, @@ -259,7 +260,6 @@ def requested_energy_mode_must_match_charge_parameter(cls, values): """ # pylint: disable=no-self-argument # pylint: disable=no-self-use - requested_energy_mode, ac_params, dc_params = ( values.get("requested_energy_mode"), values.get("ac_ev_charge_parameter"), @@ -268,9 +268,10 @@ def requested_energy_mode_must_match_charge_parameter(cls, values): if ("AC_" in requested_energy_mode and dc_params) or ( "DC_" in requested_energy_mode and ac_params ): - raise ValueError( + raise V2GMessageValidationError( "Wrong charge parameters for requested energy " - f"transfer mode {requested_energy_mode}" + f"transfer mode {requested_energy_mode}", + ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None ) return values @@ -705,7 +706,7 @@ def get_message_name(self) -> str: """Returns the name of the one V2GMessage that is set for Body.""" for k in self.__dict__.keys(): if getattr(self, k): - return str(getattr(self, k)) + return str(k) return "" diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index 9ddde40e7..868b3c707 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -12,6 +12,8 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ +from pydantic.main import object_setattr +from typing import Type, Optional, Any, Dict from pydantic import Field @@ -28,3 +30,18 @@ class V2GMessage(BaseModel): def __str__(self): return str(self.body.get_message_name()) + + @classmethod + def construct_skip_validation(cls: Type['Model'], **values: Any) -> 'Model': + """ + Creates a new model when the validation fails to get V2G message. + """ + m = cls.__new__(cls) + _fields_set = {'header', 'body'} + try: + object_setattr(m, '__fields_set__', _fields_set) + object_setattr(m, 'header', MessageHeader.construct(**values["Header"])) + object_setattr(m, 'body', Body.construct(**values["Body"])) + except Exception as e: + raise ValueError(f"Error on creating {cls} without validation: {e}") + return m diff --git a/iso15118/shared/messages/iso15118_20/ac.py b/iso15118/shared/messages/iso15118_20/ac.py index cd34e42a9..b94e7d823 100644 --- a/iso15118/shared/messages/iso15118_20/ac.py +++ b/iso15118/shared/messages/iso15118_20/ac.py @@ -13,6 +13,7 @@ from pydantic import Field, root_validator +from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.iso15118_20.common_types import ( ChargeLoopReq, @@ -23,7 +24,7 @@ DynamicChargeLoopResParams, RationalNumber, ScheduledChargeLoopReqParams, - ScheduledChargeLoopResParams, + ScheduledChargeLoopResParams, ResponseCode, ) from iso15118.shared.validators import one_field_must_be_set @@ -335,17 +336,21 @@ def either_ac_or_ac_bpt_params(cls, values): """ # pylint: disable=no-self-argument # pylint: disable=no-self-use - if one_field_must_be_set( - [ - "ac_params", - "AC_CPDReqEnergyTransferMode", - "bpt_ac_params", - "BPT_AC_CPDReqEnergyTransferMode", - ], - values, - True, - ): - return values + try: + if one_field_must_be_set( + [ + "ac_params", + "AC_CPDReqEnergyTransferMode", + "bpt_ac_params", + "BPT_AC_CPDReqEnergyTransferMode", + ], + values, + True, + ): + return values + except ValueError as exc: + raise V2GMessageValidationError(str(exc), ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, + None) def __str__(self): # The XSD-conform name diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index cc672dd60..93bb58c5a 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -12,7 +12,9 @@ """ from abc import ABC from enum import Enum -from typing import List + +from pydantic.main import object_setattr +from typing import List, Type, Any from pydantic import Field, conbytes, conint, constr, validator @@ -73,7 +75,7 @@ class V2GMessage(BaseModel, ABC): """See section 8.3 in ISO 15118-20 This class model follows the schemas, where the V2GMessage type is defined, in the V2G_CI_CommonTypes.xsd schema. - This type is the base of all messages and contains the the Header + This type is the base of all messages and contains the Header This is a tiny but quite important difference in respect to ISO 15118-2 payload structure, where the header is not included within each Request and Response message diff --git a/iso15118/shared/states.py b/iso15118/shared/states.py index 6836990b0..16ebf5dfc 100644 --- a/iso15118/shared/states.py +++ b/iso15118/shared/states.py @@ -13,7 +13,7 @@ from iso15118.shared.exi_codec import EXI from iso15118.shared.messages.app_protocol import ( SupportedAppProtocolReq, - SupportedAppProtocolRes, + SupportedAppProtocolRes, ResponseCodeSAP, ) from iso15118.shared.messages.din_spec.body import Body as BodyDINSPEC from iso15118.shared.messages.din_spec.body import BodyBase as BodyBaseDINSPEC @@ -34,13 +34,23 @@ from iso15118.shared.messages.iso15118_2.body import Body, BodyBase from iso15118.shared.messages.iso15118_2.datatypes import FaultCode, Notification from iso15118.shared.messages.iso15118_2.header import MessageHeader as MessageHeaderV2 -from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 + +from iso15118.shared.messages.din_spec.datatypes import ( + ResponseCode as ResponseCodeDINSPEC, +) +from iso15118.shared.messages.iso15118_20.common_types import ( + ResponseCode as ResponseCodeV20, +) from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) + from iso15118.shared.messages.v2gtp import V2GTPMessage from iso15118.shared.messages.xmldsig import Signature +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 +from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 + logger = logging.getLogger(__name__) if TYPE_CHECKING: @@ -349,6 +359,23 @@ def create_next_message( f"creating a V2GTPMessage. {exc}" ) + @abstractmethod + def stop_state_machine( + self, + reason: str, + faulty_request: Optional[Union[ + SupportedAppProtocolReq, + SupportedAppProtocolRes, + V2GMessageV2, + V2GMessageV20, + V2GMessageDINSPEC, + ]]=None, + response_code: Optional[Union[ + ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC + ]]=None, + ): + raise NotImplementedError + def __repr__(self): """ Returns the object representation in string format diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index 7eb20bd9e..ab5abdf1c 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -2,7 +2,12 @@ from unittest.mock import AsyncMock, Mock, patch import pytest +import json +from pydantic import ValidationError +from pydantic.main import object_setattr +from iso15118.secc import EVSEControllerInterface, Config +from iso15118.secc.comm_session_handler import SECCCommunicationSession from iso15118.secc.states.iso15118_2_states import ( Authorization, ChargeParameterDiscovery, @@ -28,7 +33,7 @@ get_dummy_v2g_message_power_delivery_req_charge_start, get_dummy_v2g_message_power_delivery_req_charge_stop, get_dummy_v2g_message_welding_detection_req, - get_v2g_message_power_delivery_req, + get_v2g_message_power_delivery_req, get_charge_parameter_discovery_req_message_dc_with_ac_parameters, ) @@ -330,3 +335,33 @@ async def test_power_delivery_contactor_get_state( await self.comm_session.evse_controller.get_contactor_state() is Contactor.CLOSED ) + + async def test_charge_parameter_discovery_res_v2g2_477(self): + # V2G2-477: The message 'ChargeParameterDiscoveryRes' shall contain the ResponseCode + # 'FAILED_WrongChargeParameter' if the content of attribute 'EVChargeParameter' + # in the ChargeParameterDiscoveryReq message is not valid, e.g. wrong parameter set is + # provided, one or multiple parameters can not be interpreted. + + # '{"V2G_Message": {"Header": {"SessionID": "FD90825A23429C9A"}, "Body": ' \ + # '{"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": "AC_three_phase_core", ' \ + # '"DC_EVChargeParameter": {"DepartureTime": 0, "DC_EVStatus": {"EVReady": true, ' \ + # '"EVErrorCode": "NO_ERROR", "EVRESSSOC": 60}, "EVMaximumCurrentLimit": ' \ + # '{"Value": 32000, "Multiplier": -3, "Unit": "A"}, "EVMaximumPowerLimit": ' \ + # '{"Value": 8000, "Multiplier": 1, "Unit": "W"}, "EVMaximumVoltageLimit": ' \ + # '{"Value": 40, "Multiplier": 1, "Unit": "V"}, "EVEnergyCapacity": ' \ + # '{"Value": 7000, "Multiplier": 1, "Unit": "Wh"}, "EVEnergyRequest": ' \ + # '{"Value": 6000, "Multiplier": 1, "Unit": "Wh"}, "FullSOC": 90, "BulkSOC": 80}}}}}' + + exi_msg = b'\x80\x98\x02\x3E\x7E\x7B\xA1\x41\x7D\x56\x0E\x10\x94\x48\x00\x08\x00\xF0\x00\x30\x80' \ + b'\xFA\x01\x02\x0A\x18\x07\xC0\x82\x01\x40\x08\x30\x6C\x1B\x00\x83\x07\x81\x70\x2D\x05\x00' \ + b'\x00\x01\x02\x03\x04\x05' + EXI().set_exi_codec(ExificientEXICodec()) + try: + msg = EXI().from_exi( + exi_msg, Namespace.ISO_V2_MSG_DEF + ) + except V2GMessageValidationError as e: + assert e.response_code is ResponseCode.FAILED_WRONG_CHARGE_PARAMETER + assert isinstance(e.message, V2GMessage) + + From c030bce9e678f1c95d6b605629a8496aed8fb73e Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:27:44 +0100 Subject: [PATCH 02/28] reformatted --- iso15118/evcc/states/evcc_state.py | 45 +++++++++++-------- iso15118/shared/comm_session.py | 7 ++- iso15118/shared/exceptions.py | 2 +- iso15118/shared/exi_codec.py | 14 ++++-- iso15118/shared/messages/din_spec/body.py | 6 ++- iso15118/shared/messages/din_spec/msgdef.py | 16 +++---- iso15118/shared/messages/iso15118_2/body.py | 3 +- iso15118/shared/messages/iso15118_2/msgdef.py | 14 +++--- iso15118/shared/messages/iso15118_20/ac.py | 8 ++-- .../messages/iso15118_20/common_types.py | 5 +-- iso15118/shared/states.py | 32 ++++++------- tests/secc/states/test_iso15118_2_states.py | 21 +++++---- 12 files changed, 97 insertions(+), 76 deletions(-) diff --git a/iso15118/evcc/states/evcc_state.py b/iso15118/evcc/states/evcc_state.py index 74cc7f472..ecc9a4b74 100644 --- a/iso15118/evcc/states/evcc_state.py +++ b/iso15118/evcc/states/evcc_state.py @@ -7,24 +7,32 @@ from iso15118.evcc.comm_session_handler import EVCCCommunicationSession from iso15118.shared.messages.app_protocol import ( + ResponseCodeSAP, SupportedAppProtocolReq, - SupportedAppProtocolRes, ResponseCodeSAP, + 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.datatypes import ( + ResponseCode as ResponseCodeDINSPEC, +) from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC from iso15118.shared.messages.iso15118_2.body import BodyBase as BodyBaseV2 from iso15118.shared.messages.iso15118_2.body import Response as ResponseV2 from iso15118.shared.messages.iso15118_2.body import ( SessionSetupRes as SessionSetupResV2, ) +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 from iso15118.shared.messages.iso15118_20.common_messages import ( SessionSetupRes as SessionSetupResV20, ) +from iso15118.shared.messages.iso15118_20.common_types import ( + ResponseCode as ResponseCodeV20, +) from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) @@ -33,12 +41,7 @@ ) from iso15118.shared.notifications import StopNotification from iso15118.shared.states import State, Terminate -from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 -from iso15118.shared.messages.iso15118_20.common_types import ( - ResponseCode as ResponseCodeV20,) -from iso15118.shared.messages.din_spec.datatypes import ( - ResponseCode as ResponseCodeDINSPEC, -) + class StateEVCC(State, ABC): """ @@ -221,18 +224,22 @@ def check_msg( return message - def stop_state_machine(self, reason: str, - faulty_request: Optional[Union[ - SupportedAppProtocolReq, - SupportedAppProtocolRes, - V2GMessageV2, - V2GMessageV20, - V2GMessageDINSPEC, - ]] = None, - response_code: Optional[Union[ - ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC - ]] = None, - ): + def stop_state_machine( + self, + reason: str, + faulty_request: Optional[ + Union[ + SupportedAppProtocolReq, + SupportedAppProtocolRes, + V2GMessageV2, + V2GMessageV20, + V2GMessageDINSPEC, + ] + ] = None, + response_code: Optional[ + Union[ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC] + ] = None, + ): """ Prepares the stop of the state machine by setting the next_state to Terminate and providing a reason for logging purposes. diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index 9343e091e..4b51c5f8d 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -18,7 +18,8 @@ EXIDecodingError, FaultyStateImplementationError, InvalidV2GTPMessageError, - MessageProcessingError, V2GMessageValidationError, + MessageProcessingError, + V2GMessageValidationError, ) from iso15118.shared.exi_codec import EXI from iso15118.shared.messages.app_protocol import ( @@ -205,7 +206,9 @@ async def process_message(self, message: bytes): ) except V2GMessageValidationError as exc: - self.comm_session.current_state.stop_state_machine(exc.reason, exc.message, exc.response_code) + self.comm_session.current_state.stop_state_machine( + exc.reason, exc.message, exc.response_code + ) return except EXIDecodingError as exc: logger.exception(f"{exc}") diff --git a/iso15118/shared/exceptions.py b/iso15118/shared/exceptions.py index 9df072c43..8f673dc4c 100644 --- a/iso15118/shared/exceptions.py +++ b/iso15118/shared/exceptions.py @@ -240,7 +240,7 @@ 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: ResponseCode, 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 7a6240961..73b788db3 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -6,7 +6,11 @@ from pydantic import ValidationError -from iso15118.shared.exceptions import EXIDecodingError, EXIEncodingError, V2GMessageValidationError +from iso15118.shared.exceptions import ( + EXIDecodingError, + EXIEncodingError, + V2GMessageValidationError, +) from iso15118.shared.exificient_exi_codec import ExificientEXICodec from iso15118.shared.iexi_codec import IEXICodec from iso15118.shared.messages import BaseModel @@ -380,9 +384,13 @@ def from_exi( raise EXIDecodingError("Can't identify protocol to use for decoding") except V2GMessageValidationError as exc: if namespace == Namespace.ISO_V2_MSG_DEF: - exc.message = V2GMessageV2.construct_skip_validation(**decoded_dict["V2G_Message"]) + exc.message = V2GMessageV2.construct_skip_validation( + **decoded_dict["V2G_Message"] + ) elif namespace == Namespace.DIN_MSG_DEF: - exc.message = V2GMessageDINSPEC.construct_skip_validation(**decoded_dict["V2G_Message"]) + exc.message = V2GMessageDINSPEC.construct_skip_validation( + **decoded_dict["V2G_Message"] + ) elif namespace.startswith(Namespace.ISO_V20_BASE): exc.message = msg_class.construct(**msg_dict) raise exc diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 81f757db7..0af706a4f 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -252,7 +252,8 @@ def validate_requested_energy_mode(cls, values): if requested_energy_mode not in ("DC_extended", "DC_core"): raise V2GMessageValidationError( f"Wrong energy transfer mode transfer mode {requested_energy_mode}", - ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, None + ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, + None, ) if ("AC_" in requested_energy_mode and dc_params) or ( "DC_" in requested_energy_mode and ac_params @@ -260,7 +261,8 @@ def validate_requested_energy_mode(cls, values): raise V2GMessageValidationError( "Wrong charge parameters for requested energy " f"transfer mode {requested_energy_mode}", - ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, None + ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, + None, ) return values diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index 9e4bd4448..c42098928 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -12,10 +12,10 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from pydantic.main import object_setattr -from typing import Type, Any +from typing import Any, Type from pydantic import Field +from pydantic.main import object_setattr from iso15118.shared.messages import BaseModel from iso15118.shared.messages.din_spec.body import Body @@ -32,16 +32,16 @@ def __str__(self): return str(self.body.get_message_name()) @classmethod - def construct_skip_validation(cls: Type['Model'], **values: Any) -> 'Model': + def construct_skip_validation(cls: Type["Model"], **values: Any) -> "Model": """ Creates a new model when the validation fails to get V2G message. """ m = cls.__new__(cls) - _fields_set = {'header', 'body'} + _fields_set = {"header", "body"} try: - object_setattr(m, '__fields_set__', _fields_set) - object_setattr(m, 'header', MessageHeader.construct(**values["Header"])) - object_setattr(m, 'body', Body.construct(**values["Body"])) + object_setattr(m, "__fields_set__", _fields_set) + object_setattr(m, "header", MessageHeader.construct(**values["Header"])) + object_setattr(m, "body", Body.construct(**values["Body"])) except Exception as e: raise ValueError(f"Error on creating {cls} without validation: {e}") - return m \ No newline at end of file + return m diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index a66900ae4..52e8cbe3e 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -271,7 +271,8 @@ def requested_energy_mode_must_match_charge_parameter(cls, values): raise V2GMessageValidationError( "Wrong charge parameters for requested energy " f"transfer mode {requested_energy_mode}", - ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None + ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, + None, ) return values diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index 868b3c707..cce41ffa4 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -12,10 +12,10 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from pydantic.main import object_setattr -from typing import Type, Optional, Any, Dict +from typing import Any, Dict, Optional, Type from pydantic import Field +from pydantic.main import object_setattr from iso15118.shared.messages import BaseModel from iso15118.shared.messages.iso15118_2.body import Body @@ -32,16 +32,16 @@ def __str__(self): return str(self.body.get_message_name()) @classmethod - def construct_skip_validation(cls: Type['Model'], **values: Any) -> 'Model': + def construct_skip_validation(cls: Type["Model"], **values: Any) -> "Model": """ Creates a new model when the validation fails to get V2G message. """ m = cls.__new__(cls) - _fields_set = {'header', 'body'} + _fields_set = {"header", "body"} try: - object_setattr(m, '__fields_set__', _fields_set) - object_setattr(m, 'header', MessageHeader.construct(**values["Header"])) - object_setattr(m, 'body', Body.construct(**values["Body"])) + object_setattr(m, "__fields_set__", _fields_set) + object_setattr(m, "header", MessageHeader.construct(**values["Header"])) + object_setattr(m, "body", Body.construct(**values["Body"])) except Exception as e: raise ValueError(f"Error on creating {cls} without validation: {e}") return m diff --git a/iso15118/shared/messages/iso15118_20/ac.py b/iso15118/shared/messages/iso15118_20/ac.py index b94e7d823..861b45f9b 100644 --- a/iso15118/shared/messages/iso15118_20/ac.py +++ b/iso15118/shared/messages/iso15118_20/ac.py @@ -23,8 +23,9 @@ DynamicChargeLoopReqParams, DynamicChargeLoopResParams, RationalNumber, + ResponseCode, ScheduledChargeLoopReqParams, - ScheduledChargeLoopResParams, ResponseCode, + ScheduledChargeLoopResParams, ) from iso15118.shared.validators import one_field_must_be_set @@ -349,8 +350,9 @@ def either_ac_or_ac_bpt_params(cls, values): ): return values except ValueError as exc: - raise V2GMessageValidationError(str(exc), ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, - None) + raise V2GMessageValidationError( + str(exc), ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None + ) def __str__(self): # The XSD-conform name diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index 93bb58c5a..49f448fea 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -12,11 +12,10 @@ """ from abc import ABC from enum import Enum - -from pydantic.main import object_setattr -from typing import List, Type, Any +from typing import Any, List, Type from pydantic import Field, conbytes, conint, constr, validator +from pydantic.main import object_setattr from iso15118.shared.messages import BaseModel from iso15118.shared.messages.enums import ( diff --git a/iso15118/shared/states.py b/iso15118/shared/states.py index 16ebf5dfc..8727fa08c 100644 --- a/iso15118/shared/states.py +++ b/iso15118/shared/states.py @@ -12,8 +12,9 @@ ) from iso15118.shared.exi_codec import EXI from iso15118.shared.messages.app_protocol import ( + ResponseCodeSAP, SupportedAppProtocolReq, - SupportedAppProtocolRes, ResponseCodeSAP, + SupportedAppProtocolRes, ) from iso15118.shared.messages.din_spec.body import Body as BodyDINSPEC from iso15118.shared.messages.din_spec.body import BodyBase as BodyBaseDINSPEC @@ -21,6 +22,9 @@ from iso15118.shared.messages.din_spec.datatypes import ( Notification as NotificationDINSPEC, ) +from iso15118.shared.messages.din_spec.datatypes import ( + ResponseCode as ResponseCodeDINSPEC, +) from iso15118.shared.messages.din_spec.header import ( MessageHeader as MessageHeaderDINSPEC, ) @@ -33,24 +37,18 @@ ) from iso15118.shared.messages.iso15118_2.body import Body, BodyBase from iso15118.shared.messages.iso15118_2.datatypes import FaultCode, Notification +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 from iso15118.shared.messages.iso15118_2.header import MessageHeader as MessageHeaderV2 - -from iso15118.shared.messages.din_spec.datatypes import ( - ResponseCode as ResponseCodeDINSPEC, -) +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, ) - from iso15118.shared.messages.v2gtp import V2GTPMessage from iso15118.shared.messages.xmldsig import Signature -from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 -from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 - logger = logging.getLogger(__name__) if TYPE_CHECKING: @@ -361,18 +359,20 @@ def create_next_message( @abstractmethod def stop_state_machine( - self, - reason: str, - faulty_request: Optional[Union[ + self, + reason: str, + faulty_request: Optional[ + Union[ SupportedAppProtocolReq, SupportedAppProtocolRes, V2GMessageV2, V2GMessageV20, V2GMessageDINSPEC, - ]]=None, - response_code: Optional[Union[ - ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC - ]]=None, + ] + ] = None, + response_code: Optional[ + Union[ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC] + ] = None, ): raise NotImplementedError diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index ab5abdf1c..db2260da3 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -1,12 +1,12 @@ from pathlib import Path +import json from unittest.mock import AsyncMock, Mock, patch import pytest -import json from pydantic import ValidationError from pydantic.main import object_setattr -from iso15118.secc import EVSEControllerInterface, Config +from iso15118.secc import Config, EVSEControllerInterface from iso15118.secc.comm_session_handler import SECCCommunicationSession from iso15118.secc.states.iso15118_2_states import ( Authorization, @@ -26,6 +26,7 @@ ) from iso15118.shared.messages.iso15118_2.datatypes import CertificateChain from tests.secc.states.test_messages import ( + get_charge_parameter_discovery_req_message_dc_with_ac_parameters, get_charge_parameter_discovery_req_message_departure_time_one_hour, get_charge_parameter_discovery_req_message_no_departure_time, get_dummy_v2g_message_authorization_req, @@ -33,7 +34,7 @@ get_dummy_v2g_message_power_delivery_req_charge_start, get_dummy_v2g_message_power_delivery_req_charge_stop, get_dummy_v2g_message_welding_detection_req, - get_v2g_message_power_delivery_req, get_charge_parameter_discovery_req_message_dc_with_ac_parameters, + get_v2g_message_power_delivery_req, ) @@ -352,16 +353,14 @@ async def test_charge_parameter_discovery_res_v2g2_477(self): # '{"Value": 7000, "Multiplier": 1, "Unit": "Wh"}, "EVEnergyRequest": ' \ # '{"Value": 6000, "Multiplier": 1, "Unit": "Wh"}, "FullSOC": 90, "BulkSOC": 80}}}}}' - exi_msg = b'\x80\x98\x02\x3E\x7E\x7B\xA1\x41\x7D\x56\x0E\x10\x94\x48\x00\x08\x00\xF0\x00\x30\x80' \ - b'\xFA\x01\x02\x0A\x18\x07\xC0\x82\x01\x40\x08\x30\x6C\x1B\x00\x83\x07\x81\x70\x2D\x05\x00' \ - b'\x00\x01\x02\x03\x04\x05' + exi_msg = ( + b"\x80\x98\x02\x3E\x7E\x7B\xA1\x41\x7D\x56\x0E\x10\x94\x48\x00\x08\x00\xF0\x00\x30\x80" + b"\xFA\x01\x02\x0A\x18\x07\xC0\x82\x01\x40\x08\x30\x6C\x1B\x00\x83\x07\x81\x70\x2D\x05\x00" + b"\x00\x01\x02\x03\x04\x05" + ) EXI().set_exi_codec(ExificientEXICodec()) try: - msg = EXI().from_exi( - exi_msg, Namespace.ISO_V2_MSG_DEF - ) + msg = EXI().from_exi(exi_msg, Namespace.ISO_V2_MSG_DEF) except V2GMessageValidationError as e: assert e.response_code is ResponseCode.FAILED_WRONG_CHARGE_PARAMETER assert isinstance(e.message, V2GMessage) - - From bbe0adea8e324864dddc8a47c33d8b96a5eaac14 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:34:49 +0100 Subject: [PATCH 03/28] fixes --- tests/secc/states/test_iso15118_2_states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index db2260da3..4774ee560 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -343,7 +343,7 @@ async def test_charge_parameter_discovery_res_v2g2_477(self): # in the ChargeParameterDiscoveryReq message is not valid, e.g. wrong parameter set is # provided, one or multiple parameters can not be interpreted. - # '{"V2G_Message": {"Header": {"SessionID": "FD90825A23429C9A"}, "Body": ' \ + # '{"V2G_Message": {"Header": {"SessionID": "FD90825A23429C9A"}, "Body": ' \ # '{"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": "AC_three_phase_core", ' \ # '"DC_EVChargeParameter": {"DepartureTime": 0, "DC_EVStatus": {"EVReady": true, ' \ # '"EVErrorCode": "NO_ERROR", "EVRESSSOC": 60}, "EVMaximumCurrentLimit": ' \ From d2ae1997810beadc8714e4ef0e6be5e998643d4a Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:46:03 +0100 Subject: [PATCH 04/28] flake8 --- iso15118/shared/exi_codec.py | 1 - iso15118/shared/messages/din_spec/msgdef.py | 2 +- iso15118/shared/messages/iso15118_2/msgdef.py | 4 +-- .../messages/iso15118_20/common_types.py | 4 +-- tests/secc/states/test_iso15118_2_states.py | 31 +++++++++---------- 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 73b788db3..2c2bd992e 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -4,7 +4,6 @@ from base64 import b64decode, b64encode from typing import Union -from pydantic import ValidationError from iso15118.shared.exceptions import ( EXIDecodingError, diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index c42098928..265cc76d3 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -15,7 +15,7 @@ from typing import Any, Type from pydantic import Field -from pydantic.main import object_setattr +from pydantic.main import object_setattr, Model from iso15118.shared.messages import BaseModel from iso15118.shared.messages.din_spec.body import Body diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index cce41ffa4..690bc86e9 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -12,10 +12,10 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from typing import Any, Dict, Optional, Type +from typing import Any, Type from pydantic import Field -from pydantic.main import object_setattr +from pydantic.main import object_setattr, Model from iso15118.shared.messages import BaseModel from iso15118.shared.messages.iso15118_2.body import Body diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index 49f448fea..c6a71d5cc 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -12,10 +12,10 @@ """ from abc import ABC from enum import Enum -from typing import Any, List, Type +from typing import List from pydantic import Field, conbytes, conint, constr, validator -from pydantic.main import object_setattr + from iso15118.shared.messages import BaseModel from iso15118.shared.messages.enums import ( diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index 4774ee560..23979eab4 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -3,11 +3,6 @@ from unittest.mock import AsyncMock, Mock, patch import pytest -from pydantic import ValidationError -from pydantic.main import object_setattr - -from iso15118.secc import Config, EVSEControllerInterface -from iso15118.secc.comm_session_handler import SECCCommunicationSession from iso15118.secc.states.iso15118_2_states import ( Authorization, ChargeParameterDiscovery, @@ -26,7 +21,6 @@ ) from iso15118.shared.messages.iso15118_2.datatypes import CertificateChain from tests.secc.states.test_messages import ( - get_charge_parameter_discovery_req_message_dc_with_ac_parameters, get_charge_parameter_discovery_req_message_departure_time_one_hour, get_charge_parameter_discovery_req_message_no_departure_time, get_dummy_v2g_message_authorization_req, @@ -338,29 +332,32 @@ async def test_power_delivery_contactor_get_state( ) async def test_charge_parameter_discovery_res_v2g2_477(self): - # V2G2-477: The message 'ChargeParameterDiscoveryRes' shall contain the ResponseCode - # 'FAILED_WrongChargeParameter' if the content of attribute 'EVChargeParameter' - # in the ChargeParameterDiscoveryReq message is not valid, e.g. wrong parameter set is - # provided, one or multiple parameters can not be interpreted. + # V2G2-477: The message 'ChargeParameterDiscoveryRes' shall contain the + # ResponseCode 'FAILED_WrongChargeParameter' if the content of attribute + # 'EVChargeParameter' in the ChargeParameterDiscoveryReq message is not valid, + # e.g. wrong parameter set is provided, one or multiple parameters can not + # be interpreted. # '{"V2G_Message": {"Header": {"SessionID": "FD90825A23429C9A"}, "Body": ' \ - # '{"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": "AC_three_phase_core", ' \ - # '"DC_EVChargeParameter": {"DepartureTime": 0, "DC_EVStatus": {"EVReady": true, ' \ + # '{"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": + # "AC_three_phase_core", '"DC_EVChargeParameter": {"DepartureTime": 0, + # "DC_EVStatus": {"EVReady": true, ' \ # '"EVErrorCode": "NO_ERROR", "EVRESSSOC": 60}, "EVMaximumCurrentLimit": ' \ # '{"Value": 32000, "Multiplier": -3, "Unit": "A"}, "EVMaximumPowerLimit": ' \ # '{"Value": 8000, "Multiplier": 1, "Unit": "W"}, "EVMaximumVoltageLimit": ' \ # '{"Value": 40, "Multiplier": 1, "Unit": "V"}, "EVEnergyCapacity": ' \ # '{"Value": 7000, "Multiplier": 1, "Unit": "Wh"}, "EVEnergyRequest": ' \ - # '{"Value": 6000, "Multiplier": 1, "Unit": "Wh"}, "FullSOC": 90, "BulkSOC": 80}}}}}' + # '{"Value": 6000, "Multiplier": 1, "Unit": "Wh"}, + # "FullSOC": 90, "BulkSOC": 80}}}}}' exi_msg = ( - b"\x80\x98\x02\x3E\x7E\x7B\xA1\x41\x7D\x56\x0E\x10\x94\x48\x00\x08\x00\xF0\x00\x30\x80" - b"\xFA\x01\x02\x0A\x18\x07\xC0\x82\x01\x40\x08\x30\x6C\x1B\x00\x83\x07\x81\x70\x2D\x05\x00" - b"\x00\x01\x02\x03\x04\x05" + b"\x80\x98\x02\x3E\x7E\x7B\xA1\x41\x7D\x56\x0E\x10\x94\x48\x00\x08" + b"\x00\xF0\x00\x30\x80\xFA\x01\x02\x0A\x18\x07\xC0\x82\x01\x40\x08" + b"\x30\x6C\x1B\x00\x83\x07\x81\x70\x2D\x05\x00\x00\x01\x02\x03\x04\x05" ) EXI().set_exi_codec(ExificientEXICodec()) try: - msg = EXI().from_exi(exi_msg, Namespace.ISO_V2_MSG_DEF) + EXI().from_exi(exi_msg, Namespace.ISO_V2_MSG_DEF) except V2GMessageValidationError as e: assert e.response_code is ResponseCode.FAILED_WRONG_CHARGE_PARAMETER assert isinstance(e.message, V2GMessage) From faf0c5ab71be73ec7f52456961973b6199c4bf35 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 17:07:35 +0100 Subject: [PATCH 05/28] setattr is fixed --- iso15118/shared/messages/din_spec/msgdef.py | 9 ++++----- iso15118/shared/messages/iso15118_2/msgdef.py | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index 265cc76d3..c468973a7 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -15,7 +15,6 @@ from typing import Any, Type from pydantic import Field -from pydantic.main import object_setattr, Model from iso15118.shared.messages import BaseModel from iso15118.shared.messages.din_spec.body import Body @@ -32,16 +31,16 @@ def __str__(self): return str(self.body.get_message_name()) @classmethod - def construct_skip_validation(cls: Type["Model"], **values: Any) -> "Model": + def construct_skip_validation(cls, **values: Any) -> "Model": """ Creates a new model when the validation fails to get V2G message. """ m = cls.__new__(cls) _fields_set = {"header", "body"} try: - object_setattr(m, "__fields_set__", _fields_set) - object_setattr(m, "header", MessageHeader.construct(**values["Header"])) - object_setattr(m, "body", Body.construct(**values["Body"])) + object.__setattr__(m, '__fields_set__', _fields_set) + m.header = MessageHeader.construct(**values["Header"]) + m.body = Body.construct(**values["Body"]) except Exception as e: raise ValueError(f"Error on creating {cls} without validation: {e}") return m diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index 690bc86e9..160b92a7c 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -15,7 +15,6 @@ from typing import Any, Type from pydantic import Field -from pydantic.main import object_setattr, Model from iso15118.shared.messages import BaseModel from iso15118.shared.messages.iso15118_2.body import Body @@ -32,16 +31,16 @@ def __str__(self): return str(self.body.get_message_name()) @classmethod - def construct_skip_validation(cls: Type["Model"], **values: Any) -> "Model": + def construct_skip_validation(cls, **values: Any) -> "Model": """ Creates a new model when the validation fails to get V2G message. """ m = cls.__new__(cls) _fields_set = {"header", "body"} try: - object_setattr(m, "__fields_set__", _fields_set) - object_setattr(m, "header", MessageHeader.construct(**values["Header"])) - object_setattr(m, "body", Body.construct(**values["Body"])) + object.__setattr__(m, '__fields_set__', _fields_set) + m.header = MessageHeader.construct(**values["Header"]) + m.body = Body.construct(**values["Body"]) except Exception as e: raise ValueError(f"Error on creating {cls} without validation: {e}") return m From c1617147494613592d1a0609a03c3ac2d342dc59 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 17:14:10 +0100 Subject: [PATCH 06/28] reformatted --- iso15118/shared/exi_codec.py | 1 - iso15118/shared/messages/din_spec/msgdef.py | 2 +- iso15118/shared/messages/iso15118_2/msgdef.py | 2 +- iso15118/shared/messages/iso15118_20/common_types.py | 1 - tests/secc/states/test_iso15118_2_states.py | 1 + 5 files changed, 3 insertions(+), 4 deletions(-) diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 2c2bd992e..d584e8059 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -4,7 +4,6 @@ from base64 import b64decode, b64encode from typing import Union - from iso15118.shared.exceptions import ( EXIDecodingError, EXIEncodingError, diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index c468973a7..2bd276698 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -38,7 +38,7 @@ def construct_skip_validation(cls, **values: Any) -> "Model": m = cls.__new__(cls) _fields_set = {"header", "body"} try: - object.__setattr__(m, '__fields_set__', _fields_set) + object.__setattr__(m, "__fields_set__", _fields_set) m.header = MessageHeader.construct(**values["Header"]) m.body = Body.construct(**values["Body"]) except Exception as e: diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index 160b92a7c..e749bef60 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -38,7 +38,7 @@ def construct_skip_validation(cls, **values: Any) -> "Model": m = cls.__new__(cls) _fields_set = {"header", "body"} try: - object.__setattr__(m, '__fields_set__', _fields_set) + object.__setattr__(m, "__fields_set__", _fields_set) m.header = MessageHeader.construct(**values["Header"]) m.body = Body.construct(**values["Body"]) except Exception as e: diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index c6a71d5cc..fbf30c1bf 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -16,7 +16,6 @@ from pydantic import Field, conbytes, conint, constr, validator - from iso15118.shared.messages import BaseModel from iso15118.shared.messages.enums import ( INT_8_MAX, diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index 23979eab4..ceb907249 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -3,6 +3,7 @@ from unittest.mock import AsyncMock, Mock, patch import pytest + from iso15118.secc.states.iso15118_2_states import ( Authorization, ChargeParameterDiscovery, From ade2098ab48d7f4063a644cc2409e986cfdd6481 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 17:17:12 +0100 Subject: [PATCH 07/28] flake8 --- iso15118/shared/messages/din_spec/msgdef.py | 5 ++--- iso15118/shared/messages/iso15118_2/msgdef.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index 2bd276698..0752cef5b 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -12,8 +12,7 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from typing import Any, Type - +from typing import Any from pydantic import Field from iso15118.shared.messages import BaseModel @@ -31,7 +30,7 @@ def __str__(self): return str(self.body.get_message_name()) @classmethod - def construct_skip_validation(cls, **values: Any) -> "Model": + def construct_skip_validation(cls, **values: Any): """ Creates a new model when the validation fails to get V2G message. """ diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index e749bef60..a703c58ec 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -12,7 +12,7 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from typing import Any, Type +from typing import Any from pydantic import Field @@ -31,7 +31,7 @@ def __str__(self): return str(self.body.get_message_name()) @classmethod - def construct_skip_validation(cls, **values: Any) -> "Model": + def construct_skip_validation(cls, **values: Any): """ Creates a new model when the validation fails to get V2G message. """ From 973f0ad6fa4c95b4a46835778d5a0c74e184fb28 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 17:24:52 +0100 Subject: [PATCH 08/28] tests removed for now --- iso15118/shared/messages/din_spec/msgdef.py | 1 + tests/secc/states/test_iso15118_2_states.py | 31 --------------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index 0752cef5b..4d228fe30 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -13,6 +13,7 @@ element names by using the 'alias' attribute. """ from typing import Any + from pydantic import Field from iso15118.shared.messages import BaseModel diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index ceb907249..826b11507 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -331,34 +331,3 @@ async def test_power_delivery_contactor_get_state( await self.comm_session.evse_controller.get_contactor_state() is Contactor.CLOSED ) - - async def test_charge_parameter_discovery_res_v2g2_477(self): - # V2G2-477: The message 'ChargeParameterDiscoveryRes' shall contain the - # ResponseCode 'FAILED_WrongChargeParameter' if the content of attribute - # 'EVChargeParameter' in the ChargeParameterDiscoveryReq message is not valid, - # e.g. wrong parameter set is provided, one or multiple parameters can not - # be interpreted. - - # '{"V2G_Message": {"Header": {"SessionID": "FD90825A23429C9A"}, "Body": ' \ - # '{"ChargeParameterDiscoveryReq": {"RequestedEnergyTransferMode": - # "AC_three_phase_core", '"DC_EVChargeParameter": {"DepartureTime": 0, - # "DC_EVStatus": {"EVReady": true, ' \ - # '"EVErrorCode": "NO_ERROR", "EVRESSSOC": 60}, "EVMaximumCurrentLimit": ' \ - # '{"Value": 32000, "Multiplier": -3, "Unit": "A"}, "EVMaximumPowerLimit": ' \ - # '{"Value": 8000, "Multiplier": 1, "Unit": "W"}, "EVMaximumVoltageLimit": ' \ - # '{"Value": 40, "Multiplier": 1, "Unit": "V"}, "EVEnergyCapacity": ' \ - # '{"Value": 7000, "Multiplier": 1, "Unit": "Wh"}, "EVEnergyRequest": ' \ - # '{"Value": 6000, "Multiplier": 1, "Unit": "Wh"}, - # "FullSOC": 90, "BulkSOC": 80}}}}}' - - exi_msg = ( - b"\x80\x98\x02\x3E\x7E\x7B\xA1\x41\x7D\x56\x0E\x10\x94\x48\x00\x08" - b"\x00\xF0\x00\x30\x80\xFA\x01\x02\x0A\x18\x07\xC0\x82\x01\x40\x08" - b"\x30\x6C\x1B\x00\x83\x07\x81\x70\x2D\x05\x00\x00\x01\x02\x03\x04\x05" - ) - EXI().set_exi_codec(ExificientEXICodec()) - try: - EXI().from_exi(exi_msg, Namespace.ISO_V2_MSG_DEF) - except V2GMessageValidationError as e: - assert e.response_code is ResponseCode.FAILED_WRONG_CHARGE_PARAMETER - assert isinstance(e.message, V2GMessage) From 243e6e40bd2d0b62be17900ea7c28170d96ff35f Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Fri, 29 Jul 2022 17:27:50 +0100 Subject: [PATCH 09/28] flake8 --- tests/secc/states/test_iso15118_2_states.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index 826b11507..57c634cbd 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -20,7 +20,6 @@ AuthorizationTokenType, Contactor, ) -from iso15118.shared.messages.iso15118_2.datatypes import CertificateChain from tests.secc.states.test_messages import ( get_charge_parameter_discovery_req_message_departure_time_one_hour, get_charge_parameter_discovery_req_message_no_departure_time, From 83cf01241370edc8f3a591f9b7cfb8b05e92058d Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 1 Aug 2022 12:04:31 +0100 Subject: [PATCH 10/28] comment fixed --- iso15118/shared/comm_session.py | 3 +++ iso15118/shared/exi_codec.py | 4 ++++ iso15118/shared/messages/din_spec/body.py | 2 +- iso15118/shared/messages/iso15118_2/body.py | 8 ++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index 4b51c5f8d..2eb6757c9 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -210,6 +210,9 @@ async def process_message(self, message: bytes): exc.reason, exc.message, exc.response_code ) return + except ValueError as exc: + logger.exception(f"{exc}") + raise exc except EXIDecodingError as exc: logger.exception(f"{exc}") raise exc diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index d584e8059..5bfa8e573 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -392,6 +392,10 @@ def from_exi( elif namespace.startswith(Namespace.ISO_V20_BASE): exc.message = msg_class.construct(**msg_dict) raise exc + except ValueError as exc: + raise ValueError( + f"Value error: {exc}. \n\nDecoded dict: " f"{decoded_dict}" + ) from exc except EXIDecodingError as exc: raise EXIDecodingError( f"EXI decoding error: {exc}. \n\nDecoded dict: " f"{decoded_dict}" diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 0af706a4f..346173b45 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -261,7 +261,7 @@ def validate_requested_energy_mode(cls, values): raise V2GMessageValidationError( "Wrong charge parameters for requested energy " f"transfer mode {requested_energy_mode}", - ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, + ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None, ) return values diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 52e8cbe3e..11c6f1c73 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -274,6 +274,14 @@ def requested_energy_mode_must_match_charge_parameter(cls, values): ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None, ) + if requested_energy_mode not in EnergyTransferModeEnum.__members__.values(): + raise V2GMessageValidationError( + "Wrong value for requested energy " + f"transfer mode {requested_energy_mode}", + ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, + None, + ) + return values From 92cbbe554bc6d4210133b43b12d522c0f72db8ce Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 1 Aug 2022 12:09:25 +0100 Subject: [PATCH 11/28] Validation error fixed --- iso15118/shared/comm_session.py | 2 +- iso15118/shared/exi_codec.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index 2eb6757c9..b3fd58bb2 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -210,7 +210,7 @@ async def process_message(self, message: bytes): exc.reason, exc.message, exc.response_code ) return - except ValueError as exc: + except ValidationError as exc: logger.exception(f"{exc}") raise exc except EXIDecodingError as exc: diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 5bfa8e573..4f3feeae8 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -2,6 +2,8 @@ import json import logging from base64 import b64decode, b64encode + +from pydantic import ValidationError from typing import Union from iso15118.shared.exceptions import ( @@ -392,9 +394,9 @@ def from_exi( elif namespace.startswith(Namespace.ISO_V20_BASE): exc.message = msg_class.construct(**msg_dict) raise exc - except ValueError as exc: - raise ValueError( - f"Value error: {exc}. \n\nDecoded dict: " f"{decoded_dict}" + except ValidationError as exc: + raise ValidationError( + f"Validation error: {exc}. \n\nDecoded dict: " f"{decoded_dict}" ) from exc except EXIDecodingError as exc: raise EXIDecodingError( From 44fefa4e4ab8b08ae13d839772b7a21b67def7f8 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 1 Aug 2022 12:21:30 +0100 Subject: [PATCH 12/28] Value error has changed to ValidationError --- iso15118/shared/exi_codec.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 4f3feeae8..d6f1adb47 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -395,8 +395,9 @@ def from_exi( exc.message = msg_class.construct(**msg_dict) raise exc except ValidationError as exc: - raise ValidationError( - f"Validation error: {exc}. \n\nDecoded dict: " f"{decoded_dict}" + raise EXIDecodingError( + f"Error parsing the decoded EXI into a Pydantic class: {exc}. " + f"\n\nDecoded dict: {decoded_dict}" ) from exc except EXIDecodingError as exc: raise EXIDecodingError( From fa6af9a83ec68ed1d95f636e55f3caf80714035f Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 1 Aug 2022 12:22:34 +0100 Subject: [PATCH 13/28] Value error has changed to ValidationError --- iso15118/shared/comm_session.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index b3fd58bb2..4b51c5f8d 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -210,9 +210,6 @@ async def process_message(self, message: bytes): exc.reason, exc.message, exc.response_code ) return - except ValidationError as exc: - logger.exception(f"{exc}") - raise exc except EXIDecodingError as exc: logger.exception(f"{exc}") raise exc From bfe301c805ec974d3a03bae4286b0f4fb8ad53b1 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 1 Aug 2022 12:27:05 +0100 Subject: [PATCH 14/28] exi codec Validation error --- iso15118/shared/exi_codec.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index d6f1adb47..8f94b9ace 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -382,6 +382,11 @@ def from_exi( return msg_class.parse_obj(msg_dict) raise EXIDecodingError("Can't identify protocol to use for decoding") + except ValidationError as exc: + raise EXIDecodingError( + f"Error parsing the decoded EXI into a Pydantic class: {exc}. " + f"\n\nDecoded dict: {decoded_dict}" + ) from exc except V2GMessageValidationError as exc: if namespace == Namespace.ISO_V2_MSG_DEF: exc.message = V2GMessageV2.construct_skip_validation( @@ -394,11 +399,6 @@ def from_exi( elif namespace.startswith(Namespace.ISO_V20_BASE): exc.message = msg_class.construct(**msg_dict) raise exc - except ValidationError as exc: - raise EXIDecodingError( - f"Error parsing the decoded EXI into a Pydantic class: {exc}. " - f"\n\nDecoded dict: {decoded_dict}" - ) from exc except EXIDecodingError as exc: raise EXIDecodingError( f"EXI decoding error: {exc}. \n\nDecoded dict: " f"{decoded_dict}" From 5b4a4cf13e42b4ec0c102dcb0462947d8180d561 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Mon, 8 Aug 2022 16:27:24 +0100 Subject: [PATCH 15/28] comment fixs --- iso15118/shared/exi_codec.py | 38 ++++++++++----------- iso15118/shared/messages/iso15118_2/body.py | 4 +-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 8f94b9ace..c6b5d28be 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -4,7 +4,7 @@ from base64 import b64decode, b64encode from pydantic import ValidationError -from typing import Union +from typing import Union, Any from iso15118.shared.exceptions import ( EXIDecodingError, @@ -18,9 +18,12 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) -from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC +from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC, \ + V2GMessage from iso15118.shared.messages.enums import Namespace -from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode +from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2, \ + V2GMessage from iso15118.shared.messages.iso15118_20.ac import ( ACChargeLoopReq, ACChargeLoopRes, @@ -263,13 +266,7 @@ def to_exi(self, msg_element: BaseModel, protocol_ns: str) -> bytes: def from_exi( self, exi_message: bytes, namespace: str - ) -> Union[ - SupportedAppProtocolReq, - SupportedAppProtocolRes, - V2GMessageV2, - V2GMessageV20, - V2GMessageDINSPEC, - ]: + ) -> SupportedAppProtocolReq | SupportedAppProtocolRes | V2GMessage | V2GMessage | V2GMessageValidationError | Any: """ Decodes the EXI encoded bytearray into a message according to the payload type provided. @@ -382,22 +379,25 @@ def from_exi( return msg_class.parse_obj(msg_dict) raise EXIDecodingError("Can't identify protocol to use for decoding") - except ValidationError as exc: - raise EXIDecodingError( - f"Error parsing the decoded EXI into a Pydantic class: {exc}. " - f"\n\nDecoded dict: {decoded_dict}" - ) from exc - except V2GMessageValidationError as exc: + + except (V2GMessageValidationError, ValidationError) as exc : if namespace == Namespace.ISO_V2_MSG_DEF: - exc.message = V2GMessageV2.construct_skip_validation( + message = V2GMessageV2.construct_skip_validation( **decoded_dict["V2G_Message"] ) elif namespace == Namespace.DIN_MSG_DEF: - exc.message = V2GMessageDINSPEC.construct_skip_validation( + message = V2GMessageDINSPEC.construct_skip_validation( **decoded_dict["V2G_Message"] ) elif namespace.startswith(Namespace.ISO_V20_BASE): - exc.message = msg_class.construct(**msg_dict) + message = msg_class.construct(**msg_dict) + if type(exc) == ValidationError: + raise V2GMessageValidationError(f"Error parsing the decoded EXI into" + f" a Pydantic class: {exc}. " + f"\n\nDecoded dict: {decoded_dict}", + ResponseCode.FAILED, + message) from exc + exc.message = message raise exc except EXIDecodingError as exc: raise EXIDecodingError( diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 11c6f1c73..c2c0710c6 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -269,14 +269,14 @@ def requested_energy_mode_must_match_charge_parameter(cls, values): "DC_" in requested_energy_mode and ac_params ): raise V2GMessageValidationError( - "Wrong charge parameters for requested energy " + "[V2G2-477] Wrong charge parameters for requested energy " f"transfer mode {requested_energy_mode}", ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None, ) if requested_energy_mode not in EnergyTransferModeEnum.__members__.values(): raise V2GMessageValidationError( - "Wrong value for requested energy " + "[V2G2-476] Wrong value for requested energy " f"transfer mode {requested_energy_mode}", ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, None, From 0be1f20ce7989eab0f74a005a88ba869e5d3ec90 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Tue, 9 Aug 2022 10:35:52 +0100 Subject: [PATCH 16/28] commit fix --- iso15118/shared/messages/din_spec/body.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 346173b45..6df5bcebc 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -251,7 +251,7 @@ def validate_requested_energy_mode(cls, values): ) if requested_energy_mode not in ("DC_extended", "DC_core"): raise V2GMessageValidationError( - f"Wrong energy transfer mode transfer mode {requested_energy_mode}", + f"[V2G2-476] Wrong energy transfer mode transfer mode {requested_energy_mode}", ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, None, ) @@ -259,7 +259,7 @@ def validate_requested_energy_mode(cls, values): "DC_" in requested_energy_mode and ac_params ): raise V2GMessageValidationError( - "Wrong charge parameters for requested energy " + "[V2G2-477] Wrong charge parameters for requested energy " f"transfer mode {requested_energy_mode}", ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None, From 35fdfcd4906be8711f3463d1112a59e0be74e017 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Tue, 9 Aug 2022 13:49:48 +0100 Subject: [PATCH 17/28] validation error added for session_id check --- iso15118/shared/messages/din_spec/body.py | 5 +++-- iso15118/shared/messages/din_spec/header.py | 8 +++++--- iso15118/shared/messages/iso15118_2/body.py | 10 ++++++---- iso15118/shared/messages/iso15118_2/header.py | 8 +++++--- .../shared/messages/iso15118_20/common_messages.py | 8 +++++--- iso15118/shared/messages/iso15118_20/common_types.py | 6 ++++-- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 6df5bcebc..1ffab2d23 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -92,9 +92,10 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise ValueError( + raise V2GMessageValidationError( f"Invalid value '{value}' for EVCCID (must be " - f"hexadecimal representation of max 6 bytes)" + f"hexadecimal representation of max 6 bytes)", + ResponseCode.FAILED_SEQUENCE_ERROR, None ) from exc diff --git a/iso15118/shared/messages/din_spec/header.py b/iso15118/shared/messages/din_spec/header.py index 349e2a6f5..9e4a66c5e 100644 --- a/iso15118/shared/messages/din_spec/header.py +++ b/iso15118/shared/messages/din_spec/header.py @@ -14,8 +14,9 @@ from pydantic import Field, validator +from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel -from iso15118.shared.messages.din_spec.datatypes import Notification +from iso15118.shared.messages.din_spec.datatypes import Notification, ResponseCode from iso15118.shared.messages.xmldsig import Signature @@ -43,7 +44,8 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise ValueError( + raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)" + f"hexadecimal representation of max 8 bytes)", + ResponseCode.FAILED_SEQUENCE_ERROR, None ) from exc diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index c2c0710c6..2b3a17a5a 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -433,9 +433,10 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise ValueError( + raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)" + f"hexadecimal representation of max 8 bytes)", + ResponseCode.FAILED_SEQUENCE_ERROR, None ) from exc @@ -609,9 +610,10 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise ValueError( + raise V2GMessageValidationError( f"Invalid value '{value}' for EVCCID (must be " - f"hexadecimal representation of max 6 bytes)" + f"hexadecimal representation of max 6 bytes)", + ResponseCode.FAILED_SEQUENCE_ERROR, None ) from exc diff --git a/iso15118/shared/messages/iso15118_2/header.py b/iso15118/shared/messages/iso15118_2/header.py index 86029298a..82c7ebd0d 100644 --- a/iso15118/shared/messages/iso15118_2/header.py +++ b/iso15118/shared/messages/iso15118_2/header.py @@ -14,8 +14,9 @@ from pydantic import Field, validator +from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel -from iso15118.shared.messages.iso15118_2.datatypes import Notification +from iso15118.shared.messages.iso15118_2.datatypes import Notification, ResponseCode from iso15118.shared.messages.xmldsig import Signature @@ -43,7 +44,8 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise ValueError( + raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)" + f"hexadecimal representation of max 8 bytes)", + ResponseCode.FAILED_SEQUENCE_ERROR, None ) from exc diff --git a/iso15118/shared/messages/iso15118_20/common_messages.py b/iso15118/shared/messages/iso15118_20/common_messages.py index 1ff73222d..70305ade1 100644 --- a/iso15118/shared/messages/iso15118_20/common_messages.py +++ b/iso15118/shared/messages/iso15118_20/common_messages.py @@ -16,6 +16,7 @@ from pydantic import Field, root_validator, validator +from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.enums import ( INT_8_MAX, @@ -41,7 +42,7 @@ Receipt, RootCertificateIDList, V2GRequest, - V2GResponse, + V2GResponse, ResponseCode, ) from iso15118.shared.validators import one_field_must_be_set @@ -1092,9 +1093,10 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise ValueError( + raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)" + f"hexadecimal representation of max 8 bytes)", + ResponseCode.FAILED_SEQUENCE_ERROR, None ) from exc diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index fbf30c1bf..c40ebdaf9 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -16,6 +16,7 @@ from pydantic import Field, conbytes, conint, constr, validator +from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.enums import ( INT_8_MAX, @@ -63,9 +64,10 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise ValueError( + raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)" + f"hexadecimal representation of max 8 bytes)", + ResponseCode.FAILED_SEQUENCE_ERROR, None ) from exc From 0843b4316aed9d0f2e7395d19986f0e7b1cffd96 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Wed, 10 Aug 2022 11:32:26 +0100 Subject: [PATCH 18/28] reformated --- iso15118/shared/exceptions.py | 2 ++ iso15118/shared/exi_codec.py | 24 ++++++++++--------- iso15118/shared/messages/din_spec/body.py | 3 ++- iso15118/shared/messages/din_spec/header.py | 3 ++- iso15118/shared/messages/iso15118_2/body.py | 6 +++-- iso15118/shared/messages/iso15118_2/header.py | 3 ++- .../messages/iso15118_20/common_messages.py | 6 +++-- .../messages/iso15118_20/common_types.py | 3 ++- tests/secc/states/test_iso15118_2_states.py | 2 +- 9 files changed, 32 insertions(+), 20 deletions(-) diff --git a/iso15118/shared/exceptions.py b/iso15118/shared/exceptions.py index 8f673dc4c..7631967b7 100644 --- a/iso15118/shared/exceptions.py +++ b/iso15118/shared/exceptions.py @@ -237,6 +237,8 @@ def __init__(self): self, "No OCSP server entry in Authority Information Access extension field.", ) + + class V2GMessageValidationError(Exception): """Is thrown if message validation is failed""" diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index c6b5d28be..b393a1e9f 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -2,9 +2,9 @@ import json import logging from base64 import b64decode, b64encode +from typing import Any, Union from pydantic import ValidationError -from typing import Union, Any from iso15118.shared.exceptions import ( EXIDecodingError, @@ -18,12 +18,12 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) -from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC, \ - V2GMessage +from iso15118.shared.messages.din_spec.msgdef import V2GMessage +from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC from iso15118.shared.messages.enums import Namespace from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode -from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2, \ - V2GMessage +from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage +from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 from iso15118.shared.messages.iso15118_20.ac import ( ACChargeLoopReq, ACChargeLoopRes, @@ -380,7 +380,7 @@ def from_exi( raise EXIDecodingError("Can't identify protocol to use for decoding") - except (V2GMessageValidationError, ValidationError) as exc : + except (V2GMessageValidationError, ValidationError) as exc: if namespace == Namespace.ISO_V2_MSG_DEF: message = V2GMessageV2.construct_skip_validation( **decoded_dict["V2G_Message"] @@ -392,11 +392,13 @@ def from_exi( elif namespace.startswith(Namespace.ISO_V20_BASE): message = msg_class.construct(**msg_dict) if type(exc) == ValidationError: - raise V2GMessageValidationError(f"Error parsing the decoded EXI into" - f" a Pydantic class: {exc}. " - f"\n\nDecoded dict: {decoded_dict}", - ResponseCode.FAILED, - message) from exc + raise V2GMessageValidationError( + f"Error parsing the decoded EXI into" + f" a Pydantic class: {exc}. " + f"\n\nDecoded dict: {decoded_dict}", + ResponseCode.FAILED, + message, + ) from exc exc.message = message raise exc except EXIDecodingError as exc: diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 1ffab2d23..478ad6a70 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -95,7 +95,8 @@ def check_sessionid_is_hexbinary(cls, value): raise V2GMessageValidationError( f"Invalid value '{value}' for EVCCID (must be " f"hexadecimal representation of max 6 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, None + ResponseCode.FAILED_SEQUENCE_ERROR, + None, ) from exc diff --git a/iso15118/shared/messages/din_spec/header.py b/iso15118/shared/messages/din_spec/header.py index 9e4a66c5e..a9a583669 100644 --- a/iso15118/shared/messages/din_spec/header.py +++ b/iso15118/shared/messages/din_spec/header.py @@ -47,5 +47,6 @@ def check_sessionid_is_hexbinary(cls, value): raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, None + ResponseCode.FAILED_SEQUENCE_ERROR, + None, ) from exc diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 2b3a17a5a..32e6055c5 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -436,7 +436,8 @@ def check_sessionid_is_hexbinary(cls, value): raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, None + ResponseCode.FAILED_SEQUENCE_ERROR, + None, ) from exc @@ -613,7 +614,8 @@ def check_sessionid_is_hexbinary(cls, value): raise V2GMessageValidationError( f"Invalid value '{value}' for EVCCID (must be " f"hexadecimal representation of max 6 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, None + ResponseCode.FAILED_SEQUENCE_ERROR, + None, ) from exc diff --git a/iso15118/shared/messages/iso15118_2/header.py b/iso15118/shared/messages/iso15118_2/header.py index 82c7ebd0d..993057b30 100644 --- a/iso15118/shared/messages/iso15118_2/header.py +++ b/iso15118/shared/messages/iso15118_2/header.py @@ -47,5 +47,6 @@ def check_sessionid_is_hexbinary(cls, value): raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, None + ResponseCode.FAILED_SEQUENCE_ERROR, + None, ) from exc diff --git a/iso15118/shared/messages/iso15118_20/common_messages.py b/iso15118/shared/messages/iso15118_20/common_messages.py index 70305ade1..1e0219e38 100644 --- a/iso15118/shared/messages/iso15118_20/common_messages.py +++ b/iso15118/shared/messages/iso15118_20/common_messages.py @@ -40,9 +40,10 @@ Processing, RationalNumber, Receipt, + ResponseCode, RootCertificateIDList, V2GRequest, - V2GResponse, ResponseCode, + V2GResponse, ) from iso15118.shared.validators import one_field_must_be_set @@ -1096,7 +1097,8 @@ def check_sessionid_is_hexbinary(cls, value): raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, None + ResponseCode.FAILED_SEQUENCE_ERROR, + None, ) from exc diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index c40ebdaf9..ac04072cd 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -67,7 +67,8 @@ def check_sessionid_is_hexbinary(cls, value): raise V2GMessageValidationError( f"Invalid value '{value}' for SessionID (must be " f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, None + ResponseCode.FAILED_SEQUENCE_ERROR, + None, ) from exc diff --git a/tests/secc/states/test_iso15118_2_states.py b/tests/secc/states/test_iso15118_2_states.py index 57c634cbd..7eb20bd9e 100644 --- a/tests/secc/states/test_iso15118_2_states.py +++ b/tests/secc/states/test_iso15118_2_states.py @@ -1,5 +1,4 @@ from pathlib import Path -import json from unittest.mock import AsyncMock, Mock, patch import pytest @@ -20,6 +19,7 @@ AuthorizationTokenType, Contactor, ) +from iso15118.shared.messages.iso15118_2.datatypes import CertificateChain from tests.secc.states.test_messages import ( get_charge_parameter_discovery_req_message_departure_time_one_hour, get_charge_parameter_discovery_req_message_no_departure_time, From 39f1c5cb353ed84bfc5816016ff025fed686892b Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Wed, 10 Aug 2022 11:50:28 +0100 Subject: [PATCH 19/28] flake8 warnings are cleared --- iso15118/shared/exi_codec.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index b393a1e9f..537464779 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -18,7 +18,7 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) -from iso15118.shared.messages.din_spec.msgdef import V2GMessage + from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC from iso15118.shared.messages.enums import Namespace from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode @@ -266,7 +266,13 @@ def to_exi(self, msg_element: BaseModel, protocol_ns: str) -> bytes: def from_exi( self, exi_message: bytes, namespace: str - ) -> SupportedAppProtocolReq | SupportedAppProtocolRes | V2GMessage | V2GMessage | V2GMessageValidationError | Any: + ) -> Union[ + SupportedAppProtocolReq, + SupportedAppProtocolRes, + V2GMessageV2, + V2GMessageV20, + V2GMessageDINSPEC, + ]: """ Decodes the EXI encoded bytearray into a message according to the payload type provided. From 730445454d670340524bdf5ad18fb97edbe3ca57 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Wed, 10 Aug 2022 11:55:28 +0100 Subject: [PATCH 20/28] flake8 warnings are cleared --- iso15118/shared/exi_codec.py | 3 +-- iso15118/shared/messages/din_spec/body.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 537464779..55eca22ab 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -2,7 +2,7 @@ import json import logging from base64 import b64decode, b64encode -from typing import Any, Union +from typing import Union from pydantic import ValidationError @@ -22,7 +22,6 @@ from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC from iso15118.shared.messages.enums import Namespace from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode -from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 from iso15118.shared.messages.iso15118_20.ac import ( ACChargeLoopReq, diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 478ad6a70..bf6a6abfe 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -253,7 +253,8 @@ def validate_requested_energy_mode(cls, values): ) if requested_energy_mode not in ("DC_extended", "DC_core"): raise V2GMessageValidationError( - f"[V2G2-476] Wrong energy transfer mode transfer mode {requested_energy_mode}", + f"[V2G2-476] Wrong energy transfer mode transfer mode " + f"{requested_energy_mode}", ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, None, ) From 8fc5cb947fe2916fdfcbea4aebb0d77f975844fe Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Wed, 10 Aug 2022 14:41:12 +0100 Subject: [PATCH 21/28] flake8 warnings are cleared --- iso15118/shared/exi_codec.py | 1 - iso15118/shared/messages/iso15118_2/body.py | 7 --- tests/secc/messages/15118_2_messages.py | 65 +++++++++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 tests/secc/messages/15118_2_messages.py diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index 55eca22ab..c0c7a1294 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -18,7 +18,6 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) - from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC from iso15118.shared.messages.enums import Namespace from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 32e6055c5..4188db285 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -274,13 +274,6 @@ def requested_energy_mode_must_match_charge_parameter(cls, values): ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None, ) - if requested_energy_mode not in EnergyTransferModeEnum.__members__.values(): - raise V2GMessageValidationError( - "[V2G2-476] Wrong value for requested energy " - f"transfer mode {requested_energy_mode}", - ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, - None, - ) return values diff --git a/tests/secc/messages/15118_2_messages.py b/tests/secc/messages/15118_2_messages.py new file mode 100644 index 000000000..258287a0e --- /dev/null +++ b/tests/secc/messages/15118_2_messages.py @@ -0,0 +1,65 @@ +import json + +from pydantic import ValidationError + +from iso15118.shared.exceptions import V2GMessageValidationError +from iso15118.shared.exi_codec import CustomJSONDecoder +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode +from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage + + +def test_requested_energy_mode_must_match_charge_parameter(): + # [V2G2-477] + message = ( + '{"V2G_Message":{"Header":{"SessionID":"82DBA3A44ED6E5B9"},"Body":' + '{"ChargeParameterDiscoveryReq":{"MaxEntriesSAScheduleTuple":16,' + '"RequestedEnergyTransferMode":"AC_three_phase_core","DC_EVChargeParameter":' + '{"DepartureTime":0,"DC_EVStatus":{"EVReady":false,"EVErrorCode":' + '"NO_ERROR","EVRESSSOC":20},"EVMaximumCurrentLimit":{"Multiplier":' + '1,"Unit":"A","Value":8},"EVMaximumPowerLimit":{"Multiplier":3,' + '"Unit":"W","Value":29},"EVMaximumVoltageLimit":{"Multiplier":2,' + '"Unit":"V","Value":5},"EVEnergyCapacity":{"Multiplier":3,"Unit":' + '"Wh","Value":200},"EVEnergyRequest":{"Multiplier":3,"Unit":"Wh",' + '"Value":160},"FullSOC":99,"BulkSOC":80}}}}}' + ) + try: + decoded_dict = json.loads(message, cls=CustomJSONDecoder) + v2g_msg = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) + except V2GMessageValidationError as exc: + assert exc.response_code == ResponseCode.FAILED_WRONG_CHARGE_PARAMETER + v2g_msg = V2GMessage.construct_skip_validation(**decoded_dict["V2G_Message"]) + assert isinstance(v2g_msg, V2GMessage) + assert str(v2g_msg) == "ChargeParameterDiscoveryReq" + + +def test_check_sessionid_is_longer_than_8(): + sessionid_is_longer_than_8 = ( + '{"V2G_Message":{"Header":{"SessionID":' + '"00000000000000000000"},"Body":' + '{"SessionSetupReq":{"EVCCID":"0000020000000001"}}}}' + ) + + try: + decoded_dict = json.loads(sessionid_is_longer_than_8, cls=CustomJSONDecoder) + v2g_msg = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) + + except ValidationError: + v2g_msg = V2GMessage.construct_skip_validation(**decoded_dict["V2G_Message"]) + assert isinstance(v2g_msg, V2GMessage) + assert str(v2g_msg) == "SessionSetupReq" + + +def test_check_sessionid_is_not_int(): + sessionid_is_not_int = ( + '{"V2G_Message":{"Header":{"SessionID":"asd"},"Body":' + '{"SessionSetupReq":{"EVCCID":"0000020000000001"}}}}' + ) + try: + decoded_dict = json.loads(sessionid_is_not_int, cls=CustomJSONDecoder) + v2g_msg = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) + print(v2g_msg) + except V2GMessageValidationError as exc: + assert exc.response_code == ResponseCode.FAILED_SEQUENCE_ERROR + v2g_msg = V2GMessage.construct_skip_validation(**decoded_dict["V2G_Message"]) + assert isinstance(v2g_msg, V2GMessage) + assert str(v2g_msg) == "SessionSetupReq" From d6260857e32a32207c58434f805c03096e4a72b7 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Wed, 10 Aug 2022 16:35:10 +0100 Subject: [PATCH 22/28] unneccessery comments deleted --- iso15118/shared/messages/din_spec/body.py | 5 ----- iso15118/shared/messages/iso15118_2/body.py | 5 ----- 2 files changed, 10 deletions(-) diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index bf6a6abfe..7995b2259 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -237,11 +237,6 @@ def validate_requested_energy_mode(cls, values): Pydantic validators are "class methods", see https://pydantic-docs.helpmanual.io/usage/validators/ - We need to actually send FAILED_WrongChargeParameter or - FAILED_WrongEnergyTransferMode if the wrong parameter set is - provided, one or multiple parameters can not be interpreted - (see [V2G2-477]). Need to check how to not just bury - that information in a pydantic validation error. """ # pylint: disable=no-self-argument # pylint: disable=no-self-use diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 4188db285..19456c689 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -252,11 +252,6 @@ def requested_energy_mode_must_match_charge_parameter(cls, values): Pydantic validators are "class methods", see https://pydantic-docs.helpmanual.io/usage/validators/ - TODO We need to actually send FAILED_WrongChargeParameter or - FAILED_WrongEnergyTransferMode if the wrong parameter set is - provided, one or multiple parameters can not be interpreted - (see [V2G2-477]). Need to check how to not just bury - that information in a pydantic validation error. """ # pylint: disable=no-self-argument # pylint: disable=no-self-use From 9a47a13e411be4803077e80a2d10100bf41442d6 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Thu, 11 Aug 2022 10:43:13 +0100 Subject: [PATCH 23/28] V2Gmessage creation --- iso15118/shared/messages/iso15118_2/body.py | 44 ++++++- iso15118/shared/messages/iso15118_2/header.py | 25 ++++ iso15118/shared/messages/iso15118_2/msgdef.py | 113 +++++++++++++++++- tests/secc/messages/15118_2_messages.py | 29 ++++- 4 files changed, 200 insertions(+), 11 deletions(-) diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 19456c689..c4e03b482 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -11,7 +11,9 @@ """ import logging from abc import ABC -from typing import Optional, Tuple, Type + +from pydantic.main import object_setattr +from typing import Optional, Tuple, Type, Any, Dict from pydantic import Field, root_validator, validator @@ -82,6 +84,25 @@ class BodyBase(BaseModel, ABC): def __str__(self): return type(self).__name__ + @classmethod + def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, + **values: Any) -> 'Model': + m = cls.__new__(cls) + fields_values: Dict[str, Any] = {} + for name, field in cls.__fields__.items(): + if field.alias in values: + fields_values[name] = values[field.alias] + elif name in values: + fields_values[name] = values[name] + elif not field.required: + fields_values[name] = field.get_default() + + object_setattr(m, '__dict__', fields_values) + if _fields_set is None: + _fields_set = set(fields_values.keys()) + object_setattr(m, '__fields_set__', _fields_set) + m._init_private_attributes() + return m class Response(BodyBase, ABC): """ @@ -707,7 +728,7 @@ def get_message_name(self) -> str: """Returns the name of the one V2GMessage that is set for Body.""" for k in self.__dict__.keys(): if getattr(self, k): - return str(k) + return str(getattr(self, k)) return "" @@ -727,6 +748,25 @@ def get_message_and_name(self) -> Tuple[Optional[BodyBase], str]: return None, "" + @classmethod + def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, + **values: Any) -> 'Model': + m = cls.__new__(cls) + fields_values: Dict[str, Any] = {} + for name, field in cls.__fields__.items(): + if field.alias in values: + fields_values[name] = values[field.alias] + elif name in values: + fields_values[name] = values[name] + elif not field.required: + fields_values[name] = field.get_default() + + object_setattr(m, '__dict__', fields_values) + if _fields_set is None: + _fields_set = set(fields_values.keys()) + object_setattr(m, '__fields_set__', _fields_set) + m._init_private_attributes() + return m def get_msg_type(msg_name: str) -> Optional[Type[BodyBase]]: """ diff --git a/iso15118/shared/messages/iso15118_2/header.py b/iso15118/shared/messages/iso15118_2/header.py index 993057b30..b93f6cd24 100644 --- a/iso15118/shared/messages/iso15118_2/header.py +++ b/iso15118/shared/messages/iso15118_2/header.py @@ -11,6 +11,8 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ +from pydantic.main import object_setattr +from typing import Type, Optional, Any from pydantic import Field, validator @@ -50,3 +52,26 @@ def check_sessionid_is_hexbinary(cls, value): ResponseCode.FAILED_SEQUENCE_ERROR, None, ) from exc + + @classmethod + def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, + **values: Any) -> 'Model': + """ + Creates a new model setting __dict__ and __fields_set__ from trusted or pre-validated data. + Default values are respected, but no other validation is performed. + Behaves as if `Config.extra = 'allow'` was set since it adds all passed values + """ + m = cls.__new__(cls) + fields_values: Dict[str, Any] = {} + for name, field in cls.__fields__.items(): + if name in values: + fields_values[name] = values[name] + elif not field.required: + fields_values[name] = field.get_default() + fields_values.update(values) + object_setattr(m, '__dict__', fields_values) + if _fields_set is None: + _fields_set = set(values.keys()) + object_setattr(m, '__fields_set__', _fields_set) + m._init_private_attributes() + return m \ No newline at end of file diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index a703c58ec..c3f135423 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -12,9 +12,17 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from typing import Any +from copy import deepcopy +from enum import Enum -from pydantic import Field +from pydantic.error_wrappers import ErrorWrapper, ValidationError +from pydantic.fields import Undefined +from pydantic.main import object_setattr +from pydantic.typing import display_as_type +from pydantic.utils import ROOT_KEY, Representation +from typing import Any, Type, Tuple, List, Set, Iterator, Optional, Dict + +from pydantic import Field, ExtraError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.iso15118_2.body import Body @@ -31,7 +39,27 @@ def __str__(self): return str(self.body.get_message_name()) @classmethod - def construct_skip_validation(cls, **values: Any): + def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, + **values: Any) -> 'Model': + m = cls.__new__(cls) + fields_values: Dict[str, Any] = {} + for name, field in cls.__fields__.items(): + if field.alias in values: + fields_values[name] = values[field.alias] + elif name in values: + fields_values[name] = values[name] + elif not field.required: + fields_values[name] = field.get_default() + + object_setattr(m, '__dict__', fields_values) + if _fields_set is None: + _fields_set = set(fields_values.keys()) + object_setattr(m, '__fields_set__', _fields_set) + m._init_private_attributes() + return m + + @classmethod + def construct_skip_validation(cls, **data: Any): """ Creates a new model when the validation fails to get V2G message. """ @@ -39,8 +67,83 @@ def construct_skip_validation(cls, **values: Any): _fields_set = {"header", "body"} try: object.__setattr__(m, "__fields_set__", _fields_set) - m.header = MessageHeader.construct(**values["Header"]) - m.body = Body.construct(**values["Body"]) + m.header = MessageHeader.construct( **data["Header"]) + m.body = Body.construct(**data["Body"]) except Exception as e: raise ValueError(f"Error on creating {cls} without validation: {e}") return m + + +_missing = object() + +def validate_model( + model: Type[BaseModel], input_data: 'DictStrAny', cls: 'ModelOrDc' = None +) -> Tuple['DictStrAny', 'SetStr']: + """ + validate data against a model. + """ + values = {} + errors = [] + # input_data names, possibly alias + names_used = set() + # field names, never aliases + fields_set = set() + cls_ = cls or model + + for name, field in model.__fields__.items(): + value = input_data.get(field.alias, _missing) + using_name = False + if value is _missing and field.alt_alias: + value = input_data.get(field.name, _missing) + using_name = True + + if value is _missing: + if field.required: + print("Missing Error") + #errors.append(ErrorWrapper(MissingError(), loc=field.alias)) + continue + + value = field.get_default() + + if not config.validate_all and not field.validate_always: + values[name] = value + continue + else: + fields_set.add(name) + if check_extra: + names_used.add(field.name if using_name else field.alias) + + v_, errors_ = field.validate(value, values, loc=field.alias, cls=cls_) + if isinstance(errors_): + errors.append(errors_) + elif isinstance(errors_, list): + errors.extend(errors_) + else: + values[name] = v_ + + if check_extra: + if isinstance(input_data, GetterDict): + extra = input_data.extra_keys() - names_used + else: + extra = input_data.keys() - names_used + if extra: + fields_set |= extra + if config.extra is Extra.allow: + for f in extra: + values[f] = input_data[f] + else: + for f in sorted(extra): + errors.append(ErrorWrapper(ExtraError(), loc=f)) + + for skip_on_failure, validator in model.__post_root_validators__: + if skip_on_failure and errors: + continue + try: + values = validator(cls_, values) + except (ValueError, TypeError, AssertionError) as exc: + errors.append(ErrorWrapper(exc, loc=ROOT_KEY)) + + if errors: + return values, fields_set, ValidationError(errors, cls_) + else: + return values, fields_set, None diff --git a/tests/secc/messages/15118_2_messages.py b/tests/secc/messages/15118_2_messages.py index 258287a0e..e79070276 100644 --- a/tests/secc/messages/15118_2_messages.py +++ b/tests/secc/messages/15118_2_messages.py @@ -4,13 +4,15 @@ from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.exi_codec import CustomJSONDecoder +from iso15118.shared.messages.din_spec.header import MessageHeader +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 def test_requested_energy_mode_must_match_charge_parameter(): # [V2G2-477] - message = ( + message1 = ( '{"V2G_Message":{"Header":{"SessionID":"82DBA3A44ED6E5B9"},"Body":' '{"ChargeParameterDiscoveryReq":{"MaxEntriesSAScheduleTuple":16,' '"RequestedEnergyTransferMode":"AC_three_phase_core","DC_EVChargeParameter":' @@ -22,12 +24,31 @@ def test_requested_energy_mode_must_match_charge_parameter(): '"Wh","Value":200},"EVEnergyRequest":{"Multiplier":3,"Unit":"Wh",' '"Value":160},"FullSOC":99,"BulkSOC":80}}}}}' ) + + message2 = ('{"V2G_Message":{"Header":{"SessionID":"0000000000000000"},"Body":' + '{"SessionSetupReq":{"EVCCID":"020000000001"}}}}') try: - decoded_dict = json.loads(message, cls=CustomJSONDecoder) - v2g_msg = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) + decoded_dict = json.loads(message2, cls=CustomJSONDecoder) + v2g_msg1 = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) + print("KARATAS") + print(v2g_msg1.body.get_message_name()) + print(v2g_msg1.body.get_message_and_name()) + + decoded_dict = json.loads(message1, cls=CustomJSONDecoder) + v2g_msg2 = V2GMessage.construct_skip_validation(**decoded_dict["V2G_Message"]) + + print("KARATAS") + print(v2g_msg2.body.get_message_name()) + print(v2g_msg2.body.get_message_and_name()) + assert isinstance(v2g_msg2, V2GMessage) + assert str(v2g_msg2) == "ChargeParameterDiscoveryReq" + except V2GMessageValidationError as exc: assert exc.response_code == ResponseCode.FAILED_WRONG_CHARGE_PARAMETER - v2g_msg = V2GMessage.construct_skip_validation(**decoded_dict["V2G_Message"]) + v2g_msg = V2GMessage.construct(**decoded_dict["V2G_Message"]) + print("KARATAS") + print(v2g_msg.body.get_message_name()) + print(v2g_msg.body.get_message_and_name()) assert isinstance(v2g_msg, V2GMessage) assert str(v2g_msg) == "ChargeParameterDiscoveryReq" From 9b69d789c5447bb4bda66399051734a7b686f258 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Tue, 16 Aug 2022 09:37:46 +0100 Subject: [PATCH 24/28] test added --- iso15118/secc/states/secc_state.py | 31 +++++-- iso15118/shared/comm_session.py | 4 +- iso15118/shared/exi_codec.py | 36 ++++---- iso15118/shared/messages/din_spec/body.py | 13 +-- iso15118/shared/messages/din_spec/header.py | 3 +- iso15118/shared/messages/iso15118_2/body.py | 11 +-- iso15118/shared/messages/iso15118_2/header.py | 31 +------ iso15118/shared/messages/iso15118_20/ac.py | 3 +- .../messages/iso15118_20/common_messages.py | 4 +- .../messages/iso15118_20/common_types.py | 6 +- iso15118/shared/states.py | 19 ---- .../secc/messages/15118_2_invalid_messages.py | 61 +++++++++++++ tests/secc/messages/15118_2_messages.py | 86 ------------------- 13 files changed, 125 insertions(+), 183 deletions(-) create mode 100644 tests/secc/messages/15118_2_invalid_messages.py delete mode 100644 tests/secc/messages/15118_2_messages.py diff --git a/iso15118/secc/states/secc_state.py b/iso15118/secc/states/secc_state.py index a67835475..14e53add8 100644 --- a/iso15118/secc/states/secc_state.py +++ b/iso15118/secc/states/secc_state.py @@ -23,7 +23,8 @@ ) 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 BodyBase as BodyBaseV2, BodyBase, \ + ChargeParameterDiscoveryReq, CableCheckReq from iso15118.shared.messages.iso15118_2.body import ( SessionSetupReq as SessionSetupReqV2, ) @@ -294,10 +295,13 @@ def stop_state_machine( V2GMessageV2, V2GMessageV20, V2GMessageDINSPEC, + None ], response_code: Union[ ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC ], + message_body_type:Optional[type] = None, + namespace: Optional[Namespace] = None, ): """ In case the processing of a message from the EVCC fails, the SECC needs @@ -317,31 +321,44 @@ def stop_state_machine( if isinstance(faulty_request, V2GMessageV2): msg_type = get_msg_type(str(faulty_request)) + msg_namespace = Namespace.ISO_V2_MSG_DEF + elif isinstance(faulty_request, V2GMessageDINSPEC): + msg_type = get_msg_type_dinspec(str(faulty_request)) + msg_namespace = Namespace.DIN_MSG_DEF + elif isinstance(faulty_request, V2GMessageV20): + msg_type = type(faulty_request) + msg_namespace = Namespace.ISO_V20_BASE + elif isinstance(faulty_request, SupportedAppProtocolReq): + msg_namespace = Namespace.SAP + msg_type = faulty_request + else: + msg_type = message_body_type + msg_namespace = namespace + + if msg_namespace == Namespace.ISO_V2_MSG_DEF: error_res = self.comm_session.failed_responses_isov2.get(msg_type) error_res.response_code = response_code self.create_next_message(Terminate, error_res, 0, Namespace.ISO_V2_MSG_DEF) - elif isinstance(faulty_request, V2GMessageDINSPEC): - msg_type = get_msg_type_dinspec(str(faulty_request)) + elif msg_namespace == Namespace.DIN_MSG_DEF: error_res = self.comm_session.failed_responses_din_spec.get(msg_type) error_res.response_code = response_code self.create_next_message(Terminate, error_res, 0, Namespace.DIN_MSG_DEF) # Here we could have been more specific and check if it is a V2GRequestV20, # but to be consistent with the other if clauses and since there is no negative # consequences in the behavior of the code, we check if it is a V2GMessageV20 - elif isinstance(faulty_request, V2GMessageV20): + elif msg_namespace.startswith(Namespace.ISO_V20_BASE): ( error_res, namespace, payload_type, - ) = self.comm_session.failed_responses_isov20.get(type(faulty_request)) + ) = self.comm_session.failed_responses_isov20.get(msg_type) # As the Header in the case of -20 is part of the -20 message payload, # we need to set the session id of the current session to it error_res.header.session_id = self.comm_session.session_id error_res.response_code = response_code self.create_next_message(Terminate, error_res, 0, namespace, payload_type) - elif isinstance(faulty_request, SupportedAppProtocolReq): + elif msg_namespace == Namespace.SAP: error_res = SupportedAppProtocolRes(response_code=response_code) - self.create_next_message(Terminate, error_res, 0, Namespace.SAP) else: # Should actually never happen diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index 4b51c5f8d..b4f548b71 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -207,7 +207,9 @@ async def process_message(self, message: bytes): except V2GMessageValidationError as exc: self.comm_session.current_state.stop_state_machine( - exc.reason, exc.message, exc.response_code + exc.reason, None, exc.response_code, + exc.message, + self.get_exi_ns(v2gtp_msg.payload_type) ) return except EXIDecodingError as exc: diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index c0c7a1294..e04d8a1f6 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -20,6 +20,8 @@ ) 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 get_msg_type +from iso15118.shared.messages.din_spec.body import get_msg_type as get_msg_type_dinspec from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 from iso15118.shared.messages.iso15118_20.ac import ( @@ -383,27 +385,25 @@ def from_exi( return msg_class.parse_obj(msg_dict) raise EXIDecodingError("Can't identify protocol to use for decoding") - - except (V2GMessageValidationError, ValidationError) as exc: + except ValidationError as exc: if namespace == Namespace.ISO_V2_MSG_DEF: - message = V2GMessageV2.construct_skip_validation( - **decoded_dict["V2G_Message"] - ) + msg_name = next(iter(decoded_dict["V2G_Message"]["Body"])) + msg_type = get_msg_type(msg_name) elif namespace == Namespace.DIN_MSG_DEF: - message = V2GMessageDINSPEC.construct_skip_validation( - **decoded_dict["V2G_Message"] - ) + msg_name = next(iter(decoded_dict["V2G_Message"]["Body"])) + msg_type = get_msg_type_dinspec(msg_name) elif namespace.startswith(Namespace.ISO_V20_BASE): - message = msg_class.construct(**msg_dict) - if type(exc) == ValidationError: - raise V2GMessageValidationError( - f"Error parsing the decoded EXI into" - f" a Pydantic class: {exc}. " - f"\n\nDecoded dict: {decoded_dict}", - ResponseCode.FAILED, - message, - ) from exc - exc.message = message + msg_type = msg_class + elif namespace == Namespace.SAP: + if "supportedAppProtocolReq" in decoded_dict: + msg_type = SupportedAppProtocolReq + elif "supportedAppProtocolRes" in decoded_dict: + msg_type = SupportedAppProtocolRes + + raise V2GMessageValidationError(f"Validation error: {exc}. \n\nDecoded dict: " f"{decoded_dict}", + ResponseCode.FAILED, msg_type) from exc + + except V2GMessageValidationError as exc: raise exc except EXIDecodingError as exc: raise EXIDecodingError( diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index 7995b2259..ca36eabc6 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -88,15 +88,17 @@ def check_sessionid_is_hexbinary(cls, value): # pylint: disable=no-self-argument # pylint: disable=no-self-use try: + raise ValueError( + f"Invalid value '{value}' for EVCCID (must be " + f"hexadecimal representation of max 6 bytes)", + ) # convert value to int, assuming base 16 int(value, 16) return value except ValueError as exc: - raise V2GMessageValidationError( + raise ValueError( f"Invalid value '{value}' for EVCCID (must be " f"hexadecimal representation of max 6 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, - None, ) from exc @@ -246,12 +248,13 @@ def validate_requested_energy_mode(cls, values): values.get("ac_ev_charge_parameter"), values.get("dc_ev_charge_parameter"), ) + if requested_energy_mode not in ("DC_extended", "DC_core"): raise V2GMessageValidationError( f"[V2G2-476] Wrong energy transfer mode transfer mode " f"{requested_energy_mode}", ResponseCode.FAILED_WRONG_ENERGY_TRANSFER_MODE, - None, + cls, ) if ("AC_" in requested_energy_mode and dc_params) or ( "DC_" in requested_energy_mode and ac_params @@ -260,7 +263,7 @@ def validate_requested_energy_mode(cls, values): "[V2G2-477] Wrong charge parameters for requested energy " f"transfer mode {requested_energy_mode}", ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, - None, + cls, ) return values diff --git a/iso15118/shared/messages/din_spec/header.py b/iso15118/shared/messages/din_spec/header.py index a9a583669..f07afec5b 100644 --- a/iso15118/shared/messages/din_spec/header.py +++ b/iso15118/shared/messages/din_spec/header.py @@ -44,9 +44,8 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise V2GMessageValidationError( + raise ValueError( f"Invalid value '{value}' for SessionID (must be " f"hexadecimal representation of max 8 bytes)", ResponseCode.FAILED_SEQUENCE_ERROR, - None, ) from exc diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index c4e03b482..b65935d44 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -226,7 +226,6 @@ class CertificateUpdateRes(Response): class ChargeParameterDiscoveryReq(BodyBase): """See section 8.4.3.8.2 in ISO 15118-2""" - # XSD type unsignedShort (16 bit integer) with value range [0..65535] max_entries_sa_schedule_tuple: int = Field( None, ge=0, le=65535, alias="MaxEntriesSAScheduleTuple" @@ -288,7 +287,7 @@ def requested_energy_mode_must_match_charge_parameter(cls, values): "[V2G2-477] Wrong charge parameters for requested energy " f"transfer mode {requested_energy_mode}", ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, - None, + cls, ) return values @@ -442,11 +441,9 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise V2GMessageValidationError( + raise ValueError( f"Invalid value '{value}' for SessionID (must be " f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, - None, ) from exc @@ -620,11 +617,9 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise V2GMessageValidationError( + raise ValueError( f"Invalid value '{value}' for EVCCID (must be " f"hexadecimal representation of max 6 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, - None, ) from exc diff --git a/iso15118/shared/messages/iso15118_2/header.py b/iso15118/shared/messages/iso15118_2/header.py index b93f6cd24..9a73f7c62 100644 --- a/iso15118/shared/messages/iso15118_2/header.py +++ b/iso15118/shared/messages/iso15118_2/header.py @@ -46,32 +46,5 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise V2GMessageValidationError( - f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, - None, - ) from exc - - @classmethod - def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, - **values: Any) -> 'Model': - """ - Creates a new model setting __dict__ and __fields_set__ from trusted or pre-validated data. - Default values are respected, but no other validation is performed. - Behaves as if `Config.extra = 'allow'` was set since it adds all passed values - """ - m = cls.__new__(cls) - fields_values: Dict[str, Any] = {} - for name, field in cls.__fields__.items(): - if name in values: - fields_values[name] = values[name] - elif not field.required: - fields_values[name] = field.get_default() - fields_values.update(values) - object_setattr(m, '__dict__', fields_values) - if _fields_set is None: - _fields_set = set(values.keys()) - object_setattr(m, '__fields_set__', _fields_set) - m._init_private_attributes() - return m \ No newline at end of file + raise ValueError(f"Invalid value '{value}' for SessionID (must be " + f"hexadecimal representation of max 8 bytes)") from exc diff --git a/iso15118/shared/messages/iso15118_20/ac.py b/iso15118/shared/messages/iso15118_20/ac.py index 861b45f9b..2397ca54a 100644 --- a/iso15118/shared/messages/iso15118_20/ac.py +++ b/iso15118/shared/messages/iso15118_20/ac.py @@ -351,7 +351,8 @@ def either_ac_or_ac_bpt_params(cls, values): return values except ValueError as exc: raise V2GMessageValidationError( - str(exc), ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, None + str(exc), ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, + ChargeParameterDiscoveryReq ) def __str__(self): diff --git a/iso15118/shared/messages/iso15118_20/common_messages.py b/iso15118/shared/messages/iso15118_20/common_messages.py index 1e0219e38..455a11338 100644 --- a/iso15118/shared/messages/iso15118_20/common_messages.py +++ b/iso15118/shared/messages/iso15118_20/common_messages.py @@ -1094,11 +1094,9 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise V2GMessageValidationError( + raise ValueError( f"Invalid value '{value}' for SessionID (must be " f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, - None, ) from exc diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index ac04072cd..689132ad5 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -64,11 +64,9 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise V2GMessageValidationError( + raise ValueError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, - None, + f"hexadecimal representation of max 8 bytes)" ) from exc diff --git a/iso15118/shared/states.py b/iso15118/shared/states.py index 8727fa08c..2ace91740 100644 --- a/iso15118/shared/states.py +++ b/iso15118/shared/states.py @@ -357,25 +357,6 @@ def create_next_message( f"creating a V2GTPMessage. {exc}" ) - @abstractmethod - def stop_state_machine( - self, - reason: str, - faulty_request: Optional[ - Union[ - SupportedAppProtocolReq, - SupportedAppProtocolRes, - V2GMessageV2, - V2GMessageV20, - V2GMessageDINSPEC, - ] - ] = None, - response_code: Optional[ - Union[ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC] - ] = None, - ): - raise NotImplementedError - def __repr__(self): """ Returns the object representation in string format diff --git a/tests/secc/messages/15118_2_invalid_messages.py b/tests/secc/messages/15118_2_invalid_messages.py new file mode 100644 index 000000000..595bbf0a4 --- /dev/null +++ b/tests/secc/messages/15118_2_invalid_messages.py @@ -0,0 +1,61 @@ +import json +from dataclasses import dataclass + +import pytest +from pydantic import ValidationError + +from iso15118.secc.comm_session_handler import SECCCommunicationSession +from iso15118.secc.controller.simulator import SimEVSEController +from iso15118.secc.states.sap_states import SupportedAppProtocol +from iso15118.shared.comm_session import SessionStateMachine +from iso15118.shared.exceptions import V2GMessageValidationError +from iso15118.shared.exi_codec import CustomJSONDecoder, EXI +from iso15118.shared.exificient_exi_codec import ExificientEXICodec +from iso15118.shared.messages.din_spec.header import MessageHeader +from iso15118.shared.messages.enums import Namespace, EnergyTransferModeEnum, Protocol, \ + ISOV2PayloadTypes +from iso15118.shared.messages.iso15118_2.body import Body, ChargeParameterDiscoveryReq, \ + SessionSetupReq +from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode +from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage +from iso15118.shared.messages.v2gtp import V2GTPMessage +from iso15118.shared.notifications import StopNotification +from tests.dinspec.secc.test_dinspec_secc_states import MockWriter + + +@dataclass +class InvalidV2GMessage: + msg: str + msg_body: Body + response_code: ResponseCode + + +invalid_v2g_2_messages = [ + (InvalidV2GMessage(( + # [V2G2-477] + # Parameters are not compatible with RequestedEnergyTransferMode + '{"V2G_Message":{"Header":{"SessionID":"82DBA3A44ED6E5B9"},"Body":' + '{"ChargeParameterDiscoveryReq":{"MaxEntriesSAScheduleTuple":16,' + '"RequestedEnergyTransferMode":"AC_three_phase_core","DC_EVChargeParameter":' + '{"DepartureTime":0,"DC_EVStatus":{"EVReady":false,"EVErrorCode":' + '"NO_ERROR","EVRESSSOC":20},"EVMaximumCurrentLimit":{"Multiplier":' + '1,"Unit":"A","Value":8},"EVMaximumPowerLimit":{"Multiplier":3,' + '"Unit":"W","Value":29},"EVMaximumVoltageLimit":{"Multiplier":2,' + '"Unit":"V","Value":5},"EVEnergyCapacity":{"Multiplier":3,"Unit":' + '"Wh","Value":200},"EVEnergyRequest":{"Multiplier":3,"Unit":"Wh",' + '"Value":160},"FullSOC":99,"BulkSOC":80}}}}}' +), + ChargeParameterDiscoveryReq, + ResponseCode.FAILED_WRONG_CHARGE_PARAMETER) +), +] + + +def test_invalid_v2g_2_messages(): + for message in invalid_v2g_2_messages: + try: + invalid_msg = json.loads(message.msg) + V2GMessage.parse_obj(invalid_msg["V2G_Message"]) + except V2GMessageValidationError as exc: + assert exc.message is message.msg_body + assert exc.response_code is message.response_code diff --git a/tests/secc/messages/15118_2_messages.py b/tests/secc/messages/15118_2_messages.py deleted file mode 100644 index e79070276..000000000 --- a/tests/secc/messages/15118_2_messages.py +++ /dev/null @@ -1,86 +0,0 @@ -import json - -from pydantic import ValidationError - -from iso15118.shared.exceptions import V2GMessageValidationError -from iso15118.shared.exi_codec import CustomJSONDecoder -from iso15118.shared.messages.din_spec.header import MessageHeader -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 - - -def test_requested_energy_mode_must_match_charge_parameter(): - # [V2G2-477] - message1 = ( - '{"V2G_Message":{"Header":{"SessionID":"82DBA3A44ED6E5B9"},"Body":' - '{"ChargeParameterDiscoveryReq":{"MaxEntriesSAScheduleTuple":16,' - '"RequestedEnergyTransferMode":"AC_three_phase_core","DC_EVChargeParameter":' - '{"DepartureTime":0,"DC_EVStatus":{"EVReady":false,"EVErrorCode":' - '"NO_ERROR","EVRESSSOC":20},"EVMaximumCurrentLimit":{"Multiplier":' - '1,"Unit":"A","Value":8},"EVMaximumPowerLimit":{"Multiplier":3,' - '"Unit":"W","Value":29},"EVMaximumVoltageLimit":{"Multiplier":2,' - '"Unit":"V","Value":5},"EVEnergyCapacity":{"Multiplier":3,"Unit":' - '"Wh","Value":200},"EVEnergyRequest":{"Multiplier":3,"Unit":"Wh",' - '"Value":160},"FullSOC":99,"BulkSOC":80}}}}}' - ) - - message2 = ('{"V2G_Message":{"Header":{"SessionID":"0000000000000000"},"Body":' - '{"SessionSetupReq":{"EVCCID":"020000000001"}}}}') - try: - decoded_dict = json.loads(message2, cls=CustomJSONDecoder) - v2g_msg1 = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) - print("KARATAS") - print(v2g_msg1.body.get_message_name()) - print(v2g_msg1.body.get_message_and_name()) - - decoded_dict = json.loads(message1, cls=CustomJSONDecoder) - v2g_msg2 = V2GMessage.construct_skip_validation(**decoded_dict["V2G_Message"]) - - print("KARATAS") - print(v2g_msg2.body.get_message_name()) - print(v2g_msg2.body.get_message_and_name()) - assert isinstance(v2g_msg2, V2GMessage) - assert str(v2g_msg2) == "ChargeParameterDiscoveryReq" - - except V2GMessageValidationError as exc: - assert exc.response_code == ResponseCode.FAILED_WRONG_CHARGE_PARAMETER - v2g_msg = V2GMessage.construct(**decoded_dict["V2G_Message"]) - print("KARATAS") - print(v2g_msg.body.get_message_name()) - print(v2g_msg.body.get_message_and_name()) - assert isinstance(v2g_msg, V2GMessage) - assert str(v2g_msg) == "ChargeParameterDiscoveryReq" - - -def test_check_sessionid_is_longer_than_8(): - sessionid_is_longer_than_8 = ( - '{"V2G_Message":{"Header":{"SessionID":' - '"00000000000000000000"},"Body":' - '{"SessionSetupReq":{"EVCCID":"0000020000000001"}}}}' - ) - - try: - decoded_dict = json.loads(sessionid_is_longer_than_8, cls=CustomJSONDecoder) - v2g_msg = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) - - except ValidationError: - v2g_msg = V2GMessage.construct_skip_validation(**decoded_dict["V2G_Message"]) - assert isinstance(v2g_msg, V2GMessage) - assert str(v2g_msg) == "SessionSetupReq" - - -def test_check_sessionid_is_not_int(): - sessionid_is_not_int = ( - '{"V2G_Message":{"Header":{"SessionID":"asd"},"Body":' - '{"SessionSetupReq":{"EVCCID":"0000020000000001"}}}}' - ) - try: - decoded_dict = json.loads(sessionid_is_not_int, cls=CustomJSONDecoder) - v2g_msg = V2GMessage.parse_obj(decoded_dict["V2G_Message"]) - print(v2g_msg) - except V2GMessageValidationError as exc: - assert exc.response_code == ResponseCode.FAILED_SEQUENCE_ERROR - v2g_msg = V2GMessage.construct_skip_validation(**decoded_dict["V2G_Message"]) - assert isinstance(v2g_msg, V2GMessage) - assert str(v2g_msg) == "SessionSetupReq" From e8c263a2f078c9d35ad72ea3e24e863872400118 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Tue, 16 Aug 2022 09:39:54 +0100 Subject: [PATCH 25/28] code quality --- iso15118/secc/states/secc_state.py | 12 +++-- iso15118/shared/comm_session.py | 6 ++- iso15118/shared/exi_codec.py | 9 ++-- iso15118/shared/messages/iso15118_2/body.py | 26 +++++---- iso15118/shared/messages/iso15118_2/header.py | 10 ++-- iso15118/shared/messages/iso15118_2/msgdef.py | 23 ++++---- iso15118/shared/messages/iso15118_20/ac.py | 5 +- .../secc/messages/15118_2_invalid_messages.py | 54 +++++++++++-------- 8 files changed, 86 insertions(+), 59 deletions(-) diff --git a/iso15118/secc/states/secc_state.py b/iso15118/secc/states/secc_state.py index 14e53add8..3afa4c680 100644 --- a/iso15118/secc/states/secc_state.py +++ b/iso15118/secc/states/secc_state.py @@ -23,8 +23,12 @@ ) 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, BodyBase, \ - ChargeParameterDiscoveryReq, CableCheckReq +from iso15118.shared.messages.iso15118_2.body import BodyBase +from iso15118.shared.messages.iso15118_2.body import BodyBase as BodyBaseV2 +from iso15118.shared.messages.iso15118_2.body import ( + CableCheckReq, + ChargeParameterDiscoveryReq, +) from iso15118.shared.messages.iso15118_2.body import ( SessionSetupReq as SessionSetupReqV2, ) @@ -295,12 +299,12 @@ def stop_state_machine( V2GMessageV2, V2GMessageV20, V2GMessageDINSPEC, - None + None, ], response_code: Union[ ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC ], - message_body_type:Optional[type] = None, + message_body_type: Optional[type] = None, namespace: Optional[Namespace] = None, ): """ diff --git a/iso15118/shared/comm_session.py b/iso15118/shared/comm_session.py index b4f548b71..84714834a 100644 --- a/iso15118/shared/comm_session.py +++ b/iso15118/shared/comm_session.py @@ -207,9 +207,11 @@ async def process_message(self, message: bytes): except V2GMessageValidationError as exc: self.comm_session.current_state.stop_state_machine( - exc.reason, None, exc.response_code, + exc.reason, + None, + exc.response_code, exc.message, - self.get_exi_ns(v2gtp_msg.payload_type) + self.get_exi_ns(v2gtp_msg.payload_type), ) return except EXIDecodingError as exc: diff --git a/iso15118/shared/exi_codec.py b/iso15118/shared/exi_codec.py index e04d8a1f6..4b6cfabbf 100644 --- a/iso15118/shared/exi_codec.py +++ b/iso15118/shared/exi_codec.py @@ -18,10 +18,10 @@ SupportedAppProtocolReq, SupportedAppProtocolRes, ) +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 get_msg_type -from iso15118.shared.messages.din_spec.body import get_msg_type as get_msg_type_dinspec from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 from iso15118.shared.messages.iso15118_20.ac import ( @@ -400,8 +400,11 @@ def from_exi( elif "supportedAppProtocolRes" in decoded_dict: msg_type = SupportedAppProtocolRes - raise V2GMessageValidationError(f"Validation error: {exc}. \n\nDecoded dict: " f"{decoded_dict}", - ResponseCode.FAILED, msg_type) from exc + raise V2GMessageValidationError( + f"Validation error: {exc}. \n\nDecoded dict: " f"{decoded_dict}", + ResponseCode.FAILED, + msg_type, + ) from exc except V2GMessageValidationError as exc: raise exc diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index b65935d44..52fe52760 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -11,11 +11,10 @@ """ import logging from abc import ABC - -from pydantic.main import object_setattr -from typing import Optional, Tuple, Type, Any, Dict +from typing import Any, Dict, Optional, Tuple, Type from pydantic import Field, root_validator, validator +from pydantic.main import object_setattr from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel @@ -85,8 +84,9 @@ def __str__(self): return type(self).__name__ @classmethod - def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, - **values: Any) -> 'Model': + def construct( + cls: Type["Model"], _fields_set: Optional["SetStr"] = None, **values: Any + ) -> "Model": m = cls.__new__(cls) fields_values: Dict[str, Any] = {} for name, field in cls.__fields__.items(): @@ -97,13 +97,14 @@ def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, elif not field.required: fields_values[name] = field.get_default() - object_setattr(m, '__dict__', fields_values) + object_setattr(m, "__dict__", fields_values) if _fields_set is None: _fields_set = set(fields_values.keys()) - object_setattr(m, '__fields_set__', _fields_set) + object_setattr(m, "__fields_set__", _fields_set) m._init_private_attributes() return m + class Response(BodyBase, ABC): """ The base class for all response messages, as they all share a response code @@ -226,6 +227,7 @@ class CertificateUpdateRes(Response): class ChargeParameterDiscoveryReq(BodyBase): """See section 8.4.3.8.2 in ISO 15118-2""" + # XSD type unsignedShort (16 bit integer) with value range [0..65535] max_entries_sa_schedule_tuple: int = Field( None, ge=0, le=65535, alias="MaxEntriesSAScheduleTuple" @@ -744,8 +746,9 @@ def get_message_and_name(self) -> Tuple[Optional[BodyBase], str]: return None, "" @classmethod - def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, - **values: Any) -> 'Model': + def construct( + cls: Type["Model"], _fields_set: Optional["SetStr"] = None, **values: Any + ) -> "Model": m = cls.__new__(cls) fields_values: Dict[str, Any] = {} for name, field in cls.__fields__.items(): @@ -756,13 +759,14 @@ def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, elif not field.required: fields_values[name] = field.get_default() - object_setattr(m, '__dict__', fields_values) + object_setattr(m, "__dict__", fields_values) if _fields_set is None: _fields_set = set(fields_values.keys()) - object_setattr(m, '__fields_set__', _fields_set) + object_setattr(m, "__fields_set__", _fields_set) m._init_private_attributes() return m + def get_msg_type(msg_name: str) -> Optional[Type[BodyBase]]: """ Returns the message type corresponding to the message name provided, or diff --git a/iso15118/shared/messages/iso15118_2/header.py b/iso15118/shared/messages/iso15118_2/header.py index 9a73f7c62..99b60eaa0 100644 --- a/iso15118/shared/messages/iso15118_2/header.py +++ b/iso15118/shared/messages/iso15118_2/header.py @@ -11,10 +11,10 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from pydantic.main import object_setattr -from typing import Type, Optional, Any +from typing import Any, Optional, Type from pydantic import Field, validator +from pydantic.main import object_setattr from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel @@ -46,5 +46,7 @@ def check_sessionid_is_hexbinary(cls, value): int(value, 16) return value except ValueError as exc: - raise ValueError(f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)") from exc + raise ValueError( + f"Invalid value '{value}' for SessionID (must be " + f"hexadecimal representation of max 8 bytes)" + ) from exc diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index c3f135423..66a57beb2 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -14,15 +14,14 @@ """ from copy import deepcopy from enum import Enum +from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type +from pydantic import ExtraError, Field from pydantic.error_wrappers import ErrorWrapper, ValidationError from pydantic.fields import Undefined from pydantic.main import object_setattr from pydantic.typing import display_as_type from pydantic.utils import ROOT_KEY, Representation -from typing import Any, Type, Tuple, List, Set, Iterator, Optional, Dict - -from pydantic import Field, ExtraError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.iso15118_2.body import Body @@ -39,8 +38,9 @@ def __str__(self): return str(self.body.get_message_name()) @classmethod - def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, - **values: Any) -> 'Model': + def construct( + cls: Type["Model"], _fields_set: Optional["SetStr"] = None, **values: Any + ) -> "Model": m = cls.__new__(cls) fields_values: Dict[str, Any] = {} for name, field in cls.__fields__.items(): @@ -51,10 +51,10 @@ def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, elif not field.required: fields_values[name] = field.get_default() - object_setattr(m, '__dict__', fields_values) + object_setattr(m, "__dict__", fields_values) if _fields_set is None: _fields_set = set(fields_values.keys()) - object_setattr(m, '__fields_set__', _fields_set) + object_setattr(m, "__fields_set__", _fields_set) m._init_private_attributes() return m @@ -67,7 +67,7 @@ def construct_skip_validation(cls, **data: Any): _fields_set = {"header", "body"} try: object.__setattr__(m, "__fields_set__", _fields_set) - m.header = MessageHeader.construct( **data["Header"]) + m.header = MessageHeader.construct(**data["Header"]) m.body = Body.construct(**data["Body"]) except Exception as e: raise ValueError(f"Error on creating {cls} without validation: {e}") @@ -76,9 +76,10 @@ def construct_skip_validation(cls, **data: Any): _missing = object() + def validate_model( - model: Type[BaseModel], input_data: 'DictStrAny', cls: 'ModelOrDc' = None -) -> Tuple['DictStrAny', 'SetStr']: + model: Type[BaseModel], input_data: "DictStrAny", cls: "ModelOrDc" = None +) -> Tuple["DictStrAny", "SetStr"]: """ validate data against a model. """ @@ -100,7 +101,7 @@ def validate_model( if value is _missing: if field.required: print("Missing Error") - #errors.append(ErrorWrapper(MissingError(), loc=field.alias)) + # errors.append(ErrorWrapper(MissingError(), loc=field.alias)) continue value = field.get_default() diff --git a/iso15118/shared/messages/iso15118_20/ac.py b/iso15118/shared/messages/iso15118_20/ac.py index 2397ca54a..e7eea26b3 100644 --- a/iso15118/shared/messages/iso15118_20/ac.py +++ b/iso15118/shared/messages/iso15118_20/ac.py @@ -351,8 +351,9 @@ def either_ac_or_ac_bpt_params(cls, values): return values except ValueError as exc: raise V2GMessageValidationError( - str(exc), ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, - ChargeParameterDiscoveryReq + str(exc), + ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, + ChargeParameterDiscoveryReq, ) def __str__(self): diff --git a/tests/secc/messages/15118_2_invalid_messages.py b/tests/secc/messages/15118_2_invalid_messages.py index 595bbf0a4..425b78d26 100644 --- a/tests/secc/messages/15118_2_invalid_messages.py +++ b/tests/secc/messages/15118_2_invalid_messages.py @@ -9,13 +9,20 @@ from iso15118.secc.states.sap_states import SupportedAppProtocol from iso15118.shared.comm_session import SessionStateMachine from iso15118.shared.exceptions import V2GMessageValidationError -from iso15118.shared.exi_codec import CustomJSONDecoder, EXI +from iso15118.shared.exi_codec import EXI, CustomJSONDecoder from iso15118.shared.exificient_exi_codec import ExificientEXICodec from iso15118.shared.messages.din_spec.header import MessageHeader -from iso15118.shared.messages.enums import Namespace, EnergyTransferModeEnum, Protocol, \ - ISOV2PayloadTypes -from iso15118.shared.messages.iso15118_2.body import Body, ChargeParameterDiscoveryReq, \ - SessionSetupReq +from iso15118.shared.messages.enums import ( + EnergyTransferModeEnum, + ISOV2PayloadTypes, + Namespace, + Protocol, +) +from iso15118.shared.messages.iso15118_2.body import ( + Body, + ChargeParameterDiscoveryReq, + SessionSetupReq, +) from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage from iso15118.shared.messages.v2gtp import V2GTPMessage @@ -31,23 +38,26 @@ class InvalidV2GMessage: invalid_v2g_2_messages = [ - (InvalidV2GMessage(( - # [V2G2-477] - # Parameters are not compatible with RequestedEnergyTransferMode - '{"V2G_Message":{"Header":{"SessionID":"82DBA3A44ED6E5B9"},"Body":' - '{"ChargeParameterDiscoveryReq":{"MaxEntriesSAScheduleTuple":16,' - '"RequestedEnergyTransferMode":"AC_three_phase_core","DC_EVChargeParameter":' - '{"DepartureTime":0,"DC_EVStatus":{"EVReady":false,"EVErrorCode":' - '"NO_ERROR","EVRESSSOC":20},"EVMaximumCurrentLimit":{"Multiplier":' - '1,"Unit":"A","Value":8},"EVMaximumPowerLimit":{"Multiplier":3,' - '"Unit":"W","Value":29},"EVMaximumVoltageLimit":{"Multiplier":2,' - '"Unit":"V","Value":5},"EVEnergyCapacity":{"Multiplier":3,"Unit":' - '"Wh","Value":200},"EVEnergyRequest":{"Multiplier":3,"Unit":"Wh",' - '"Value":160},"FullSOC":99,"BulkSOC":80}}}}}' -), - ChargeParameterDiscoveryReq, - ResponseCode.FAILED_WRONG_CHARGE_PARAMETER) -), + ( + InvalidV2GMessage( + ( + # [V2G2-477] + # Parameters are not compatible with RequestedEnergyTransferMode + '{"V2G_Message":{"Header":{"SessionID":"82DBA3A44ED6E5B9"},"Body":' + '{"ChargeParameterDiscoveryReq":{"MaxEntriesSAScheduleTuple":16,' + '"RequestedEnergyTransferMode":"AC_three_phase_core","DC_EVChargeParameter":' + '{"DepartureTime":0,"DC_EVStatus":{"EVReady":false,"EVErrorCode":' + '"NO_ERROR","EVRESSSOC":20},"EVMaximumCurrentLimit":{"Multiplier":' + '1,"Unit":"A","Value":8},"EVMaximumPowerLimit":{"Multiplier":3,' + '"Unit":"W","Value":29},"EVMaximumVoltageLimit":{"Multiplier":2,' + '"Unit":"V","Value":5},"EVEnergyCapacity":{"Multiplier":3,"Unit":' + '"Wh","Value":200},"EVEnergyRequest":{"Multiplier":3,"Unit":"Wh",' + '"Value":160},"FullSOC":99,"BulkSOC":80}}}}}' + ), + ChargeParameterDiscoveryReq, + ResponseCode.FAILED_WRONG_CHARGE_PARAMETER, + ) + ), ] From 56e35ae10141436dcb5955b27d14c34a9efca6fe Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Tue, 16 Aug 2022 09:56:06 +0100 Subject: [PATCH 26/28] code quality --- iso15118/evcc/states/evcc_state.py | 27 +---- iso15118/shared/messages/din_spec/body.py | 7 +- iso15118/shared/messages/din_spec/header.py | 3 +- iso15118/shared/messages/din_spec/msgdef.py | 15 --- iso15118/shared/messages/iso15118_2/body.py | 44 +------ iso15118/shared/messages/iso15118_2/msgdef.py | 112 ------------------ .../messages/iso15118_20/common_messages.py | 1 - 7 files changed, 4 insertions(+), 205 deletions(-) diff --git a/iso15118/evcc/states/evcc_state.py b/iso15118/evcc/states/evcc_state.py index ecc9a4b74..e8ddc9f08 100644 --- a/iso15118/evcc/states/evcc_state.py +++ b/iso15118/evcc/states/evcc_state.py @@ -7,7 +7,6 @@ from iso15118.evcc.comm_session_handler import EVCCCommunicationSession from iso15118.shared.messages.app_protocol import ( - ResponseCodeSAP, SupportedAppProtocolReq, SupportedAppProtocolRes, ) @@ -16,23 +15,16 @@ from iso15118.shared.messages.din_spec.body import ( SessionSetupRes as SessionSetupResDINSPEC, ) -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.iso15118_2.body import BodyBase as BodyBaseV2 from iso15118.shared.messages.iso15118_2.body import Response as ResponseV2 from iso15118.shared.messages.iso15118_2.body import ( SessionSetupRes as SessionSetupResV2, ) -from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 from iso15118.shared.messages.iso15118_20.common_messages import ( SessionSetupRes as SessionSetupResV20, ) -from iso15118.shared.messages.iso15118_20.common_types import ( - ResponseCode as ResponseCodeV20, -) from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) @@ -224,22 +216,7 @@ def check_msg( return message - def stop_state_machine( - self, - reason: str, - faulty_request: Optional[ - Union[ - SupportedAppProtocolReq, - SupportedAppProtocolRes, - V2GMessageV2, - V2GMessageV20, - V2GMessageDINSPEC, - ] - ] = None, - response_code: Optional[ - Union[ResponseCodeSAP, ResponseCodeV2, ResponseCodeV20, ResponseCodeDINSPEC] - ] = None, - ): + def stop_state_machine(self, reason: str): """ Prepares the stop of the state machine by setting the next_state to Terminate and providing a reason for logging purposes. @@ -247,8 +224,6 @@ def stop_state_machine( Args: reason: Additional information as to why the communication session is about to be terminated. Helpful for further debugging. - faulty_request: None for evcc implemantation - response_code: None for evcc implemantation """ self.comm_session.stop_reason = StopNotification( False, reason, self.comm_session.writer.get_extra_info("peername") diff --git a/iso15118/shared/messages/din_spec/body.py b/iso15118/shared/messages/din_spec/body.py index ca36eabc6..3f95132c8 100644 --- a/iso15118/shared/messages/din_spec/body.py +++ b/iso15118/shared/messages/din_spec/body.py @@ -88,17 +88,13 @@ def check_sessionid_is_hexbinary(cls, value): # pylint: disable=no-self-argument # pylint: disable=no-self-use try: - raise ValueError( - f"Invalid value '{value}' for EVCCID (must be " - f"hexadecimal representation of max 6 bytes)", - ) # convert value to int, assuming base 16 int(value, 16) return value except ValueError as exc: raise ValueError( f"Invalid value '{value}' for EVCCID (must be " - f"hexadecimal representation of max 6 bytes)", + f"hexadecimal representation of max 6 bytes)" ) from exc @@ -248,7 +244,6 @@ def validate_requested_energy_mode(cls, values): values.get("ac_ev_charge_parameter"), values.get("dc_ev_charge_parameter"), ) - if requested_energy_mode not in ("DC_extended", "DC_core"): raise V2GMessageValidationError( f"[V2G2-476] Wrong energy transfer mode transfer mode " diff --git a/iso15118/shared/messages/din_spec/header.py b/iso15118/shared/messages/din_spec/header.py index f07afec5b..ce2449886 100644 --- a/iso15118/shared/messages/din_spec/header.py +++ b/iso15118/shared/messages/din_spec/header.py @@ -46,6 +46,5 @@ def check_sessionid_is_hexbinary(cls, value): except ValueError as exc: raise ValueError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)", - ResponseCode.FAILED_SEQUENCE_ERROR, + f"hexadecimal representation of max 8 bytes)" ) from exc diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index 4d228fe30..f23e663f9 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -29,18 +29,3 @@ class V2GMessage(BaseModel): def __str__(self): return str(self.body.get_message_name()) - - @classmethod - def construct_skip_validation(cls, **values: Any): - """ - Creates a new model when the validation fails to get V2G message. - """ - m = cls.__new__(cls) - _fields_set = {"header", "body"} - try: - object.__setattr__(m, "__fields_set__", _fields_set) - m.header = MessageHeader.construct(**values["Header"]) - m.body = Body.construct(**values["Body"]) - except Exception as e: - raise ValueError(f"Error on creating {cls} without validation: {e}") - return m diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 52fe52760..3a2316f1f 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -83,27 +83,6 @@ class BodyBase(BaseModel, ABC): def __str__(self): return type(self).__name__ - @classmethod - def construct( - cls: Type["Model"], _fields_set: Optional["SetStr"] = None, **values: Any - ) -> "Model": - m = cls.__new__(cls) - fields_values: Dict[str, Any] = {} - for name, field in cls.__fields__.items(): - if field.alias in values: - fields_values[name] = values[field.alias] - elif name in values: - fields_values[name] = values[name] - elif not field.required: - fields_values[name] = field.get_default() - - object_setattr(m, "__dict__", fields_values) - if _fields_set is None: - _fields_set = set(fields_values.keys()) - object_setattr(m, "__fields_set__", _fields_set) - m._init_private_attributes() - return m - class Response(BodyBase, ABC): """ @@ -621,7 +600,7 @@ def check_sessionid_is_hexbinary(cls, value): except ValueError as exc: raise ValueError( f"Invalid value '{value}' for EVCCID (must be " - f"hexadecimal representation of max 6 bytes)", + f"hexadecimal representation of max 6 bytes)" ) from exc @@ -745,27 +724,6 @@ def get_message_and_name(self) -> Tuple[Optional[BodyBase], str]: return None, "" - @classmethod - def construct( - cls: Type["Model"], _fields_set: Optional["SetStr"] = None, **values: Any - ) -> "Model": - m = cls.__new__(cls) - fields_values: Dict[str, Any] = {} - for name, field in cls.__fields__.items(): - if field.alias in values: - fields_values[name] = values[field.alias] - elif name in values: - fields_values[name] = values[name] - elif not field.required: - fields_values[name] = field.get_default() - - object_setattr(m, "__dict__", fields_values) - if _fields_set is None: - _fields_set = set(fields_values.keys()) - object_setattr(m, "__fields_set__", _fields_set) - m._init_private_attributes() - return m - def get_msg_type(msg_name: str) -> Optional[Type[BodyBase]]: """ diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index 66a57beb2..f2f0cebde 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -36,115 +36,3 @@ class V2GMessage(BaseModel): def __str__(self): return str(self.body.get_message_name()) - - @classmethod - def construct( - cls: Type["Model"], _fields_set: Optional["SetStr"] = None, **values: Any - ) -> "Model": - m = cls.__new__(cls) - fields_values: Dict[str, Any] = {} - for name, field in cls.__fields__.items(): - if field.alias in values: - fields_values[name] = values[field.alias] - elif name in values: - fields_values[name] = values[name] - elif not field.required: - fields_values[name] = field.get_default() - - object_setattr(m, "__dict__", fields_values) - if _fields_set is None: - _fields_set = set(fields_values.keys()) - object_setattr(m, "__fields_set__", _fields_set) - m._init_private_attributes() - return m - - @classmethod - def construct_skip_validation(cls, **data: Any): - """ - Creates a new model when the validation fails to get V2G message. - """ - m = cls.__new__(cls) - _fields_set = {"header", "body"} - try: - object.__setattr__(m, "__fields_set__", _fields_set) - m.header = MessageHeader.construct(**data["Header"]) - m.body = Body.construct(**data["Body"]) - except Exception as e: - raise ValueError(f"Error on creating {cls} without validation: {e}") - return m - - -_missing = object() - - -def validate_model( - model: Type[BaseModel], input_data: "DictStrAny", cls: "ModelOrDc" = None -) -> Tuple["DictStrAny", "SetStr"]: - """ - validate data against a model. - """ - values = {} - errors = [] - # input_data names, possibly alias - names_used = set() - # field names, never aliases - fields_set = set() - cls_ = cls or model - - for name, field in model.__fields__.items(): - value = input_data.get(field.alias, _missing) - using_name = False - if value is _missing and field.alt_alias: - value = input_data.get(field.name, _missing) - using_name = True - - if value is _missing: - if field.required: - print("Missing Error") - # errors.append(ErrorWrapper(MissingError(), loc=field.alias)) - continue - - value = field.get_default() - - if not config.validate_all and not field.validate_always: - values[name] = value - continue - else: - fields_set.add(name) - if check_extra: - names_used.add(field.name if using_name else field.alias) - - v_, errors_ = field.validate(value, values, loc=field.alias, cls=cls_) - if isinstance(errors_): - errors.append(errors_) - elif isinstance(errors_, list): - errors.extend(errors_) - else: - values[name] = v_ - - if check_extra: - if isinstance(input_data, GetterDict): - extra = input_data.extra_keys() - names_used - else: - extra = input_data.keys() - names_used - if extra: - fields_set |= extra - if config.extra is Extra.allow: - for f in extra: - values[f] = input_data[f] - else: - for f in sorted(extra): - errors.append(ErrorWrapper(ExtraError(), loc=f)) - - for skip_on_failure, validator in model.__post_root_validators__: - if skip_on_failure and errors: - continue - try: - values = validator(cls_, values) - except (ValueError, TypeError, AssertionError) as exc: - errors.append(ErrorWrapper(exc, loc=ROOT_KEY)) - - if errors: - return values, fields_set, ValidationError(errors, cls_) - else: - return values, fields_set, None diff --git a/iso15118/shared/messages/iso15118_20/common_messages.py b/iso15118/shared/messages/iso15118_20/common_messages.py index 455a11338..ca5d4ab0c 100644 --- a/iso15118/shared/messages/iso15118_20/common_messages.py +++ b/iso15118/shared/messages/iso15118_20/common_messages.py @@ -16,7 +16,6 @@ from pydantic import Field, root_validator, validator -from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.enums import ( INT_8_MAX, From acc14d3b06376f30aa7518e114e6337f378ef49b Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Tue, 16 Aug 2022 10:09:53 +0100 Subject: [PATCH 27/28] code quality --- iso15118/secc/states/secc_state.py | 5 ---- iso15118/shared/messages/din_spec/header.py | 3 +-- iso15118/shared/messages/din_spec/msgdef.py | 1 - iso15118/shared/messages/iso15118_2/body.py | 5 ++-- iso15118/shared/messages/iso15118_2/header.py | 5 +--- iso15118/shared/messages/iso15118_2/msgdef.py | 11 +------- .../messages/iso15118_20/common_messages.py | 3 +-- .../messages/iso15118_20/common_types.py | 1 - iso15118/shared/states.py | 10 ++----- .../secc/messages/15118_2_invalid_messages.py | 26 +++---------------- 10 files changed, 12 insertions(+), 58 deletions(-) diff --git a/iso15118/secc/states/secc_state.py b/iso15118/secc/states/secc_state.py index 3afa4c680..3b982814b 100644 --- a/iso15118/secc/states/secc_state.py +++ b/iso15118/secc/states/secc_state.py @@ -23,12 +23,7 @@ ) 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 from iso15118.shared.messages.iso15118_2.body import BodyBase as BodyBaseV2 -from iso15118.shared.messages.iso15118_2.body import ( - CableCheckReq, - ChargeParameterDiscoveryReq, -) from iso15118.shared.messages.iso15118_2.body import ( SessionSetupReq as SessionSetupReqV2, ) diff --git a/iso15118/shared/messages/din_spec/header.py b/iso15118/shared/messages/din_spec/header.py index ce2449886..349e2a6f5 100644 --- a/iso15118/shared/messages/din_spec/header.py +++ b/iso15118/shared/messages/din_spec/header.py @@ -14,9 +14,8 @@ from pydantic import Field, validator -from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel -from iso15118.shared.messages.din_spec.datatypes import Notification, ResponseCode +from iso15118.shared.messages.din_spec.datatypes import Notification from iso15118.shared.messages.xmldsig import Signature diff --git a/iso15118/shared/messages/din_spec/msgdef.py b/iso15118/shared/messages/din_spec/msgdef.py index f23e663f9..0705fdc57 100644 --- a/iso15118/shared/messages/din_spec/msgdef.py +++ b/iso15118/shared/messages/din_spec/msgdef.py @@ -12,7 +12,6 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from typing import Any from pydantic import Field diff --git a/iso15118/shared/messages/iso15118_2/body.py b/iso15118/shared/messages/iso15118_2/body.py index 3a2316f1f..92996dcc1 100644 --- a/iso15118/shared/messages/iso15118_2/body.py +++ b/iso15118/shared/messages/iso15118_2/body.py @@ -11,10 +11,9 @@ """ import logging from abc import ABC -from typing import Any, Dict, Optional, Tuple, Type +from typing import Optional, Tuple, Type from pydantic import Field, root_validator, validator -from pydantic.main import object_setattr from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel @@ -424,7 +423,7 @@ def check_sessionid_is_hexbinary(cls, value): except ValueError as exc: raise ValueError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)", + f"hexadecimal representation of max 8 bytes)" ) from exc diff --git a/iso15118/shared/messages/iso15118_2/header.py b/iso15118/shared/messages/iso15118_2/header.py index 99b60eaa0..86029298a 100644 --- a/iso15118/shared/messages/iso15118_2/header.py +++ b/iso15118/shared/messages/iso15118_2/header.py @@ -11,14 +11,11 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from typing import Any, Optional, Type from pydantic import Field, validator -from pydantic.main import object_setattr -from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel -from iso15118.shared.messages.iso15118_2.datatypes import Notification, ResponseCode +from iso15118.shared.messages.iso15118_2.datatypes import Notification from iso15118.shared.messages.xmldsig import Signature diff --git a/iso15118/shared/messages/iso15118_2/msgdef.py b/iso15118/shared/messages/iso15118_2/msgdef.py index f2f0cebde..22dbed7bc 100644 --- a/iso15118/shared/messages/iso15118_2/msgdef.py +++ b/iso15118/shared/messages/iso15118_2/msgdef.py @@ -12,16 +12,7 @@ (or class) that matches the definitions in the XSD schema, including the XSD element names by using the 'alias' attribute. """ -from copy import deepcopy -from enum import Enum -from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type - -from pydantic import ExtraError, Field -from pydantic.error_wrappers import ErrorWrapper, ValidationError -from pydantic.fields import Undefined -from pydantic.main import object_setattr -from pydantic.typing import display_as_type -from pydantic.utils import ROOT_KEY, Representation +from pydantic import Field from iso15118.shared.messages import BaseModel from iso15118.shared.messages.iso15118_2.body import Body diff --git a/iso15118/shared/messages/iso15118_20/common_messages.py b/iso15118/shared/messages/iso15118_20/common_messages.py index ca5d4ab0c..1ff73222d 100644 --- a/iso15118/shared/messages/iso15118_20/common_messages.py +++ b/iso15118/shared/messages/iso15118_20/common_messages.py @@ -39,7 +39,6 @@ Processing, RationalNumber, Receipt, - ResponseCode, RootCertificateIDList, V2GRequest, V2GResponse, @@ -1095,7 +1094,7 @@ def check_sessionid_is_hexbinary(cls, value): except ValueError as exc: raise ValueError( f"Invalid value '{value}' for SessionID (must be " - f"hexadecimal representation of max 8 bytes)", + f"hexadecimal representation of max 8 bytes)" ) from exc diff --git a/iso15118/shared/messages/iso15118_20/common_types.py b/iso15118/shared/messages/iso15118_20/common_types.py index 689132ad5..fbf30c1bf 100644 --- a/iso15118/shared/messages/iso15118_20/common_types.py +++ b/iso15118/shared/messages/iso15118_20/common_types.py @@ -16,7 +16,6 @@ from pydantic import Field, conbytes, conint, constr, validator -from iso15118.shared.exceptions import V2GMessageValidationError from iso15118.shared.messages import BaseModel from iso15118.shared.messages.enums import ( INT_8_MAX, diff --git a/iso15118/shared/states.py b/iso15118/shared/states.py index 2ace91740..3a9537e9e 100644 --- a/iso15118/shared/states.py +++ b/iso15118/shared/states.py @@ -12,7 +12,6 @@ ) from iso15118.shared.exi_codec import EXI from iso15118.shared.messages.app_protocol import ( - ResponseCodeSAP, SupportedAppProtocolReq, SupportedAppProtocolRes, ) @@ -22,9 +21,7 @@ from iso15118.shared.messages.din_spec.datatypes import ( Notification as NotificationDINSPEC, ) -from iso15118.shared.messages.din_spec.datatypes import ( - ResponseCode as ResponseCodeDINSPEC, -) + from iso15118.shared.messages.din_spec.header import ( MessageHeader as MessageHeaderDINSPEC, ) @@ -37,12 +34,9 @@ ) from iso15118.shared.messages.iso15118_2.body import Body, BodyBase from iso15118.shared.messages.iso15118_2.datatypes import FaultCode, Notification -from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode as ResponseCodeV2 from iso15118.shared.messages.iso15118_2.header import MessageHeader as MessageHeaderV2 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, ) diff --git a/tests/secc/messages/15118_2_invalid_messages.py b/tests/secc/messages/15118_2_invalid_messages.py index 425b78d26..91b3f55ad 100644 --- a/tests/secc/messages/15118_2_invalid_messages.py +++ b/tests/secc/messages/15118_2_invalid_messages.py @@ -1,33 +1,14 @@ import json from dataclasses import dataclass -import pytest -from pydantic import ValidationError - -from iso15118.secc.comm_session_handler import SECCCommunicationSession -from iso15118.secc.controller.simulator import SimEVSEController -from iso15118.secc.states.sap_states import SupportedAppProtocol -from iso15118.shared.comm_session import SessionStateMachine from iso15118.shared.exceptions import V2GMessageValidationError -from iso15118.shared.exi_codec import EXI, CustomJSONDecoder -from iso15118.shared.exificient_exi_codec import ExificientEXICodec -from iso15118.shared.messages.din_spec.header import MessageHeader -from iso15118.shared.messages.enums import ( - EnergyTransferModeEnum, - ISOV2PayloadTypes, - Namespace, - Protocol, -) + from iso15118.shared.messages.iso15118_2.body import ( Body, - ChargeParameterDiscoveryReq, - SessionSetupReq, + ChargeParameterDiscoveryReq ) from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage -from iso15118.shared.messages.v2gtp import V2GTPMessage -from iso15118.shared.notifications import StopNotification -from tests.dinspec.secc.test_dinspec_secc_states import MockWriter @dataclass @@ -45,7 +26,8 @@ class InvalidV2GMessage: # Parameters are not compatible with RequestedEnergyTransferMode '{"V2G_Message":{"Header":{"SessionID":"82DBA3A44ED6E5B9"},"Body":' '{"ChargeParameterDiscoveryReq":{"MaxEntriesSAScheduleTuple":16,' - '"RequestedEnergyTransferMode":"AC_three_phase_core","DC_EVChargeParameter":' + '"RequestedEnergyTransferMode":"AC_three_phase_core",' + '"DC_EVChargeParameter":' '{"DepartureTime":0,"DC_EVStatus":{"EVReady":false,"EVErrorCode":' '"NO_ERROR","EVRESSSOC":20},"EVMaximumCurrentLimit":{"Multiplier":' '1,"Unit":"A","Value":8},"EVMaximumPowerLimit":{"Multiplier":3,' From 9658535b6a8d6847051f6d6a458d20614f1cc9b4 Mon Sep 17 00:00:00 2001 From: ikaratass <89934937+ikaratass@users.noreply.github.com> Date: Tue, 16 Aug 2022 10:12:41 +0100 Subject: [PATCH 28/28] reformat --- iso15118/shared/states.py | 2 -- tests/secc/messages/15118_2_invalid_messages.py | 6 +----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/iso15118/shared/states.py b/iso15118/shared/states.py index 3a9537e9e..6836990b0 100644 --- a/iso15118/shared/states.py +++ b/iso15118/shared/states.py @@ -21,7 +21,6 @@ from iso15118.shared.messages.din_spec.datatypes import ( Notification as NotificationDINSPEC, ) - from iso15118.shared.messages.din_spec.header import ( MessageHeader as MessageHeaderDINSPEC, ) @@ -36,7 +35,6 @@ from iso15118.shared.messages.iso15118_2.datatypes import FaultCode, Notification from iso15118.shared.messages.iso15118_2.header import MessageHeader as MessageHeaderV2 from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2 - from iso15118.shared.messages.iso15118_20.common_types import ( V2GMessage as V2GMessageV20, ) diff --git a/tests/secc/messages/15118_2_invalid_messages.py b/tests/secc/messages/15118_2_invalid_messages.py index 91b3f55ad..1b6bf853c 100644 --- a/tests/secc/messages/15118_2_invalid_messages.py +++ b/tests/secc/messages/15118_2_invalid_messages.py @@ -2,11 +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, ChargeParameterDiscoveryReq from iso15118.shared.messages.iso15118_2.datatypes import ResponseCode from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage