diff --git a/iso15118/secc/controller/interface.py b/iso15118/secc/controller/interface.py index 08ab6498..0979602d 100644 --- a/iso15118/secc/controller/interface.py +++ b/iso15118/secc/controller/interface.py @@ -160,9 +160,7 @@ async def get_schedule_exchange_params( selected_energy_service: SelectedEnergyService, control_mode: ControlMode, schedule_exchange_req: ScheduleExchangeReq, - ) -> Optional[ - Union[ScheduledScheduleExchangeResParams, DynamicScheduleExchangeResParams] - ]: + ) -> Union[ScheduledScheduleExchangeResParams, DynamicScheduleExchangeResParams]: """ Gets the parameters for a ScheduleExchangeResponse. If the parameters are not yet ready when requested, diff --git a/iso15118/secc/controller/simulator.py b/iso15118/secc/controller/simulator.py index 389c052e..f85b62a9 100644 --- a/iso15118/secc/controller/simulator.py +++ b/iso15118/secc/controller/simulator.py @@ -298,9 +298,7 @@ async def get_schedule_exchange_params( selected_energy_service: SelectedEnergyService, control_mode: ControlMode, schedule_exchange_req: ScheduleExchangeReq, - ) -> Optional[ - Union[ScheduledScheduleExchangeResParams, DynamicScheduleExchangeResParams] - ]: + ) -> Union[ScheduledScheduleExchangeResParams, DynamicScheduleExchangeResParams]: if control_mode == ControlMode.SCHEDULED: return await self.get_scheduled_se_params( selected_energy_service, schedule_exchange_req @@ -314,7 +312,7 @@ async def get_scheduled_se_params( self, selected_energy_service: SelectedEnergyService, schedule_exchange_req: ScheduleExchangeReq, - ) -> Optional[ScheduledScheduleExchangeResParams]: + ) -> ScheduledScheduleExchangeResParams: """Overrides EVSEControllerInterface.get_scheduled_se_params().""" charging_power_schedule_entry = PowerScheduleEntry( duration=3600, @@ -510,7 +508,7 @@ async def get_dynamic_se_params( self, selected_energy_service: SelectedEnergyService, schedule_exchange_req: ScheduleExchangeReq, - ) -> Optional[DynamicScheduleExchangeResParams]: + ) -> DynamicScheduleExchangeResParams: """Overrides EVSEControllerInterface.get_dynamic_se_params().""" price_level_schedule_entry = PriceLevelScheduleEntry( duration=3600, price_level=1 diff --git a/iso15118/secc/states/iso15118_20_states.py b/iso15118/secc/states/iso15118_20_states.py index 2bc0207f..1d538422 100644 --- a/iso15118/secc/states/iso15118_20_states.py +++ b/iso15118/secc/states/iso15118_20_states.py @@ -53,6 +53,7 @@ CertificateInstallationReq, ChargeProgress, ChargingSession, + DynamicScheduleExchangeResParams, EIMAuthSetupResParams, EVPowerProfile, MatchedService, @@ -457,7 +458,6 @@ async def process_message( if ( current_authorization_status.authorization_status == AuthorizationStatus.ACCEPTED - and self.comm_session.evse_controller.ready_to_charge() ): evse_processing = Processing.FINISHED elif ( @@ -931,25 +931,39 @@ async def process_message( ev_data_context.update_schedule_exchange_parameters( control_mode, schedule_exchange_req ) - evse_processing = Processing.ONGOING + + # As per Table 49 of ISO15118-20 spec: one of scheduled_params/dynamic_params is + # required even if EVSEProcessing is ongoing. The SECC shall only omit the + # parameter 'ScheduleList' in case EVSEProcessing is set to 'Ongoing'. + # However, the schema file doesn't permit this as minOccurs = 0 is not set in + # schema here: https://github.com/SwitchEV/iso15118/blob/769eddb0cb780db629b4c736de270d381516abd1/iso15118/shared/schemas/iso15118_20/V2G_CI_CommonMessages.xsd#L467-L466 # noqa params = await self.comm_session.evse_controller.get_schedule_exchange_params( self.comm_session.selected_energy_service, control_mode, schedule_exchange_req, ) - if params: + + if ( + control_mode == ControlMode.SCHEDULED + and type(params) is not ScheduledScheduleExchangeResParams + ) or ( + control_mode == ControlMode.DYNAMIC + and type(params) is not DynamicScheduleExchangeResParams + ): + self.stop_state_machine( + f"Unexpected control_mode {control_mode}," + f" for params type {type(params)}", + message, + ResponseCode.FAILED, + ) + return + + if self.comm_session.evse_controller.ready_to_charge(): evse_processing = Processing.FINISHED - if control_mode == ControlMode.SCHEDULED: - if type(params) is ScheduledScheduleExchangeResParams: - self.comm_session.offered_schedules_V20 = params.schedule_tuples - else: - self.stop_state_machine( - f"Unexpected control_mode {control_mode}," - f" for params type {type(params)}", - message, - ResponseCode.FAILED, - ) - return + if type(params) is ScheduledScheduleExchangeResParams: + self.comm_session.offered_schedules_V20 = params.schedule_tuples + else: + evse_processing = Processing.ONGOING schedule_exchange_res = ScheduleExchangeRes( header=MessageHeader( @@ -960,10 +974,12 @@ async def process_message( scheduled_params=params if control_mode == ControlMode.SCHEDULED else None, dynamic_params=params if control_mode == ControlMode.DYNAMIC else None, ) - evse_data_context = self.comm_session.evse_controller.evse_data_context - evse_data_context.update_schedule_exchange_parameters( - control_mode, schedule_exchange_res - ) + + if evse_processing == Processing.FINISHED: + evse_data_context = self.comm_session.evse_controller.evse_data_context + evse_data_context.update_schedule_exchange_parameters( + control_mode, schedule_exchange_res + ) # We don't know what request will come next (which state to transition to), # unless the schedule parameters are ready and we're in AC charging.