Skip to content

Commit a219774

Browse files
authored
Adding ISO15118-20 pause/resume to the ev side (#37)
Signed-off-by: Sebastian Lukas <sebastian.lukas@pionix.de>
1 parent 8d0192b commit a219774

7 files changed

Lines changed: 120 additions & 22 deletions

File tree

iso15118/evcc/comm_session_handler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ def save_session_info(self):
263263
evcc_settings.ev_session_context.session_id = self.session_id
264264
evcc_settings.ev_session_context.selected_auth_option = self.selected_auth_option
265265
evcc_settings.ev_session_context.requested_energy_mode = self.selected_energy_mode
266+
evcc_settings.ev_session_context.selected_energy_service = self.selected_energy_service
266267

267268
class CommunicationSessionHandler:
268269
"""

iso15118/evcc/controller/interface.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class EVSessionContext:
8383
session_id: Optional[str] = None
8484
selected_auth_option: Optional[AuthEnum] = None
8585
requested_energy_mode: Optional[EnergyTransferModeEnum] = None
86+
selected_energy_service: Optional[SelectedEnergyService] = None
8687

8788
class EVControllerInterface(ABC):
8889
# ============================================================================

iso15118/evcc/states/evcc_state.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ def stop_state_machine(self, reason: str):
246246
self.next_state = Terminate
247247

248248
def stop_v20_charging(
249-
self, next_state: Type["State"], renegotiate_requested: bool = False
249+
self, next_state: Type["State"], renegotiate_requested: bool = False,
250+
pause: bool = False
250251
):
251252
power_delivery_req = PowerDeliveryReq(
252253
header=MessageHeader(
@@ -280,6 +281,8 @@ def stop_v20_charging(
280281
logger.debug(
281282
f"ChargeProgress is set to {ChargeProgress.SCHEDULE_RENEGOTIATION}"
282283
)
284+
elif pause:
285+
self.comm_session.charging_session_stop_v20 = ChargingSession.PAUSE
283286
else:
284287
self.comm_session.charging_session_stop_v20 = ChargingSession.TERMINATE
285288
# TODO Implement also a mechanism for pausing

iso15118/evcc/states/iso15118_20_states.py

Lines changed: 103 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import Any, List, Union, cast
1010
import os
1111

12+
from iso15118.evcc import evcc_settings
1213
from iso15118.evcc.comm_session_handler import EVCCCommunicationSession
1314
from iso15118.evcc.states.evcc_state import StateEVCC
1415
from iso15118.shared.exceptions import PrivateKeyReadError
@@ -100,7 +101,7 @@
100101
load_cert_chain,
101102
load_priv_key,
102103
)
103-
from iso15118.shared.states import Terminate
104+
from iso15118.shared.states import Pause, Terminate
104105
from iso15118.shared.settings import get_PKI_PATH
105106

106107
logger = logging.getLogger(__name__)
@@ -143,19 +144,92 @@ async def process_message(
143144
self.comm_session.session_id = msg.header.session_id
144145
self.comm_session.evse_id = session_setup_res.evse_id
145146

146-
auth_setup_req = AuthorizationSetupReq(
147-
header=MessageHeader(
148-
session_id=self.comm_session.session_id, timestamp=time.time()
147+
old_session_joined: bool = False
148+
149+
if evcc_settings.ev_session_context.selected_energy_service:
150+
old_session_joined = True
151+
self.comm_session.selected_energy_service = evcc_settings.ev_session_context.selected_energy_service
152+
153+
if self.comm_session.selected_energy_service:
154+
parameter_set = self.comm_session.selected_energy_service.parameter_set
155+
for param in parameter_set.parameters:
156+
if param.name == ParameterName.CONTROL_MODE:
157+
self.comm_session.control_mode = ControlMode(param.int_value)
158+
159+
if old_session_joined and self.comm_session.selected_energy_service.service in (ServiceV20.AC, ServiceV20.AC_BPT):
160+
ac_params, bpt_ac_params = None, None
161+
self.comm_session.selected_charging_type_is_ac = True
162+
if self.comm_session.selected_energy_service.service == ServiceV20.AC:
163+
ac_params = await self.comm_session.ev_controller.get_charge_params_v20(
164+
self.comm_session.selected_energy_service
165+
)
166+
else:
167+
bpt_ac_params = (
168+
await self.comm_session.ev_controller.get_charge_params_v20(
169+
self.comm_session.selected_energy_service
170+
)
171+
)
172+
173+
next_req = ACChargeParameterDiscoveryReq(
174+
header=MessageHeader(
175+
session_id=self.comm_session.session_id,
176+
timestamp=time.time(),
177+
),
178+
ac_params=ac_params,
179+
bpt_ac_params=bpt_ac_params,
149180
)
150-
)
181+
self.create_next_message(
182+
ACChargeParameterDiscovery,
183+
next_req,
184+
Timeouts.CHARGE_PARAMETER_DISCOVERY_REQ,
185+
Namespace.ISO_V20_AC,
186+
ISOV20PayloadTypes.AC_MAINSTREAM,
187+
)
188+
elif old_session_joined and self.comm_session.selected_energy_service.service in (ServiceV20.DC, ServiceV20.DC_BPT):
151189

152-
self.create_next_message(
153-
AuthorizationSetup,
154-
auth_setup_req,
155-
Timeouts.AUTHORIZATION_SETUP_REQ,
156-
Namespace.ISO_V20_COMMON_MSG,
157-
ISOV20PayloadTypes.MAINSTREAM,
158-
)
190+
dc_params, bpt_dc_params = None, None
191+
self.comm_session.selected_charging_type_is_ac = False
192+
if self.comm_session.selected_energy_service.service == ServiceV20.DC:
193+
dc_params = await self.comm_session.ev_controller.get_charge_params_v20(
194+
self.comm_session.selected_energy_service
195+
)
196+
else:
197+
bpt_dc_params = (
198+
await self.comm_session.ev_controller.get_charge_params_v20(
199+
self.comm_session.selected_energy_service
200+
)
201+
)
202+
203+
next_req = DCChargeParameterDiscoveryReq(
204+
header=MessageHeader(
205+
session_id=self.comm_session.session_id,
206+
timestamp=time.time(),
207+
),
208+
dc_params=dc_params,
209+
bpt_dc_params=bpt_dc_params,
210+
)
211+
212+
self.create_next_message(
213+
DCChargeParameterDiscovery,
214+
next_req,
215+
Timeouts.CHARGE_PARAMETER_DISCOVERY_REQ,
216+
Namespace.ISO_V20_DC,
217+
ISOV20PayloadTypes.DC_MAINSTREAM,
218+
)
219+
else:
220+
auth_setup_req = AuthorizationSetupReq(
221+
header=MessageHeader(
222+
session_id=self.comm_session.session_id, timestamp=time.time()
223+
)
224+
)
225+
226+
self.create_next_message(
227+
AuthorizationSetup,
228+
auth_setup_req,
229+
Timeouts.AUTHORIZATION_SETUP_REQ,
230+
Namespace.ISO_V20_COMMON_MSG,
231+
ISOV20PayloadTypes.MAINSTREAM,
232+
)
159233

160234

161235
class AuthorizationSetup(StateEVCC):
@@ -935,6 +1009,7 @@ async def process_message(
9351009
if self.comm_session.charging_session_stop_v20 in (
9361010
ChargingSession.SERVICE_RENEGOTIATION,
9371011
ChargingSession.TERMINATE,
1012+
ChargingSession.PAUSE
9381013
):
9391014
await self.comm_session.ev_controller.enable_charging(False)
9401015
if self.comm_session.selected_energy_service.service in [
@@ -1179,6 +1254,8 @@ async def process_message(
11791254
):
11801255
self.comm_session.renegotiation_requested = False
11811256
self.next_state = ServiceDiscovery
1257+
elif self.comm_session.charging_session_stop_v20 == ChargingSession.PAUSE:
1258+
self.next_state = Pause
11821259
else:
11831260
self.next_state = Terminate
11841261

@@ -1671,9 +1748,11 @@ async def process_message(
16711748
if charge_loop_res.evse_status:
16721749
renegotiation = False
16731750
evse_notification = charge_loop_res.evse_status.evse_notification
1751+
pause = False
16741752
if evse_notification not in [
16751753
EVSENotification.SERVICE_RENEGOTIATION,
16761754
EVSENotification.TERMINATE,
1755+
EVSENotification.PAUSE,
16771756
]:
16781757
raise NotImplementedError(
16791758
f"Processing for EVSE Notification "
@@ -1682,13 +1761,20 @@ async def process_message(
16821761
)
16831762
if evse_notification == EVSENotification.SERVICE_RENEGOTIATION:
16841763
renegotiation = True
1764+
elif evse_notification == EVSENotification.PAUSE:
1765+
pause = True
1766+
EVEREST_CTX.publish('pause_from_charger', None)
1767+
else:
1768+
EVEREST_CTX.publish('AC_StopFromCharger', None)
16851769

1686-
EVEREST_CTX.publish('AC_StopFromCharger', None)
1687-
16881770
self.stop_v20_charging(
1689-
next_state=PowerDelivery, renegotiate_requested=renegotiation
1771+
next_state=PowerDelivery, renegotiate_requested=renegotiation, pause=pause
1772+
)
1773+
# TODO(sl): Check if dynamic mode is active and the ev wants to do a pause
1774+
elif await self.comm_session.ev_controller.pause():
1775+
self.stop_v20_charging(
1776+
next_state=PowerDelivery, renegotiate_requested=False, pause=True
16901777
)
1691-
16921778
elif await self.comm_session.ev_controller.continue_charging():
16931779
current_demand_req = await self.build_current_demand_data()
16941780

@@ -1778,7 +1864,7 @@ async def process_message(
17781864
session_id=self.comm_session.session_id,
17791865
timestamp=time.time(),
17801866
),
1781-
charging_session=ChargingSession.TERMINATE,
1867+
charging_session=self.comm_session.charging_session_stop_v20,
17821868
)
17831869
next_state = SessionStop
17841870
next_request: V2GRequest = session_stop_req

iso15118/evcc/states/sap_states.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,5 +183,9 @@ def get_session_id(self, length=1) -> str:
183183
evcc_settings.ev_session_context.session_id = None
184184
else:
185185
self.comm_session.session_id = bytes(length).hex().upper()
186+
# Reset whole ev_session_context
187+
evcc_settings.ev_session_context.selected_auth_option = None
188+
evcc_settings.ev_session_context.requested_energy_mode = None
189+
evcc_settings.ev_session_context.selected_energy_service = None
186190

187191
return self.comm_session.session_id

iso15118/shared/security.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
Transform,
8282
Transforms,
8383
)
84-
from iso15118.shared.settings import enabled_tls_1_3, get_PKI_PATH
84+
from iso15118.shared.settings import is_tls_1_3_enabled, get_PKI_PATH
8585

8686
logger = logging.getLogger(__name__)
8787

@@ -129,7 +129,7 @@ def get_ssl_context(server_side: bool, ciphersuites: str = None) -> Optional[SSL
129129
as well as read the password.
130130
"""
131131

132-
if enabled_tls_1_3:
132+
if is_tls_1_3_enabled():
133133
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS)
134134
else:
135135
# Specifying protocol as `PROTOCOL_TLS` does best effort.
@@ -164,7 +164,7 @@ def get_ssl_context(server_side: bool, ciphersuites: str = None) -> Optional[SSL
164164
logger.exception(exc)
165165
return None
166166

167-
if enabled_tls_1_3:
167+
if is_tls_1_3_enabled():
168168
# In 15118-20 we should also verify EVCC's certificate chain.
169169
# The spec however says TLS 1.3 should also support 15118-2
170170
# (Table 5 in V2G20 specification)
@@ -187,7 +187,7 @@ def get_ssl_context(server_side: bool, ciphersuites: str = None) -> Optional[SSL
187187
ssl_context.verify_mode = VerifyMode.CERT_REQUIRED
188188
ssl_context.set_ciphers(ciphersuites)
189189

190-
if enabled_tls_1_3:
190+
if is_tls_1_3_enabled():
191191
try:
192192
ssl_context.load_cert_chain(
193193
certfile=os.path.join(get_PKI_PATH(), CertPath.VEHICLE_CERT_CHAIN_PEM),

iso15118/shared/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ def enable_tls_1_3() -> None:
2626
global enabled_tls_1_3
2727
enabled_tls_1_3 = True
2828

29+
def is_tls_1_3_enabled() -> bool:
30+
return enabled_tls_1_3
31+
2932
shared_settings = None
3033

3134
ignoring_value_range = False

0 commit comments

Comments
 (0)