Skip to content

Commit

Permalink
Contactor status check timeout included in cable check overall timeou…
Browse files Browse the repository at this point in the history
…t now.
  • Loading branch information
shalinnijel2 committed May 1, 2024
1 parent aabe8ac commit 1fb4e44
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 60 deletions.
2 changes: 1 addition & 1 deletion iso15118/secc/controller/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ async def is_contactor_opened(self) -> bool:
raise NotImplementedError

@abstractmethod
async def is_contactor_closed(self) -> bool:
async def is_contactor_closed(self) -> Optional[bool]:
"""
Sends a command to the SECC to get the contactor status is closed
Expand Down
2 changes: 1 addition & 1 deletion iso15118/secc/controller/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ async def service_renegotiation_supported(self) -> bool:
"""Overrides EVSEControllerInterface.service_renegotiation_supported()."""
return False

async def is_contactor_closed(self) -> bool:
async def is_contactor_closed(self) -> Optional[bool]:
"""Overrides EVSEControllerInterface.is_contactor_closed()."""
return True

Expand Down
66 changes: 43 additions & 23 deletions iso15118/secc/states/din_spec_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,9 @@ class CableCheck(StateSECC):

def __init__(self, comm_session: SECCCommunicationSession):
super().__init__(comm_session, Timeouts.V2G_SECC_SEQUENCE_TIMEOUT)
self.cable_check_req_was_received = False
self.cable_check_req_was_received: bool = False
self.cable_check_started: bool = False
self.contactors_closed_for_cable_check: Optional[bool] = None

async def process_message(
self,
Expand Down Expand Up @@ -467,21 +469,57 @@ async def process_message(
)
return

evse_processing: EVSEProcessing = EVSEProcessing.ONGOING
response_code: ResponseCode = ResponseCode.OK
next_state = None
if not self.cable_check_req_was_received:
# Requirement in 6.4.3.106 of the IEC 61851-23
# Any relays in the DC output circuit of the DC station shall
# be closed during the insulation test
if not await self.comm_session.evse_controller.is_contactor_closed():
self.contactors_closed_for_cable_check = (
await self.comm_session.evse_controller.is_contactor_closed()
)
self.cable_check_req_was_received = True

if self.contactors_closed_for_cable_check is not None:
if not self.contactors_closed_for_cable_check:
self.stop_state_machine(
"Contactor didnt close for Cable Check",
message,
ResponseCode.FAILED,
)
return

# First CableCheckReq received. Start cable check.
await self.comm_session.evse_controller.start_cable_check()
self.cable_check_req_was_received = True
if self.cable_check_started:
isolation_level = (
await self.comm_session.evse_controller.get_cable_check_status()
) # noqa

evse_processing = EVSEProcessing.ONGOING
next_state = None
if isolation_level in [
IsolationLevel.VALID,
IsolationLevel.WARNING,
]:
if isolation_level == IsolationLevel.WARNING:
logger.warning(
"Isolation resistance measured by EVSE is in Warning-Range"
)
evse_processing = EVSEProcessing.FINISHED
next_state = PreCharge
elif isolation_level in [
IsolationLevel.FAULT,
IsolationLevel.INVALID,
]:
self.stop_state_machine(
f"Isolation Failure: {isolation_level}",
message,
ResponseCode.FAILED,
)
return
else:
await self.comm_session.evse_controller.start_cable_check()
self.cable_check_started = True

self.comm_session.evse_controller.ev_data_context.present_soc = (
cable_check_req.dc_ev_status.ev_ress_soc
Expand All @@ -494,24 +532,6 @@ async def process_message(
# [V2G-DC-418] Stay in CableCheck state until EVSEProcessing is complete.
# Until EVSEProcessing is completed, EV will send identical
# CableCheckReq message.
evse_processing: EVSEProcessing = EVSEProcessing.ONGOING
response_code: ResponseCode = ResponseCode.OK
next_state: Type["State"] = None
if isolation_level in [
IsolationLevel.VALID,
IsolationLevel.WARNING,
]:
if isolation_level == IsolationLevel.WARNING:
logger.warning(
"Isolation resistance measured by EVSE is in Warning-Range"
)
next_state = PreCharge
evse_processing = EVSEProcessing.FINISHED
elif isolation_level in [IsolationLevel.FAULT, IsolationLevel.NO_IMD]:
response_code = ResponseCode.FAILED
next_state = Terminate
evse_processing = EVSEProcessing.FINISHED

cable_check_res: CableCheckRes = CableCheckRes(
response_code=response_code,
dc_evse_status=await self.comm_session.evse_controller.get_dc_evse_status(),
Expand Down
74 changes: 43 additions & 31 deletions iso15118/secc/states/iso15118_2_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -2187,6 +2187,8 @@ class CableCheck(StateSECC):
def __init__(self, comm_session: SECCCommunicationSession):
super().__init__(comm_session, Timeouts.V2G_SECC_SEQUENCE_TIMEOUT)
self.cable_check_req_was_received = False
self.cable_check_started = False
self.contactors_closed_for_cable_check: Optional[bool] = None

async def process_message(
self,
Expand Down Expand Up @@ -2214,53 +2216,63 @@ async def process_message(
)
return

next_state = None
evse_processing = EVSEProcessing.ONGOING

if not self.cable_check_req_was_received:
# Requirement in 6.4.3.106 of the IEC 61851-23
# Any relays in the DC output circuit of the DC station shall
# be closed during the insulation test
if not await self.comm_session.evse_controller.is_contactor_closed():
self.contactors_closed_for_cable_check = (
await self.comm_session.evse_controller.is_contactor_closed()
)
self.cable_check_req_was_received = True

if self.contactors_closed_for_cable_check is not None:
if not self.contactors_closed_for_cable_check:
self.stop_state_machine(
"Contactor didnt close for Cable Check",
message,
ResponseCode.FAILED,
)
return

# First CableCheckReq received. Start cable check.
await self.comm_session.evse_controller.start_cable_check()
self.cable_check_req_was_received = True
if self.cable_check_started:
isolation_level = (
await self.comm_session.evse_controller.get_cable_check_status()
) # noqa

evse_processing = EVSEProcessing.ONGOING
next_state = None
if isolation_level in [
IsolationLevel.VALID,
IsolationLevel.WARNING,
]:
if isolation_level == IsolationLevel.WARNING:
logger.warning(
"Isolation resistance measured by EVSE is in Warning-Range"
)
evse_processing = EVSEProcessing.FINISHED
next_state = PreCharge
elif isolation_level in [
IsolationLevel.FAULT,
IsolationLevel.NO_IMD,
IsolationLevel.INVALID,
]:
self.stop_state_machine(
f"Isolation Failure: {isolation_level}",
message,
ResponseCode.FAILED,
)
return
else:
await self.comm_session.evse_controller.start_cable_check()
self.cable_check_started = True

self.comm_session.evse_controller.ev_data_context.present_soc = (
cable_check_req.dc_ev_status.ev_ress_soc
)

isolation_level = (
await self.comm_session.evse_controller.get_cable_check_status()
) # noqa

evse_processing = EVSEProcessing.ONGOING
next_state = None
if isolation_level in [
IsolationLevel.VALID,
IsolationLevel.WARNING,
]:
if isolation_level == IsolationLevel.WARNING:
logger.warning(
"Isolation resistance measured by EVSE is in Warning-Range"
)
evse_processing = EVSEProcessing.FINISHED
next_state = PreCharge
elif isolation_level in [
IsolationLevel.FAULT,
IsolationLevel.NO_IMD,
]:
self.stop_state_machine(
f"Isolation Failure: {isolation_level}",
message,
ResponseCode.FAILED,
)
return

cable_check_res = CableCheckRes(
response_code=ResponseCode.OK,
dc_evse_status=await self.comm_session.evse_controller.get_dc_evse_status(),
Expand Down
102 changes: 100 additions & 2 deletions tests/dinspec/secc/test_dinspec_secc_states.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Type
from unittest.mock import AsyncMock, Mock, patch

import pytest
Expand All @@ -12,9 +13,24 @@
EVSESessionLimits,
)
from iso15118.secc.controller.simulator import SimEVSEController
from iso15118.secc.states.din_spec_states import CurrentDemand, PowerDelivery
from iso15118.shared.messages.enums import EnergyTransferModeEnum, Protocol
from iso15118.secc.failed_responses import init_failed_responses_din_spec_70121
from iso15118.secc.states.din_spec_states import (
CableCheck,
CurrentDemand,
PowerDelivery,
PreCharge,
)
from iso15118.shared.messages.din_spec.body import Body, CableCheckReq
from iso15118.shared.messages.din_spec.datatypes import DCEVStatus, IsolationLevel
from iso15118.shared.messages.din_spec.header import MessageHeader
from iso15118.shared.messages.din_spec.msgdef import V2GMessage
from iso15118.shared.messages.enums import (
DCEVErrorCode,
EnergyTransferModeEnum,
Protocol,
)
from iso15118.shared.notifications import StopNotification
from iso15118.shared.states import State, Terminate


class MockWriter:
Expand All @@ -39,6 +55,9 @@ def _comm_session(self):
self.comm_session.writer = MockWriter()
self.comm_session.ev_session_context = EVSessionContext15118()
self.comm_session.evse_controller.evse_data_context = self.get_evse_data()
self.comm_session.failed_responses_din_spec = (
init_failed_responses_din_spec_70121()
)

def get_evse_data(self) -> EVSEDataContext:
dc_limits = EVSEDCCPDLimits(
Expand Down Expand Up @@ -113,3 +132,82 @@ async def test_power_delivery_req_set_hlc_charging(
await power_delivery.process_message(message=power_delivery_req_charge_stop)

self.comm_session.evse_controller.set_hlc_charging.assert_called_with(False)

@pytest.mark.parametrize(
"cable_check_req_received, "
"is_contactor_closed, "
"cable_check_started, "
"cable_check_status, "
"expected_state",
[
(False, None, False, None, None), # First request.
(
True,
None,
False,
None,
None,
), # Not first request. Contactor status unknown.
(True, True, False, None, None), # Not first request. Contactor closed.
(True, False, False, None, Terminate), # Contactor close failed.
(
True,
True,
True,
IsolationLevel.VALID,
PreCharge,
), # noqa Contactor closed. Isolation response received - Valid. Next stage Precharge.
(
True,
True,
True,
IsolationLevel.INVALID,
Terminate,
), # noqa Contactor closed. Isolation response received - Invalid. Terminate.
(
True,
True,
True,
IsolationLevel.WARNING,
PreCharge,
), # noqa Contactor closed. Isolation response received - Warning. Next stage Precharge.
(
True,
True,
True,
IsolationLevel.FAULT,
Terminate,
), # noqa Contactor closed. Isolation response received - Fault. Terminate session.
],
)
async def test_15118_dinspec_dc_cable_check(
self,
cable_check_req_received: bool,
is_contactor_closed: bool,
cable_check_started: bool,
cable_check_status: IsolationLevel,
expected_state: Type[State],
):
cable_check_req = V2GMessage(
header=MessageHeader(session_id=self.comm_session.session_id),
body=Body(
cable_check_req=CableCheckReq(
dc_ev_status=DCEVStatus(
ev_ready=True,
ev_error_code=DCEVErrorCode.NO_ERROR,
ev_ress_soc=35,
)
)
),
)

dc_cable_check = CableCheck(self.comm_session)
dc_cable_check.cable_check_req_was_received = cable_check_req_received
dc_cable_check.contactors_closed_for_cable_check = is_contactor_closed
dc_cable_check.cable_check_started = cable_check_started
contactor_status = AsyncMock(return_value=is_contactor_closed)
self.comm_session.evse_controller.is_contactor_closed = contactor_status
cable_check_status = AsyncMock(return_value=cable_check_status)
self.comm_session.evse_controller.get_cable_check_status = cable_check_status
await dc_cable_check.process_message(message=cable_check_req)
assert dc_cable_check.next_state is expected_state
Loading

0 comments on commit 1fb4e44

Please sign in to comment.