carconnectivity-connector-skoda 0.1a9__tar.gz → 0.1a11__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.
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/PKG-INFO +2 -2
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/setup_requirements.txt +1 -1
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connector_skoda.egg-info/PKG-INFO +2 -2
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/_version.py +1 -1
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/auth/openid_session.py +4 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/connector.py +129 -19
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/mqtt_client.py +88 -5
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/.flake8 +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/.github/dependabot.yml +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/.github/workflows/build.yml +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/.github/workflows/build_and_publish.yml +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/.github/workflows/codeql-analysis.yml +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/.gitignore +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/LICENSE +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/Makefile +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/README.md +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/doc/Config.md +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/pyproject.toml +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/setup.cfg +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connector_skoda.egg-info/SOURCES.txt +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connector_skoda.egg-info/dependency_links.txt +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connector_skoda.egg-info/requires.txt +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connector_skoda.egg-info/top_level.txt +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/__init__.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/auth/__init__.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/auth/auth_util.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/auth/my_skoda_session.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/auth/session_manager.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/auth/skoda_web_session.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/capability.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/charging.py +0 -0
- {carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/src/carconnectivity_connectors/skoda/vehicle.py +0 -0
|
@@ -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,11 +304,15 @@ 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
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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)
|
|
314
|
+
|
|
315
|
+
def fetch_charging(self, vehicle: SkodaElectricVehicle, no_cache: bool = False) -> SkodaElectricVehicle:
|
|
311
316
|
"""
|
|
312
317
|
Fetches the charging information for a given Skoda electric vehicle.
|
|
313
318
|
|
|
@@ -324,7 +329,7 @@ class Connector(BaseConnector):
|
|
|
324
329
|
if vehicle.charging is None:
|
|
325
330
|
raise ValueError('Vehicle has no charging object')
|
|
326
331
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}'
|
|
327
|
-
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)
|
|
328
333
|
if data is not None:
|
|
329
334
|
if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
|
|
330
335
|
captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
|
|
@@ -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.
|
|
365
|
+
vehicle.charging.estimated_date_reached._set_value(value=estimated_date_reached, measured=captured_at)
|
|
360
366
|
else:
|
|
361
|
-
vehicle.charging.
|
|
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',
|
|
@@ -366,7 +372,7 @@ class Connector(BaseConnector):
|
|
|
366
372
|
log_extra_keys(LOG_API, 'charging data', data, {'carCapturedTimestamp', 'status'})
|
|
367
373
|
return vehicle
|
|
368
374
|
|
|
369
|
-
def fetch_position(self, vehicle: SkodaVehicle) -> SkodaVehicle:
|
|
375
|
+
def fetch_position(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
|
|
370
376
|
"""
|
|
371
377
|
Fetches the position of the given Skoda vehicle and updates its position attributes.
|
|
372
378
|
|
|
@@ -386,7 +392,7 @@ class Connector(BaseConnector):
|
|
|
386
392
|
if vehicle.position is None:
|
|
387
393
|
raise ValueError('Vehicle has no charging object')
|
|
388
394
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/maps/positions?vin={vin}'
|
|
389
|
-
data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
|
|
395
|
+
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
|
390
396
|
if data is not None:
|
|
391
397
|
if 'positions' in data and data['positions'] is not None:
|
|
392
398
|
for position_dict in data['positions']:
|
|
@@ -420,7 +426,110 @@ class Connector(BaseConnector):
|
|
|
420
426
|
vehicle.position.position_type._set_value(None) # pylint: disable=protected-access
|
|
421
427
|
return vehicle
|
|
422
428
|
|
|
423
|
-
def
|
|
429
|
+
def fetch_air_conditioning(self, vehicle: SkodaVehicle, no_cache: bool = False) -> 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=url, session=self.session, no_cache=no_cache)
|
|
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
|
+
|
|
532
|
+
def fetch_vehicle_details(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
|
|
424
533
|
"""
|
|
425
534
|
Fetches the details of a vehicle from the Skoda API.
|
|
426
535
|
|
|
@@ -435,7 +544,7 @@ class Connector(BaseConnector):
|
|
|
435
544
|
raise APIError('VIN is missing')
|
|
436
545
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/garage/vehicles/{vin}?' \
|
|
437
546
|
'connectivityGenerations=MOD1&connectivityGenerations=MOD2&connectivityGenerations=MOD3&connectivityGenerations=MOD4'
|
|
438
|
-
vehicle_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
|
|
547
|
+
vehicle_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
|
439
548
|
if vehicle_data:
|
|
440
549
|
if 'softwareVersion' in vehicle_data and vehicle_data['softwareVersion'] is not None:
|
|
441
550
|
vehicle.software.version._set_value(vehicle_data['softwareVersion']) # pylint: disable=protected-access
|
|
@@ -473,7 +582,7 @@ class Connector(BaseConnector):
|
|
|
473
582
|
log_extra_keys(LOG_API, 'api/v2/garage/vehicles/VIN', vehicle_data, {'softwareVersion'})
|
|
474
583
|
return vehicle
|
|
475
584
|
|
|
476
|
-
def fetch_driving_range(self, vehicle: SkodaVehicle) -> SkodaVehicle:
|
|
585
|
+
def fetch_driving_range(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
|
|
477
586
|
"""
|
|
478
587
|
Fetches the driving range data for a given Skoda vehicle and updates the vehicle object accordingly.
|
|
479
588
|
|
|
@@ -496,7 +605,7 @@ class Connector(BaseConnector):
|
|
|
496
605
|
if vin is None:
|
|
497
606
|
raise APIError('VIN is missing')
|
|
498
607
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}/driving-range'
|
|
499
|
-
range_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
|
|
608
|
+
range_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
|
500
609
|
if range_data:
|
|
501
610
|
captured_at: datetime = robust_time_parse(range_data['carCapturedTimestamp'])
|
|
502
611
|
# Check vehicle type and if it does not match the current vehicle type, create a new vehicle object using copy constructor
|
|
@@ -583,7 +692,7 @@ class Connector(BaseConnector):
|
|
|
583
692
|
'secondaryEngineRange'})
|
|
584
693
|
return vehicle
|
|
585
694
|
|
|
586
|
-
def fetch_vehicle_status_second_api(self, vehicle: SkodaVehicle) -> SkodaVehicle:
|
|
695
|
+
def fetch_vehicle_status_second_api(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
|
|
587
696
|
"""
|
|
588
697
|
Fetches the status of a vehicle from other Skoda API.
|
|
589
698
|
|
|
@@ -597,7 +706,7 @@ class Connector(BaseConnector):
|
|
|
597
706
|
if vin is None:
|
|
598
707
|
raise APIError('VIN is missing')
|
|
599
708
|
url = f'https://api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}'
|
|
600
|
-
vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
|
|
709
|
+
vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
|
601
710
|
if vehicle_status_data:
|
|
602
711
|
if 'remote' in vehicle_status_data and vehicle_status_data['remote'] is not None:
|
|
603
712
|
vehicle_status_data = vehicle_status_data['remote']
|
|
@@ -772,10 +881,11 @@ class Connector(BaseConnector):
|
|
|
772
881
|
"""
|
|
773
882
|
self._elapsed.append(elapsed)
|
|
774
883
|
|
|
775
|
-
def _fetch_data(self, url, session,
|
|
884
|
+
def _fetch_data(self, url, session, no_cache=False, allow_empty=False, allow_http_error=False,
|
|
885
|
+
allowed_errors=None) -> Optional[Dict[str, Any]]: # noqa: C901
|
|
776
886
|
data: Optional[Dict[str, Any]] = None
|
|
777
887
|
cache_date: Optional[datetime] = None
|
|
778
|
-
if not
|
|
888
|
+
if not no_cache and (self.max_age is not None and session.cache is not None and url in session.cache):
|
|
779
889
|
data, cache_date_string = session.cache[url]
|
|
780
890
|
cache_date = datetime.fromisoformat(cache_date_string)
|
|
781
891
|
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
|
|
@@ -20,7 +21,7 @@ from carconnectivity.drive import ElectricDrive
|
|
|
20
21
|
from carconnectivity.util import robust_time_parse, log_extra_keys
|
|
21
22
|
from carconnectivity.charging import Charging
|
|
22
23
|
|
|
23
|
-
from carconnectivity_connectors.skoda.vehicle import SkodaElectricVehicle
|
|
24
|
+
from carconnectivity_connectors.skoda.vehicle import SkodaVehicle, SkodaElectricVehicle
|
|
24
25
|
from carconnectivity_connectors.skoda.charging import SkodaCharging, mapping_skoda_charging_state
|
|
25
26
|
|
|
26
27
|
|
|
@@ -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
|
|
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,17 +484,18 @@ 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 \
|
|
488
491
|
and vehicle.charging is not None:
|
|
489
492
|
try:
|
|
490
493
|
remaining_duration: Optional[timedelta] = timedelta(minutes=int(data['data']['timeToFinish']))
|
|
494
|
+
estimated_date_reached: Optional[datetime] = measured_at + remaining_duration
|
|
491
495
|
except ValueError:
|
|
492
|
-
|
|
496
|
+
estimated_date_reached: Optional[datetime] = None
|
|
493
497
|
# pylint: disable-next=protected-access
|
|
494
|
-
vehicle.charging.
|
|
498
|
+
vehicle.charging.estimated_date_reached._set_value(measured=measured_at, value=estimated_date_reached)
|
|
495
499
|
log_extra_keys(LOG_API, 'data', data['data'], {'vin', 'userId', 'soc', 'chargedRange', 'timeToFinish', 'state'})
|
|
496
500
|
LOG.debug('Received %s event for vehicle %s from user %s', data['name'], vin, user_id)
|
|
497
501
|
return
|
|
@@ -500,6 +504,85 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
500
504
|
LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
|
|
501
505
|
service_event, vin, user_id, msg.payload)
|
|
502
506
|
return
|
|
507
|
+
elif service_event == 'air-conditioning':
|
|
508
|
+
if 'name' in data and data['name'] == 'change-remaining-time':
|
|
509
|
+
if 'data' in data and data['data'] is not None:
|
|
510
|
+
vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
|
|
511
|
+
if isinstance(vehicle, SkodaVehicle):
|
|
512
|
+
try:
|
|
513
|
+
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
|
|
514
|
+
except CarConnectivityError as e:
|
|
515
|
+
LOG.error('Error while fetching charging: %s', e)
|
|
516
|
+
LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
|
|
517
|
+
service_event, vin, user_id, msg.payload)
|
|
518
|
+
return
|
|
519
|
+
elif service_event == 'vehicle-status/access':
|
|
520
|
+
if 'name' in data and data['name'] == 'change-access':
|
|
521
|
+
if 'data' in data and data['data'] is not None:
|
|
522
|
+
vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
|
|
523
|
+
if isinstance(vehicle, SkodaVehicle):
|
|
524
|
+
def delayed_access_function(vehicle: SkodaVehicle):
|
|
525
|
+
"""
|
|
526
|
+
Function to be executed after a delay of two seconds.
|
|
527
|
+
"""
|
|
528
|
+
vin = vehicle.id
|
|
529
|
+
self.delayed_access_function_timers.pop(vin)
|
|
530
|
+
if vehicle.capabilities is not None and vehicle.capabilities.enabled \
|
|
531
|
+
and vehicle.capabilities.has_capability('CHARGING') and isinstance(vehicle, SkodaElectricVehicle):
|
|
532
|
+
try:
|
|
533
|
+
self._skoda_connector.fetch_charging(vehicle, no_cache=True)
|
|
534
|
+
except CarConnectivityError as e:
|
|
535
|
+
LOG.error('Error while fetching charging: %s', e)
|
|
536
|
+
if vehicle.capabilities is not None and vehicle.capabilities.enabled \
|
|
537
|
+
and vehicle.capabilities.has_capability('PARKING_POSITION'):
|
|
538
|
+
try:
|
|
539
|
+
self._skoda_connector.fetch_position(vehicle, no_cache=True)
|
|
540
|
+
except CarConnectivityError as e:
|
|
541
|
+
LOG.error('Error while fetching position: %s', e)
|
|
542
|
+
if vehicle.capabilities is not None and vehicle.capabilities.enabled \
|
|
543
|
+
and vehicle.capabilities.has_capability('AIR_CONDITIONING'):
|
|
544
|
+
try:
|
|
545
|
+
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
|
|
546
|
+
except CarConnectivityError as e:
|
|
547
|
+
LOG.error('Error while fetching air conditioning: %s', e)
|
|
548
|
+
try:
|
|
549
|
+
self._skoda_connector.fetch_vehicle_status_second_api(vehicle, no_cache=True)
|
|
550
|
+
except CarConnectivityError as e:
|
|
551
|
+
LOG.error('Error while fetching status second API: %s', e)
|
|
552
|
+
try:
|
|
553
|
+
self._skoda_connector.fetch_driving_range(vehicle, no_cache=True)
|
|
554
|
+
except CarConnectivityError as e:
|
|
555
|
+
LOG.error('Error while fetching driving range: %s', e)
|
|
556
|
+
|
|
557
|
+
if vin in self.delayed_access_function_timers:
|
|
558
|
+
self.delayed_access_function_timers[vin].cancel()
|
|
559
|
+
self.delayed_access_function_timers[vin] = threading.Timer(2.0, delayed_access_function, kwargs={'vehicle': vehicle})
|
|
560
|
+
self.delayed_access_function_timers[vin].start()
|
|
561
|
+
|
|
562
|
+
LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
|
|
563
|
+
service_event, vin, user_id, msg.payload)
|
|
564
|
+
return
|
|
503
565
|
LOG_API.info('Received unknown service event %s for vehicle %s from user %s: %s', service_event, vin, user_id, msg.payload)
|
|
504
566
|
return
|
|
567
|
+
# service_events
|
|
568
|
+
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)
|
|
569
|
+
if match:
|
|
570
|
+
user_id: str = match.group('user_id')
|
|
571
|
+
vin: str = match.group('vin')
|
|
572
|
+
operation_request: str = match.group('operation_request')
|
|
573
|
+
data: Dict[str, Any] = json.loads(msg.payload)
|
|
574
|
+
if data is not None:
|
|
575
|
+
if operation_request == 'air-conditioning/start-stop-air-conditioning':
|
|
576
|
+
vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
|
|
577
|
+
if isinstance(vehicle, SkodaVehicle):
|
|
578
|
+
if 'status' in data and data['status'] is not None:
|
|
579
|
+
if data['status'] == 'COMPLETED_SUCCESS':
|
|
580
|
+
LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
|
|
581
|
+
try:
|
|
582
|
+
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
|
|
583
|
+
except CarConnectivityError as e:
|
|
584
|
+
LOG.error('Error while fetching air-conditioning: %s', e)
|
|
585
|
+
return
|
|
586
|
+
LOG_API.info('Received unknown operation request %s for vehicle %s from user %s: %s', operation_request, vin, user_id, msg.payload)
|
|
587
|
+
return
|
|
505
588
|
LOG_API.info('I don\'t understand message %s: %s', msg.topic, msg.payload)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/doc/Config.md
RENAMED
|
File without changes
|
{carconnectivity_connector_skoda-0.1a9 → carconnectivity_connector_skoda-0.1a11}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|