Skip to content

Commit

Permalink
try to deal with access messages from the broker
Browse files Browse the repository at this point in the history
  • Loading branch information
tillsteinbach committed Jan 9, 2025
1 parent a662b40 commit 6ce1bbf
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 18 deletions.
29 changes: 15 additions & 14 deletions src/carconnectivity_connectors/skoda/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def update_vehicles(self) -> None:
if vehicle_to_update.capabilities.has_capability('AIR_CONDITIONING'):
vehicle_to_update = self.fetch_air_conditioning(vehicle_to_update)

def fetch_charging(self, vehicle: SkodaElectricVehicle) -> SkodaElectricVehicle:
def fetch_charging(self, vehicle: SkodaElectricVehicle, no_cache: bool = False) -> SkodaElectricVehicle:
"""
Fetches the charging information for a given Skoda electric vehicle.
Expand All @@ -329,7 +329,7 @@ def fetch_charging(self, vehicle: SkodaElectricVehicle) -> SkodaElectricVehicle:
if vehicle.charging is None:
raise ValueError('Vehicle has no charging object')
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}'
data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
if data is not None:
if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
Expand Down Expand Up @@ -372,7 +372,7 @@ def fetch_charging(self, vehicle: SkodaElectricVehicle) -> SkodaElectricVehicle:
log_extra_keys(LOG_API, 'charging data', data, {'carCapturedTimestamp', 'status'})
return vehicle

def fetch_position(self, vehicle: SkodaVehicle) -> SkodaVehicle:
def fetch_position(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
"""
Fetches the position of the given Skoda vehicle and updates its position attributes.
Expand All @@ -392,7 +392,7 @@ def fetch_position(self, vehicle: SkodaVehicle) -> SkodaVehicle:
if vehicle.position is None:
raise ValueError('Vehicle has no charging object')
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/maps/positions?vin={vin}'
data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
if data is not None:
if 'positions' in data and data['positions'] is not None:
for position_dict in data['positions']:
Expand Down Expand Up @@ -426,7 +426,7 @@ def fetch_position(self, vehicle: SkodaVehicle) -> SkodaVehicle:
vehicle.position.position_type._set_value(None) # pylint: disable=protected-access
return vehicle

def fetch_air_conditioning(self, vehicle: SkodaVehicle) -> SkodaVehicle:
def fetch_air_conditioning(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
"""
Fetches the air conditioning data for a given Skoda vehicle and updates the vehicle object with the retrieved data.
Expand All @@ -451,7 +451,7 @@ def fetch_air_conditioning(self, vehicle: SkodaVehicle) -> SkodaVehicle:
if vehicle.position is None:
raise ValueError('Vehicle has no charging object')
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}'
data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
if data is not None:
if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
Expand Down Expand Up @@ -529,7 +529,7 @@ def fetch_air_conditioning(self, vehicle: SkodaVehicle) -> SkodaVehicle:
'targetTemperature', 'outsideTemperature'})
return vehicle

def fetch_vehicle_details(self, vehicle: SkodaVehicle) -> SkodaVehicle:
def fetch_vehicle_details(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
"""
Fetches the details of a vehicle from the Skoda API.
Expand All @@ -544,7 +544,7 @@ def fetch_vehicle_details(self, vehicle: SkodaVehicle) -> SkodaVehicle:
raise APIError('VIN is missing')
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/garage/vehicles/{vin}?' \
'connectivityGenerations=MOD1&connectivityGenerations=MOD2&connectivityGenerations=MOD3&connectivityGenerations=MOD4'
vehicle_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
vehicle_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
if vehicle_data:
if 'softwareVersion' in vehicle_data and vehicle_data['softwareVersion'] is not None:
vehicle.software.version._set_value(vehicle_data['softwareVersion']) # pylint: disable=protected-access
Expand Down Expand Up @@ -582,7 +582,7 @@ def fetch_vehicle_details(self, vehicle: SkodaVehicle) -> SkodaVehicle:
log_extra_keys(LOG_API, 'api/v2/garage/vehicles/VIN', vehicle_data, {'softwareVersion'})
return vehicle

def fetch_driving_range(self, vehicle: SkodaVehicle) -> SkodaVehicle:
def fetch_driving_range(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
"""
Fetches the driving range data for a given Skoda vehicle and updates the vehicle object accordingly.
Expand All @@ -605,7 +605,7 @@ def fetch_driving_range(self, vehicle: SkodaVehicle) -> SkodaVehicle:
if vin is None:
raise APIError('VIN is missing')
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}/driving-range'
range_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
range_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
if range_data:
captured_at: datetime = robust_time_parse(range_data['carCapturedTimestamp'])
# Check vehicle type and if it does not match the current vehicle type, create a new vehicle object using copy constructor
Expand Down Expand Up @@ -692,7 +692,7 @@ def fetch_driving_range(self, vehicle: SkodaVehicle) -> SkodaVehicle:
'secondaryEngineRange'})
return vehicle

def fetch_vehicle_status_second_api(self, vehicle: SkodaVehicle) -> SkodaVehicle:
def fetch_vehicle_status_second_api(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
"""
Fetches the status of a vehicle from other Skoda API.
Expand All @@ -706,7 +706,7 @@ def fetch_vehicle_status_second_api(self, vehicle: SkodaVehicle) -> SkodaVehicle
if vin is None:
raise APIError('VIN is missing')
url = f'https://api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}'
vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
if vehicle_status_data:
if 'remote' in vehicle_status_data and vehicle_status_data['remote'] is not None:
vehicle_status_data = vehicle_status_data['remote']
Expand Down Expand Up @@ -881,10 +881,11 @@ def _record_elapsed(self, elapsed: timedelta) -> None:
"""
self._elapsed.append(elapsed)

def _fetch_data(self, url, session, force=False, allow_empty=False, allow_http_error=False, allowed_errors=None) -> Optional[Dict[str, Any]]: # noqa: C901
def _fetch_data(self, url, session, no_cache=False, allow_empty=False, allow_http_error=False,
allowed_errors=None) -> Optional[Dict[str, Any]]: # noqa: C901
data: Optional[Dict[str, Any]] = None
cache_date: Optional[datetime] = None
if not force and (self.max_age is not None and session.cache is not None and url in session.cache):
if not no_cache and (self.max_age is not None and session.cache is not None and url in session.cache):
data, cache_date_string = session.cache[url]
cache_date = datetime.fromisoformat(cache_date_string)
if data is None or self.max_age is None \
Expand Down
57 changes: 53 additions & 4 deletions src/carconnectivity_connectors/skoda/mqtt_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import uuid
import ssl
import json
import threading
from datetime import timedelta, timezone

from paho.mqtt.client import Client
Expand Down Expand Up @@ -57,6 +58,8 @@ def __init__(self, skoda_connector: Connector) -> None:
self.on_subscribe = self._on_subscribe_callback
self.subscribed_topics: Set[str] = set()

self.delayed_access_function_timers: Dict[str, threading.Timer] = {}

self.tls_set(cert_reqs=ssl.CERT_NONE)

def connect(self, *args, **kwargs) -> MQTTErrorCode:
Expand Down Expand Up @@ -437,7 +440,7 @@ def _on_message_callback(self, mqttc, obj, msg) -> None: # noqa: C901
return

# service_events
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)
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)
if match:
user_id: str = match.group('user_id')
vin: str = match.group('vin')
Expand Down Expand Up @@ -481,7 +484,7 @@ def _on_message_callback(self, mqttc, obj, msg) -> None: # noqa: C901
# If charging state changed, fetch charging again
if old_charging_state != charging_state:
try:
self._skoda_connector.fetch_charging(vehicle)
self._skoda_connector.fetch_charging(vehicle, no_cache=True)
except CarConnectivityError as e:
LOG.error('Error while fetching charging: %s', e)
if 'timeToFinish' in data['data'] and data['data']['timeToFinish'] is not None \
Expand All @@ -507,12 +510,58 @@ def _on_message_callback(self, mqttc, obj, msg) -> None: # noqa: C901
vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
if isinstance(vehicle, SkodaVehicle):
try:
self._skoda_connector.fetch_air_conditioning(vehicle)
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
except CarConnectivityError as e:
LOG.error('Error while fetching charging: %s', e)
LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
service_event, vin, user_id, msg.payload)
return
elif service_event == 'vehicle-status/access':
if 'name' in data and data['name'] == 'change-access':
if 'data' in data and data['data'] is not None:
vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
if isinstance(vehicle, SkodaVehicle):
def delayed_access_function(vehicle: SkodaVehicle):
"""
Function to be executed after a delay of two seconds.
"""
vin = vehicle.id
self.delayed_access_function_timers.pop(vin)
if vehicle.capabilities is not None and vehicle.capabilities.enabled \
and vehicle.capabilities.has_capability('CHARGING') and isinstance(vehicle, SkodaElectricVehicle):
try:
self._skoda_connector.fetch_charging(vehicle, no_cache=True)
except CarConnectivityError as e:
LOG.error('Error while fetching charging: %s', e)
if vehicle.capabilities is not None and vehicle.capabilities.enabled \
and vehicle.capabilities.has_capability('PARKING_POSITION'):
try:
self._skoda_connector.fetch_position(vehicle, no_cache=True)
except CarConnectivityError as e:
LOG.error('Error while fetching position: %s', e)
if vehicle.capabilities is not None and vehicle.capabilities.enabled \
and vehicle.capabilities.has_capability('AIR_CONDITIONING'):
try:
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
except CarConnectivityError as e:
LOG.error('Error while fetching air conditioning: %s', e)
try:
self._skoda_connector.fetch_vehicle_status_second_api(vehicle, no_cache=True)
except CarConnectivityError as e:
LOG.error('Error while fetching status second API: %s', e)
try:
self._skoda_connector.fetch_driving_range(vehicle, no_cache=True)
except CarConnectivityError as e:
LOG.error('Error while fetching driving range: %s', e)

if vin in self.delayed_access_function_timers:
self.delayed_access_function_timers[vin].cancel()
self.delayed_access_function_timers[vin] = threading.Timer(2.0, delayed_access_function, kwargs={'vehicle': vehicle})
self.delayed_access_function_timers[vin].start()

LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
service_event, vin, user_id, msg.payload)
return
LOG_API.info('Received unknown service event %s for vehicle %s from user %s: %s', service_event, vin, user_id, msg.payload)
return
# service_events
Expand All @@ -530,7 +579,7 @@ def _on_message_callback(self, mqttc, obj, msg) -> None: # noqa: C901
if data['status'] == 'COMPLETED_SUCCESS':
LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
try:
self._skoda_connector.fetch_air_conditioning(vehicle)
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
except CarConnectivityError as e:
LOG.error('Error while fetching air-conditioning: %s', e)
return
Expand Down

0 comments on commit 6ce1bbf

Please sign in to comment.