carconnectivity-connector-skoda 0.1a11__py3-none-any.whl → 0.8.2__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.
@@ -0,0 +1,53 @@
1
+ """Module for Skoda vehicle capability class."""
2
+ from __future__ import annotations
3
+ from typing import TYPE_CHECKING
4
+
5
+ from enum import Enum
6
+
7
+ from carconnectivity.objects import GenericObject
8
+ from carconnectivity.attributes import EnumAttribute, StringAttribute
9
+
10
+ if TYPE_CHECKING:
11
+ from typing import Optional
12
+
13
+
14
+ class Error(GenericObject):
15
+ """
16
+ Represents an error object in the car connectivity context.
17
+ """
18
+
19
+ def __init__(self, object_id, parent: Optional[GenericObject] = None) -> None:
20
+ super().__init__(object_id, parent=parent)
21
+ self.type: EnumAttribute = EnumAttribute("type", parent=self, tags={'connector_custom'})
22
+ self.description: StringAttribute = StringAttribute("description", parent=self, tags={'connector_custom'})
23
+
24
+ class ChargingError(Enum):
25
+ """
26
+ Enum representing various charging errors for Skoda car connectivity.
27
+
28
+ Attributes:
29
+ STATUS_OF_CHARGING_NOT_AVAILABLE: Indicates that the status of charging is not available.
30
+ STATUS_OF_CONNECTION_NOT_AVAILABLE: Indicates that the status of connection is not available.
31
+ CARE_MODE_IS_NOT_AVAILABLE: Indicates that the care mode is not available.
32
+ AUTO_UNLOCK_IS_NOT_AVAILABLE: Indicates that the auto unlock feature is not available.
33
+ MAX_CHARGE_CURRENT_IS_NOT_AVAILABLE: Indicates that the maximum charge current setting is not available.
34
+ CHARGE_LIMIT_IS_NOT_AVAILABLE: Indicates that the charge limit setting is not available.
35
+ """
36
+ STATUS_OF_CHARGING_NOT_AVAILABLE = 'STATUS_OF_CHARGING_NOT_AVAILABLE'
37
+ STATUS_OF_CONNECTION_NOT_AVAILABLE = 'STATUS_OF_CONNECTION_NOT_AVAILABLE'
38
+ CARE_MODE_IS_NOT_AVAILABLE = 'CARE_MODE_IS_NOT_AVAILABLE'
39
+ AUTO_UNLOCK_IS_NOT_AVAILABLE = 'AUTO_UNLOCK_IS_NOT_AVAILABLE'
40
+ MAX_CHARGE_CURRENT_IS_NOT_AVAILABLE = 'MAX_CHARGE_CURRENT_IS_NOT_AVAILABLE'
41
+ CHARGE_LIMIT_IS_NOT_AVAILABLE = 'CHARGE_LIMIT_IS_NOT_AVAILABLE'
42
+ UNKNOWN = 'UNKNOWN'
43
+
44
+ class ClimatizationError(Enum):
45
+ """
46
+ ClimatizationError is an enumeration for representing various errors
47
+ related to the climatization system in a Skoda car.
48
+
49
+ This enum can be extended to include specific error codes and messages
50
+ that correspond to different climatization issues.
51
+ """
52
+ UNAVAILABLE_CHARGING_INFORMATION = 'UNAVAILABLE_CHARGING_INFORMATION'
53
+ UNKNOWN = 'UNKNOWN'
@@ -13,22 +13,31 @@ from datetime import timedelta, timezone
13
13
  from paho.mqtt.client import Client
14
14
  from paho.mqtt.enums import MQTTProtocolVersion, CallbackAPIVersion, MQTTErrorCode
15
15
 
16
- from carconnectivity.errors import CarConnectivityError
16
+ from carconnectivity.errors import CarConnectivityError, TemporaryAuthenticationError
17
17
  from carconnectivity.observable import Observable
18
18
  from carconnectivity.vehicle import GenericVehicle
19
19
 
20
20
  from carconnectivity.drive import ElectricDrive
21
21
  from carconnectivity.util import robust_time_parse, log_extra_keys
22
22
  from carconnectivity.charging import Charging
23
+ from carconnectivity.climatization import Climatization
24
+ from carconnectivity.units import Speed, Power, Length
25
+ from carconnectivity.enums import ConnectionState
23
26
 
24
27
  from carconnectivity_connectors.skoda.vehicle import SkodaVehicle, SkodaElectricVehicle
25
28
  from carconnectivity_connectors.skoda.charging import SkodaCharging, mapping_skoda_charging_state
26
29
 
27
30
 
28
31
  if TYPE_CHECKING:
29
- from typing import Set, Dict, Any, Optional
32
+ from typing import Set, Dict, Any, Optional, List
30
33
  from datetime import datetime
31
34
 
35
+ from paho.mqtt.client import MQTTMessage, DisconnectFlags, ConnectFlags
36
+ from paho.mqtt.reasoncodes import ReasonCode
37
+ from paho.mqtt.properties import Properties
38
+
39
+ from carconnectivity.attributes import GenericAttribute
40
+
32
41
  from carconnectivity_connectors.skoda.connector import Connector
33
42
 
34
43
 
@@ -61,6 +70,8 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
61
70
  self.delayed_access_function_timers: Dict[str, threading.Timer] = {}
62
71
 
63
72
  self.tls_set(cert_reqs=ssl.CERT_NONE)
73
+
74
+ self._retry_refresh_login_once = True
64
75
 
65
76
  def connect(self, *args, **kwargs) -> MQTTErrorCode:
66
77
  """
@@ -69,9 +80,10 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
69
80
  Returns:
70
81
  MQTTErrorCode: The result of the connection attempt.
71
82
  """
83
+ self._skoda_connector.connection_state._set_value(value=ConnectionState.CONNECTING) # pylint: disable=protected-access
72
84
  return super().connect(*args, host='mqtt.messagehub.de', port=8883, keepalive=60, **kwargs)
73
85
 
74
- def _on_pre_connect_callback(self, client, userdata) -> None:
86
+ def _on_pre_connect_callback(self, client: Client, userdata: Any) -> None:
75
87
  """
76
88
  Callback function that is called before the MQTT client connects to the broker.
77
89
 
@@ -88,12 +100,17 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
88
100
  del userdata
89
101
 
90
102
  if self._skoda_connector.session.expired or self._skoda_connector.session.access_token is None:
91
- self._skoda_connector.session.refresh()
103
+ try:
104
+ self._skoda_connector.session.refresh()
105
+ except ConnectionError as exc:
106
+ LOG.error('Token refresh failed due to connection error: %s', exc)
107
+ except TemporaryAuthenticationError as exc:
108
+ LOG.error('Token refresh failed due to temporary MySkoda error: %s', exc)
92
109
  if not self._skoda_connector.session.expired and self._skoda_connector.session.access_token is not None:
93
110
  # pylint: disable-next=attribute-defined-outside-init # this is a false positive, password has a setter in super class
94
111
  self._password = self._skoda_connector.session.access_token # This is a bit hacky but if password attribute is used here there is an Exception
95
112
 
96
- def _on_carconnectivity_vehicle_enabled(self, element, flags):
113
+ def _on_carconnectivity_vehicle_enabled(self, element: GenericAttribute, flags: Observable.ObserverEvent) -> None:
97
114
  """
98
115
  Handles the event when a vehicle is enabled or disabled in the car connectivity system.
99
116
 
@@ -171,7 +188,10 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
171
188
  vin: str = vehicle.vin.value
172
189
  # If the skoda connector is managing this vehicle
173
190
  if self._skoda_connector in vehicle.managing_connectors:
174
- account_events: Set[str] = {'privacy'}
191
+ account_events: Set[str] = {'privacy',
192
+ 'guest-user-nomination',
193
+ 'primary-user-nomination'}
194
+ vehicle_status_events: Set[str] = {'vehicle-connection-status'}
175
195
  operation_requests: Set[str] = {
176
196
  'air-conditioning/set-air-conditioning-at-unlock',
177
197
  'air-conditioning/set-air-conditioning-seats-heating',
@@ -208,6 +228,8 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
208
228
  # Compile all possible topics
209
229
  for event in account_events:
210
230
  possible_topics.add(f'{user_id}/{vin}/account-event/{event}')
231
+ for event in vehicle_status_events:
232
+ possible_topics.add(f'{user_id}/{vin}/vehicle-status/{event}')
211
233
  for event in operation_requests:
212
234
  possible_topics.add(f'{user_id}/{vin}/operation-request/{event}')
213
235
  for event in service_events:
@@ -251,7 +273,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
251
273
  self.subscribed_topics.remove(topic)
252
274
  LOG.debug('Unsubscribed from topic %s', topic)
253
275
 
254
- def _on_connect_callback(self, mqttc, obj, flags, reason_code, properties) -> None:
276
+ def _on_connect_callback(self, client: Client, obj: Any, flags: ConnectFlags, reason_code: ReasonCode, properties: Optional[Properties]) -> None:
255
277
  """
256
278
  Callback function that is called when the MQTT client connects to the broker.
257
279
 
@@ -292,18 +314,21 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
292
314
  - 159: Connection rate exceeded.
293
315
  - Other: Generic connection error.
294
316
  """
295
- del mqttc # unused
317
+ del client # unused
296
318
  del obj # unused
297
319
  del flags # unused
298
320
  del properties
299
321
  # reason_code 0 means success
300
322
  if reason_code == 0:
301
323
  LOG.info('Connected to Skoda MQTT server')
302
- self._skoda_connector.connected._set_value(value=True) # pylint: disable=protected-access
324
+ if self._skoda_connector.rest_connected:
325
+ self._skoda_connector.connection_state._set_value(value=ConnectionState.CONNECTED) # pylint: disable=protected-access
326
+ self._skoda_connector.mqtt_connected = True
303
327
  observer_flags: Observable.ObserverEvent = Observable.ObserverEvent.ENABLED | Observable.ObserverEvent.DISABLED
304
328
  self._skoda_connector.car_connectivity.garage.add_observer(observer=self._on_carconnectivity_vehicle_enabled,
305
329
  flag=observer_flags,
306
330
  priority=Observable.ObserverPriority.USER_MID)
331
+ self._retry_refresh_login_once = True
307
332
  self._subscribe_vehicles()
308
333
 
309
334
  # Handle different reason codes
@@ -321,6 +346,15 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
321
346
  LOG.error('Could not connect (%s): Client identifier not valid', reason_code)
322
347
  elif reason_code == 134:
323
348
  LOG.error('Could not connect (%s): Bad user name or password', reason_code)
349
+ if self._retry_refresh_login_once == True:
350
+ self._retry_refresh_login_once = False
351
+ LOG.info('trying a relogin once to resolve the error')
352
+ try:
353
+ self._skoda_connector.session.login()
354
+ except TemporaryAuthenticationError as exc:
355
+ LOG.error('Login failed due to temporary MySkoda error: %s', exc)
356
+ except ConnectionError as exc:
357
+ LOG.error('Login failed due to connection error: %s', exc)
324
358
  elif reason_code == 135:
325
359
  LOG.error('Could not connect (%s): Not authorized', reason_code)
326
360
  elif reason_code == 136:
@@ -350,8 +384,8 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
350
384
  else:
351
385
  LOG.error('Could not connect (%s)', reason_code)
352
386
 
353
- def _on_disconnect_callback(self, client, userdata, flags, reason_code, properties) -> None:
354
- """
387
+ def _on_disconnect_callback(self, client: Client, userdata, flags: DisconnectFlags, reason_code: ReasonCode, properties: Optional[Properties]) -> None:
388
+ """["Client", Any, DisconnectFlags, ReasonCode, Union[Properties, None]
355
389
  Callback function that is called when the MQTT client disconnects.
356
390
 
357
391
  This function handles the disconnection of the MQTT client and logs the appropriate
@@ -372,7 +406,8 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
372
406
  del properties
373
407
  del flags
374
408
 
375
- self._skoda_connector.connected._set_value(value=False) # pylint: disable=protected-access
409
+ self._skoda_connector.connection_state._set_value(value=ConnectionState.DISCONNECTED) # pylint: disable=protected-access
410
+ self._skoda_connector.mqtt_connected = False
376
411
  self._skoda_connector.car_connectivity.garage.remove_observer(observer=self._on_carconnectivity_vehicle_enabled)
377
412
 
378
413
  self.subscribed_topics.clear()
@@ -390,9 +425,9 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
390
425
  elif reason_code == 160:
391
426
  LOG.error('Client disconnected: Maximum connect time')
392
427
  else:
393
- LOG.error('Client unexpectedly disconnected (%d: %s), trying to reconnect', reason_code, reason_code)
428
+ LOG.error('Client unexpectedly disconnected (%d: %s), trying to reconnect', reason_code.value, reason_code.getName())
394
429
 
395
- def _on_subscribe_callback(self, mqttc, obj, mid, reason_codes, properties) -> None:
430
+ def _on_subscribe_callback(self, client: Client, obj: Any, mid: int, reason_codes: List[ReasonCode], properties: Optional[Properties]) -> None:
396
431
  """
397
432
  Callback function for MQTT subscription.
398
433
 
@@ -409,15 +444,15 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
409
444
  Returns:
410
445
  None
411
446
  """
412
- del mqttc # unused
447
+ del client # unused
413
448
  del obj # unused
414
449
  del properties # unused
415
450
  if any(x in [0, 1, 2] for x in reason_codes):
416
451
  LOG.debug('sucessfully subscribed to topic of mid %d', mid)
417
452
  else:
418
- LOG.error('Subscribe was not successfull (%s)', ', '.join(reason_codes))
453
+ LOG.error('Subscribe was not successfull (%s)', ', '.join([reason_code.getName() for reason_code in reason_codes]))
419
454
 
420
- def _on_message_callback(self, mqttc, obj, msg) -> None: # noqa: C901
455
+ def _on_message_callback(self, client: Client, obj: Any, msg: MQTTMessage) -> None: # noqa: C901
421
456
  """
422
457
  Callback function for handling incoming MQTT messages.
423
458
 
@@ -433,7 +468,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
433
468
  Returns:
434
469
  None
435
470
  """
436
- del mqttc # unused
471
+ del client # unused
437
472
  del obj # unused
438
473
  if len(msg.payload) == 0:
439
474
  LOG_API.debug('MQTT topic %s: ignoring empty message', msg.topic)
@@ -456,10 +491,19 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
456
491
  if 'data' in data and data['data'] is not None:
457
492
  vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
458
493
  if isinstance(vehicle, SkodaElectricVehicle):
459
- electric_drive: ElectricDrive = vehicle.get_electric_drive()
494
+ electric_drive: Optional[ElectricDrive] = vehicle.get_electric_drive()
460
495
  if electric_drive is not None:
461
496
  charging_state: Optional[Charging.ChargingState] = vehicle.charging.state.value
462
497
  old_charging_state: Optional[Charging.ChargingState] = charging_state
498
+ if 'mode' in data['data'] and data['data']['mode'] is not None \
499
+ and vehicle.charging is not None and isinstance(vehicle.charging.settings, SkodaCharging.Settings):
500
+ if data['data']['mode'] in [item.value for item in SkodaCharging.SkodaChargeMode]:
501
+ skoda_charging_mode = SkodaCharging.SkodaChargeMode(data['data']['mode'])
502
+ else:
503
+ LOG_API.info('Unkown charging mode %s not in %s', data['data']['mode'], str(SkodaCharging.SkodaChargeMode))
504
+ skoda_charging_mode = Charging.ChargingState.UNKNOWN
505
+ # pylint: disable-next=protected-access
506
+ vehicle.charging.settings.preferred_charge_mode._set_value(value=skoda_charging_mode, measured=measured_at)
463
507
  if 'state' in data['data'] and data['data']['state'] is not None:
464
508
  if data['data']['state'] in [item.value for item in SkodaCharging.SkodaChargingState]:
465
509
  skoda_charging_state = SkodaCharging.SkodaChargingState(data['data']['state'])
@@ -473,18 +517,21 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
473
517
  # pylint: disable-next=protected-access
474
518
  vehicle.charging.type._set_value(value=Charging.ChargingType.OFF, measured=measured_at)
475
519
  # pylint: disable-next=protected-access
476
- vehicle.charging.rate._set_value(value=0, measured=measured_at)
520
+ vehicle.charging.rate._set_value(value=0, measured=measured_at, unit=Speed.KMH)
477
521
  # pylint: disable-next=protected-access
478
- vehicle.charging.power._set_value(value=0, measured=measured_at)
522
+ vehicle.charging.power._set_value(value=0, measured=measured_at, unit=Power.KW)
479
523
  if 'soc' in data['data'] and data['data']['soc'] is not None:
524
+ if isinstance(data['data']['soc'], str):
525
+ data['data']['soc'] = int(data['data']['soc'])
480
526
  electric_drive.level._set_value(measured=measured_at, value=data['data']['soc']) # pylint: disable=protected-access
481
527
  if 'chargedRange' in data['data'] and data['data']['chargedRange'] is not None:
482
528
  # pylint: disable-next=protected-access
483
- electric_drive.range._set_value(measured=measured_at, value=data['data']['chargedRange'])
529
+ electric_drive.range._set_value(measured=measured_at, value=data['data']['chargedRange'], unit=Length.KM)
484
530
  # If charging state changed, fetch charging again
485
531
  if old_charging_state != charging_state:
486
532
  try:
487
533
  self._skoda_connector.fetch_charging(vehicle, no_cache=True)
534
+ self._skoda_connector.car_connectivity.transaction_end()
488
535
  except CarConnectivityError as e:
489
536
  LOG.error('Error while fetching charging: %s', e)
490
537
  if 'timeToFinish' in data['data'] and data['data']['timeToFinish'] is not None \
@@ -492,11 +539,12 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
492
539
  try:
493
540
  remaining_duration: Optional[timedelta] = timedelta(minutes=int(data['data']['timeToFinish']))
494
541
  estimated_date_reached: Optional[datetime] = measured_at + remaining_duration
542
+ estimated_date_reached = estimated_date_reached.replace(second=0, microsecond=0)
495
543
  except ValueError:
496
544
  estimated_date_reached: Optional[datetime] = None
497
545
  # pylint: disable-next=protected-access
498
546
  vehicle.charging.estimated_date_reached._set_value(measured=measured_at, value=estimated_date_reached)
499
- log_extra_keys(LOG_API, 'data', data['data'], {'vin', 'userId', 'soc', 'chargedRange', 'timeToFinish', 'state'})
547
+ log_extra_keys(LOG_API, 'data', data['data'], {'vin', 'userId', 'soc', 'chargedRange', 'timeToFinish', 'state', 'mode'})
500
548
  LOG.debug('Received %s event for vehicle %s from user %s', data['name'], vin, user_id)
501
549
  return
502
550
  else:
@@ -511,8 +559,17 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
511
559
  if isinstance(vehicle, SkodaVehicle):
512
560
  try:
513
561
  self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
562
+ self._skoda_connector.car_connectivity.transaction_end()
514
563
  except CarConnectivityError as e:
515
- LOG.error('Error while fetching charging: %s', e)
564
+ LOG.error('Error while fetching air conditioning: %s', e)
565
+ elif 'name' in data and data['name'] == 'climatisation-completed':
566
+ if 'data' in data and data['data'] is not None:
567
+ vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
568
+ if vehicle is not None and vehicle.climatization is not None:
569
+ # pylint: disable-next=protected-access
570
+ vehicle.climatization.state._set_value(value=Climatization.ClimatizationState.OFF, measured=measured_at)
571
+ # pylint: disable-next=protected-access
572
+ vehicle.climatization.estimated_date_reached._set_value(value=measured_at, measured=measured_at)
516
573
  LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
517
574
  service_event, vin, user_id, msg.payload)
518
575
  return
@@ -527,6 +584,10 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
527
584
  """
528
585
  vin = vehicle.id
529
586
  self.delayed_access_function_timers.pop(vin)
587
+ try:
588
+ self._skoda_connector.fetch_vehicle_status(vehicle, no_cache=True)
589
+ except CarConnectivityError as e:
590
+ LOG.error('Error while fetching vehicle status: %s', e)
530
591
  if vehicle.capabilities is not None and vehicle.capabilities.enabled \
531
592
  and vehicle.capabilities.has_capability('CHARGING') and isinstance(vehicle, SkodaElectricVehicle):
532
593
  try:
@@ -545,26 +606,33 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
545
606
  self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
546
607
  except CarConnectivityError as e:
547
608
  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)
609
+ self._skoda_connector.car_connectivity.transaction_end()
556
610
 
557
611
  if vin in self.delayed_access_function_timers:
558
612
  self.delayed_access_function_timers[vin].cancel()
559
613
  self.delayed_access_function_timers[vin] = threading.Timer(2.0, delayed_access_function, kwargs={'vehicle': vehicle})
560
614
  self.delayed_access_function_timers[vin].start()
561
615
 
616
+ LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
617
+ service_event, vin, user_id, msg.payload)
618
+ return
619
+ elif service_event == 'vehicle-status/lights':
620
+ if 'name' in data and data['name'] == 'change-lights':
621
+ if 'data' in data and data['data'] is not None:
622
+ vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
623
+ if isinstance(vehicle, SkodaVehicle):
624
+ try:
625
+ self._skoda_connector.fetch_vehicle_status(vehicle, no_cache=True)
626
+ self._skoda_connector.car_connectivity.transaction_end()
627
+ except CarConnectivityError as e:
628
+ LOG.error('Error while fetching vehicle status: %s', e)
629
+
562
630
  LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
563
631
  service_event, vin, user_id, msg.payload)
564
632
  return
565
633
  LOG_API.info('Received unknown service event %s for vehicle %s from user %s: %s', service_event, vin, user_id, msg.payload)
566
634
  return
567
- # service_events
635
+ # operation-requests
568
636
  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
637
  if match:
570
638
  user_id: str = match.group('user_id')
@@ -572,17 +640,49 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
572
640
  operation_request: str = match.group('operation_request')
573
641
  data: Dict[str, Any] = json.loads(msg.payload)
574
642
  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)
643
+ vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
644
+ if operation_request == 'air-conditioning/set-air-conditioning-at-unlock' \
645
+ or operation_request == 'air-conditioning/set-air-conditioning-seats-heating' \
646
+ or operation_request == 'air-conditioning/set-air-conditioning-timers' \
647
+ or operation_request == 'air-conditioning/set-air-conditioning-without-external-power' \
648
+ or operation_request == 'air-conditioning/set-target-temperature' \
649
+ or operation_request == 'air-conditioning/start-stop-air-conditioning' \
650
+ or operation_request == 'air-conditioning/start-stop-window-heating' \
651
+ or operation_request == 'air-conditioning/windows-heating':
577
652
  if isinstance(vehicle, SkodaVehicle):
578
653
  if 'status' in data and data['status'] is not None:
579
654
  if data['status'] == 'COMPLETED_SUCCESS':
580
655
  LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
581
656
  try:
582
657
  self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
658
+ self._skoda_connector.car_connectivity.transaction_end()
583
659
  except CarConnectivityError as e:
584
660
  LOG.error('Error while fetching air-conditioning: %s', e)
585
661
  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
662
+ elif data['status'] == 'IN_PROGRESS':
663
+ LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
664
+ return
665
+ elif operation_request == 'charging/start-stop-charging' \
666
+ or operation_request == 'charging/update-battery-support' \
667
+ or operation_request == 'charging/update-auto-unlock-plug' \
668
+ or operation_request == 'charging/update-care-mode' \
669
+ or operation_request == 'charging/update-charge-limit' \
670
+ or operation_request == 'charging/update-charge-mode' \
671
+ or operation_request == 'charging/update-charging-profiles' \
672
+ or operation_request == 'charging/update-charging-current':
673
+ if isinstance(vehicle, SkodaElectricVehicle):
674
+ if 'status' in data and data['status'] is not None:
675
+ if data['status'] == 'COMPLETED_SUCCESS':
676
+ LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
677
+ try:
678
+ self._skoda_connector.fetch_charging(vehicle, no_cache=True)
679
+ self._skoda_connector.car_connectivity.transaction_end()
680
+ except CarConnectivityError as e:
681
+ LOG.error('Error while fetching charging: %s', e)
682
+ return
683
+ elif data['status'] == 'IN_PROGRESS':
684
+ LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
685
+ return
686
+ LOG_API.info('Received unknown operation request %s for vehicle %s from user %s: %s', operation_request, vin, user_id, msg.payload)
687
+ return
588
688
  LOG_API.info('I don\'t understand message %s: %s', msg.topic, msg.payload)
@@ -0,0 +1,39 @@
1
+ """ User interface for the Skoda connector in the Car Connectivity application. """
2
+ from __future__ import annotations
3
+ from typing import TYPE_CHECKING
4
+
5
+ import os
6
+
7
+ import flask
8
+
9
+ from carconnectivity_connectors.base.ui.connector_ui import BaseConnectorUI
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Optional, List, Dict, Union, Literal
13
+
14
+ from carconnectivity_connectors.base.connector import BaseConnector
15
+
16
+
17
+ class ConnectorUI(BaseConnectorUI):
18
+ """
19
+ A user interface class for the Skoda connector in the Car Connectivity application.
20
+ """
21
+ def __init__(self, connector: BaseConnector):
22
+ blueprint: Optional[flask.Blueprint] = flask.Blueprint(name=connector.id, import_name='carconnectivity-connector-skoda', url_prefix=f'/{connector.id}',
23
+ template_folder=os.path.dirname(__file__) + '/templates')
24
+ super().__init__(connector, blueprint=blueprint)
25
+
26
+ def get_nav_items(self) -> List[Dict[Literal['text', 'url', 'sublinks', 'divider'], Union[str, List]]]:
27
+ """
28
+ Generates a list of navigation items for the Skoda connector UI.
29
+ """
30
+ return super().get_nav_items()
31
+
32
+ def get_title(self) -> str:
33
+ """
34
+ Returns the title of the connector.
35
+
36
+ Returns:
37
+ str: The title of the connector, which is "Skoda".
38
+ """
39
+ return "Skoda"
@@ -3,11 +3,22 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from carconnectivity.vehicle import GenericVehicle, ElectricVehicle, CombustionVehicle, HybridVehicle
6
+ from carconnectivity.charging import Charging
7
+ from carconnectivity.attributes import BooleanAttribute
6
8
 
7
9
  from carconnectivity_connectors.skoda.capability import Capabilities
10
+ from carconnectivity_connectors.skoda.charging import SkodaCharging
11
+ from carconnectivity_connectors.skoda.climatization import SkodaClimatization
12
+
13
+ SUPPORT_IMAGES = False
14
+ try:
15
+ from PIL import Image
16
+ SUPPORT_IMAGES = True
17
+ except ImportError:
18
+ pass
8
19
 
9
20
  if TYPE_CHECKING:
10
- from typing import Optional
21
+ from typing import Optional, Dict
11
22
  from carconnectivity.garage import Garage
12
23
  from carconnectivity_connectors.base.connector import BaseConnector
13
24
 
@@ -19,16 +30,22 @@ class SkodaVehicle(GenericVehicle): # pylint: disable=too-many-instance-attribu
19
30
  def __init__(self, vin: Optional[str] = None, garage: Optional[Garage] = None, managing_connector: Optional[BaseConnector] = None,
20
31
  origin: Optional[SkodaVehicle] = None) -> None:
21
32
  if origin is not None:
22
- super().__init__(origin=origin)
33
+ super().__init__(garage=garage, origin=origin)
23
34
  self.capabilities: Capabilities = origin.capabilities
24
35
  self.capabilities.parent = self
36
+ self.in_motion: BooleanAttribute = origin.in_motion
37
+ self.in_motion.parent = self
38
+ if SUPPORT_IMAGES:
39
+ self._car_images = origin._car_images
40
+
25
41
  else:
26
42
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
43
+ self.climatization = SkodaClimatization(vehicle=self, origin=self.climatization)
27
44
  self.capabilities = Capabilities(vehicle=self)
28
-
29
- def __str__(self) -> str:
30
- return_string: str = f'\t{self.capabilities}\n'
31
- return return_string
45
+ self.in_motion = BooleanAttribute(name='in_motion', parent=self, tags={'connector_custom'})
46
+ if SUPPORT_IMAGES:
47
+ self._car_images: Dict[str, Image.Image] = {}
48
+ self.manufacturer._set_value(value='Škoda') # pylint: disable=protected-access
32
49
 
33
50
 
34
51
  class SkodaElectricVehicle(ElectricVehicle, SkodaVehicle):
@@ -38,9 +55,14 @@ class SkodaElectricVehicle(ElectricVehicle, SkodaVehicle):
38
55
  def __init__(self, vin: Optional[str] = None, garage: Optional[Garage] = None, managing_connector: Optional[BaseConnector] = None,
39
56
  origin: Optional[SkodaVehicle] = None) -> None:
40
57
  if origin is not None:
41
- super().__init__(origin=origin)
58
+ super().__init__(garage=garage, origin=origin)
59
+ if isinstance(origin, ElectricVehicle):
60
+ self.charging: Charging = SkodaCharging(vehicle=self, origin=origin.charging)
61
+ else:
62
+ self.charging: Charging = SkodaCharging(vehicle=self, origin=self.charging)
42
63
  else:
43
64
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
65
+ self.charging: Charging = SkodaCharging(vehicle=self, origin=self.charging)
44
66
 
45
67
 
46
68
  class SkodaCombustionVehicle(CombustionVehicle, SkodaVehicle):
@@ -50,7 +72,7 @@ class SkodaCombustionVehicle(CombustionVehicle, SkodaVehicle):
50
72
  def __init__(self, vin: Optional[str] = None, garage: Optional[Garage] = None, managing_connector: Optional[BaseConnector] = None,
51
73
  origin: Optional[SkodaVehicle] = None) -> None:
52
74
  if origin is not None:
53
- super().__init__(origin=origin)
75
+ super().__init__(garage=garage, origin=origin)
54
76
  else:
55
77
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
56
78
 
@@ -62,6 +84,6 @@ class SkodaHybridVehicle(HybridVehicle, SkodaVehicle):
62
84
  def __init__(self, vin: Optional[str] = None, garage: Optional[Garage] = None, managing_connector: Optional[BaseConnector] = None,
63
85
  origin: Optional[SkodaVehicle] = None) -> None:
64
86
  if origin is not None:
65
- super().__init__(origin=origin)
87
+ super().__init__(garage=garage, origin=origin)
66
88
  else:
67
89
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
@@ -1,19 +0,0 @@
1
- carconnectivity_connectors/skoda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- carconnectivity_connectors/skoda/_version.py,sha256=lXj09wNlQE2ygonVVc_Fk2FN_1_4nx6sHnElAwbvzHM,409
3
- carconnectivity_connectors/skoda/capability.py,sha256=JlNEaisVYF8qWv0wNDHTaas36uIpTIQ3NVR69wesiYQ,4513
4
- carconnectivity_connectors/skoda/charging.py,sha256=oDHxZxrfTMvtYCJxmGfKFeWVMH4ceQ5HTKRAspnsunU,3312
5
- carconnectivity_connectors/skoda/connector.py,sha256=xiPPT6XTVWsLvu0iqZvSPpgtHm3DrR1TKUL4d4NRz2Y,62158
6
- carconnectivity_connectors/skoda/mqtt_client.py,sha256=ZB56Za1k5vbxQPl5qCvukE46Tq1O0-Cn19kBz_bDJZM,31782
7
- carconnectivity_connectors/skoda/vehicle.py,sha256=H3GRDNimMghFwFi--y9BsgoSK3pMibNf_l6SsDN6gvQ,2759
8
- carconnectivity_connectors/skoda/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- carconnectivity_connectors/skoda/auth/auth_util.py,sha256=dGLUbUre0HBsTg_Ii5vW34f8DLrCykYJYCyzEvUBBEE,4434
10
- carconnectivity_connectors/skoda/auth/my_skoda_session.py,sha256=lSh23SFJs8opjmPwHTv-KNIKDep_WY4aItSP4Zq7bT8,10396
11
- carconnectivity_connectors/skoda/auth/openid_session.py,sha256=LusWi2FZZIL3buodGXZKUR0naLhhqeYv0uRW4V3wI2w,16842
12
- carconnectivity_connectors/skoda/auth/session_manager.py,sha256=Uf1vujuDBYUCAXhYToOsZkgbTtfmY3Qe0ICTfwomBpI,2899
13
- carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=cjzMkzx473Sh-4RgZAQULeRRcxB1MboddldCVM_y5LE,10640
14
- carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
15
- carconnectivity_connector_skoda-0.1a11.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
16
- carconnectivity_connector_skoda-0.1a11.dist-info/METADATA,sha256=tS2ccqTBaAHPxFbNRA7wlbJQPiqLDU8J9h62FQcXvb8,5327
17
- carconnectivity_connector_skoda-0.1a11.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
18
- carconnectivity_connector_skoda-0.1a11.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
19
- carconnectivity_connector_skoda-0.1a11.dist-info/RECORD,,