carconnectivity-connector-skoda 0.1a10__tar.gz → 0.1a12__tar.gz

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.

Files changed (35) hide show
  1. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/PKG-INFO +1 -1
  2. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connector_skoda.egg-info/PKG-INFO +1 -1
  3. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/_version.py +1 -1
  4. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/connector.py +17 -15
  5. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/mqtt_client.py +50 -4
  6. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/.flake8 +0 -0
  7. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  8. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  9. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/.github/dependabot.yml +0 -0
  10. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/.github/workflows/build.yml +0 -0
  11. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/.github/workflows/build_and_publish.yml +0 -0
  12. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/.github/workflows/codeql-analysis.yml +0 -0
  13. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/.gitignore +0 -0
  14. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/LICENSE +0 -0
  15. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/Makefile +0 -0
  16. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/README.md +0 -0
  17. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/doc/Config.md +0 -0
  18. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/pyproject.toml +0 -0
  19. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/setup.cfg +0 -0
  20. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/setup_requirements.txt +0 -0
  21. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connector_skoda.egg-info/SOURCES.txt +0 -0
  22. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connector_skoda.egg-info/dependency_links.txt +0 -0
  23. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connector_skoda.egg-info/requires.txt +0 -0
  24. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connector_skoda.egg-info/top_level.txt +0 -0
  25. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/__init__.py +0 -0
  26. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/auth/__init__.py +0 -0
  27. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/auth/auth_util.py +0 -0
  28. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py +0 -0
  29. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/auth/my_skoda_session.py +0 -0
  30. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/auth/openid_session.py +0 -0
  31. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/auth/session_manager.py +0 -0
  32. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/auth/skoda_web_session.py +0 -0
  33. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/capability.py +0 -0
  34. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/charging.py +0 -0
  35. {carconnectivity_connector_skoda-0.1a10 → carconnectivity_connector_skoda-0.1a12}/src/carconnectivity_connectors/skoda/vehicle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: carconnectivity-connector-skoda
3
- Version: 0.1a10
3
+ Version: 0.1a12
4
4
  Summary: CarConnectivity connector for Skoda services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: carconnectivity-connector-skoda
3
- Version: 0.1a10
3
+ Version: 0.1a12
4
4
  Summary: CarConnectivity connector for Skoda services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.1a10'
15
+ __version__ = version = '0.1a12'
16
16
  __version_tuple__ = version_tuple = (0, 1)
@@ -302,7 +302,7 @@ class Connector(BaseConnector):
302
302
  for vin in set(garage.list_vehicle_vins()):
303
303
  vehicle_to_update: Optional[GenericVehicle] = garage.get_vehicle(vin)
304
304
  if vehicle_to_update is not None and isinstance(vehicle_to_update, SkodaVehicle) and vehicle_to_update.is_managed_by_connector(self):
305
- vehicle_to_update = self.fetch_vehicle_status_second_api(vehicle_to_update)
305
+ #vehicle_to_update = self.fetch_vehicle_status_second_api(vehicle_to_update)
306
306
  vehicle_to_update = self.fetch_driving_range(vehicle_to_update)
307
307
  if vehicle_to_update.capabilities is not None and vehicle_to_update.capabilities.enabled:
308
308
  if vehicle_to_update.capabilities.has_capability('PARKING_POSITION'):
@@ -312,7 +312,7 @@ class Connector(BaseConnector):
312
312
  if vehicle_to_update.capabilities.has_capability('AIR_CONDITIONING'):
313
313
  vehicle_to_update = self.fetch_air_conditioning(vehicle_to_update)
314
314
 
315
- def fetch_charging(self, vehicle: SkodaElectricVehicle) -> SkodaElectricVehicle:
315
+ def fetch_charging(self, vehicle: SkodaElectricVehicle, no_cache: bool = False) -> SkodaElectricVehicle:
316
316
  """
317
317
  Fetches the charging information for a given Skoda electric vehicle.
318
318
 
@@ -329,7 +329,7 @@ class Connector(BaseConnector):
329
329
  if vehicle.charging is None:
330
330
  raise ValueError('Vehicle has no charging object')
331
331
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}'
332
- data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
332
+ data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
333
333
  if data is not None:
334
334
  if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
335
335
  captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
@@ -361,6 +361,7 @@ class Connector(BaseConnector):
361
361
  if 'remainingTimeToFullyChargedInMinutes' in data['status'] and data['status']['remainingTimeToFullyChargedInMinutes'] is not None:
362
362
  remaining_duration: timedelta = timedelta(minutes=data['status']['remainingTimeToFullyChargedInMinutes'])
363
363
  estimated_date_reached: datetime = captured_at + remaining_duration
364
+ estimated_date_reached = estimated_date_reached.replace(second=0, microsecond=0)
364
365
  # pylint: disable-next=protected-access
365
366
  vehicle.charging.estimated_date_reached._set_value(value=estimated_date_reached, measured=captured_at)
366
367
  else:
@@ -372,7 +373,7 @@ class Connector(BaseConnector):
372
373
  log_extra_keys(LOG_API, 'charging data', data, {'carCapturedTimestamp', 'status'})
373
374
  return vehicle
374
375
 
375
- def fetch_position(self, vehicle: SkodaVehicle) -> SkodaVehicle:
376
+ def fetch_position(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
376
377
  """
377
378
  Fetches the position of the given Skoda vehicle and updates its position attributes.
378
379
 
@@ -392,7 +393,7 @@ class Connector(BaseConnector):
392
393
  if vehicle.position is None:
393
394
  raise ValueError('Vehicle has no charging object')
394
395
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/maps/positions?vin={vin}'
395
- data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
396
+ data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
396
397
  if data is not None:
397
398
  if 'positions' in data and data['positions'] is not None:
398
399
  for position_dict in data['positions']:
@@ -426,7 +427,7 @@ class Connector(BaseConnector):
426
427
  vehicle.position.position_type._set_value(None) # pylint: disable=protected-access
427
428
  return vehicle
428
429
 
429
- def fetch_air_conditioning(self, vehicle: SkodaVehicle) -> SkodaVehicle:
430
+ def fetch_air_conditioning(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
430
431
  """
431
432
  Fetches the air conditioning data for a given Skoda vehicle and updates the vehicle object with the retrieved data.
432
433
 
@@ -451,7 +452,7 @@ class Connector(BaseConnector):
451
452
  if vehicle.position is None:
452
453
  raise ValueError('Vehicle has no charging object')
453
454
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}'
454
- data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
455
+ data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
455
456
  if data is not None:
456
457
  if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
457
458
  captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
@@ -529,7 +530,7 @@ class Connector(BaseConnector):
529
530
  'targetTemperature', 'outsideTemperature'})
530
531
  return vehicle
531
532
 
532
- def fetch_vehicle_details(self, vehicle: SkodaVehicle) -> SkodaVehicle:
533
+ def fetch_vehicle_details(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
533
534
  """
534
535
  Fetches the details of a vehicle from the Skoda API.
535
536
 
@@ -544,7 +545,7 @@ class Connector(BaseConnector):
544
545
  raise APIError('VIN is missing')
545
546
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/garage/vehicles/{vin}?' \
546
547
  'connectivityGenerations=MOD1&connectivityGenerations=MOD2&connectivityGenerations=MOD3&connectivityGenerations=MOD4'
547
- vehicle_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
548
+ vehicle_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
548
549
  if vehicle_data:
549
550
  if 'softwareVersion' in vehicle_data and vehicle_data['softwareVersion'] is not None:
550
551
  vehicle.software.version._set_value(vehicle_data['softwareVersion']) # pylint: disable=protected-access
@@ -582,7 +583,7 @@ class Connector(BaseConnector):
582
583
  log_extra_keys(LOG_API, 'api/v2/garage/vehicles/VIN', vehicle_data, {'softwareVersion'})
583
584
  return vehicle
584
585
 
585
- def fetch_driving_range(self, vehicle: SkodaVehicle) -> SkodaVehicle:
586
+ def fetch_driving_range(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
586
587
  """
587
588
  Fetches the driving range data for a given Skoda vehicle and updates the vehicle object accordingly.
588
589
 
@@ -605,7 +606,7 @@ class Connector(BaseConnector):
605
606
  if vin is None:
606
607
  raise APIError('VIN is missing')
607
608
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}/driving-range'
608
- range_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
609
+ range_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
609
610
  if range_data:
610
611
  captured_at: datetime = robust_time_parse(range_data['carCapturedTimestamp'])
611
612
  # Check vehicle type and if it does not match the current vehicle type, create a new vehicle object using copy constructor
@@ -692,7 +693,7 @@ class Connector(BaseConnector):
692
693
  'secondaryEngineRange'})
693
694
  return vehicle
694
695
 
695
- def fetch_vehicle_status_second_api(self, vehicle: SkodaVehicle) -> SkodaVehicle:
696
+ def fetch_vehicle_status_second_api(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
696
697
  """
697
698
  Fetches the status of a vehicle from other Skoda API.
698
699
 
@@ -706,7 +707,7 @@ class Connector(BaseConnector):
706
707
  if vin is None:
707
708
  raise APIError('VIN is missing')
708
709
  url = f'https://api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}'
709
- vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
710
+ vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
710
711
  if vehicle_status_data:
711
712
  if 'remote' in vehicle_status_data and vehicle_status_data['remote'] is not None:
712
713
  vehicle_status_data = vehicle_status_data['remote']
@@ -881,10 +882,11 @@ class Connector(BaseConnector):
881
882
  """
882
883
  self._elapsed.append(elapsed)
883
884
 
884
- def _fetch_data(self, url, session, force=False, allow_empty=False, allow_http_error=False, allowed_errors=None) -> Optional[Dict[str, Any]]: # noqa: C901
885
+ def _fetch_data(self, url, session, no_cache=False, allow_empty=False, allow_http_error=False,
886
+ allowed_errors=None) -> Optional[Dict[str, Any]]: # noqa: C901
885
887
  data: Optional[Dict[str, Any]] = None
886
888
  cache_date: Optional[datetime] = None
887
- if not force and (self.max_age is not None and session.cache is not None and url in session.cache):
889
+ if not no_cache and (self.max_age is not None and session.cache is not None and url in session.cache):
888
890
  data, cache_date_string = session.cache[url]
889
891
  cache_date = datetime.fromisoformat(cache_date_string)
890
892
  if data is None or self.max_age is None \
@@ -7,6 +7,7 @@ import logging
7
7
  import uuid
8
8
  import ssl
9
9
  import json
10
+ import threading
10
11
  from datetime import timedelta, timezone
11
12
 
12
13
  from paho.mqtt.client import Client
@@ -57,6 +58,8 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
57
58
  self.on_subscribe = self._on_subscribe_callback
58
59
  self.subscribed_topics: Set[str] = set()
59
60
 
61
+ self.delayed_access_function_timers: Dict[str, threading.Timer] = {}
62
+
60
63
  self.tls_set(cert_reqs=ssl.CERT_NONE)
61
64
 
62
65
  def connect(self, *args, **kwargs) -> MQTTErrorCode:
@@ -437,7 +440,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
437
440
  return
438
441
 
439
442
  # service_events
440
- match = re.match(r'^(?P<user_id>[0-9a-fA-F-]+)/(?P<vin>[A-Z0-9]+)/service-event/(?P<service_event>[a-zA-Z0-9-_]+)$', msg.topic)
443
+ match = re.match(r'^(?P<user_id>[0-9a-fA-F-]+)/(?P<vin>[A-Z0-9]+)/service-event/(?P<service_event>[a-zA-Z0-9-_/]+)$', msg.topic)
441
444
  if match:
442
445
  user_id: str = match.group('user_id')
443
446
  vin: str = match.group('vin')
@@ -481,7 +484,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
481
484
  # If charging state changed, fetch charging again
482
485
  if old_charging_state != charging_state:
483
486
  try:
484
- self._skoda_connector.fetch_charging(vehicle)
487
+ self._skoda_connector.fetch_charging(vehicle, no_cache=True)
485
488
  except CarConnectivityError as e:
486
489
  LOG.error('Error while fetching charging: %s', e)
487
490
  if 'timeToFinish' in data['data'] and data['data']['timeToFinish'] is not None \
@@ -489,6 +492,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
489
492
  try:
490
493
  remaining_duration: Optional[timedelta] = timedelta(minutes=int(data['data']['timeToFinish']))
491
494
  estimated_date_reached: Optional[datetime] = measured_at + remaining_duration
495
+ estimated_date_reached = estimated_date_reached.replace(second=0, microsecond=0)
492
496
  except ValueError:
493
497
  estimated_date_reached: Optional[datetime] = None
494
498
  # pylint: disable-next=protected-access
@@ -507,12 +511,54 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
507
511
  vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
508
512
  if isinstance(vehicle, SkodaVehicle):
509
513
  try:
510
- self._skoda_connector.fetch_air_conditioning(vehicle)
514
+ self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
511
515
  except CarConnectivityError as e:
512
516
  LOG.error('Error while fetching charging: %s', e)
513
517
  LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
514
518
  service_event, vin, user_id, msg.payload)
515
519
  return
520
+ elif service_event == 'vehicle-status/access':
521
+ if 'name' in data and data['name'] == 'change-access':
522
+ if 'data' in data and data['data'] is not None:
523
+ vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
524
+ if isinstance(vehicle, SkodaVehicle):
525
+ def delayed_access_function(vehicle: SkodaVehicle):
526
+ """
527
+ Function to be executed after a delay of two seconds.
528
+ """
529
+ vin = vehicle.id
530
+ self.delayed_access_function_timers.pop(vin)
531
+ if vehicle.capabilities is not None and vehicle.capabilities.enabled \
532
+ and vehicle.capabilities.has_capability('CHARGING') and isinstance(vehicle, SkodaElectricVehicle):
533
+ try:
534
+ self._skoda_connector.fetch_charging(vehicle, no_cache=True)
535
+ except CarConnectivityError as e:
536
+ LOG.error('Error while fetching charging: %s', e)
537
+ if vehicle.capabilities is not None and vehicle.capabilities.enabled \
538
+ and vehicle.capabilities.has_capability('PARKING_POSITION'):
539
+ try:
540
+ self._skoda_connector.fetch_position(vehicle, no_cache=True)
541
+ except CarConnectivityError as e:
542
+ LOG.error('Error while fetching position: %s', e)
543
+ if vehicle.capabilities is not None and vehicle.capabilities.enabled \
544
+ and vehicle.capabilities.has_capability('AIR_CONDITIONING'):
545
+ try:
546
+ self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
547
+ except CarConnectivityError as e:
548
+ LOG.error('Error while fetching air conditioning: %s', e)
549
+ #try:
550
+ # self._skoda_connector.fetch_vehicle_status_second_api(vehicle, no_cache=True)
551
+ #except CarConnectivityError as e:
552
+ # LOG.error('Error while fetching status second API: %s', e)
553
+
554
+ if vin in self.delayed_access_function_timers:
555
+ self.delayed_access_function_timers[vin].cancel()
556
+ self.delayed_access_function_timers[vin] = threading.Timer(2.0, delayed_access_function, kwargs={'vehicle': vehicle})
557
+ self.delayed_access_function_timers[vin].start()
558
+
559
+ LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
560
+ service_event, vin, user_id, msg.payload)
561
+ return
516
562
  LOG_API.info('Received unknown service event %s for vehicle %s from user %s: %s', service_event, vin, user_id, msg.payload)
517
563
  return
518
564
  # service_events
@@ -530,7 +576,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
530
576
  if data['status'] == 'COMPLETED_SUCCESS':
531
577
  LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
532
578
  try:
533
- self._skoda_connector.fetch_air_conditioning(vehicle)
579
+ self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
534
580
  except CarConnectivityError as e:
535
581
  LOG.error('Error while fetching air-conditioning: %s', e)
536
582
  return