2424 ROOT_MESSAGES ,
2525 ROOT_VEHICLES ,
2626 ROOT_REMOTE_CLIMATE_CONTROL ,
27- ROOT_ENERGY_TRANSFER_STATUS ,
27+ ROOT_PREFERRED_CHARGE_TIMES ,
2828 ROOT_UPDTIME
2929)
3030
@@ -130,7 +130,7 @@ class ConnectedFordPassVehicle:
130130 _LAST_MESSAGES_UPDATE : float = 0.0
131131 _last_ignition_state : str | None = None
132132 _ws_debounced_full_refresh_task : asyncio .Task | None = None
133-
133+ _ws_debounced_preferred_charge_times_refresh_task : asyncio . Task | None = None
134134 # when you have multiple vehicles, you need to set the vehicle log id
135135 # (v)ehicle (l)og (i)d
136136 vli : str = ""
@@ -190,13 +190,14 @@ def __init__(self, web_session, username, vin, region_key, coordinator: DataUpda
190190 self ._cached_vehicles_data = {}
191191 self ._remote_climate_control_supported = None
192192 self ._cached_rcc_data = {}
193- self ._energy_transfer_status_supported = None
194- self ._cached_ets_data = {}
193+ self ._preferred_charge_times_supported = None
194+ self ._cached_pct_data = {}
195195 self .coordinator = coordinator
196196
197197 # websocket connection related variables
198198 self ._ws_debounced_update_task = None
199199 self ._ws_debounced_full_refresh_task = None
200+ self ._ws_debounced_preferred_charge_times_refresh_task = None
200201 self ._ws_in_use_access_token = None
201202 self .ws_connected = False
202203 self ._ws_LAST_UPDATE = 0
@@ -866,7 +867,10 @@ def _ws_update_key(self, data_obj, a_root_key, collected_keys):
866867 # we have a special handling for the 'updateChargeProfilesCommand'
867868 # -> when we receive a 'success' state, we will update our
868869 # energy_transfer_object...
869- asyncio .create_task (self .update_energy_transfer_status_int ())
870+ if self ._ws_debounced_preferred_charge_times_refresh_task is not None and not self ._ws_debounced_preferred_charge_times_refresh_task .done ():
871+ self ._ws_debounced_preferred_charge_times_refresh_task .cancel ()
872+ _LOGGER .debug (f"{ self .vli } ws(): updateChargeProfilesCommand -> triggering 'preferred_charge_times' data update (will be started in 30sec)" )
873+ self ._ws_debounced_preferred_charge_times_refresh_task = asyncio .create_task (self ._ws_debounce_update_preferred_charge_times ())
870874
871875 else :
872876 _LOGGER .debug (f"{ self .vli } ws(): new state (without toState) '{ a_state_name } ' arrived: { a_value_obj } " )
@@ -1081,7 +1085,7 @@ async def update_all(self):
10811085 self ._remote_climate_control_supported = a_vehicle_profile ["remoteClimateControl" ]
10821086
10831087 if "showEVBatteryLevel" in a_vehicle_profile :
1084- self ._energy_transfer_status_supported = a_vehicle_profile ["showEVBatteryLevel" ]
1088+ self ._preferred_charge_times_supported = a_vehicle_profile ["showEVBatteryLevel" ]
10851089
10861090 break
10871091
@@ -1095,13 +1099,13 @@ async def update_all(self):
10951099 data [ROOT_REMOTE_CLIMATE_CONTROL ] = self ._cached_rcc_data
10961100
10971101 # only update energy-status if not present yet
1098- if self ._energy_transfer_status_supported :
1099- if self ._cached_ets_data is None or len (self ._cached_ets_data ) == 0 :
1100- _LOGGER .debug (f"{ self .vli } update_all(): request 'energy transfer status ' data..." )
1101- self ._cached_ets_data = await self .req_energy_transfer_status ()
1102+ if self ._preferred_charge_times_supported :
1103+ if self ._cached_pct_data is None or len (self ._cached_pct_data ) == 0 :
1104+ _LOGGER .debug (f"{ self .vli } update_all(): request 'preferred_charge_times ' data..." )
1105+ self ._cached_pct_data = await self .req_preferred_charge_times ()
11021106
1103- if self ._cached_ets_data is not None and len (self ._cached_ets_data ) > 0 :
1104- data [ROOT_ENERGY_TRANSFER_STATUS ] = self ._cached_ets_data
1107+ if self ._cached_pct_data is not None and len (self ._cached_pct_data ) > 0 :
1108+ data [ROOT_PREFERRED_CHARGE_TIMES ] = self ._cached_pct_data
11051109
11061110
11071111 # ok finally store the data in our main data container...
@@ -1120,14 +1124,32 @@ async def update_remote_climate_int(self):
11201124 self ._data_container [ROOT_REMOTE_CLIMATE_CONTROL ] = self ._cached_rcc_data
11211125
11221126
1123- async def update_energy_transfer_status_int (self ):
1127+ async def update_preferred_charge_times_int (self ):
11241128 # only update remote climate data if not present yet
1125- if self ._energy_transfer_status_supported :
1126- _LOGGER .debug (f"{ self .vli } update_energy_transfer_status_int(): request 'energy transfer status' data..." )
1127- self ._cached_ets_data = await self .req_energy_transfer_status ()
1129+ if self ._preferred_charge_times_supported :
1130+ _LOGGER .debug (f"{ self .vli } update_preferred_charge_times_int(): request 'energy transfer status' data..." )
1131+ self ._cached_pct_data = await self .req_preferred_charge_times ()
1132+
1133+ if self ._cached_pct_data is not None and len (self ._cached_pct_data ) > 0 :
1134+ self ._data_container [ROOT_PREFERRED_CHARGE_TIMES ] = self ._cached_pct_data
1135+ return True
1136+
1137+ return False
1138+
1139+ async def _ws_debounce_update_preferred_charge_times (self ):
1140+ if self ._preferred_charge_times_supported :
1141+ try :
1142+ _LOGGER .debug (f"{ self .vli } _ws_debounce_update_preferred_charge_times(): started" )
1143+ await asyncio .sleep (30 )
1144+ _LOGGER .debug (f"{ self .vli } _ws_debounce_update_preferred_charge_times(): starting the 'update_preferred_charge_times_int()' update now" )
1145+ success = await self .update_preferred_charge_times_int ()
1146+ if success and self .coordinator is not None :
1147+ self .coordinator .async_set_updated_data (self ._data_container )
11281148
1129- if self ._cached_ets_data is not None and len (self ._cached_ets_data ) > 0 :
1130- self ._data_container [ROOT_ENERGY_TRANSFER_STATUS ] = self ._cached_ets_data
1149+ except CancelledError :
1150+ _LOGGER .debug (f"{ self .vli } _ws_debounce_update_preferred_charge_times(): was canceled - all good" )
1151+ except BaseException as ex :
1152+ _LOGGER .warning (f"{ self .vli } _ws_debounce_update_preferred_charge_times(): Error during 'preferred_charge_times' data refresh - { type (ex )} - { ex } " )
11311153
11321154
11331155 async def req_status (self ):
@@ -1384,7 +1406,79 @@ async def req_remote_climate(self):
13841406 self ._HAS_COM_ERROR = True
13851407 return None
13861408
1409+ async def req_preferred_charge_times (self ):
1410+ global _FOUR_NULL_ONE_COUNTER
1411+ try :
1412+ await self .__ensure_valid_tokens ()
1413+ if self ._HAS_COM_ERROR :
1414+ _LOGGER .debug (f"{ self .vli } req_preferred_charge_times(): - COMM ERROR" )
1415+ return None
1416+ else :
1417+ _LOGGER .debug (f"{ self .vli } req_preferred_charge_times(): - access_token exist? { self .access_token is not None } " )
1418+
1419+ # and the 'preferred-charge-times' request will get a 'vin' in the header
1420+ headers_veh = {
1421+ ** apiHeaders ,
1422+ "auth-token" : self .access_token ,
1423+ "Application-Id" : self .app_id ,
1424+ "vin" : self .vin
1425+ }
1426+ response_pct = await self .session .get (
1427+ f"{ FORD_VEHICLE_API } /electrification/experiences/v2/vehicles/preferred-charge-times" ,
1428+ headers = headers_veh ,
1429+ timeout = self .timeout
1430+ )
1431+ if response_pct .status == 200 :
1432+ # ok first resetting the counter for 401 errors (if we had any)
1433+ _FOUR_NULL_ONE_COUNTER [self .vin ] = 0
1434+
1435+ result_pct = await response_pct .json ()
1436+ if self ._LOCAL_LOGGING :
1437+ await self ._local_logging ("pct" , result_pct )
1438+
1439+ # we are going to transform our result! - we create a dict with the 'location.id' as a key
1440+ if isinstance (result_pct , list ):
1441+ modified_result = {}
1442+ for a_entry in result_pct :
1443+ if "vin" in a_entry :
1444+ if a_entry ["vin" ].upper () == self .vin .upper ():
1445+ modified_result [a_entry ["location" ]["id" ]] = a_entry
1446+ result_pct = modified_result
1447+ else :
1448+ _LOGGER .warning (f"{ self .vli } req_preferred_charge_times(): received unexpected data format: { type (result_pct )} - expected a list of entries" )
1449+
1450+ #_LOGGER.error(f"--------------------------")
1451+ #_LOGGER.error(f"--------------------------")
1452+ #_LOGGER.error(f"{self.vli}req_preferred_charge_times(): received data: {result_pct}")
1453+ #_LOGGER.error(f"--------------------------")
1454+ #_LOGGER.error(f"--------------------------")
1455+ return result_pct
1456+
1457+ elif response_pct .status == 401 :
1458+ _FOUR_NULL_ONE_COUNTER [self .vin ] += 1
1459+ if _FOUR_NULL_ONE_COUNTER [self .vin ] > MAX_401_RESPONSE_COUNT :
1460+ _LOGGER .error (f"{ self .vli } req_preferred_charge_times(): status_code: 401 - mark_re_auth_required()" )
1461+ self .mark_re_auth_required ()
1462+ else :
1463+ (_LOGGER .warning if _FOUR_NULL_ONE_COUNTER [self .vin ] > 2 else _LOGGER .info )(f"{ self .vli } req_preferred_charge_times(): status_code: 401 - counter: { _FOUR_NULL_ONE_COUNTER } " )
1464+ await asyncio .sleep (5 )
1465+
1466+ return None
1467+ else :
1468+ _LOGGER .info (f"{ self .vli } req_preferred_charge_times(): status_code: { response_pct .status } - { response_pct .real_url } - Received response: { await response_pct .text ()} " )
1469+ self ._HAS_COM_ERROR = True
1470+ return None
1471+
1472+ except BaseException as e :
1473+ if not await self .__check_for_closed_session (e ):
1474+ _LOGGER .warning (f"{ self .vli } req_preferred_charge_times(): Error while '_request_token' for vehicle { self .vin } - { type (e )} - { e } " )
1475+ else :
1476+ _LOGGER .info (f"{ self .vli } req_preferred_charge_times(): RuntimeError - Session was closed occurred - but a new Session could be generated" )
1477+ self ._HAS_COM_ERROR = True
1478+ return None
1479+
13871480 async def req_energy_transfer_status (self ):
1481+ # this function will only return a valid object if the vehicle is located at a KNOWN charging location
13881482 global _FOUR_NULL_ONE_COUNTER
13891483 try :
13901484 await self .__ensure_valid_tokens ()
@@ -1440,7 +1534,6 @@ async def req_energy_transfer_status(self):
14401534 self ._HAS_COM_ERROR = True
14411535 return None
14421536
1443-
14441537 # ***********************************************************
14451538 # ***********************************************************
14461539 # ***********************************************************
0 commit comments