carconnectivity-connector-skoda 0.1a9__py3-none-any.whl → 0.1a10__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
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: carconnectivity-connector-skoda
3
- Version: 0.1a9
3
+ Version: 0.1a10
4
4
  Summary: CarConnectivity connector for Skoda services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -1,19 +1,19 @@
1
1
  carconnectivity_connectors/skoda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- carconnectivity_connectors/skoda/_version.py,sha256=SmWP1wxex0Jz9QX1vssYQ7oA2LcafCkYCa3HnNL8JV8,408
2
+ carconnectivity_connectors/skoda/_version.py,sha256=-6RZUfHP4tKhu5IUXqJlxl_E1uda42gm5BR0t7cpGAg,409
3
3
  carconnectivity_connectors/skoda/capability.py,sha256=JlNEaisVYF8qWv0wNDHTaas36uIpTIQ3NVR69wesiYQ,4513
4
4
  carconnectivity_connectors/skoda/charging.py,sha256=oDHxZxrfTMvtYCJxmGfKFeWVMH4ceQ5HTKRAspnsunU,3312
5
- carconnectivity_connectors/skoda/connector.py,sha256=dPdNsgurP8N7DZCfTuNBSohmeph2-vocfJgnVfmJYvI,53482
6
- carconnectivity_connectors/skoda/mqtt_client.py,sha256=VoYGHTxQbLX8WW1HWWZaJF1-hLxz0FGYR9RfLrY2LCI,25387
5
+ carconnectivity_connectors/skoda/connector.py,sha256=c0vi2H-yoJM6uX7PMIWLzJ-YeksyXyqBMpJT21uyc_I,61826
6
+ carconnectivity_connectors/skoda/mqtt_client.py,sha256=ZESgR4JRPPgAiLLEPvcnD6UeSaL-dMC1MvOWaqDdvj8,27944
7
7
  carconnectivity_connectors/skoda/vehicle.py,sha256=H3GRDNimMghFwFi--y9BsgoSK3pMibNf_l6SsDN6gvQ,2759
8
8
  carconnectivity_connectors/skoda/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  carconnectivity_connectors/skoda/auth/auth_util.py,sha256=dGLUbUre0HBsTg_Ii5vW34f8DLrCykYJYCyzEvUBBEE,4434
10
10
  carconnectivity_connectors/skoda/auth/my_skoda_session.py,sha256=lSh23SFJs8opjmPwHTv-KNIKDep_WY4aItSP4Zq7bT8,10396
11
- carconnectivity_connectors/skoda/auth/openid_session.py,sha256=PLWSSKw9Dg7hBbhzJ_nEycNrqiG6GiEM15h2wduL8jI,16592
11
+ carconnectivity_connectors/skoda/auth/openid_session.py,sha256=LusWi2FZZIL3buodGXZKUR0naLhhqeYv0uRW4V3wI2w,16842
12
12
  carconnectivity_connectors/skoda/auth/session_manager.py,sha256=Uf1vujuDBYUCAXhYToOsZkgbTtfmY3Qe0ICTfwomBpI,2899
13
13
  carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=cjzMkzx473Sh-4RgZAQULeRRcxB1MboddldCVM_y5LE,10640
14
14
  carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
15
- carconnectivity_connector_skoda-0.1a9.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
16
- carconnectivity_connector_skoda-0.1a9.dist-info/METADATA,sha256=dbKJrcPF2Xvw3mFX5H6njG5cFKPbWDl-MHn444vV4H0,5326
17
- carconnectivity_connector_skoda-0.1a9.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
18
- carconnectivity_connector_skoda-0.1a9.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
19
- carconnectivity_connector_skoda-0.1a9.dist-info/RECORD,,
15
+ carconnectivity_connector_skoda-0.1a10.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
16
+ carconnectivity_connector_skoda-0.1a10.dist-info/METADATA,sha256=_xdTO18nMfBi4hYnpdo6Cr3kCHegfhu9IJ1_EJIJOBo,5327
17
+ carconnectivity_connector_skoda-0.1a10.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
18
+ carconnectivity_connector_skoda-0.1a10.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
19
+ carconnectivity_connector_skoda-0.1a10.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.7.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.1a9'
15
+ __version__ = version = '0.1a10'
16
16
  __version_tuple__ = version_tuple = (0, 1)
@@ -112,6 +112,10 @@ class OpenIDSession(requests.Session):
112
112
  if new_retries_value:
113
113
  # Retry on internal server error (500)
114
114
  retries = BlacklistRetry(total=new_retries_value,
115
+ connect=new_retries_value,
116
+ read=new_retries_value,
117
+ status=new_retries_value,
118
+ other=new_retries_value,
115
119
  backoff_factor=0.1,
116
120
  status_forcelist=[500],
117
121
  status_blacklist=[429],
@@ -14,7 +14,7 @@ from carconnectivity.vehicle import GenericVehicle
14
14
  from carconnectivity.errors import AuthenticationError, TooManyRequestsError, RetrievalError, APIError, APICompatibilityError, \
15
15
  TemporaryAuthenticationError, ConfigurationError
16
16
  from carconnectivity.util import robust_time_parse, log_extra_keys, config_remove_credentials
17
- from carconnectivity.units import Length, Speed, Power
17
+ from carconnectivity.units import Length, Speed, Power, Temperature
18
18
  from carconnectivity.doors import Doors
19
19
  from carconnectivity.windows import Windows
20
20
  from carconnectivity.lights import Lights
@@ -22,6 +22,7 @@ from carconnectivity.drive import GenericDrive, ElectricDrive, CombustionDrive
22
22
  from carconnectivity.attributes import BooleanAttribute, DurationAttribute
23
23
  from carconnectivity.charging import Charging
24
24
  from carconnectivity.position import Position
25
+ from carconnectivity.climatization import Climatization
25
26
 
26
27
  from carconnectivity_connectors.base.connector import BaseConnector
27
28
  from carconnectivity_connectors.skoda.auth.session_manager import SessionManager, SessionUser, Service
@@ -303,9 +304,13 @@ class Connector(BaseConnector):
303
304
  if vehicle_to_update is not None and isinstance(vehicle_to_update, SkodaVehicle) and vehicle_to_update.is_managed_by_connector(self):
304
305
  vehicle_to_update = self.fetch_vehicle_status_second_api(vehicle_to_update)
305
306
  vehicle_to_update = self.fetch_driving_range(vehicle_to_update)
306
- vehicle_to_update = self.fetch_position(vehicle_to_update)
307
- if isinstance(vehicle_to_update, SkodaElectricVehicle):
308
- vehicle_to_update = self.fetch_charging(vehicle_to_update)
307
+ if vehicle_to_update.capabilities is not None and vehicle_to_update.capabilities.enabled:
308
+ if vehicle_to_update.capabilities.has_capability('PARKING_POSITION'):
309
+ vehicle_to_update = self.fetch_position(vehicle_to_update)
310
+ if vehicle_to_update.capabilities.has_capability('CHARGING') and isinstance(vehicle_to_update, SkodaElectricVehicle):
311
+ vehicle_to_update = self.fetch_charging(vehicle_to_update)
312
+ if vehicle_to_update.capabilities.has_capability('AIR_CONDITIONING'):
313
+ vehicle_to_update = self.fetch_air_conditioning(vehicle_to_update)
309
314
 
310
315
  def fetch_charging(self, vehicle: SkodaElectricVehicle) -> SkodaElectricVehicle:
311
316
  """
@@ -355,10 +360,11 @@ class Connector(BaseConnector):
355
360
  vehicle.charging.power._set_value(None, measured=captured_at, unit=Power.KW) # pylint: disable=protected-access
356
361
  if 'remainingTimeToFullyChargedInMinutes' in data['status'] and data['status']['remainingTimeToFullyChargedInMinutes'] is not None:
357
362
  remaining_duration: timedelta = timedelta(minutes=data['status']['remainingTimeToFullyChargedInMinutes'])
363
+ estimated_date_reached: datetime = captured_at + remaining_duration
358
364
  # pylint: disable-next=protected-access
359
- vehicle.charging.remaining_duration._set_value(value=remaining_duration, measured=captured_at)
365
+ vehicle.charging.estimated_date_reached._set_value(value=estimated_date_reached, measured=captured_at)
360
366
  else:
361
- vehicle.charging.remaining_duration._set_value(None, measured=captured_at) # pylint: disable=protected-access
367
+ vehicle.charging.estimated_date_reached._set_value(None, measured=captured_at) # pylint: disable=protected-access
362
368
  log_extra_keys(LOG_API, 'status', data['status'], {'chargingRateInKilometersPerHour',
363
369
  'chargePowerInKw',
364
370
  'remainingTimeToFullyChargedInMinutes',
@@ -420,6 +426,109 @@ class Connector(BaseConnector):
420
426
  vehicle.position.position_type._set_value(None) # pylint: disable=protected-access
421
427
  return vehicle
422
428
 
429
+ def fetch_air_conditioning(self, vehicle: SkodaVehicle) -> SkodaVehicle:
430
+ """
431
+ Fetches the air conditioning data for a given Skoda vehicle and updates the vehicle object with the retrieved data.
432
+
433
+ Args:
434
+ vehicle (SkodaVehicle): The vehicle object for which to fetch air conditioning data.
435
+
436
+ Returns:
437
+ SkodaVehicle: The updated vehicle object with the fetched air conditioning data.
438
+
439
+ Raises:
440
+ APIError: If the VIN is missing or if the carCapturedTimestamp is missing in the response data.
441
+ ValueError: If the vehicle has no charging object.
442
+
443
+ Notes:
444
+ - The method fetches data from the Skoda API using the vehicle's VIN.
445
+ - It updates the vehicle's climatization state, estimated date to reach target temperature, target temperature, and outside temperature.
446
+ - Logs additional keys found in the response data for debugging purposes.
447
+ """
448
+ vin = vehicle.vin.value
449
+ if vin is None:
450
+ raise APIError('VIN is missing')
451
+ if vehicle.position is None:
452
+ raise ValueError('Vehicle has no charging object')
453
+ 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
+ if data is not None:
456
+ if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
457
+ captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
458
+ else:
459
+ raise APIError('Could not fetch air conditioning, carCapturedTimestamp missing')
460
+ if 'state' in data and data['state'] is not None:
461
+ if data['state'] in [item.name for item in Climatization.ClimatizationState]:
462
+ climatization_state: Climatization.ClimatizationState = Climatization.ClimatizationState[data['state']]
463
+ else:
464
+ LOG_API.info('Unknown climatization state %s not in %s', data['state'], str(Climatization.ClimatizationState))
465
+ climatization_state = Climatization.ClimatizationState.UNKNOWN
466
+ vehicle.climatization.state._set_value(value=climatization_state, measured=captured_at) # pylint: disable=protected-access
467
+ else:
468
+ vehicle.climatization.state._set_value(None, measured=captured_at) # pylint: disable=protected-access
469
+ if 'estimatedDateTimeToReachTargetTemperature' in data and data['estimatedDateTimeToReachTargetTemperature'] is not None:
470
+ estimated_reach: datetime = robust_time_parse(data['estimatedDateTimeToReachTargetTemperature'])
471
+ if estimated_reach is not None:
472
+ vehicle.climatization.estimated_date_reached._set_value(value=estimated_reach, measured=captured_at) # pylint: disable=protected-access
473
+ else:
474
+ vehicle.climatization.estimated_date_reached._set_value(value=None, measured=captured_at) # pylint: disable=protected-access
475
+ else:
476
+ vehicle.climatization.estimated_date_reached._set_value(value=None, measured=captured_at) # pylint: disable=protected-access
477
+ if 'targetTemperature' in data and data['targetTemperature'] is not None:
478
+ unit: Temperature = Temperature.UNKNOWN
479
+ if 'unitInCar' in data['targetTemperature'] and data['targetTemperature']['unitInCar'] is not None:
480
+ if data['targetTemperature']['unitInCar'] == 'CELSIUS':
481
+ unit = Temperature.C
482
+ elif data['targetTemperature']['unitInCar'] == 'FAHRENHEIT':
483
+ unit = Temperature.F
484
+ elif data['targetTemperature']['unitInCar'] == 'KELVIN':
485
+ unit = Temperature.K
486
+ else:
487
+ LOG_API.info('Unknown temperature unit for targetTemperature in air-conditioning %s', data['targetTemperature']['unitInCar'])
488
+ if 'temperatureValue' in data['targetTemperature'] and data['targetTemperature']['temperatureValue'] is not None:
489
+ # pylint: disable-next=protected-access
490
+ vehicle.climatization.target_temperature._set_value(value=data['targetTemperature']['temperatureValue'],
491
+ measured=captured_at,
492
+ unit=unit)
493
+ else:
494
+ vehicle.climatization.target_temperature._set_value(value=None, measured=captured_at, unit=unit) # pylint: disable=protected-access
495
+ log_extra_keys(LOG_API, 'targetTemperature', data['targetTemperature'], {'unitInCar', 'temperatureValue'})
496
+ else:
497
+ # pylint: disable-next=protected-access
498
+ vehicle.climatization.target_temperature._set_value(value=None, measured=captured_at, unit=Temperature.UNKNOWN)
499
+ if 'outsideTemperature' in data and data['outsideTemperature'] is not None:
500
+ if 'carCapturedTimestamp' in data['outsideTemperature'] and data['outsideTemperature']['carCapturedTimestamp'] is not None:
501
+ outside_captured_at: datetime = robust_time_parse(data['outsideTemperature']['carCapturedTimestamp'])
502
+ else:
503
+ outside_captured_at = captured_at
504
+ if 'temperatureUnit' in data['outsideTemperature'] and data['outsideTemperature']['temperatureUnit'] is not None:
505
+ unit: Temperature = Temperature.UNKNOWN
506
+ if data['outsideTemperature']['temperatureUnit'] == 'CELSIUS':
507
+ unit = Temperature.C
508
+ elif data['outsideTemperature']['temperatureUnit'] == 'FAHRENHEIT':
509
+ unit = Temperature.F
510
+ elif data['outsideTemperature']['temperatureUnit'] == 'KELVIN':
511
+ unit = Temperature.K
512
+ else:
513
+ LOG_API.info('Unknown temperature unit for outsideTemperature in air-conditioning %s', data['targetTemperature']['temperatureUnit'])
514
+ if 'temperatureValue' in data['outsideTemperature'] and data['outsideTemperature']['temperatureValue'] is not None:
515
+ # pylint: disable-next=protected-access
516
+ vehicle.outside_temperature._set_value(value=data['outsideTemperature']['temperatureValue'],
517
+ measured=outside_captured_at,
518
+ unit=unit)
519
+ else:
520
+ # pylint: disable-next=protected-access
521
+ vehicle.outside_temperature._set_value(value=None, measured=outside_captured_at, unit=Temperature.UNKNOWN)
522
+ else:
523
+ # pylint: disable-next=protected-access
524
+ vehicle.outside_temperature._set_value(value=None, measured=outside_captured_at, unit=Temperature.UNKNOWN)
525
+ log_extra_keys(LOG_API, 'targetTemperature', data['outsideTemperature'], {'carCapturedTimestamp', 'temperatureUnit', 'temperatureValue'})
526
+ else:
527
+ vehicle.outside_temperature._set_value(value=None, measured=None, unit=Temperature.UNKNOWN) # pylint: disable=protected-access
528
+ log_extra_keys(LOG_API, 'air-condition', data, {'carCapturedTimestamp', 'state', 'estimatedDateTimeToReachTargetTemperature'
529
+ 'targetTemperature', 'outsideTemperature'})
530
+ return vehicle
531
+
423
532
  def fetch_vehicle_details(self, vehicle: SkodaVehicle) -> SkodaVehicle:
424
533
  """
425
534
  Fetches the details of a vehicle from the Skoda API.
@@ -20,7 +20,7 @@ from carconnectivity.drive import ElectricDrive
20
20
  from carconnectivity.util import robust_time_parse, log_extra_keys
21
21
  from carconnectivity.charging import Charging
22
22
 
23
- from carconnectivity_connectors.skoda.vehicle import SkodaElectricVehicle
23
+ from carconnectivity_connectors.skoda.vehicle import SkodaVehicle, SkodaElectricVehicle
24
24
  from carconnectivity_connectors.skoda.charging import SkodaCharging, mapping_skoda_charging_state
25
25
 
26
26
 
@@ -437,7 +437,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
437
437
  return
438
438
 
439
439
  # service_events
440
- match = re.match(r'^(?P<user_id>[0-9a-fA-F-]+)/(?P<vin>[A-Z0-9]+)/service-event/(?P<service_event>\w+)$', msg.topic)
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)
441
441
  if match:
442
442
  user_id: str = match.group('user_id')
443
443
  vin: str = match.group('vin')
@@ -488,10 +488,11 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
488
488
  and vehicle.charging is not None:
489
489
  try:
490
490
  remaining_duration: Optional[timedelta] = timedelta(minutes=int(data['data']['timeToFinish']))
491
+ estimated_date_reached: Optional[datetime] = measured_at + remaining_duration
491
492
  except ValueError:
492
- remaining_duration: Optional[timedelta] = None
493
+ estimated_date_reached: Optional[datetime] = None
493
494
  # pylint: disable-next=protected-access
494
- vehicle.charging.remaining_duration._set_value(measured=measured_at, value=remaining_duration)
495
+ vehicle.charging.estimated_date_reached._set_value(measured=measured_at, value=estimated_date_reached)
495
496
  log_extra_keys(LOG_API, 'data', data['data'], {'vin', 'userId', 'soc', 'chargedRange', 'timeToFinish', 'state'})
496
497
  LOG.debug('Received %s event for vehicle %s from user %s', data['name'], vin, user_id)
497
498
  return
@@ -500,6 +501,39 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
500
501
  LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
501
502
  service_event, vin, user_id, msg.payload)
502
503
  return
504
+ elif service_event == 'air-conditioning':
505
+ if 'name' in data and data['name'] == 'change-remaining-time':
506
+ if 'data' in data and data['data'] is not None:
507
+ vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
508
+ if isinstance(vehicle, SkodaVehicle):
509
+ try:
510
+ self._skoda_connector.fetch_air_conditioning(vehicle)
511
+ except CarConnectivityError as e:
512
+ LOG.error('Error while fetching charging: %s', e)
513
+ LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
514
+ service_event, vin, user_id, msg.payload)
515
+ return
503
516
  LOG_API.info('Received unknown service event %s for vehicle %s from user %s: %s', service_event, vin, user_id, msg.payload)
504
517
  return
518
+ # service_events
519
+ match = re.match(r'^(?P<user_id>[0-9a-fA-F-]+)/(?P<vin>[A-Z0-9]+)/operation-request/(?P<operation_request>[a-zA-Z0-9-_/]+)$', msg.topic)
520
+ if match:
521
+ user_id: str = match.group('user_id')
522
+ vin: str = match.group('vin')
523
+ operation_request: str = match.group('operation_request')
524
+ data: Dict[str, Any] = json.loads(msg.payload)
525
+ if data is not None:
526
+ if operation_request == 'air-conditioning/start-stop-air-conditioning':
527
+ vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
528
+ if isinstance(vehicle, SkodaVehicle):
529
+ if 'status' in data and data['status'] is not None:
530
+ if data['status'] == 'COMPLETED_SUCCESS':
531
+ LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
532
+ try:
533
+ self._skoda_connector.fetch_air_conditioning(vehicle)
534
+ except CarConnectivityError as e:
535
+ LOG.error('Error while fetching air-conditioning: %s', e)
536
+ return
537
+ LOG_API.info('Received unknown operation request %s for vehicle %s from user %s: %s', operation_request, vin, user_id, msg.payload)
538
+ return
505
539
  LOG_API.info('I don\'t understand message %s: %s', msg.topic, msg.payload)