Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report of the evse status during the charging loop of both AC and DC in -20 #207

Merged
merged 4 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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