carconnectivity-connector-seatcupra 0.1a17__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: carconnectivity-connector-seatcupra
3
- Version: 0.1a17
3
+ Version: 0.1.1
4
4
  Summary: CarConnectivity connector for Seat and Cupra services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -37,7 +37,7 @@ Classifier: Topic :: Software Development :: Libraries
37
37
  Requires-Python: >=3.9
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
- Requires-Dist: carconnectivity>=0.4a11
40
+ Requires-Dist: carconnectivity>=0.4
41
41
  Requires-Dist: oauthlib~=3.2.2
42
42
  Requires-Dist: requests~=2.32.3
43
43
  Requires-Dist: jwt~=1.3.1
@@ -54,9 +54,6 @@ Requires-Dist: jwt~=1.3.1
54
54
  [![Donate at PayPal](https://img.shields.io/badge/Donate-PayPal-2997d8)](https://www.paypal.com/donate?hosted_button_id=2BVFF5GJ9SXAJ)
55
55
  [![Sponsor at Github](https://img.shields.io/badge/Sponsor-GitHub-28a745)](https://github.com/sponsors/tillsteinbach)
56
56
 
57
-
58
- ## Due to lack of access to a Cupra car the development of this conenctor is currently stuck. If you want to help me with access to your account, please contact me!
59
-
60
57
  [CarConnectivity](https://github.com/tillsteinbach/CarConnectivity) is a python API to connect to various car services. This connector enables the integration of seat and cupra vehicles through the MyCupra API. Look at [CarConnectivity](https://github.com/tillsteinbach/CarConnectivity) for other supported brands.
61
58
 
62
59
  ## Configuration
@@ -1,10 +1,10 @@
1
1
  carconnectivity_connectors/seatcupra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- carconnectivity_connectors/seatcupra/_version.py,sha256=h6qKff23EXoCChHlB_Nct7DIvHThmGCDSsQuZQK2zzA,509
3
- carconnectivity_connectors/seatcupra/capability.py,sha256=Oe9tC_u69bj6VmOuNJ21RKoETe2j3QyZCoz-VgcZPQ0,4523
2
+ carconnectivity_connectors/seatcupra/_version.py,sha256=Mmxse1R0ki5tjz9qzU8AQyqUsLt8nTyCAbYQp8R87PU,511
3
+ carconnectivity_connectors/seatcupra/capability.py,sha256=936V06hOX8AuAMxL_S9wVyVa36Xw1bo9081X0xf5f94,5064
4
4
  carconnectivity_connectors/seatcupra/charging.py,sha256=BJe_5GEB0JkP78tpU6kyKpwuwjDZHvm-kt3PTlpQHeU,3336
5
5
  carconnectivity_connectors/seatcupra/climatization.py,sha256=0xxWlxrheAPzkVT8WRQtbm6ExZmVdgW7lUdOXyS_qWY,1695
6
6
  carconnectivity_connectors/seatcupra/command_impl.py,sha256=LmBOCWGZPfJCG_4-5449xvO6NAvnPDsAWEBKlsG4WoI,3051
7
- carconnectivity_connectors/seatcupra/connector.py,sha256=cOuQOlnImZlVMOjHbRrm2vo02zlNB4m87uO88subqZk,102253
7
+ carconnectivity_connectors/seatcupra/connector.py,sha256=llxFhVdpnL5MxmHbOej1wMio5tareY3zha0qaFhnPgs,107680
8
8
  carconnectivity_connectors/seatcupra/vehicle.py,sha256=s0G-HqG5qcwStDxD3649KgLMa3lKPZ4TOGWRJEuQzsQ,3403
9
9
  carconnectivity_connectors/seatcupra/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  carconnectivity_connectors/seatcupra/auth/auth_util.py,sha256=Y81h8fGOMSMgPtE4wI_TI9WgE_s43uaPjRLBBINhj4g,4433
@@ -14,8 +14,8 @@ carconnectivity_connectors/seatcupra/auth/session_manager.py,sha256=ZIDvC848T3fy
14
14
  carconnectivity_connectors/seatcupra/auth/vw_web_session.py,sha256=CcI6m68IyRs6WsMDu-IsW3Dj85vyGiMmxvFqNETMHO0,10929
15
15
  carconnectivity_connectors/seatcupra/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
16
16
  carconnectivity_connectors/seatcupra/ui/connector_ui.py,sha256=SNYnlcGJpbWhuLiIHD2l6H9IfSiMz3IgmvXsdossDnE,1412
17
- carconnectivity_connector_seatcupra-0.1a17.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
18
- carconnectivity_connector_seatcupra-0.1a17.dist-info/METADATA,sha256=Dat_Q7Tw4hiyrVptq1LiL5xLSAGzj2RH56jSJFyxR0c,5642
19
- carconnectivity_connector_seatcupra-0.1a17.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
20
- carconnectivity_connector_seatcupra-0.1a17.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
21
- carconnectivity_connector_seatcupra-0.1a17.dist-info/RECORD,,
17
+ carconnectivity_connector_seatcupra-0.1.1.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
18
+ carconnectivity_connector_seatcupra-0.1.1.dist-info/METADATA,sha256=Vh1baAcurqTyzfkXNO_Ts4GNO8nbFwXgpDS3r8kK2xs,5471
19
+ carconnectivity_connector_seatcupra-0.1.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
20
+ carconnectivity_connector_seatcupra-0.1.1.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
21
+ carconnectivity_connector_seatcupra-0.1.1.dist-info/RECORD,,
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1a17'
21
- __version_tuple__ = version_tuple = (0, 1)
20
+ __version__ = version = '0.1.1'
21
+ __version_tuple__ = version_tuple = (0, 1, 1)
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from enum import IntEnum
6
6
 
7
7
  from carconnectivity.objects import GenericObject
8
- from carconnectivity.attributes import StringAttribute, BooleanAttribute, DateAttribute
8
+ from carconnectivity.attributes import StringAttribute, BooleanAttribute, DateAttribute, GenericAttribute
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from typing import Dict, Optional, List
@@ -77,7 +77,7 @@ class Capabilities(GenericObject):
77
77
  """
78
78
  return self.__capabilities.get(capability_id)
79
79
 
80
- def has_capability(self, capability_id: str) -> bool:
80
+ def has_capability(self, capability_id: str, check_status_ok=False) -> bool:
81
81
  """
82
82
  Check if the Capabilities contains a capability with the specified ID.
83
83
 
@@ -87,7 +87,14 @@ class Capabilities(GenericObject):
87
87
  Returns:
88
88
  bool: True if the capability exists, otherwise False.
89
89
  """
90
- return capability_id in self.__capabilities
90
+ if check_status_ok:
91
+ if capability_id in self.__capabilities and self.__capabilities[capability_id].enabled:
92
+ capability: Capability = self.__capabilities[capability_id]
93
+ if capability.status.enabled and capability.status.value is not None and len(capability.status.value) > 0:
94
+ return False
95
+ return True
96
+ return False
97
+ return capability_id in self.__capabilities and self.__capabilities[capability_id].enabled
91
98
 
92
99
 
93
100
  class Capability(GenericObject):
@@ -105,7 +112,7 @@ class Capability(GenericObject):
105
112
  self.capability_id = StringAttribute("id", self, capability_id, tags={'connector_custom'})
106
113
  self.expiration_date = DateAttribute("expiration_date", self, tags={'connector_custom'})
107
114
  self.editable = BooleanAttribute("editable", self, tags={'connector_custom'})
108
- self.statuses: List[Capability.Status] = []
115
+ self.status = GenericAttribute("status", self, value=[], tags={'connector_custom'})
109
116
  self.parameters: Dict[str, bool] = {}
110
117
  self.enabled = True
111
118
  self.delay_notifications = False
@@ -191,25 +191,34 @@ class Connector(BaseConnector):
191
191
  raise
192
192
  except TooManyRequestsError as err:
193
193
  LOG.error('Retrieval error during update. Too many requests from your account (%s). Will try again after 15 minutes', str(err))
194
+ self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
194
195
  self._stop_event.wait(900)
195
196
  except RetrievalError as err:
196
197
  LOG.error('Retrieval error during update (%s). Will try again after configured interval of %ss', str(err), interval)
198
+ self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
197
199
  self._stop_event.wait(interval)
198
200
  except APIError as err:
199
201
  LOG.error('API error during update (%s). Will try again after configured interval of %ss', str(err), interval)
202
+ self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
200
203
  self._stop_event.wait(interval)
201
204
  except APICompatibilityError as err:
202
205
  LOG.error('API compatability error during update (%s). Will try again after configured interval of %ss', str(err), interval)
206
+ self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
203
207
  self._stop_event.wait(interval)
204
208
  except TemporaryAuthenticationError as err:
205
209
  LOG.error('Temporary authentification error during update (%s). Will try again after configured interval of %ss', str(err), interval)
210
+ self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
206
211
  self._stop_event.wait(interval)
207
212
  except Exception as err:
208
213
  LOG.critical('Critical error during update: %s', traceback.format_exc())
214
+ self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
209
215
  self.healthy._set_value(value=False) # pylint: disable=protected-access
210
216
  raise err
211
217
  else:
218
+ self.connection_state._set_value(value=ConnectionState.CONNECTED) # pylint: disable=protected-access
212
219
  self._stop_event.wait(interval)
220
+ # When leaving the loop, set the connection state to disconnected
221
+ self.connection_state._set_value(value=ConnectionState.DISCONNECTED) # pylint: disable=protected-access
213
222
 
214
223
  def persist(self) -> None:
215
224
  """
@@ -279,16 +288,32 @@ class Connector(BaseConnector):
279
288
  vehicle_to_update = self.fetch_vehicle_status(vehicle_to_update)
280
289
  vehicle_to_update = self.fetch_vehicle_mycar_status(vehicle_to_update)
281
290
  vehicle_to_update = self.fetch_mileage(vehicle_to_update)
282
- if vehicle_to_update.capabilities.has_capability('climatisation'):
291
+ if vehicle_to_update.capabilities.has_capability('climatisation', check_status_ok=True):
283
292
  vehicle_to_update = self.fetch_climatisation(vehicle_to_update)
284
- if vehicle_to_update.capabilities.has_capability('charging'):
293
+ if vehicle_to_update.capabilities.has_capability('charging', check_status_ok=True):
285
294
  vehicle_to_update = self.fetch_charging(vehicle_to_update)
286
- if vehicle_to_update.capabilities.has_capability('parkingPosition'):
295
+ if vehicle_to_update.capabilities.has_capability('parkingPosition', check_status_ok=True):
287
296
  vehicle_to_update = self.fetch_parking_position(vehicle_to_update)
288
- if vehicle_to_update.capabilities.has_capability('vehicleHealthInspection'):
297
+ if vehicle_to_update.capabilities.has_capability('vehicleHealthInspection', check_status_ok=True):
289
298
  vehicle_to_update = self.fetch_maintenance(vehicle_to_update)
299
+ vehicle_to_update = self.fetch_connection_status(vehicle_to_update)
300
+ self.decide_state(vehicle_to_update)
290
301
  self.car_connectivity.transaction_end()
291
302
 
303
+ def decide_state(self, vehicle: SeatCupraVehicle) -> None:
304
+ """
305
+ Decides the state of the vehicle based on the current data.
306
+
307
+ Args:
308
+ vehicle (SeatCupraVehicle): The SeatCupra vehicle object.
309
+ """
310
+ if vehicle is not None:
311
+ if vehicle.position is not None and vehicle.position.enabled and vehicle.position.position_type is not None \
312
+ and vehicle.position.position_type.enabled and vehicle.position.position_type.value == Position.PositionType.PARKING:
313
+ vehicle.state._set_value(GenericVehicle.State.PARKED) # pylint: disable=protected-access
314
+ else:
315
+ vehicle.state._set_value(None) # pylint: disable=protected-access
316
+
292
317
  def fetch_vehicles(self) -> None:
293
318
  """
294
319
  Fetches the list of vehicles from the Seat/Cupra Connect API and updates the garage with new vehicles.
@@ -360,6 +385,19 @@ class Connector(BaseConnector):
360
385
  else:
361
386
  capability = Capability(capability_id=capability_id, capabilities=vehicle.capabilities)
362
387
  vehicle.capabilities.add_capability(capability_id, capability)
388
+ if 'status' in capability_dict and capability_dict['status'] is not None:
389
+ statuses = capability_dict['status']
390
+ if isinstance(statuses, list):
391
+ for status in statuses:
392
+ if status in [item.value for item in Capability.Status]:
393
+ capability.status.value.append(Capability.Status(status))
394
+ else:
395
+ LOG_API.warning('Capability status unkown %s', status)
396
+ capability.status.value.append(Capability.Status.UNKNOWN)
397
+ else:
398
+ LOG_API.warning('Capability status not a list in %s', statuses)
399
+ else:
400
+ capability.status.value.clear()
363
401
  if 'expirationDate' in capability_dict and capability_dict['expirationDate'] is not None \
364
402
  and capability_dict['expirationDate'] != '':
365
403
  expiration_date: datetime = robust_time_parse(capability_dict['expirationDate'])
@@ -381,7 +419,7 @@ class Connector(BaseConnector):
381
419
  for capability_id in vehicle.capabilities.capabilities.keys() - found_capabilities:
382
420
  vehicle.capabilities.remove_capability(capability_id)
383
421
 
384
- if vehicle.capabilities.has_capability('charging'):
422
+ if vehicle.capabilities.has_capability('charging', check_status_ok=True):
385
423
  if not isinstance(vehicle, SeatCupraElectricVehicle):
386
424
  LOG.debug('Promoting %s to SeatCupraElectricVehicle object for %s', vehicle.__class__.__name__, vin)
387
425
  vehicle = SeatCupraElectricVehicle(origin=vehicle)
@@ -392,7 +430,7 @@ class Connector(BaseConnector):
392
430
  charging_start_stop_command.enabled = True
393
431
  vehicle.charging.commands.add_command(charging_start_stop_command)
394
432
 
395
- if vehicle.capabilities.has_capability('climatisation'):
433
+ if vehicle.capabilities.has_capability('climatisation', check_status_ok=True):
396
434
  if vehicle.climatization is not None and vehicle.climatization.commands is not None \
397
435
  and not vehicle.climatization.commands.contains_command('start-stop'):
398
436
  climatisation_start_stop_command: ClimatizationStartStopCommand = \
@@ -402,7 +440,7 @@ class Connector(BaseConnector):
402
440
  climatisation_start_stop_command.enabled = True
403
441
  vehicle.climatization.commands.add_command(climatisation_start_stop_command)
404
442
 
405
- if vehicle.capabilities.has_capability('vehicleWakeUpTrigger'):
443
+ if vehicle.capabilities.has_capability('vehicleWakeUpTrigger', check_status_ok=True):
406
444
  if vehicle.commands is not None and vehicle.commands.commands is not None \
407
445
  and not vehicle.commands.contains_command('wake-sleep'):
408
446
  wake_sleep_command = WakeSleepCommand(parent=vehicle.commands)
@@ -411,7 +449,7 @@ class Connector(BaseConnector):
411
449
  vehicle.commands.add_command(wake_sleep_command)
412
450
 
413
451
  # Add honkAndFlash command if necessary capabilities are available
414
- if vehicle.capabilities.has_capability('honkAndFlash'):
452
+ if vehicle.capabilities.has_capability('honkAndFlash', check_status_ok=True):
415
453
  if vehicle.commands is not None and vehicle.commands.commands is not None \
416
454
  and not vehicle.commands.contains_command('honk-flash'):
417
455
  honk_flash_command = HonkAndFlashCommand(parent=vehicle.commands, with_duration=True)
@@ -420,7 +458,7 @@ class Connector(BaseConnector):
420
458
  vehicle.commands.add_command(honk_flash_command)
421
459
 
422
460
  # Add lock and unlock command
423
- if vehicle.capabilities.has_capability('access'):
461
+ if vehicle.capabilities.has_capability('access', check_status_ok=True):
424
462
  if vehicle.doors is not None and vehicle.doors.commands is not None and vehicle.doors.commands.commands is not None \
425
463
  and not vehicle.doors.commands.contains_command('lock-unlock'):
426
464
  lock_unlock_command = LockUnlockCommand(parent=vehicle.doors.commands)
@@ -701,6 +739,43 @@ class Connector(BaseConnector):
701
739
  'remainingTime'})
702
740
  return vehicle
703
741
 
742
+ def fetch_connection_status(self, vehicle: SeatCupraVehicle, no_cache: bool = False) -> SeatCupraVehicle:
743
+ """
744
+ Fetches the connection status of the given Seat/Cupra vehicle and updates its connection attributes.
745
+
746
+ Args:
747
+ vehicle (SeatCupraVehicle): The Seat/Cupra vehicle object containing the VIN and connection attributes.
748
+
749
+ Returns:
750
+ SeatCupraVehicle: The updated Seat/Cupra vehicle object with the fetched connection data.
751
+
752
+ Raises:
753
+ APIError: If the VIN is missing.
754
+ ValueError: If the vehicle has no connection object.
755
+ """
756
+ vin = vehicle.vin.value
757
+ if vin is None:
758
+ raise APIError('VIN is missing')
759
+ url = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/connection'
760
+ data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
761
+ # {'connection': {'mode': 'online'}
762
+ if data is not None:
763
+ if 'connection' in data and data['connection'] is not None:
764
+ if 'mode' in data['connection'] and data['connection']['mode'] is not None:
765
+ if data['connection']['mode'] in [item.value for item in GenericVehicle.ConnectionState]:
766
+ connection_state: GenericVehicle.ConnectionState = GenericVehicle.ConnectionState(data['connection']['mode'])
767
+ vehicle.connection_state._set_value(connection_state) # pylint: disable=protected-access
768
+ else:
769
+ vehicle.connection_state._set_value(GenericVehicle.ConnectionState.UNKNOWN) # pylint: disable=protected-access
770
+ LOG_API.info('Unknown connection state %s', data['connection']['mode'])
771
+ else:
772
+ vehicle.connection_state._set_value(None) # pylint: disable=protected-access
773
+ log_extra_keys(LOG_API, 'connection status', data['connection'], {'mode'})
774
+ else:
775
+ vehicle.connection_state._set_value(None) # pylint: disable=protected-access
776
+ log_extra_keys(LOG_API, 'connection status', data, {'connection'})
777
+ return vehicle
778
+
704
779
  def fetch_parking_position(self, vehicle: SeatCupraVehicle, no_cache: bool = False) -> SeatCupraVehicle:
705
780
  """
706
781
  Fetches the position of the given vehicle and updates its position attributes.