carconnectivity-connector-seatcupra 0.2a1__py3-none-any.whl → 0.2a3__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.2a1
3
+ Version: 0.2a3
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.5a1
40
+ Requires-Dist: carconnectivity>=0.5a4
41
41
  Requires-Dist: oauthlib~=3.2.2
42
42
  Requires-Dist: requests~=2.32.3
43
43
  Requires-Dist: jwt~=1.3.1
@@ -1,11 +1,11 @@
1
1
  carconnectivity_connectors/seatcupra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- carconnectivity_connectors/seatcupra/_version.py,sha256=nv-ayVPlh7g8avzeIuoqWWMLTrpQh-Tk42DRZnMODN4,508
2
+ carconnectivity_connectors/seatcupra/_version.py,sha256=p4Js672VV1iTEpV8FJbTUFuZfQ85fDWLAcrDlOGj1Is,508
3
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=N2uFvzuxRj1se3157TWtQ80WSkjwh7w462D4E8c4ZlM,110344
8
- carconnectivity_connectors/seatcupra/vehicle.py,sha256=s0G-HqG5qcwStDxD3649KgLMa3lKPZ4TOGWRJEuQzsQ,3403
7
+ carconnectivity_connectors/seatcupra/connector.py,sha256=h2sJEET4S91zgNUTGXW1FcTB22kOW2Gy1FxV9RRpn1I,118558
8
+ carconnectivity_connectors/seatcupra/vehicle.py,sha256=-M_d1Boly5DLJSQT_Zc8R3JJ7Csi_M4kktgGqjTrPAQ,3463
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
11
11
  carconnectivity_connectors/seatcupra/auth/my_cupra_session.py,sha256=VF_9U8fESLkndVaPn2W1ZxZwNr9-ndeaegeTVT5FyYk,13904
@@ -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.2a1.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
18
- carconnectivity_connector_seatcupra-0.2a1.dist-info/METADATA,sha256=-evGY15ib_P4dy75Cnjmm-Q_u8GOqFK6Nk_XGHNMbAs,5473
19
- carconnectivity_connector_seatcupra-0.2a1.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
20
- carconnectivity_connector_seatcupra-0.2a1.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
21
- carconnectivity_connector_seatcupra-0.2a1.dist-info/RECORD,,
17
+ carconnectivity_connector_seatcupra-0.2a3.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
18
+ carconnectivity_connector_seatcupra-0.2a3.dist-info/METADATA,sha256=N3SPjIWL2Wq3WePfjThTnotIRhaEtEySwiajDaQFGhU,5473
19
+ carconnectivity_connector_seatcupra-0.2a3.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
20
+ carconnectivity_connector_seatcupra-0.2a3.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
21
+ carconnectivity_connector_seatcupra-0.2a3.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.2a1'
20
+ __version__ = version = '0.2a3'
21
21
  __version_tuple__ = version_tuple = (0, 2)
@@ -24,13 +24,15 @@ from carconnectivity.drive import GenericDrive, ElectricDrive, CombustionDrive,
24
24
  from carconnectivity.vehicle import GenericVehicle, ElectricVehicle
25
25
  from carconnectivity.attributes import BooleanAttribute, DurationAttribute, GenericAttribute, TemperatureAttribute, EnumAttribute
26
26
  from carconnectivity.units import Temperature
27
- from carconnectivity.command_impl import ClimatizationStartStopCommand, WakeSleepCommand, HonkAndFlashCommand, LockUnlockCommand, ChargingStartStopCommand
27
+ from carconnectivity.command_impl import ClimatizationStartStopCommand, WakeSleepCommand, HonkAndFlashCommand, LockUnlockCommand, ChargingStartStopCommand, \
28
+ WindowHeatingStartStopCommand
28
29
  from carconnectivity.climatization import Climatization
29
30
  from carconnectivity.commands import Commands
30
31
  from carconnectivity.charging import Charging
31
32
  from carconnectivity.charging_connector import ChargingConnector
32
33
  from carconnectivity.position import Position
33
34
  from carconnectivity.enums import ConnectionState
35
+ from carconnectivity.window_heating import WindowHeatings
34
36
 
35
37
  from carconnectivity_connectors.base.connector import BaseConnector
36
38
  from carconnectivity_connectors.seatcupra.auth.session_manager import SessionManager, SessionUser, Service
@@ -423,7 +425,7 @@ class Connector(BaseConnector):
423
425
  if vehicle.capabilities.has_capability('charging', check_status_ok=True):
424
426
  if not isinstance(vehicle, SeatCupraElectricVehicle):
425
427
  LOG.debug('Promoting %s to SeatCupraElectricVehicle object for %s', vehicle.__class__.__name__, vin)
426
- vehicle = SeatCupraElectricVehicle(origin=vehicle)
428
+ vehicle = SeatCupraElectricVehicle(garage=self.car_connectivity.garage, origin=vehicle)
427
429
  self.car_connectivity.garage.replace_vehicle(vin, vehicle)
428
430
  if not vehicle.charging.commands.contains_command('start-stop'):
429
431
  charging_start_stop_command: ChargingStartStopCommand = ChargingStartStopCommand(parent=vehicle.charging.commands)
@@ -687,15 +689,15 @@ class Connector(BaseConnector):
687
689
  has_combustion = True
688
690
  if has_electric and not has_combustion and not isinstance(vehicle, SeatCupraElectricVehicle):
689
691
  LOG.debug('Promoting %s to SeatCupraElectricVehicle object for %s', vehicle.__class__.__name__, vin)
690
- vehicle = SeatCupraElectricVehicle(origin=vehicle)
692
+ vehicle = SeatCupraElectricVehicle(garage=self.car_connectivity.garage, origin=vehicle)
691
693
  self.car_connectivity.garage.replace_vehicle(vin, vehicle)
692
694
  elif has_combustion and not has_electric and not isinstance(vehicle, SeatCupraCombustionVehicle):
693
695
  LOG.debug('Promoting %s to SeatCupraCombustionVehicle object for %s', vehicle.__class__.__name__, vin)
694
- vehicle = SeatCupraCombustionVehicle(origin=vehicle)
696
+ vehicle = SeatCupraCombustionVehicle(garage=self.car_connectivity.garage, origin=vehicle)
695
697
  self.car_connectivity.garage.replace_vehicle(vin, vehicle)
696
698
  elif has_combustion and has_electric and not isinstance(vehicle, SeatCupraHybridVehicle):
697
699
  LOG.debug('Promoting %s to SeatCupraHybridVehicle object for %s', vehicle.__class__.__name__, vin)
698
- vehicle = SeatCupraHybridVehicle(origin=vehicle)
700
+ vehicle = SeatCupraHybridVehicle(garage=self.car_connectivity.garage, origin=vehicle)
699
701
  self.car_connectivity.garage.replace_vehicle(vin, vehicle)
700
702
  if 'services' in vehicle_status_data and vehicle_status_data['services'] is not None:
701
703
  if 'charging' in vehicle_status_data['services'] and vehicle_status_data['services']['charging'] is not None:
@@ -860,21 +862,25 @@ class Connector(BaseConnector):
860
862
  for drive in vehicle.drives.drives.values():
861
863
  if drive.type.enabled and drive.type.value == GenericDrive.Type.ELECTRIC:
862
864
  for range_dict in data['ranges']:
863
- if 'electricRangeKm' in range_dict and range_dict['electricRangeKm'] is not None:
864
- drive.range._set_value(range_dict['electricRangeKm'], unit=Length.KM) # pylint: disable=protected-access
865
+ if 'rangeName' in range_dict and range_dict['rangeName'] is not None and range_dict['rangeName'] == 'electricRangeKm' \
866
+ and 'value' in range_dict and range_dict['value'] is not None:
867
+ drive.range._set_value(range_dict['value'], unit=Length.KM) # pylint: disable=protected-access
865
868
  break
866
869
  elif drive.type.enabled and drive.type.value == GenericDrive.Type.GASOLINE:
867
870
  for range_dict in data['ranges']:
868
- if 'gasolineRangeKm' in range_dict and range_dict['gasolineRangeKm'] is not None:
869
- drive.range._set_value(range_dict['gasolineRangeKm'], unit=Length.KM) # pylint: disable=protected-access
871
+ if 'rangeName' in range_dict and range_dict['rangeName'] is not None and range_dict['rangeName'] == 'gasolineRangeKm' \
872
+ and 'value' in range_dict and range_dict['value'] is not None:
873
+ drive.range._set_value(range_dict['value'], unit=Length.KM) # pylint: disable=protected-access
870
874
  break
871
875
  elif drive.type.enabled and drive.type.value == GenericDrive.Type.DIESEL:
872
876
  for range_dict in data['ranges']:
873
- if 'dieselRangeKm' in range_dict and range_dict['dieselRangeKm'] is not None:
874
- drive.range._set_value(range_dict['dieselRangeKm'], unit=Length.KM) # pylint: disable=protected-access
875
- elif 'adBlueKm' in range_dict and range_dict['adBlueKm'] is not None:
877
+ if 'rangeName' in range_dict and range_dict['rangeName'] is not None and range_dict['rangeName'] == 'dieselRangeKm' \
878
+ and 'value' in range_dict and range_dict['value'] is not None:
879
+ drive.range._set_value(range_dict['value'], unit=Length.KM) # pylint: disable=protected-access
880
+ elif 'rangeName' in range_dict and range_dict['rangeName'] is not None and range_dict['rangeName'] == 'adBlueKm' \
881
+ and 'value' in range_dict and range_dict['value'] is not None:
876
882
  if isinstance(drive, DieselDrive):
877
- drive.adblue_range._set_value(range_dict['adBlueKm'], unit=Length.KM) # pylint: disable=protected-access
883
+ drive.adblue_range._set_value(range_dict['value'], unit=Length.KM) # pylint: disable=protected-access
878
884
  log_extra_keys(LOG_API, f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/ranges', data, {'ranges'})
879
885
  return vehicle
880
886
 
@@ -954,7 +960,57 @@ class Connector(BaseConnector):
954
960
  log_extra_keys(LOG_API, 'climatisationStatus', data['climatisationStatus'], {'carCapturedTimestamp', 'climatisationState'})
955
961
  else:
956
962
  vehicle.climatization.state._set_value(None) # pylint: disable=protected-access
957
- log_extra_keys(LOG_API, 'climatisation', data, {'climatisationStatus'})
963
+ if 'windowHeatingStatus' in data and data['windowHeatingStatus'] is not None:
964
+ window_heating_status: Dict = data['windowHeatingStatus']
965
+ if 'carCapturedTimestamp' not in window_heating_status or window_heating_status['carCapturedTimestamp'] is None:
966
+ raise APIError('Could not fetch vehicle status, carCapturedTimestamp missing')
967
+ captured_at: datetime = robust_time_parse(window_heating_status['carCapturedTimestamp'])
968
+ if 'windowHeatingStatus' in window_heating_status and window_heating_status['windowHeatingStatus'] is not None:
969
+ heating_on: bool = False
970
+ all_heating_invalid: bool = True
971
+ for window_heating in window_heating_status['windowHeatingStatus']:
972
+ if 'windowLocation' in window_heating and window_heating['windowLocation'] is not None:
973
+ window_id = window_heating['windowLocation']
974
+ if window_id in vehicle.window_heatings.windows:
975
+ window: WindowHeatings.WindowHeating = vehicle.window_heatings.windows[window_id]
976
+ else:
977
+ window = WindowHeatings.WindowHeating(window_id=window_id, window_heatings=vehicle.window_heatings)
978
+ vehicle.window_heatings.windows[window_id] = window
979
+ if 'windowHeatingState' in window_heating and window_heating['windowHeatingState'] is not None:
980
+ if window_heating['windowHeatingState'] in [item.value for item in WindowHeatings.HeatingState]:
981
+ window_heating_state: WindowHeatings.HeatingState = WindowHeatings.HeatingState(window_heating['windowHeatingState'])
982
+ if window_heating_state == WindowHeatings.HeatingState.ON:
983
+ heating_on = True
984
+ if window_heating_state in [WindowHeatings.HeatingState.ON,
985
+ WindowHeatings.HeatingState.OFF]:
986
+ all_heating_invalid = False
987
+ window.heating_state._set_value(window_heating_state, measured=captured_at) # pylint: disable=protected-access
988
+ else:
989
+ LOG_API.info('Unknown window heating state %s not in %s', window_heating['windowHeatingState'],
990
+ str(WindowHeatings.HeatingState))
991
+ # pylint: disable-next=protected-access
992
+ window.heating_state._set_value(WindowHeatings.HeatingState.UNKNOWN, measured=captured_at)
993
+ else:
994
+ window.heating_state._set_value(None, measured=captured_at) # pylint: disable=protected-access
995
+ log_extra_keys(LOG_API, 'windowHeatingStatus', window_heating, {'windowLocation', 'windowHeatingState'})
996
+ if all_heating_invalid:
997
+ # pylint: disable-next=protected-access
998
+ vehicle.window_heatings.heating_state._set_value(WindowHeatings.HeatingState.INVALID, measured=captured_at)
999
+ else:
1000
+ if heating_on:
1001
+ # pylint: disable-next=protected-access
1002
+ vehicle.window_heatings.heating_state._set_value(WindowHeatings.HeatingState.ON, measured=captured_at)
1003
+ else:
1004
+ # pylint: disable-next=protected-access
1005
+ vehicle.window_heatings.heating_state._set_value(WindowHeatings.HeatingState.OFF, measured=captured_at)
1006
+ if vehicle.window_heatings is not None and vehicle.window_heatings.commands is not None \
1007
+ and not vehicle.window_heatings.commands.contains_command('start-stop'):
1008
+ start_stop_command = WindowHeatingStartStopCommand(parent=vehicle.window_heatings.commands)
1009
+ start_stop_command._add_on_set_hook(self.__on_window_heating_start_stop) # pylint: disable=protected-access
1010
+ start_stop_command.enabled = True
1011
+ vehicle.window_heatings.commands.add_command(start_stop_command)
1012
+ log_extra_keys(LOG_API, 'windowHeatingStatus', window_heating_status, {'carCapturedTimestamp', 'windowHeatingStatus'})
1013
+ log_extra_keys(LOG_API, 'climatisation', data, {'climatisationStatus', 'windowHeatingStatus'})
958
1014
  url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/{vin}/climatisation/settings'
959
1015
  data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
960
1016
  if data is not None:
@@ -1238,7 +1294,6 @@ class Connector(BaseConnector):
1238
1294
  raise CommandError('VIN in object hierarchy missing')
1239
1295
  if 'command' not in command_arguments:
1240
1296
  raise CommandError('Command argument missing')
1241
- command_dict: Dict = {}
1242
1297
  try:
1243
1298
  if command_arguments['command'] == ChargingStartStopCommand.Command.START:
1244
1299
  url = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/charging/requests/start'
@@ -1559,6 +1614,43 @@ class Connector(BaseConnector):
1559
1614
  raise SetterError(f'Retrying failed: {retry_error}') from retry_error
1560
1615
  return value
1561
1616
 
1617
+ def __on_window_heating_start_stop(self, start_stop_command: WindowHeatingStartStopCommand, command_arguments: Union[str, Dict[str, Any]]) \
1618
+ -> Union[str, Dict[str, Any]]:
1619
+ if start_stop_command.parent is None or start_stop_command.parent.parent is None \
1620
+ or start_stop_command.parent.parent.parent is None or not isinstance(start_stop_command.parent.parent.parent, SeatCupraVehicle):
1621
+ raise CommandError('Object hierarchy is not as expected')
1622
+ if not isinstance(command_arguments, dict):
1623
+ raise CommandError('Command arguments are not a dictionary')
1624
+ vehicle: SeatCupraVehicle = start_stop_command.parent.parent.parent
1625
+ vin: Optional[str] = vehicle.vin.value
1626
+ if vin is None:
1627
+ raise CommandError('VIN in object hierarchy missing')
1628
+ if 'command' not in command_arguments:
1629
+ raise CommandError('Command argument missing')
1630
+ try:
1631
+ if command_arguments['command'] == WindowHeatingStartStopCommand.Command.START:
1632
+ url = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/windowheating/requests/start'
1633
+ command_response: requests.Response = self.session.post(url, allow_redirects=True)
1634
+ elif command_arguments['command'] == WindowHeatingStartStopCommand.Command.STOP:
1635
+ url = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/windowheating/requests/stop'
1636
+ command_response: requests.Response = self.session.post(url, allow_redirects=True)
1637
+ else:
1638
+ raise CommandError(f'Unknown command {command_arguments["command"]}')
1639
+
1640
+ if command_response.status_code not in [requests.codes['ok'], requests.codes['created']]:
1641
+ LOG.error('Could not start/stop window heating (%s: %s)', command_response.status_code, command_response.text)
1642
+ raise CommandError(f'Could not start/stop window heating ({command_response.status_code}: {command_response.text})')
1643
+ except requests.exceptions.ConnectionError as connection_error:
1644
+ raise CommandError(f'Connection error: {connection_error}.'
1645
+ ' If this happens frequently, please check if other applications communicate with the Seat/Cupra server.') from connection_error
1646
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1647
+ raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1648
+ except requests.exceptions.ReadTimeout as timeout_error:
1649
+ raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
1650
+ except requests.exceptions.RetryError as retry_error:
1651
+ raise CommandError(f'Retrying failed: {retry_error}') from retry_error
1652
+ return command_arguments
1653
+
1562
1654
  def get_version(self) -> str:
1563
1655
  return __version__
1564
1656
 
@@ -34,7 +34,7 @@ class SeatCupraVehicle(GenericVehicle): # pylint: disable=too-many-instance-att
34
34
  def __init__(self, vin: Optional[str] = None, garage: Optional[Garage] = None, managing_connector: Optional[BaseConnector] = None,
35
35
  origin: Optional[SeatCupraVehicle] = None) -> None:
36
36
  if origin is not None:
37
- super().__init__(origin=origin)
37
+ super().__init__(garage=garage, origin=origin)
38
38
  self.capabilities: Capabilities = origin.capabilities
39
39
  self.capabilities.parent = self
40
40
  if SUPPORT_IMAGES:
@@ -54,7 +54,7 @@ class SeatCupraElectricVehicle(ElectricVehicle, SeatCupraVehicle):
54
54
  def __init__(self, vin: Optional[str] = None, garage: Optional[Garage] = None, managing_connector: Optional[BaseConnector] = None,
55
55
  origin: Optional[SeatCupraVehicle] = None) -> None:
56
56
  if origin is not None:
57
- super().__init__(origin=origin)
57
+ super().__init__(garage=garage, origin=origin)
58
58
  else:
59
59
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
60
60
 
@@ -66,7 +66,7 @@ class SeatCupraCombustionVehicle(CombustionVehicle, SeatCupraVehicle):
66
66
  def __init__(self, vin: Optional[str] = None, garage: Optional[Garage] = None, managing_connector: Optional[BaseConnector] = None,
67
67
  origin: Optional[SeatCupraVehicle] = None) -> None:
68
68
  if origin is not None:
69
- super().__init__(origin=origin)
69
+ super().__init__(garage=garage, origin=origin)
70
70
  else:
71
71
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
72
72
 
@@ -78,6 +78,6 @@ class SeatCupraHybridVehicle(HybridVehicle, SeatCupraVehicle):
78
78
  def __init__(self, vin: Optional[str] = None, garage: Optional[Garage] = None, managing_connector: Optional[BaseConnector] = None,
79
79
  origin: Optional[SeatCupraVehicle] = None) -> None:
80
80
  if origin is not None:
81
- super().__init__(origin=origin)
81
+ super().__init__(garage=garage, origin=origin)
82
82
  else:
83
83
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)