carconnectivity-connector-seatcupra 0.1a18__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.1a18
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=AzwM9w1dt-7j68mrwryycYGn4KhF1VZVvPVM1rH_oUo,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=BIt3jaOgStNZHqKPk7NQ-S4B7bUOkl-tEtuDx2zG1jA,103242
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.1a18.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
18
- carconnectivity_connector_seatcupra-0.1a18.dist-info/METADATA,sha256=D_FAa_WnsSe_1hHGfMKcMKqVsW2f3ibtubdFyv_uX1c,5642
19
- carconnectivity_connector_seatcupra-0.1a18.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
20
- carconnectivity_connector_seatcupra-0.1a18.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
21
- carconnectivity_connector_seatcupra-0.1a18.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.1a18'
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
@@ -288,16 +288,32 @@ class Connector(BaseConnector):
288
288
  vehicle_to_update = self.fetch_vehicle_status(vehicle_to_update)
289
289
  vehicle_to_update = self.fetch_vehicle_mycar_status(vehicle_to_update)
290
290
  vehicle_to_update = self.fetch_mileage(vehicle_to_update)
291
- if vehicle_to_update.capabilities.has_capability('climatisation'):
291
+ if vehicle_to_update.capabilities.has_capability('climatisation', check_status_ok=True):
292
292
  vehicle_to_update = self.fetch_climatisation(vehicle_to_update)
293
- if vehicle_to_update.capabilities.has_capability('charging'):
293
+ if vehicle_to_update.capabilities.has_capability('charging', check_status_ok=True):
294
294
  vehicle_to_update = self.fetch_charging(vehicle_to_update)
295
- if vehicle_to_update.capabilities.has_capability('parkingPosition'):
295
+ if vehicle_to_update.capabilities.has_capability('parkingPosition', check_status_ok=True):
296
296
  vehicle_to_update = self.fetch_parking_position(vehicle_to_update)
297
- if vehicle_to_update.capabilities.has_capability('vehicleHealthInspection'):
297
+ if vehicle_to_update.capabilities.has_capability('vehicleHealthInspection', check_status_ok=True):
298
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)
299
301
  self.car_connectivity.transaction_end()
300
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
+
301
317
  def fetch_vehicles(self) -> None:
302
318
  """
303
319
  Fetches the list of vehicles from the Seat/Cupra Connect API and updates the garage with new vehicles.
@@ -369,6 +385,19 @@ class Connector(BaseConnector):
369
385
  else:
370
386
  capability = Capability(capability_id=capability_id, capabilities=vehicle.capabilities)
371
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()
372
401
  if 'expirationDate' in capability_dict and capability_dict['expirationDate'] is not None \
373
402
  and capability_dict['expirationDate'] != '':
374
403
  expiration_date: datetime = robust_time_parse(capability_dict['expirationDate'])
@@ -390,7 +419,7 @@ class Connector(BaseConnector):
390
419
  for capability_id in vehicle.capabilities.capabilities.keys() - found_capabilities:
391
420
  vehicle.capabilities.remove_capability(capability_id)
392
421
 
393
- if vehicle.capabilities.has_capability('charging'):
422
+ if vehicle.capabilities.has_capability('charging', check_status_ok=True):
394
423
  if not isinstance(vehicle, SeatCupraElectricVehicle):
395
424
  LOG.debug('Promoting %s to SeatCupraElectricVehicle object for %s', vehicle.__class__.__name__, vin)
396
425
  vehicle = SeatCupraElectricVehicle(origin=vehicle)
@@ -401,7 +430,7 @@ class Connector(BaseConnector):
401
430
  charging_start_stop_command.enabled = True
402
431
  vehicle.charging.commands.add_command(charging_start_stop_command)
403
432
 
404
- if vehicle.capabilities.has_capability('climatisation'):
433
+ if vehicle.capabilities.has_capability('climatisation', check_status_ok=True):
405
434
  if vehicle.climatization is not None and vehicle.climatization.commands is not None \
406
435
  and not vehicle.climatization.commands.contains_command('start-stop'):
407
436
  climatisation_start_stop_command: ClimatizationStartStopCommand = \
@@ -411,7 +440,7 @@ class Connector(BaseConnector):
411
440
  climatisation_start_stop_command.enabled = True
412
441
  vehicle.climatization.commands.add_command(climatisation_start_stop_command)
413
442
 
414
- if vehicle.capabilities.has_capability('vehicleWakeUpTrigger'):
443
+ if vehicle.capabilities.has_capability('vehicleWakeUpTrigger', check_status_ok=True):
415
444
  if vehicle.commands is not None and vehicle.commands.commands is not None \
416
445
  and not vehicle.commands.contains_command('wake-sleep'):
417
446
  wake_sleep_command = WakeSleepCommand(parent=vehicle.commands)
@@ -420,7 +449,7 @@ class Connector(BaseConnector):
420
449
  vehicle.commands.add_command(wake_sleep_command)
421
450
 
422
451
  # Add honkAndFlash command if necessary capabilities are available
423
- if vehicle.capabilities.has_capability('honkAndFlash'):
452
+ if vehicle.capabilities.has_capability('honkAndFlash', check_status_ok=True):
424
453
  if vehicle.commands is not None and vehicle.commands.commands is not None \
425
454
  and not vehicle.commands.contains_command('honk-flash'):
426
455
  honk_flash_command = HonkAndFlashCommand(parent=vehicle.commands, with_duration=True)
@@ -429,7 +458,7 @@ class Connector(BaseConnector):
429
458
  vehicle.commands.add_command(honk_flash_command)
430
459
 
431
460
  # Add lock and unlock command
432
- if vehicle.capabilities.has_capability('access'):
461
+ if vehicle.capabilities.has_capability('access', check_status_ok=True):
433
462
  if vehicle.doors is not None and vehicle.doors.commands is not None and vehicle.doors.commands.commands is not None \
434
463
  and not vehicle.doors.commands.contains_command('lock-unlock'):
435
464
  lock_unlock_command = LockUnlockCommand(parent=vehicle.doors.commands)
@@ -710,6 +739,43 @@ class Connector(BaseConnector):
710
739
  'remainingTime'})
711
740
  return vehicle
712
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
+
713
779
  def fetch_parking_position(self, vehicle: SeatCupraVehicle, no_cache: bool = False) -> SeatCupraVehicle:
714
780
  """
715
781
  Fetches the position of the given vehicle and updates its position attributes.