Skip to content

Commit

Permalink
Report of the evse status during the charging loop of both AC and DC …
Browse files Browse the repository at this point in the history
…in -20 (#207)

* Added report of the evse status during the charging loop of both AC and DC in -20

* reformatted the code

* refactored stop_charging method in EVCC and allowed stopping the session when evse notification is set to terminate

* renamed stop_charging to stop_v20_charging, so that it is not confused with the stop_charging used in -2 and refactored the code
  • Loading branch information
tropxy authored Mar 9, 2023
1 parent 0e20722 commit 3b91fa7
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 121 deletions.
3 changes: 2 additions & 1 deletion iso15118/evcc/controller/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,11 +614,12 @@ async def get_ac_charge_loop_params_v20(
else:
# Dynamic Mode
dynamic_params = DynamicACChargeLoopReqParams(
departure_time=2000,
ev_target_energy_request=RationalNumber(exponent=3, value=40),
ev_max_energy_request=RationalNumber(exponent=3, value=60),
ev_min_energy_request=RationalNumber(exponent=3, value=-20),
ev_max_charge_power=RationalNumber(exponent=3, value=300),
ev_min_charge_power=RationalNumber(exponent=0, value=-100),
ev_min_charge_power=RationalNumber(exponent=0, value=100),
ev_present_active_power=RationalNumber(exponent=3, value=200),
ev_present_reactive_power=RationalNumber(exponent=3, value=20),
# Add more optional fields if wanted
Expand Down
52 changes: 52 additions & 0 deletions iso15118/evcc/states/evcc_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
This module contains the abstract class for an EVCC-specific state,
which extends the state shared between the EVCC and SECC.
"""
import logging
import time
from abc import ABC
from typing import Optional, Type, TypeVar, Union

Expand All @@ -16,24 +18,34 @@
SessionSetupRes as SessionSetupResDINSPEC,
)
from iso15118.shared.messages.din_spec.msgdef import V2GMessage as V2GMessageDINSPEC
from iso15118.shared.messages.enums import ISOV20PayloadTypes, Namespace
from iso15118.shared.messages.iso15118_2.body import BodyBase as BodyBaseV2
from iso15118.shared.messages.iso15118_2.body import Response as ResponseV2
from iso15118.shared.messages.iso15118_2.body import (
SessionSetupRes as SessionSetupResV2,
)
from iso15118.shared.messages.iso15118_2.msgdef import V2GMessage as V2GMessageV2
from iso15118.shared.messages.iso15118_20.common_messages import (
ChargeProgress,
ChargingSession,
PowerDeliveryReq,
)
from iso15118.shared.messages.iso15118_20.common_messages import (
SessionSetupRes as SessionSetupResV20,
)
from iso15118.shared.messages.iso15118_20.common_types import MessageHeader, Processing
from iso15118.shared.messages.iso15118_20.common_types import (
V2GMessage as V2GMessageV20,
)
from iso15118.shared.messages.iso15118_20.common_types import (
V2GResponse as V2GResponseV20,
)
from iso15118.shared.messages.iso15118_20.timeouts import Timeouts
from iso15118.shared.notifications import StopNotification
from iso15118.shared.states import State, Terminate

logger = logging.getLogger(__name__)


class StateEVCC(State, ABC):
"""
Expand Down Expand Up @@ -230,3 +242,43 @@ def stop_state_machine(self, reason: str):
)

self.next_state = Terminate

def stop_v20_charging(
self, next_state: Type["State"], renegotiate_requested: bool = False
):
power_delivery_req = PowerDeliveryReq(
header=MessageHeader(
session_id=self.comm_session.session_id,
timestamp=time.time(),
),
ev_processing=Processing.FINISHED,
charge_progress=ChargeProgress.STOP,
)

if next_state.__name__ != "PowerDelivery":
raise ValueError(
f"Attempt to stop charging by going to "
f"state {next_state.__name__} when "
f" 'PowerDelivery' was expected"
)

self.create_next_message(
next_state,
power_delivery_req,
Timeouts.POWER_DELIVERY_REQ,
Namespace.ISO_V20_COMMON_MSG,
ISOV20PayloadTypes.MAINSTREAM,
)

if renegotiate_requested:
self.comm_session.renegotiation_requested = True
self.comm_session.charging_session_stop_v20 = (
ChargingSession.SERVICE_RENEGOTIATION
)
logger.debug(
f"ChargeProgress is set to {ChargeProgress.SCHEDULE_RENEGOTIATION}"
)
else:
self.comm_session.charging_session_stop_v20 = ChargingSession.TERMINATE
# TODO Implement also a mechanism for pausing
logger.debug(f"ChargeProgress is set to {ChargeProgress.STOP}")
111 changes: 39 additions & 72 deletions iso15118/evcc/states/iso15118_20_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
AuthorizationSetupRes,
CertificateInstallationReq,
ChannelSelection,
ChargeProgress,
ChargingSession,
EIMAuthReqParams,
MatchedService,
Expand Down Expand Up @@ -1246,12 +1245,23 @@ async def process_message(
# check if SECC requested a renegotiation.
# evse_status field in ACChargeLoopRes is optional
if ac_charge_loop_res.evse_status:
if (
ac_charge_loop_res.evse_status.evse_notification
== EVSENotification.SERVICE_RENEGOTIATION
):
self.comm_session.renegotiation_requested = True
self.stop_charging(True)
renegotiation = False
evse_notification = ac_charge_loop_res.evse_status.evse_notification
if evse_notification not in [
EVSENotification.SERVICE_RENEGOTIATION,
EVSENotification.TERMINATE,
]:
raise NotImplementedError(
f"Processing for EVSE Notification "
f"{evse_notification} is not "
f"supported at the moment"
)
if evse_notification == EVSENotification.SERVICE_RENEGOTIATION:
renegotiation = True
self.stop_v20_charging(
next_state=PowerDelivery, renegotiate_requested=renegotiation
)

elif await self.comm_session.ev_controller.continue_charging():
scheduled_params, dynamic_params = None, None
bpt_scheduled_params, bpt_dynamic_params = None, None
Expand Down Expand Up @@ -1308,38 +1318,7 @@ async def process_message(
ISOV20PayloadTypes.AC_MAINSTREAM,
)
else:
self.stop_charging(False)
return

def stop_charging(self, renegotiate_requested: bool):
power_delivery_req = PowerDeliveryReq(
header=MessageHeader(
session_id=self.comm_session.session_id,
timestamp=time.time(),
),
ev_processing=Processing.FINISHED,
charge_progress=ChargeProgress.STOP,
)

self.create_next_message(
PowerDelivery,
power_delivery_req,
Timeouts.POWER_DELIVERY_REQ,
Namespace.ISO_V20_COMMON_MSG,
ISOV20PayloadTypes.MAINSTREAM,
)

if renegotiate_requested:
self.comm_session.charging_session_stop_v20 = (
ChargingSession.SERVICE_RENEGOTIATION
)
logger.debug(
f"ChargeProgress is set to {ChargeProgress.SCHEDULE_RENEGOTIATION}"
)
else:
self.comm_session.charging_session_stop_v20 = ChargingSession.TERMINATE
# TODO Implement also a mechanism for pausing
logger.debug(f"ChargeProgress is set to {ChargeProgress.STOP}")
self.stop_v20_charging(next_state=PowerDelivery)


# ============================================================================
Expand Down Expand Up @@ -1638,9 +1617,27 @@ async def process_message(
charge_loop_res: DCChargeLoopRes = msg # noqa

# if charge_loop_res.evse_power_limit_achieved:
# await self.stop_charging(False)
# self.stop_v20_charging(False)

if charge_loop_res.evse_status:
renegotiation = False
evse_notification = charge_loop_res.evse_status.evse_notification
if evse_notification not in [
EVSENotification.SERVICE_RENEGOTIATION,
EVSENotification.TERMINATE,
]:
raise NotImplementedError(
f"Processing for EVSE Notification "
f"{evse_notification} is not "
f"supported at the moment"
)
if evse_notification == EVSENotification.SERVICE_RENEGOTIATION:
renegotiation = True
self.stop_v20_charging(
next_state=PowerDelivery, renegotiate_requested=renegotiation
)

if await self.comm_session.ev_controller.continue_charging():
elif await self.comm_session.ev_controller.continue_charging():
current_demand_req = await self.build_current_demand_data()

self.create_next_message(
Expand All @@ -1651,7 +1648,7 @@ async def process_message(
ISOV20PayloadTypes.DC_MAINSTREAM,
)
else:
await self.stop_charging(False)
self.stop_v20_charging(next_state=PowerDelivery)

async def build_current_demand_data(self):
scheduled_params, dynamic_params = None, None
Expand Down Expand Up @@ -1689,36 +1686,6 @@ async def build_current_demand_data(self):
)
return dc_charge_loop_req

async def stop_charging(self, renegotiate_requested: bool):
power_delivery_req = PowerDeliveryReq(
header=MessageHeader(
session_id=self.comm_session.session_id,
timestamp=time.time(),
),
ev_processing=Processing.FINISHED,
charge_progress=ChargeProgress.STOP,
)

self.create_next_message(
PowerDelivery,
power_delivery_req,
Timeouts.POWER_DELIVERY_REQ,
Namespace.ISO_V20_COMMON_MSG,
ISOV20PayloadTypes.MAINSTREAM,
)

if renegotiate_requested:
self.comm_session.charging_session_stop_v20 = (
ChargingSession.SERVICE_RENEGOTIATION
)
logger.debug(
f"ChargeProgress is set to {ChargeProgress.SCHEDULE_RENEGOTIATION}"
)
else:
self.comm_session.charging_session_stop_v20 = ChargingSession.TERMINATE
# TODO Implement also a mechanism for pausing
logger.debug(f"ChargeProgress is set to {ChargeProgress.STOP}")


class DCWeldingDetection(StateEVCC):
"""
Expand Down
8 changes: 7 additions & 1 deletion iso15118/secc/controller/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ class EVDataContext:
soc: Optional[int] = None # 0-100

# from ISO 15118-20 AC
departure_time: Optional[int] = None
ev_target_energy_request: float = 0.0
ev_max_energy_request: float = 0.0
ev_min_energy_request: float = 0.0

ev_max_charge_power: float = 0.0
ev_max_charge_power_l2: Optional[float] = None
ev_max_charge_power_l3: Optional[float] = None
Expand All @@ -94,6 +99,7 @@ class EVDataContext:
ev_present_reactive_power_l2: Optional[float] = None
ev_present_reactive_power_l3: Optional[float] = None

# BPT values
ev_max_discharge_power: float = 0.0
ev_max_discharge_power_l2: Optional[float] = None
ev_max_discharge_power_l3: Optional[float] = None
Expand Down Expand Up @@ -445,7 +451,7 @@ async def is_contactor_closed(self) -> bool:
raise NotImplementedError

@abstractmethod
async def get_evse_status(self) -> EVSEStatus:
async def get_evse_status(self) -> Optional[EVSEStatus]:
"""
Gets the status of the EVSE
Expand Down
39 changes: 26 additions & 13 deletions iso15118/secc/controller/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,6 @@
TaxRule,
TaxRuleList,
)
from iso15118.shared.messages.iso15118_20.common_types import (
EVSENotification as EVSENotificationV20,
)
from iso15118.shared.messages.iso15118_20.common_types import EVSEStatus
from iso15118.shared.messages.iso15118_20.common_types import MeterInfo as MeterInfoV20
from iso15118.shared.messages.iso15118_20.common_types import RationalNumber
Expand Down Expand Up @@ -569,11 +566,27 @@ async def is_contactor_opened(self) -> bool:
"""Overrides EVSEControllerInterface.is_contactor_opened()."""
return True

async def get_evse_status(self) -> EVSEStatus:
async def get_evse_status(self) -> Optional[EVSEStatus]:
"""Overrides EVSEControllerInterface.get_evse_status()."""
return EVSEStatus(
notification_max_delay=0, evse_notification=EVSENotificationV20.TERMINATE
)
# TODO: this function can be generic to all protocols.
# We can make use of the method `get_evse_id`
# or other way to get the evse_id to request
# status of a specific evse_id. We can also use the
# `self.comm_session.protocol` obtained during SAP,
# and inject its value into the `get_evse_status`
# to decide on providing the -2ß EVSEStatus or the
# -2 AC or DC one and the `selected_charging_type_is_ac` in -2
# to decide on returning the ACEVSEStatus or the DCEVSEStatus
#
# Just as an example, here is how the return could look like
# from iso15118.shared.messages.iso15118_20.common_types import (
# EVSENotification as EVSENotificationV20,
# )
# return EVSEStatus(
# notification_max_delay=0,
# evse_notification=EVSENotificationV20.TERMINATE
# )
return None

async def set_present_protocol_state(self, state_name: str):
pass
Expand Down Expand Up @@ -623,12 +636,12 @@ async def send_charging_power_limits(
ev_data_context.ev_min_discharge_power,
charge_parameters.evse_min_discharge_power.get_decimal_value(),
)
logger.info(
f"\n Power limits \n"
f"max_charge_power: {max_charge_power}\n"
f"min_charge_power: {min_charge_power}\n"
f"max_discharge_power: {max_discharge_power}\n"
f"min_discharge_power: {min_discharge_power}\n"
logger.debug(
f"\n\r --- EV-EVSE System Power Limits --- \n"
f"max_charge_power [W]: {max_charge_power}\n"
f"min_charge_power [W]: {min_charge_power}\n"
f"max_discharge_power [W]: {max_discharge_power}\n"
f"min_discharge_power [W]: {min_discharge_power}\n"
)
# NOTE: Currently reactive limits are not available
# https://iso15118.elaad.io/pt2/15118-20/user-group/-/issues/65
Expand Down
Loading

0 comments on commit 3b91fa7

Please sign in to comment.