carconnectivity-connector-skoda 0.4a2__py3-none-any.whl → 0.4a4__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.

Potentially problematic release.


This version of carconnectivity-connector-skoda might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: carconnectivity-connector-skoda
3
- Version: 0.4a2
3
+ Version: 0.4a4
4
4
  Summary: CarConnectivity connector for Skoda 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.8
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
- Requires-Dist: carconnectivity>=0.4a4
40
+ Requires-Dist: carconnectivity>=0.4a6
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,23 +1,23 @@
1
1
  carconnectivity_connectors/skoda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- carconnectivity_connectors/skoda/_version.py,sha256=k0XQJjsv6zABE1_Xxh5_DMFT99-ORJxNLpqt6IuzIT0,508
2
+ carconnectivity_connectors/skoda/_version.py,sha256=aPSfkGMKPO_uUo6Bnojoe6ufdjZuk1DWHeQpLhElsN8,508
3
3
  carconnectivity_connectors/skoda/capability.py,sha256=vbAKK8KKre-CndLF6_5qyWLpfa4KZHk1U-hpb6nCL5w,4225
4
4
  carconnectivity_connectors/skoda/charging.py,sha256=CoUOYHHUPPPldKQvv0h-qrUsoEtstR3iUx-l0IU5rNM,6798
5
5
  carconnectivity_connectors/skoda/climatization.py,sha256=Jut468SkxjPBDTqroWFvDifVPfJBxGjsFed5pc4kZkg,1768
6
6
  carconnectivity_connectors/skoda/command_impl.py,sha256=WdgxWPgi82-UgmyFpiSZE-KHRtRjqn7CH-YX9N3bAoI,2875
7
- carconnectivity_connectors/skoda/connector.py,sha256=7nrQiB1njFPgUYQx-ryrEyJXARSopwC8WPWQabmL2jc,114063
7
+ carconnectivity_connectors/skoda/connector.py,sha256=oYgAfDg2ku6307Geb1xXWxZMAP72Fnx-k70d6P6Ie7o,125479
8
8
  carconnectivity_connectors/skoda/error.py,sha256=ffxzvjmci7vtp9Q1K4DR1kBF0kTJxN5Gluci3kDBkEI,2459
9
9
  carconnectivity_connectors/skoda/mqtt_client.py,sha256=PHvMkNhmkP_FxHvVlXzFLJA6Q3vkFCK8jZHkfII_j74,38123
10
- carconnectivity_connectors/skoda/vehicle.py,sha256=OhB0MUsCnaqQCnWXBlf_RFUUc9F4W8Gtk-30WYEN6nk,3554
10
+ carconnectivity_connectors/skoda/vehicle.py,sha256=TeY3qKWbBfxFxt6UzSDrB-YZ4L8GURAvINzBiFm9Y9E,3819
11
11
  carconnectivity_connectors/skoda/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  carconnectivity_connectors/skoda/auth/auth_util.py,sha256=dGLUbUre0HBsTg_Ii5vW34f8DLrCykYJYCyzEvUBBEE,4434
13
13
  carconnectivity_connectors/skoda/auth/my_skoda_session.py,sha256=lSh23SFJs8opjmPwHTv-KNIKDep_WY4aItSP4Zq7bT8,10396
14
14
  carconnectivity_connectors/skoda/auth/openid_session.py,sha256=5JfR-gS1uKpE8DD-sx5Qvw6zv-OJhzcRlt0D-cm38-Y,16832
15
15
  carconnectivity_connectors/skoda/auth/session_manager.py,sha256=Uf1vujuDBYUCAXhYToOsZkgbTtfmY3Qe0ICTfwomBpI,2899
16
- carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=cjzMkzx473Sh-4RgZAQULeRRcxB1MboddldCVM_y5LE,10640
16
+ carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=tapjCRRPBu3tHrDoKmtuAlQhgxktib3oWTB8MHEzZTY,10816
17
17
  carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
18
18
  carconnectivity_connectors/skoda/ui/connector_ui.py,sha256=2Gywhyki52IxIZXV6DhWhzrBLn2293LlUMhK1Rxnw9w,1376
19
- carconnectivity_connector_skoda-0.4a2.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
20
- carconnectivity_connector_skoda-0.4a2.dist-info/METADATA,sha256=nOT8M1dofmrvCcxQtB0I3xG_RdqtojQ5nf06jzwHu0w,5365
21
- carconnectivity_connector_skoda-0.4a2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
22
- carconnectivity_connector_skoda-0.4a2.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
23
- carconnectivity_connector_skoda-0.4a2.dist-info/RECORD,,
19
+ carconnectivity_connector_skoda-0.4a4.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
20
+ carconnectivity_connector_skoda-0.4a4.dist-info/METADATA,sha256=EewPAyt3-5S7A48_1-xOewJpGgPPpLe8VHo7JbkLXhI,5365
21
+ carconnectivity_connector_skoda-0.4a4.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
22
+ carconnectivity_connector_skoda-0.4a4.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
23
+ carconnectivity_connector_skoda-0.4a4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.4a2'
20
+ __version__ = version = '0.4a4'
21
21
  __version_tuple__ = version_tuple = (0, 4)
@@ -121,6 +121,8 @@ class SkodaWebSession(OpenIDSession):
121
121
  raise RetrievalError('Temporary server error during login')
122
122
 
123
123
  if 'Location' not in response.headers:
124
+ if 'consent' in url:
125
+ raise AuthenticationError('Could not find Location in headers, probably due to missing consent. Try visiting: ' + url)
124
126
  raise APICompatibilityError('Forwarding without Location in headers')
125
127
 
126
128
  url = response.headers['Location']
@@ -22,7 +22,7 @@ from carconnectivity.doors import Doors
22
22
  from carconnectivity.windows import Windows
23
23
  from carconnectivity.lights import Lights
24
24
  from carconnectivity.drive import GenericDrive, ElectricDrive, CombustionDrive
25
- from carconnectivity.attributes import BooleanAttribute, DurationAttribute, TemperatureAttribute
25
+ from carconnectivity.attributes import GenericAttribute, BooleanAttribute, DurationAttribute, TemperatureAttribute
26
26
  from carconnectivity.charging import Charging
27
27
  from carconnectivity.position import Position
28
28
  from carconnectivity.climatization import Climatization
@@ -81,6 +81,16 @@ class Connector(BaseConnector):
81
81
 
82
82
  self.connected: BooleanAttribute = BooleanAttribute(name="connected", parent=self, tags={'connector_custom'})
83
83
  self.interval: DurationAttribute = DurationAttribute(name="interval", parent=self, tags={'connector_custom'})
84
+
85
+ def __check_interval(attribute: GenericAttribute, value: Any) -> Any:
86
+ del attribute
87
+ if value is not None and value < timedelta(seconds=180):
88
+ raise ValueError('Intervall must be at least 180 seconds')
89
+ return value
90
+
91
+ self.interval._is_changeable = True # pylint: disable=protected-access
92
+ self.interval._add_on_set_hook(__check_interval) # pylint: disable=protected-access
93
+
84
94
  self.commands: Commands = Commands(parent=self)
85
95
 
86
96
  self.user_id: Optional[str] = None
@@ -154,9 +164,11 @@ class Connector(BaseConnector):
154
164
  self._stop_event.clear()
155
165
  # Start background thread for Rest API polling
156
166
  self._background_thread = threading.Thread(target=self._background_loop, daemon=False)
167
+ self._background_thread.name = 'carconnectivity.connectors.skoda-background'
157
168
  self._background_thread.start()
158
169
  # Start background thread for MQTT connection
159
170
  self._background_connect_thread = threading.Thread(target=self._background_connect_loop, daemon=False)
171
+ self._background_connect_thread.name = 'carconnectivity.connectors.skoda-background_connect'
160
172
  self._background_connect_thread.start()
161
173
  # Start MQTT thread
162
174
  self._mqtt_client.loop_start()
@@ -236,12 +248,12 @@ class Connector(BaseConnector):
236
248
  self.car_connectivity.garage.remove_vehicle(vehicle.id)
237
249
  vehicle.enabled = False
238
250
  self._stop_event.set()
251
+ self.session.close()
239
252
  if self._background_thread is not None:
240
253
  self._background_thread.join()
241
254
  if self._background_connect_thread is not None:
242
255
  self._background_connect_thread.join()
243
256
  self.persist()
244
- self.session.close()
245
257
  return super().shutdown()
246
258
 
247
259
  def fetch_all(self) -> None:
@@ -339,6 +351,8 @@ class Connector(BaseConnector):
339
351
  vehicle_to_update = self.fetch_vehicle_status(vehicle_to_update)
340
352
  vehicle_to_update = self.fetch_driving_range(vehicle_to_update)
341
353
  if vehicle_to_update.capabilities is not None and vehicle_to_update.capabilities.enabled:
354
+ if vehicle_to_update.capabilities.has_capability('READINESS'):
355
+ vehicle_to_update = self.fetch_connection_status(vehicle_to_update)
342
356
  if vehicle_to_update.capabilities.has_capability('PARKING_POSITION'):
343
357
  vehicle_to_update = self.fetch_position(vehicle_to_update)
344
358
  if vehicle_to_update.capabilities.has_capability('CHARGING') and isinstance(vehicle_to_update, SkodaElectricVehicle):
@@ -347,6 +361,27 @@ class Connector(BaseConnector):
347
361
  vehicle_to_update = self.fetch_air_conditioning(vehicle_to_update)
348
362
  if vehicle_to_update.capabilities.has_capability('VEHICLE_HEALTH_INSPECTION'):
349
363
  vehicle_to_update = self.fetch_maintenance(vehicle_to_update)
364
+ vehicle_to_update = self.decide_state(vehicle_to_update)
365
+
366
+ def decide_state(self, vehicle: SkodaVehicle) -> SkodaVehicle:
367
+ """
368
+ Decides the state of the vehicle based on the current data.
369
+
370
+ Args:
371
+ vehicle (SkodaVehicle): The Skoda vehicle object.
372
+
373
+ Returns:
374
+ SkodaVehicle: The Skoda vehicle object with the updated state.
375
+ """
376
+ if vehicle is not None:
377
+ if vehicle.in_motion is not None and vehicle.in_motion.enabled and vehicle.in_motion.value:
378
+ vehicle.state._set_value(GenericVehicle.State.IGNITION_ON) # pylint: disable=protected-access
379
+ elif vehicle.position is not None and vehicle.position.enabled and vehicle.position.position_type is not None \
380
+ and vehicle.position.position_type.enabled and vehicle.position.position_type.value == Position.PositionType.PARKING:
381
+ vehicle.state._set_value(GenericVehicle.State.PARKED) # pylint: disable=protected-access
382
+ else:
383
+ vehicle.state._set_value(None) # pylint: disable=protected-access
384
+ return vehicle
350
385
 
351
386
  def fetch_charging(self, vehicle: SkodaElectricVehicle, no_cache: bool = False) -> SkodaElectricVehicle:
352
387
  """
@@ -555,6 +590,41 @@ class Connector(BaseConnector):
555
590
  vehicle.charging.errors.clear()
556
591
  log_extra_keys(LOG_API, 'charging data', data, {'carCapturedTimestamp', 'status', 'isVehicleInSavedLocation', 'errors', 'settings'})
557
592
  return vehicle
593
+
594
+ def fetch_connection_status(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
595
+ """
596
+ Fetches the connection status of the given Skoda vehicle and updates its connection attributes.
597
+
598
+ Args:
599
+ vehicle (SkodaVehicle): The Skoda vehicle object containing the VIN and connection attributes.
600
+
601
+ Returns:
602
+ SkodaVehicle: The updated Skoda vehicle object with the fetched connection data.
603
+
604
+ Raises:
605
+ APIError: If the VIN is missing.
606
+ ValueError: If the vehicle has no connection object.
607
+ """
608
+ vin = vehicle.vin.value
609
+ if vin is None:
610
+ raise APIError('VIN is missing')
611
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/connection-status/{vin}/readiness'
612
+ data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
613
+ # {'unreachable': False, 'inMotion': False, 'batteryProtectionLimitOn': False}
614
+ if data is not None:
615
+ if 'unreachable' in data and data['unreachable'] is not None:
616
+ if data['unreachable']:
617
+ vehicle.connection_state._set_value(vehicle.ConnectionState.OFFLINE) # pylint: disable=protected-access
618
+ else:
619
+ vehicle.connection_state._set_value(vehicle.ConnectionState.REACHABLE) # pylint: disable=protected-access
620
+ else:
621
+ vehicle.connection_state._set_value(None) # pylint: disable=protected-access
622
+ if 'inMotion' in data and data['inMotion'] is not None:
623
+ vehicle.in_motion._set_value(data['inMotion']) # pylint: disable=protected-access
624
+ else:
625
+ vehicle.in_motion._set_value(None) # pylint: disable=protected-access
626
+ log_extra_keys(LOG_API, 'connection status', data, {'unreachable'})
627
+ return vehicle
558
628
 
559
629
  def fetch_position(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
560
630
  """
@@ -1336,10 +1406,20 @@ class Connector(BaseConnector):
1336
1406
  raise SetterError(f'Unknown temperature unit {temperature_attribute.unit}')
1337
1407
 
1338
1408
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/target-temperature'
1339
- settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1340
- if settings_response.status_code != requests.codes['accepted']:
1341
- LOG.error('Could not set target temperature (%s)', settings_response.status_code)
1342
- raise SetterError(f'Could not set value ({settings_response.status_code})')
1409
+ try:
1410
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1411
+ if settings_response.status_code != requests.codes['accepted']:
1412
+ LOG.error('Could not set target temperature (%s)', settings_response.status_code)
1413
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1414
+ except requests.exceptions.ConnectionError as connection_error:
1415
+ raise SetterError(f'Connection error: {connection_error}.'
1416
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1417
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1418
+ raise SetterError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1419
+ except requests.exceptions.ReadTimeout as timeout_error:
1420
+ raise SetterError(f'Timeout during read: {timeout_error}') from timeout_error
1421
+ except requests.exceptions.RetryError as retry_error:
1422
+ raise SetterError(f'Retrying failed: {retry_error}') from retry_error
1343
1423
  return target_temperature
1344
1424
 
1345
1425
  def __on_air_conditioning_at_unlock_change(self, at_unlock_attribute: BooleanAttribute, at_unlock_value: bool) -> bool:
@@ -1355,10 +1435,20 @@ class Connector(BaseConnector):
1355
1435
  setting_dict['airConditioningAtUnlockEnabled'] = at_unlock_value
1356
1436
 
1357
1437
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/ac-at-unlock'
1358
- settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1359
- if settings_response.status_code != requests.codes['accepted']:
1360
- LOG.error('Could not set air conditioning at unlock (%s)', settings_response.status_code)
1361
- raise SetterError(f'Could not set value ({settings_response.status_code})')
1438
+ try:
1439
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1440
+ if settings_response.status_code != requests.codes['accepted']:
1441
+ LOG.error('Could not set air conditioning at unlock (%s)', settings_response.status_code)
1442
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1443
+ except requests.exceptions.ConnectionError as connection_error:
1444
+ raise SetterError(f'Connection error: {connection_error}.'
1445
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1446
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1447
+ raise SetterError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1448
+ except requests.exceptions.ReadTimeout as timeout_error:
1449
+ raise SetterError(f'Timeout during read: {timeout_error}') from timeout_error
1450
+ except requests.exceptions.RetryError as retry_error:
1451
+ raise SetterError(f'Retrying failed: {retry_error}') from retry_error
1362
1452
  return at_unlock_value
1363
1453
 
1364
1454
  def __on_air_conditioning_window_heating_change(self, window_heating_attribute: BooleanAttribute, window_heating_value: bool) -> bool:
@@ -1374,10 +1464,20 @@ class Connector(BaseConnector):
1374
1464
  setting_dict['windowHeatingEnabled'] = window_heating_value
1375
1465
 
1376
1466
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/ac-at-unlock'
1377
- settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1378
- if settings_response.status_code != requests.codes['accepted']:
1379
- LOG.error('Could not set air conditioning window heating (%s)', settings_response.status_code)
1380
- raise SetterError(f'Could not set value ({settings_response.status_code})')
1467
+ try:
1468
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1469
+ if settings_response.status_code != requests.codes['accepted']:
1470
+ LOG.error('Could not set air conditioning window heating (%s)', settings_response.status_code)
1471
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1472
+ except requests.exceptions.ConnectionError as connection_error:
1473
+ raise SetterError(f'Connection error: {connection_error}.'
1474
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1475
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1476
+ raise SetterError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1477
+ except requests.exceptions.ReadTimeout as timeout_error:
1478
+ raise SetterError(f'Timeout during read: {timeout_error}') from timeout_error
1479
+ except requests.exceptions.RetryError as retry_error:
1480
+ raise SetterError(f'Retrying failed: {retry_error}') from retry_error
1381
1481
  return window_heating_value
1382
1482
 
1383
1483
  def __on_air_conditioning_start_stop(self, start_stop_command: ClimatizationStartStopCommand, command_arguments: Union[str, Dict[str, Any]]) \
@@ -1394,53 +1494,63 @@ class Connector(BaseConnector):
1394
1494
  if 'command' not in command_arguments:
1395
1495
  raise CommandError('Command argument missing')
1396
1496
  command_dict = {}
1397
- if command_arguments['command'] == ClimatizationStartStopCommand.Command.START:
1398
- command_dict['heaterSource'] = 'ELECTRIC'
1399
- command_dict['targetTemperature'] = {}
1400
- if 'target_temperature' in command_arguments:
1401
- # Round target temperature to nearest 0.5
1402
- command_dict['targetTemperature']['temperatureValue'] = round(command_arguments['target_temperature'] * 2) / 2
1403
- if 'target_temperature_unit' in command_arguments:
1404
- if not isinstance(command_arguments['target_temperature_unit'], Temperature):
1405
- raise CommandError('Temperature unit is not of type Temperature')
1406
- if command_arguments['target_temperature_unit'] == Temperature.C:
1497
+ try:
1498
+ if command_arguments['command'] == ClimatizationStartStopCommand.Command.START:
1499
+ command_dict['heaterSource'] = 'ELECTRIC'
1500
+ command_dict['targetTemperature'] = {}
1501
+ if 'target_temperature' in command_arguments:
1502
+ # Round target temperature to nearest 0.5
1503
+ command_dict['targetTemperature']['temperatureValue'] = round(command_arguments['target_temperature'] * 2) / 2
1504
+ if 'target_temperature_unit' in command_arguments:
1505
+ if not isinstance(command_arguments['target_temperature_unit'], Temperature):
1506
+ raise CommandError('Temperature unit is not of type Temperature')
1507
+ if command_arguments['target_temperature_unit'] == Temperature.C:
1508
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1509
+ elif command_arguments['target_temperature_unit'] == Temperature.F:
1510
+ command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
1511
+ elif command_arguments['target_temperature_unit'] == Temperature.K:
1512
+ command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
1513
+ else:
1514
+ raise CommandError(f'Unknown temperature unit {command_arguments["target_temperature_unit"]}')
1515
+ else:
1516
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1517
+ elif start_stop_command.parent is not None and (climatization := start_stop_command.parent.parent) is not None \
1518
+ and isinstance(climatization, Climatization) and climatization.settings is not None \
1519
+ and climatization.settings.target_temperature is not None and climatization.settings.target_temperature.enabled \
1520
+ and climatization.settings.target_temperature.value is not None: # pylint: disable=too-many-boolean-expressions
1521
+ # Round target temperature to nearest 0.5
1522
+ command_dict['targetTemperature']['temperatureValue'] = round(climatization.settings.target_temperature.value * 2) / 2
1523
+ if climatization.settings.target_temperature.unit == Temperature.C:
1407
1524
  command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1408
- elif command_arguments['target_temperature_unit'] == Temperature.F:
1525
+ elif climatization.settings.target_temperature.unit == Temperature.F:
1409
1526
  command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
1410
- elif command_arguments['target_temperature_unit'] == Temperature.K:
1527
+ elif climatization.settings.target_temperature.unit == Temperature.K:
1411
1528
  command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
1412
1529
  else:
1413
- raise CommandError(f'Unknown temperature unit {command_arguments["target_temperature_unit"]}')
1530
+ raise CommandError(f'Unknown temperature unit {climatization.settings.target_temperature.unit}')
1414
1531
  else:
1532
+ command_dict['targetTemperature']['temperatureValue'] = 25.0
1415
1533
  command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1416
- elif start_stop_command.parent is not None and (climatization := start_stop_command.parent.parent) is not None \
1417
- and isinstance(climatization, Climatization) and climatization.settings is not None \
1418
- and climatization.settings.target_temperature is not None and climatization.settings.target_temperature.enabled \
1419
- and climatization.settings.target_temperature.value is not None: # pylint: disable=too-many-boolean-expressions
1420
- # Round target temperature to nearest 0.5
1421
- command_dict['targetTemperature']['temperatureValue'] = round(climatization.settings.target_temperature.value * 2) / 2
1422
- if climatization.settings.target_temperature.unit == Temperature.C:
1423
- command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1424
- elif climatization.settings.target_temperature.unit == Temperature.F:
1425
- command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
1426
- elif climatization.settings.target_temperature.unit == Temperature.K:
1427
- command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
1428
- else:
1429
- raise CommandError(f'Unknown temperature unit {climatization.settings.target_temperature.unit}')
1534
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/start'
1535
+ command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1536
+ elif command_arguments['command'] == ClimatizationStartStopCommand.Command.STOP:
1537
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/stop'
1538
+ command_response: requests.Response = self.session.post(url, allow_redirects=True)
1430
1539
  else:
1431
- command_dict['targetTemperature']['temperatureValue'] = 25.0
1432
- command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1433
- url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/start'
1434
- command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1435
- elif command_arguments['command'] == ClimatizationStartStopCommand.Command.STOP:
1436
- url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/stop'
1437
- command_response: requests.Response = self.session.post(url, allow_redirects=True)
1438
- else:
1439
- raise CommandError(f'Unknown command {command_arguments["command"]}')
1540
+ raise CommandError(f'Unknown command {command_arguments["command"]}')
1440
1541
 
1441
- if command_response.status_code != requests.codes['accepted']:
1442
- LOG.error('Could not start/stop air conditioning (%s: %s)', command_response.status_code, command_response.text)
1443
- raise CommandError(f'Could not start/stop air conditioning ({command_response.status_code}: {command_response.text})')
1542
+ if command_response.status_code != requests.codes['accepted']:
1543
+ LOG.error('Could not start/stop air conditioning (%s: %s)', command_response.status_code, command_response.text)
1544
+ raise CommandError(f'Could not start/stop air conditioning ({command_response.status_code}: {command_response.text})')
1545
+ except requests.exceptions.ConnectionError as connection_error:
1546
+ raise CommandError(f'Connection error: {connection_error}.'
1547
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1548
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1549
+ raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1550
+ except requests.exceptions.ReadTimeout as timeout_error:
1551
+ raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
1552
+ except requests.exceptions.RetryError as retry_error:
1553
+ raise CommandError(f'Retrying failed: {retry_error}') from retry_error
1444
1554
  return command_arguments
1445
1555
 
1446
1556
  def __on_charging_start_stop(self, start_stop_command: ChargingStartStopCommand, command_arguments: Union[str, Dict[str, Any]]) \
@@ -1456,18 +1566,29 @@ class Connector(BaseConnector):
1456
1566
  raise CommandError('VIN in object hierarchy missing')
1457
1567
  if 'command' not in command_arguments:
1458
1568
  raise CommandError('Command argument missing')
1459
- if command_arguments['command'] == ChargingStartStopCommand.Command.START:
1460
- url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}/start'
1461
- command_response: requests.Response = self.session.post(url, allow_redirects=True)
1462
- elif command_arguments['command'] == ChargingStartStopCommand.Command.STOP:
1463
- url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}/stop'
1464
- command_response: requests.Response = self.session.post(url, allow_redirects=True)
1465
- else:
1466
- raise CommandError(f'Unknown command {command_arguments["command"]}')
1569
+ try:
1570
+ if command_arguments['command'] == ChargingStartStopCommand.Command.START:
1571
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}/start'
1572
+ command_response: requests.Response = self.session.post(url, allow_redirects=True)
1573
+ elif command_arguments['command'] == ChargingStartStopCommand.Command.STOP:
1574
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}/stop'
1575
+
1576
+ command_response: requests.Response = self.session.post(url, allow_redirects=True)
1577
+ else:
1578
+ raise CommandError(f'Unknown command {command_arguments["command"]}')
1467
1579
 
1468
- if command_response.status_code != requests.codes['accepted']:
1469
- LOG.error('Could not start/stop charging (%s: %s)', command_response.status_code, command_response.text)
1470
- raise CommandError(f'Could not start/stop charging ({command_response.status_code}: {command_response.text})')
1580
+ if command_response.status_code != requests.codes['accepted']:
1581
+ LOG.error('Could not start/stop charging (%s: %s)', command_response.status_code, command_response.text)
1582
+ raise CommandError(f'Could not start/stop charging ({command_response.status_code}: {command_response.text})')
1583
+ except requests.exceptions.ConnectionError as connection_error:
1584
+ raise CommandError(f'Connection error: {connection_error}.'
1585
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1586
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1587
+ raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1588
+ except requests.exceptions.ReadTimeout as timeout_error:
1589
+ raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
1590
+ except requests.exceptions.RetryError as retry_error:
1591
+ raise CommandError(f'Retrying failed: {retry_error}') from retry_error
1471
1592
  return command_arguments
1472
1593
 
1473
1594
  def __on_honk_flash(self, honk_flash_command: HonkAndFlashCommand, command_arguments: Union[str, Dict[str, Any]]) \
@@ -1497,10 +1618,20 @@ class Connector(BaseConnector):
1497
1618
  command_dict['vehiclePosition']['longitude'] = vehicle.position.longitude.value
1498
1619
 
1499
1620
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-access/{vin}/honk-and-flash'
1500
- command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1501
- if command_response.status_code != requests.codes['accepted']:
1502
- LOG.error('Could not execute honk or flash command (%s: %s)', command_response.status_code, command_response.text)
1503
- raise CommandError(f'Could not execute honk or flash command ({command_response.status_code}: {command_response.text})')
1621
+ try:
1622
+ command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1623
+ if command_response.status_code != requests.codes['accepted']:
1624
+ LOG.error('Could not execute honk or flash command (%s: %s)', command_response.status_code, command_response.text)
1625
+ raise CommandError(f'Could not execute honk or flash command ({command_response.status_code}: {command_response.text})')
1626
+ except requests.exceptions.ConnectionError as connection_error:
1627
+ raise CommandError(f'Connection error: {connection_error}.'
1628
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1629
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1630
+ raise SetterError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1631
+ except requests.exceptions.ReadTimeout as timeout_error:
1632
+ raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
1633
+ except requests.exceptions.RetryError as retry_error:
1634
+ raise CommandError(f'Retrying failed: {retry_error}') from retry_error
1504
1635
  else:
1505
1636
  raise CommandError(f'Unknown command {command_arguments["command"]}')
1506
1637
  return command_arguments
@@ -1531,10 +1662,20 @@ class Connector(BaseConnector):
1531
1662
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-access/{vin}/unlock'
1532
1663
  else:
1533
1664
  raise CommandError(f'Unknown command {command_arguments["command"]}')
1534
- command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1535
- if command_response.status_code != requests.codes['accepted']:
1536
- LOG.error('Could not execute locking command (%s: %s)', command_response.status_code, command_response.text)
1537
- raise CommandError(f'Could not execute locking command ({command_response.status_code}: {command_response.text})')
1665
+ try:
1666
+ command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1667
+ if command_response.status_code != requests.codes['accepted']:
1668
+ LOG.error('Could not execute locking command (%s: %s)', command_response.status_code, command_response.text)
1669
+ raise CommandError(f'Could not execute locking command ({command_response.status_code}: {command_response.text})')
1670
+ except requests.exceptions.ConnectionError as connection_error:
1671
+ raise CommandError(f'Connection error: {connection_error}.'
1672
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1673
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1674
+ raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1675
+ except requests.exceptions.ReadTimeout as timeout_error:
1676
+ raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
1677
+ except requests.exceptions.RetryError as retry_error:
1678
+ raise CommandError(f'Retrying failed: {retry_error}') from retry_error
1538
1679
  return command_arguments
1539
1680
 
1540
1681
  def __on_wake_sleep(self, wake_sleep_command: WakeSleepCommand, command_arguments: Union[str, Dict[str, Any]]) \
@@ -1553,10 +1694,20 @@ class Connector(BaseConnector):
1553
1694
  if command_arguments['command'] == WakeSleepCommand.Command.WAKE:
1554
1695
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-wakeup/{vin}?applyRequestLimiter=true'
1555
1696
 
1556
- command_response: requests.Response = self.session.post(url, data='{}', allow_redirects=True)
1557
- if command_response.status_code != requests.codes['accepted']:
1558
- LOG.error('Could not execute wake command (%s: %s)', command_response.status_code, command_response.text)
1559
- raise CommandError(f'Could not execute wake command ({command_response.status_code}: {command_response.text})')
1697
+ try:
1698
+ command_response: requests.Response = self.session.post(url, data='{}', allow_redirects=True)
1699
+ if command_response.status_code != requests.codes['accepted']:
1700
+ LOG.error('Could not execute wake command (%s: %s)', command_response.status_code, command_response.text)
1701
+ raise CommandError(f'Could not execute wake command ({command_response.status_code}: {command_response.text})')
1702
+ except requests.exceptions.ConnectionError as connection_error:
1703
+ raise CommandError(f'Connection error: {connection_error}.'
1704
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1705
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1706
+ raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1707
+ except requests.exceptions.ReadTimeout as timeout_error:
1708
+ raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
1709
+ except requests.exceptions.RetryError as retry_error:
1710
+ raise CommandError(f'Retrying failed: {retry_error}') from retry_error
1560
1711
  elif command_arguments['command'] == WakeSleepCommand.Command.SLEEP:
1561
1712
  raise CommandError('Sleep command not supported by vehicle. Vehicle will put itself to sleep')
1562
1713
  else:
@@ -1583,10 +1734,20 @@ class Connector(BaseConnector):
1583
1734
  url = 'https://mysmob.api.connect.skoda-auto.cz/api/v1/spin/verify'
1584
1735
  else:
1585
1736
  raise CommandError(f'Unknown command {command_arguments["command"]}')
1586
- command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1587
- if command_response.status_code != requests.codes['ok']:
1588
- LOG.error('Could not execute spin command (%s: %s)', command_response.status_code, command_response.text)
1589
- raise CommandError(f'Could not execute spin command ({command_response.status_code}: {command_response.text})')
1590
- else:
1591
- LOG.info('Spin verify command executed successfully')
1737
+ try:
1738
+ command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1739
+ if command_response.status_code != requests.codes['ok']:
1740
+ LOG.error('Could not execute spin command (%s: %s)', command_response.status_code, command_response.text)
1741
+ raise CommandError(f'Could not execute spin command ({command_response.status_code}: {command_response.text})')
1742
+ else:
1743
+ LOG.info('Spin verify command executed successfully')
1744
+ except requests.exceptions.ConnectionError as connection_error:
1745
+ raise CommandError(f'Connection error: {connection_error}.'
1746
+ ' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
1747
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
1748
+ raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
1749
+ except requests.exceptions.ReadTimeout as timeout_error:
1750
+ raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
1751
+ except requests.exceptions.RetryError as retry_error:
1752
+ raise CommandError(f'Retrying failed: {retry_error}') from retry_error
1592
1753
  return command_arguments
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  from carconnectivity.vehicle import GenericVehicle, ElectricVehicle, CombustionVehicle, HybridVehicle
6
6
  from carconnectivity.charging import Charging
7
+ from carconnectivity.attributes import BooleanAttribute
7
8
 
8
9
  from carconnectivity_connectors.skoda.capability import Capabilities
9
10
  from carconnectivity_connectors.skoda.charging import SkodaCharging
@@ -32,6 +33,8 @@ class SkodaVehicle(GenericVehicle): # pylint: disable=too-many-instance-attribu
32
33
  super().__init__(origin=origin)
33
34
  self.capabilities: Capabilities = origin.capabilities
34
35
  self.capabilities.parent = self
36
+ self.in_motion: BooleanAttribute = origin.in_motion
37
+ self.in_motion.parent = self
35
38
  if SUPPORT_IMAGES:
36
39
  self._car_images = origin._car_images
37
40
 
@@ -39,6 +42,7 @@ class SkodaVehicle(GenericVehicle): # pylint: disable=too-many-instance-attribu
39
42
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
40
43
  self.climatization = SkodaClimatization(vehicle=self, origin=self.climatization)
41
44
  self.capabilities = Capabilities(vehicle=self)
45
+ self.in_motion = BooleanAttribute(name='in_motion', parent=self, tags={'connector_custom'})
42
46
  if SUPPORT_IMAGES:
43
47
  self._car_images: Dict[str, Image.Image] = {}
44
48
  self.manufacturer._set_value(value='Škoda') # pylint: disable=protected-access