33from __future__ import annotations
44
55from collections .abc import Callable
6- from datetime import timedelta
6+ from datetime import datetime , timedelta
77from http import HTTPStatus
88import logging
99from typing import Any , Concatenate
2727 CHARGER_ECO_SMART_STATUS_KEY ,
2828 CHARGER_ENERGY_PRICE_KEY ,
2929 CHARGER_FEATURES_KEY ,
30+ CHARGER_JWT_REFRESH_TOKEN ,
31+ CHARGER_JWT_REFRESH_TTL ,
32+ CHARGER_JWT_TOKEN ,
33+ CHARGER_JWT_TTL ,
3034 CHARGER_LOCKED_UNLOCKED_KEY ,
3135 CHARGER_MAX_CHARGING_CURRENT_KEY ,
3236 CHARGER_MAX_CHARGING_CURRENT_POST_KEY ,
@@ -86,27 +90,25 @@ def _require_authentication[_WallboxCoordinatorT: WallboxCoordinator, **_P](
8690) -> Callable [Concatenate [_WallboxCoordinatorT , _P ], Any ]:
8791 """Authenticate with decorator using Wallbox API."""
8892
89- def require_authentication (
93+ async def require_authentication (
9094 self : _WallboxCoordinatorT , * args : _P .args , ** kwargs : _P .kwargs
9195 ) -> Any :
9296 """Authenticate using Wallbox API."""
93- try :
94- self .authenticate ()
95- return func (self , * args , ** kwargs )
96- except requests .exceptions .HTTPError as wallbox_connection_error :
97- if wallbox_connection_error .response .status_code == HTTPStatus .FORBIDDEN :
98- raise ConfigEntryAuthFailed (
99- translation_domain = DOMAIN , translation_key = "invalid_auth"
100- ) from wallbox_connection_error
101- raise HomeAssistantError (
102- translation_domain = DOMAIN , translation_key = "api_failed"
103- ) from wallbox_connection_error
97+ await self .async_authenticate ()
98+ return await func (self , * args , ** kwargs )
10499
105100 return require_authentication
106101
107102
103+ def check_token_validity (jwt_token_ttl : int , jwt_token_drift : int ) -> bool :
104+ """Check if the jwtToken is still valid in order to reuse if possible."""
105+ return round ((jwt_token_ttl / 1000 ) - jwt_token_drift , 0 ) > datetime .timestamp (
106+ datetime .now ()
107+ )
108+
109+
108110def _validate (wallbox : Wallbox ) -> None :
109- """Authenticate using Wallbox API."""
111+ """Authenticate using Wallbox API to check if the used credentials are valid ."""
110112 try :
111113 wallbox .authenticate ()
112114 except requests .exceptions .HTTPError as wallbox_connection_error :
@@ -142,11 +144,38 @@ def __init__(
142144 update_interval = timedelta (seconds = UPDATE_INTERVAL ),
143145 )
144146
145- def authenticate (self ) -> None :
147+ def _authenticate (self ) -> dict [str , str ]:
148+ """Authenticate using Wallbox API. First check token validity."""
149+ data = dict (self .config_entry .data )
150+ if not check_token_validity (
151+ jwt_token_ttl = data .get (CHARGER_JWT_TTL , 0 ),
152+ jwt_token_drift = UPDATE_INTERVAL ,
153+ ):
154+ try :
155+ self ._wallbox .authenticate ()
156+ except requests .exceptions .HTTPError as wallbox_connection_error :
157+ if (
158+ wallbox_connection_error .response .status_code
159+ == HTTPStatus .FORBIDDEN
160+ ):
161+ raise ConfigEntryAuthFailed (
162+ translation_domain = DOMAIN , translation_key = "invalid_auth"
163+ ) from wallbox_connection_error
164+ raise HomeAssistantError (
165+ translation_domain = DOMAIN , translation_key = "api_failed"
166+ ) from wallbox_connection_error
167+ else :
168+ data [CHARGER_JWT_TOKEN ] = self ._wallbox .jwtToken
169+ data [CHARGER_JWT_REFRESH_TOKEN ] = self ._wallbox .jwtRefreshToken
170+ data [CHARGER_JWT_TTL ] = self ._wallbox .jwtTokenTtl
171+ data [CHARGER_JWT_REFRESH_TTL ] = self ._wallbox .jwtRefreshTokenTtl
172+ return data
173+
174+ async def async_authenticate (self ) -> None :
146175 """Authenticate using Wallbox API."""
147- self ._wallbox .authenticate ()
176+ data = await self .hass .async_add_executor_job (self ._authenticate )
177+ self .hass .config_entries .async_update_entry (self .config_entry , data = data )
148178
149- @_require_authentication
150179 def _get_data (self ) -> dict [str , Any ]:
151180 """Get new sensor data for Wallbox component."""
152181 try :
@@ -208,6 +237,7 @@ def _get_data(self) -> dict[str, Any]:
208237 translation_domain = DOMAIN , translation_key = "api_failed"
209238 ) from wallbox_connection_error
210239
240+ @_require_authentication
211241 async def _async_update_data (self ) -> dict [str , Any ]:
212242 """Get new sensor data for Wallbox component. Set update interval to be UPDATE_INTERVAL * #wallbox chargers configured, this is necessary due to rate limitations."""
213243
@@ -217,7 +247,6 @@ async def _async_update_data(self) -> dict[str, Any]:
217247 )
218248 return await self .hass .async_add_executor_job (self ._get_data )
219249
220- @_require_authentication
221250 def _set_charging_current (
222251 self , charging_current : float
223252 ) -> dict [str , dict [str , dict [str , Any ]]]:
@@ -246,14 +275,14 @@ def _set_charging_current(
246275 translation_domain = DOMAIN , translation_key = "api_failed"
247276 ) from wallbox_connection_error
248277
278+ @_require_authentication
249279 async def async_set_charging_current (self , charging_current : float ) -> None :
250280 """Set maximum charging current for Wallbox."""
251281 data = await self .hass .async_add_executor_job (
252282 self ._set_charging_current , charging_current
253283 )
254284 self .async_set_updated_data (data )
255285
256- @_require_authentication
257286 def _set_icp_current (self , icp_current : float ) -> dict [str , Any ]:
258287 """Set maximum icp current for Wallbox."""
259288 try :
@@ -276,14 +305,14 @@ def _set_icp_current(self, icp_current: float) -> dict[str, Any]:
276305 translation_domain = DOMAIN , translation_key = "api_failed"
277306 ) from wallbox_connection_error
278307
308+ @_require_authentication
279309 async def async_set_icp_current (self , icp_current : float ) -> None :
280310 """Set maximum icp current for Wallbox."""
281311 data = await self .hass .async_add_executor_job (
282312 self ._set_icp_current , icp_current
283313 )
284314 self .async_set_updated_data (data )
285315
286- @_require_authentication
287316 def _set_energy_cost (self , energy_cost : float ) -> dict [str , Any ]:
288317 """Set energy cost for Wallbox."""
289318 try :
@@ -300,14 +329,14 @@ def _set_energy_cost(self, energy_cost: float) -> dict[str, Any]:
300329 translation_domain = DOMAIN , translation_key = "api_failed"
301330 ) from wallbox_connection_error
302331
332+ @_require_authentication
303333 async def async_set_energy_cost (self , energy_cost : float ) -> None :
304334 """Set energy cost for Wallbox."""
305335 data = await self .hass .async_add_executor_job (
306336 self ._set_energy_cost , energy_cost
307337 )
308338 self .async_set_updated_data (data )
309339
310- @_require_authentication
311340 def _set_lock_unlock (self , lock : bool ) -> dict [str , dict [str , dict [str , Any ]]]:
312341 """Set wallbox to locked or unlocked."""
313342 try :
@@ -335,12 +364,12 @@ def _set_lock_unlock(self, lock: bool) -> dict[str, dict[str, dict[str, Any]]]:
335364 translation_domain = DOMAIN , translation_key = "api_failed"
336365 ) from wallbox_connection_error
337366
367+ @_require_authentication
338368 async def async_set_lock_unlock (self , lock : bool ) -> None :
339369 """Set wallbox to locked or unlocked."""
340370 data = await self .hass .async_add_executor_job (self ._set_lock_unlock , lock )
341371 self .async_set_updated_data (data )
342372
343- @_require_authentication
344373 def _pause_charger (self , pause : bool ) -> None :
345374 """Set wallbox to pause or resume."""
346375 try :
@@ -357,12 +386,12 @@ def _pause_charger(self, pause: bool) -> None:
357386 translation_domain = DOMAIN , translation_key = "api_failed"
358387 ) from wallbox_connection_error
359388
389+ @_require_authentication
360390 async def async_pause_charger (self , pause : bool ) -> None :
361391 """Set wallbox to pause or resume."""
362392 await self .hass .async_add_executor_job (self ._pause_charger , pause )
363393 await self .async_request_refresh ()
364394
365- @_require_authentication
366395 def _set_eco_smart (self , option : str ) -> None :
367396 """Set wallbox solar charging mode."""
368397 try :
@@ -381,6 +410,7 @@ def _set_eco_smart(self, option: str) -> None:
381410 translation_domain = DOMAIN , translation_key = "api_failed"
382411 ) from wallbox_connection_error
383412
413+ @_require_authentication
384414 async def async_set_eco_smart (self , option : str ) -> None :
385415 """Set wallbox solar charging mode."""
386416
0 commit comments