carconnectivity-connector-skoda 0.1a17__py3-none-any.whl → 0.1a19__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
1
  Metadata-Version: 2.2
2
2
  Name: carconnectivity-connector-skoda
3
- Version: 0.1a17
3
+ Version: 0.1a19
4
4
  Summary: CarConnectivity connector for Skoda services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -1,10 +1,11 @@
1
1
  carconnectivity_connectors/skoda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- carconnectivity_connectors/skoda/_version.py,sha256=waXAIf0j4Aq__xvEJNJvHzQvbn3T7HTylT_B7dyZT10,409
2
+ carconnectivity_connectors/skoda/_version.py,sha256=aeCFhA4z1zQM56Os0M87SAzNslmAdFOPIN0S12RtpgI,409
3
3
  carconnectivity_connectors/skoda/capability.py,sha256=JlNEaisVYF8qWv0wNDHTaas36uIpTIQ3NVR69wesiYQ,4513
4
- carconnectivity_connectors/skoda/charging.py,sha256=OXy4Yr9bw5H_6BnGbvQTDyvxaIg8QN9YUog0dashdGE,6669
5
- carconnectivity_connectors/skoda/connector.py,sha256=7307glMfF1EbhFzxrdomC_pvbHysyvq_cd4YoiHvG2M,79859
6
- carconnectivity_connectors/skoda/error.py,sha256=xM8Ldgj-Biy793if3yTP5U7QIaBnOT7haEkxbXH4uYA,1962
7
- carconnectivity_connectors/skoda/mqtt_client.py,sha256=AKRv2nmH3MfppFR3ub4Bg0oTZEhqxC-i9YVrmyHGq3U,34824
4
+ carconnectivity_connectors/skoda/charging.py,sha256=wkwZG4m-aZn2TGX7BzzZ0J8-mRnSsPmxcFmT0bXy0WU,6669
5
+ carconnectivity_connectors/skoda/climatization.py,sha256=-Nk4tO5C5_YYNQfUIUWBL7mGgR6-J0_pOZplLK8p_ms,1627
6
+ carconnectivity_connectors/skoda/connector.py,sha256=ULF57ZCNk5co04tEeM7_zrpI5xBeoFFRPt1y6VIeql4,105676
7
+ carconnectivity_connectors/skoda/error.py,sha256=EnzzDxxJ1fswYT5QnMOVSebfoAcqoPmHKcG5i0Tqk3E,2405
8
+ carconnectivity_connectors/skoda/mqtt_client.py,sha256=QUdpjWaEe2zVHS1yZAqfFsmGGcXAkpKUcyeG29StJ5E,37325
8
9
  carconnectivity_connectors/skoda/vehicle.py,sha256=FbrhxZF-5TOUiPzUvryeFZrT-ie1XIyjRO4RbMymlJs,3115
9
10
  carconnectivity_connectors/skoda/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
11
  carconnectivity_connectors/skoda/auth/auth_util.py,sha256=dGLUbUre0HBsTg_Ii5vW34f8DLrCykYJYCyzEvUBBEE,4434
@@ -13,8 +14,8 @@ carconnectivity_connectors/skoda/auth/openid_session.py,sha256=LusWi2FZZIL3buodG
13
14
  carconnectivity_connectors/skoda/auth/session_manager.py,sha256=Uf1vujuDBYUCAXhYToOsZkgbTtfmY3Qe0ICTfwomBpI,2899
14
15
  carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=cjzMkzx473Sh-4RgZAQULeRRcxB1MboddldCVM_y5LE,10640
15
16
  carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
16
- carconnectivity_connector_skoda-0.1a17.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
17
- carconnectivity_connector_skoda-0.1a17.dist-info/METADATA,sha256=mlKJheSBMcIcszI4631ex5Eve1lzOt-a38eW1xRUBBs,5327
18
- carconnectivity_connector_skoda-0.1a17.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
19
- carconnectivity_connector_skoda-0.1a17.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
20
- carconnectivity_connector_skoda-0.1a17.dist-info/RECORD,,
17
+ carconnectivity_connector_skoda-0.1a19.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
18
+ carconnectivity_connector_skoda-0.1a19.dist-info/METADATA,sha256=YxflkB35eOkKtS7D4nT67RG5Wqw689dGB5toDCb3pY0,5327
19
+ carconnectivity_connector_skoda-0.1a19.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
20
+ carconnectivity_connector_skoda-0.1a19.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
21
+ carconnectivity_connector_skoda-0.1a19.dist-info/RECORD,,
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.1a17'
15
+ __version__ = version = '0.1a19'
16
16
  __version_tuple__ = version_tuple = (0, 1)
@@ -97,9 +97,9 @@ class SkodaCharging(Charging): # pylint: disable=too-many-instance-attributes
97
97
  ONLY_OWN_CURRENT = 'ONLY_OWN_CURRENT'
98
98
  PREFERRED_CHARGING_TIMES = 'PREFERRED_CHARGING_TIMES'
99
99
  TIMER_CHARGING_WITH_CLIMATISATION = 'TIMER_CHARGING_WITH_CLIMATISATION'
100
- TIMER = 'TIMER'
101
- MANUAL = 'MANUAL'
102
- OFF = 'OFF'
100
+ TIMER = 'timer'
101
+ MANUAL = 'manual'
102
+ OFF = 'off'
103
103
  UNKNOWN = 'unknown charge mode'
104
104
 
105
105
  class SkodaChargingCareMode(Enum,):
@@ -0,0 +1,41 @@
1
+ """
2
+ Module for charging for skoda vehicles.
3
+ """
4
+ from __future__ import annotations
5
+ from typing import TYPE_CHECKING
6
+
7
+ from carconnectivity.climatization import Climatization
8
+ from carconnectivity.objects import GenericObject
9
+ from carconnectivity.vehicle import ElectricVehicle
10
+
11
+ from carconnectivity_connectors.skoda.error import Error
12
+
13
+ if TYPE_CHECKING:
14
+ from typing import Optional, Dict
15
+
16
+
17
+ class SkodaClimatization(Climatization): # pylint: disable=too-many-instance-attributes
18
+ """
19
+ SkodaClimatization class for handling Skoda vehicle climatization information.
20
+
21
+ This class extends the Climatization class and includes an enumeration of various
22
+ charging states specific to Skoda vehicles.
23
+ """
24
+ def __init__(self, vehicle: ElectricVehicle | None = None, origin: Optional[Climatization] = None) -> None:
25
+ if origin is not None:
26
+ super().__init__(origin=origin)
27
+ self.settings: Climatization.Settings = SkodaClimatization.Settings(origin=origin.settings)
28
+ else:
29
+ super().__init__(vehicle=vehicle)
30
+ self.settings: Climatization.Settings = SkodaClimatization.Settings(origin=self.settings)
31
+ self.errors: Dict[str, Error] = {}
32
+
33
+ class Settings(Climatization.Settings):
34
+ """
35
+ This class represents the settings for a skoda car climatiation.
36
+ """
37
+ def __init__(self, parent: Optional[GenericObject] = None, origin: Optional[Climatization.Settings] = None) -> None:
38
+ if origin is not None:
39
+ super().__init__(origin=origin)
40
+ else:
41
+ super().__init__(parent=parent)
@@ -7,23 +7,27 @@ import os
7
7
  import logging
8
8
  import netrc
9
9
  from datetime import datetime, timedelta, timezone
10
+ import json
11
+
10
12
  import requests
11
13
 
14
+
12
15
  from carconnectivity.garage import Garage
13
16
  from carconnectivity.vehicle import GenericVehicle
14
17
  from carconnectivity.errors import AuthenticationError, TooManyRequestsError, RetrievalError, APIError, APICompatibilityError, \
15
- TemporaryAuthenticationError, ConfigurationError
18
+ TemporaryAuthenticationError, ConfigurationError, SetterError
16
19
  from carconnectivity.util import robust_time_parse, log_extra_keys, config_remove_credentials
17
20
  from carconnectivity.units import Length, Speed, Power, Temperature
18
- # from carconnectivity.doors import Doors
19
- # from carconnectivity.windows import Windows
20
- # from carconnectivity.lights import Lights
21
+ from carconnectivity.doors import Doors
22
+ from carconnectivity.windows import Windows
23
+ from carconnectivity.lights import Lights
21
24
  from carconnectivity.drive import GenericDrive, ElectricDrive, CombustionDrive
22
- from carconnectivity.attributes import BooleanAttribute, DurationAttribute
25
+ from carconnectivity.attributes import BooleanAttribute, DurationAttribute, TemperatureAttribute
23
26
  from carconnectivity.charging import Charging
24
27
  from carconnectivity.position import Position
25
28
  from carconnectivity.climatization import Climatization
26
29
  from carconnectivity.charging_connector import ChargingConnector
30
+ from carconnectivity.command_impl import ClimatizationStartStopCommand
27
31
 
28
32
  from carconnectivity_connectors.base.connector import BaseConnector
29
33
  from carconnectivity_connectors.skoda.auth.session_manager import SessionManager, SessionUser, Service
@@ -31,12 +35,13 @@ from carconnectivity_connectors.skoda.auth.my_skoda_session import MySkodaSessio
31
35
  from carconnectivity_connectors.skoda.vehicle import SkodaVehicle, SkodaElectricVehicle, SkodaCombustionVehicle, SkodaHybridVehicle
32
36
  from carconnectivity_connectors.skoda.capability import Capability
33
37
  from carconnectivity_connectors.skoda.charging import SkodaCharging, mapping_skoda_charging_state
38
+ from carconnectivity_connectors.skoda.climatization import SkodaClimatization
34
39
  from carconnectivity_connectors.skoda.error import Error
35
40
  from carconnectivity_connectors.skoda._version import __version__
36
41
  from carconnectivity_connectors.skoda.mqtt_client import SkodaMQTTClient
37
42
 
38
43
  if TYPE_CHECKING:
39
- from typing import Dict, List, Optional, Any, Set
44
+ from typing import Dict, List, Optional, Any, Set, Union
40
45
 
41
46
  from carconnectivity.carconnectivity import CarConnectivity
42
47
 
@@ -170,10 +175,10 @@ class Connector(BaseConnector):
170
175
  self.update_vehicles()
171
176
  self.last_update._set_value(value=datetime.now(tz=timezone.utc)) # pylint: disable=protected-access
172
177
  if self.interval.value is not None:
173
- interval: int = self.interval.value.total_seconds()
178
+ interval: float = self.interval.value.total_seconds()
174
179
  except Exception:
175
180
  if self.interval.value is not None:
176
- interval: int = self.interval.value.total_seconds()
181
+ interval: float = self.interval.value.total_seconds()
177
182
  raise
178
183
  except TooManyRequestsError as err:
179
184
  LOG.error('Retrieval error during update. Too many requests from your account (%s). Will try again after 15 minutes', str(err))
@@ -309,6 +314,7 @@ class Connector(BaseConnector):
309
314
  for vin in set(garage.list_vehicle_vins()):
310
315
  vehicle_to_update: Optional[GenericVehicle] = garage.get_vehicle(vin)
311
316
  if vehicle_to_update is not None and isinstance(vehicle_to_update, SkodaVehicle) and vehicle_to_update.is_managed_by_connector(self):
317
+ vehicle_to_update = self.fetch_vehicle_status(vehicle_to_update)
312
318
  vehicle_to_update = self.fetch_driving_range(vehicle_to_update)
313
319
  if vehicle_to_update.capabilities is not None and vehicle_to_update.capabilities.enabled:
314
320
  if vehicle_to_update.capabilities.has_capability('PARKING_POSITION'):
@@ -602,6 +608,13 @@ class Connector(BaseConnector):
602
608
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}'
603
609
  data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
604
610
  if data is not None:
611
+ if vehicle.climatization is not None and vehicle.climatization.commands is not None \
612
+ and not vehicle.climatization.commands.contains_command('start-stop'):
613
+ start_stop_command = ClimatizationStartStopCommand(parent=vehicle.climatization.commands)
614
+ start_stop_command._add_on_set_hook(self.__on_air_conditioning_start_stop) # pylint: disable=protected-access
615
+ start_stop_command.enabled = True
616
+ vehicle.climatization.commands.add_command(start_stop_command)
617
+
605
618
  if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
606
619
  captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
607
620
  else:
@@ -624,6 +637,9 @@ class Connector(BaseConnector):
624
637
  else:
625
638
  vehicle.climatization.estimated_date_reached._set_value(value=None, measured=captured_at) # pylint: disable=protected-access
626
639
  if 'targetTemperature' in data and data['targetTemperature'] is not None:
640
+ # pylint: disable-next=protected-access
641
+ vehicle.climatization.settings.target_temperature._add_on_set_hook(self.__on_air_conditioning_target_temperature_change)
642
+ vehicle.climatization.settings.target_temperature._is_changeable = True # pylint: disable=protected-access
627
643
  unit: Temperature = Temperature.UNKNOWN
628
644
  if 'unitInCar' in data['targetTemperature'] and data['targetTemperature']['unitInCar'] is not None:
629
645
  if data['targetTemperature']['unitInCar'] == 'CELSIUS':
@@ -636,15 +652,16 @@ class Connector(BaseConnector):
636
652
  LOG_API.info('Unknown temperature unit for targetTemperature in air-conditioning %s', data['targetTemperature']['unitInCar'])
637
653
  if 'temperatureValue' in data['targetTemperature'] and data['targetTemperature']['temperatureValue'] is not None:
638
654
  # pylint: disable-next=protected-access
639
- vehicle.climatization.target_temperature._set_value(value=data['targetTemperature']['temperatureValue'],
640
- measured=captured_at,
641
- unit=unit)
655
+ vehicle.climatization.settings.target_temperature._set_value(value=data['targetTemperature']['temperatureValue'],
656
+ measured=captured_at,
657
+ unit=unit)
642
658
  else:
643
- vehicle.climatization.target_temperature._set_value(value=None, measured=captured_at, unit=unit) # pylint: disable=protected-access
659
+ # pylint: disable-next=protected-access
660
+ vehicle.climatization.settings.target_temperature._set_value(value=None, measured=captured_at, unit=unit)
644
661
  log_extra_keys(LOG_API, 'targetTemperature', data['targetTemperature'], {'unitInCar', 'temperatureValue'})
645
662
  else:
646
663
  # pylint: disable-next=protected-access
647
- vehicle.climatization.target_temperature._set_value(value=None, measured=captured_at, unit=Temperature.UNKNOWN)
664
+ vehicle.climatization.settings.target_temperature._set_value(value=None, measured=captured_at, unit=Temperature.UNKNOWN)
648
665
  if 'outsideTemperature' in data and data['outsideTemperature'] is not None:
649
666
  if 'carCapturedTimestamp' in data['outsideTemperature'] and data['outsideTemperature']['carCapturedTimestamp'] is not None:
650
667
  outside_captured_at: datetime = robust_time_parse(data['outsideTemperature']['carCapturedTimestamp'])
@@ -659,7 +676,7 @@ class Connector(BaseConnector):
659
676
  elif data['outsideTemperature']['temperatureUnit'] == 'KELVIN':
660
677
  unit = Temperature.K
661
678
  else:
662
- LOG_API.info('Unknown temperature unit for outsideTemperature in air-conditioning %s', data['targetTemperature']['temperatureUnit'])
679
+ LOG_API.info('Unknown temperature unit for outsideTemperature in air-conditioning %s', data['outsideTemperature']['temperatureUnit'])
663
680
  if 'temperatureValue' in data['outsideTemperature'] and data['outsideTemperature']['temperatureValue'] is not None:
664
681
  # pylint: disable-next=protected-access
665
682
  vehicle.outside_temperature._set_value(value=data['outsideTemperature']['temperatureValue'],
@@ -674,6 +691,72 @@ class Connector(BaseConnector):
674
691
  log_extra_keys(LOG_API, 'targetTemperature', data['outsideTemperature'], {'carCapturedTimestamp', 'temperatureUnit', 'temperatureValue'})
675
692
  else:
676
693
  vehicle.outside_temperature._set_value(value=None, measured=None, unit=Temperature.UNKNOWN) # pylint: disable=protected-access
694
+ if 'airConditioningAtUnlock' in data and data['airConditioningAtUnlock'] is not None:
695
+ if vehicle.climatization is not None and vehicle.climatization.settings is not None:
696
+ # pylint: disable-next=protected-access
697
+ vehicle.climatization.settings.climatization_at_unlock._add_on_set_hook(self.__on_air_conditioning_at_unlock_change)
698
+ vehicle.climatization.settings.climatization_at_unlock._is_changeable = True # pylint: disable=protected-access
699
+ if data['airConditioningAtUnlock'] is True:
700
+ # pylint: disable-next=protected-access
701
+ vehicle.climatization.settings.climatization_at_unlock._set_value(True, measured=captured_at)
702
+ elif data['airConditioningAtUnlock'] is False:
703
+ # pylint: disable-next=protected-access
704
+ vehicle.climatization.settings.climatization_at_unlock._set_value(False, measured=captured_at)
705
+ else:
706
+ # pylint: disable-next=protected-access
707
+ vehicle.climatization.settings.climatization_at_unlock._set_value(None, measured=captured_at)
708
+ else:
709
+ if vehicle.climatization is not None and vehicle.climatization.settings is not None:
710
+ # pylint: disable-next=protected-access
711
+ vehicle.climatization.settings.climatization_at_unlock._set_value(None, measured=captured_at)
712
+ if 'steeringWheelPosition' in data and data['steeringWheelPosition'] is not None:
713
+ if vehicle.specification is not None:
714
+ if data['steeringWheelPosition'] in [item.name for item in GenericVehicle.VehicleSpecification.SteeringPosition]:
715
+ steering_wheel_position: GenericVehicle.VehicleSpecification.SteeringPosition = \
716
+ GenericVehicle.VehicleSpecification.SteeringPosition[data['steeringWheelPosition']]
717
+ else:
718
+ LOG_API.info('Unknown steering wheel position %s not in %s', data['steeringWheelPosition'],
719
+ str(GenericVehicle.VehicleSpecification.SteeringPosition))
720
+ steering_wheel_position = GenericVehicle.VehicleSpecification.SteeringPosition.UNKNOWN
721
+ # pylint: disable-next=protected-access
722
+ vehicle.specification.steering_wheel_position._set_value(value=steering_wheel_position, measured=captured_at)
723
+ else:
724
+ if vehicle.specification is not None:
725
+ # pylint: disable-next=protected-access
726
+ vehicle.specification.steering_wheel_position._set_value(None, measured=captured_at)
727
+ if 'windowHeatingEnabled' in data and data['windowHeatingEnabled'] is not None:
728
+ if vehicle.climatization is not None and vehicle.climatization.settings is not None:
729
+ # pylint: disable-next=protected-access
730
+ vehicle.climatization.settings.window_heating._add_on_set_hook(self.__on_air_conditioning_window_heating_change)
731
+ vehicle.climatization.settings.window_heating._is_changeable = True # pylint: disable=protected-access
732
+ if data['windowHeatingEnabled'] is True:
733
+ # pylint: disable-next=protected-access
734
+ vehicle.climatization.settings.window_heating._set_value(True, measured=captured_at)
735
+ elif data['windowHeatingEnabled'] is False:
736
+ # pylint: disable-next=protected-access
737
+ vehicle.climatization.settings.window_heating._set_value(False, measured=captured_at)
738
+ else:
739
+ # pylint: disable-next=protected-access
740
+ vehicle.climatization.settings.window_heating._set_value(None, measured=captured_at)
741
+ else:
742
+ if vehicle.climatization is not None and vehicle.climatization.settings is not None:
743
+ # pylint: disable-next=protected-access
744
+ vehicle.climatization.settings.window_heating._set_value(None, measured=captured_at)
745
+ if 'seatHeatingActivated' in data and data['seatHeatingActivated'] is not None:
746
+ if vehicle.climatization is not None and vehicle.climatization.settings is not None:
747
+ if data['seatHeatingActivated'] is True:
748
+ # pylint: disable-next=protected-access
749
+ vehicle.climatization.settings.seat_heating._set_value(True, measured=captured_at)
750
+ elif data['seatHeatingActivated'] is False:
751
+ # pylint: disable-next=protected-access
752
+ vehicle.climatization.settings.seat_heating._set_value(False, measured=captured_at)
753
+ else:
754
+ # pylint: disable-next=protected-access
755
+ vehicle.climatization.settings.seat_heating._set_value(None, measured=captured_at)
756
+ else:
757
+ if vehicle.climatization is not None and vehicle.climatization.settings is not None:
758
+ # pylint: disable-next=protected-access
759
+ vehicle.climatization.settings.seat_heating._set_value(None, measured=captured_at)
677
760
  if isinstance(vehicle, SkodaElectricVehicle):
678
761
  if 'chargerConnectionState' in data and data['chargerConnectionState'] is not None \
679
762
  and vehicle.charging is not None and vehicle.charging.connector is not None:
@@ -707,9 +790,35 @@ class Connector(BaseConnector):
707
790
  vehicle.charging.connector.lock_state._set_value(value=None, measured=captured_at)
708
791
  if 'windowHeatingState' in data and data['windowHeatingState'] is not None:
709
792
  pass
710
- log_extra_keys(LOG_API, 'air-condition', data, {'carCapturedTimestamp', 'state', 'estimatedDateTimeToReachTargetTemperature'
793
+ if 'errors' in data and data['errors'] is not None:
794
+ found_errors: Set[str] = set()
795
+ if not isinstance(vehicle.climatization, SkodaClimatization):
796
+ vehicle.climatization = SkodaClimatization(origin=vehicle.climatization)
797
+ for error_dict in data['errors']:
798
+ if 'type' in error_dict and error_dict['type'] is not None:
799
+ if error_dict['type'] not in vehicle.climatization.errors:
800
+ error: Error = Error(object_id=error_dict['type'])
801
+ else:
802
+ error = vehicle.climatization.errors[error_dict['type']]
803
+ if error_dict['type'] in [item.name for item in Error.ClimatizationError]:
804
+ error_type: Error.ClimatizationError = Error.ClimatizationError[error_dict['type']]
805
+ else:
806
+ LOG_API.info('Unknown climatization error type %s not in %s', error_dict['type'], str(Error.ClimatizationError))
807
+ error_type = Error.ClimatizationError.UNKNOWN
808
+ error.type._set_value(error_type, measured=captured_at) # pylint: disable=protected-access
809
+ if 'description' in error_dict and error_dict['description'] is not None:
810
+ error.description._set_value(error_dict['description'], measured=captured_at) # pylint: disable=protected-access
811
+ log_extra_keys(LOG_API, 'errors', error_dict, {'type', 'description'})
812
+ if vehicle.climatization is not None and vehicle.climatization.errors is not None and len(vehicle.climatization.errors) > 0:
813
+ for error_id in vehicle.climatization.errors.keys()-found_errors:
814
+ vehicle.climatization.errors.pop(error_id)
815
+ else:
816
+ if isinstance(vehicle.climatization, SkodaClimatization):
817
+ vehicle.climatization.errors.clear()
818
+ log_extra_keys(LOG_API, 'air-condition', data, {'carCapturedTimestamp', 'state', 'estimatedDateTimeToReachTargetTemperature',
711
819
  'targetTemperature', 'outsideTemperature', 'chargerConnectionState',
712
- 'chargerLockState'})
820
+ 'chargerLockState', 'airConditioningAtUnlock', 'steeringWheelPosition',
821
+ 'windowHeatingEnabled', 'seatHeatingActivated', 'errors'})
713
822
  return vehicle
714
823
 
715
824
  def fetch_vehicle_details(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
@@ -875,6 +984,97 @@ class Connector(BaseConnector):
875
984
  'secondaryEngineRange'})
876
985
  return vehicle
877
986
 
987
+ def fetch_vehicle_status(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
988
+ """
989
+ Fetches the status of a vehicle from other Skoda API.
990
+
991
+ Args:
992
+ vehicle (GenericVehicle): The vehicle object containing the VIN.
993
+
994
+ Returns:
995
+ None
996
+ """
997
+ vin = vehicle.vin.value
998
+ if vin is None:
999
+ raise APIError('VIN is missing')
1000
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}'
1001
+ vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
1002
+ if vehicle_status_data:
1003
+ captured_at: datetime = robust_time_parse(vehicle_status_data['carCapturedTimestamp'])
1004
+ if 'overall' in vehicle_status_data and vehicle_status_data['overall'] is not None:
1005
+ if 'doorsLocked' in vehicle_status_data['overall'] and vehicle_status_data['overall']['doorsLocked'] is not None \
1006
+ and vehicle.doors is not None:
1007
+ if vehicle_status_data['overall']['doorsLocked'] == 'YES':
1008
+ vehicle.doors.lock_state._set_value(Doors.LockState.LOCKED, measured=captured_at) # pylint: disable=protected-access
1009
+ vehicle.doors.open_state._set_value(Doors.OpenState.CLOSED, measured=captured_at) # pylint: disable=protected-access
1010
+ elif vehicle_status_data['overall']['doorsLocked'] == 'OPENED':
1011
+ vehicle.doors.lock_state._set_value(Doors.LockState.UNLOCKED, measured=captured_at) # pylint: disable=protected-access
1012
+ vehicle.doors.open_state._set_value(Doors.OpenState.OPEN, measured=captured_at) # pylint: disable=protected-access
1013
+ elif vehicle_status_data['overall']['doorsLocked'] == 'UNLOCKED':
1014
+ vehicle.doors.lock_state._set_value(Doors.LockState.UNLOCKED, measured=captured_at) # pylint: disable=protected-access
1015
+ vehicle.doors.open_state._set_value(Doors.OpenState.CLOSED, measured=captured_at) # pylint: disable=protected-access
1016
+ elif vehicle_status_data['overall']['doorsLocked'] == 'TRUNK_OPENED':
1017
+ vehicle.doors.lock_state._set_value(Doors.LockState.UNLOCKED, measured=captured_at) # pylint: disable=protected-access
1018
+ vehicle.doors.open_state._set_value(Doors.OpenState.OPEN, measured=captured_at) # pylint: disable=protected-access
1019
+ elif vehicle_status_data['overall']['doorsLocked'] == 'UNKNOWN':
1020
+ vehicle.doors.lock_state._set_value(Doors.LockState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1021
+ vehicle.doors.open_state._set_value(Doors.OpenState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1022
+ else:
1023
+ LOG_API.info('Unknown doorsLocked state %s', vehicle_status_data['overall']['doorsLocked'])
1024
+ vehicle.doors.lock_state._set_value(Doors.LockState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1025
+ vehicle.doors.open_state._set_value(Doors.OpenState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1026
+ if 'locked' in vehicle_status_data['overall'] and vehicle_status_data['overall']['locked'] is not None:
1027
+ if vehicle_status_data['overall']['locked'] == 'YES':
1028
+ vehicle.doors.lock_state._set_value(Doors.LockState.LOCKED, measured=captured_at) # pylint: disable=protected-access
1029
+ elif vehicle_status_data['overall']['locked'] == 'NO':
1030
+ vehicle.doors.lock_state._set_value(Doors.LockState.UNLOCKED, measured=captured_at) # pylint: disable=protected-access
1031
+ elif vehicle_status_data['overall']['locked'] == 'UNKNOWN':
1032
+ vehicle.doors.lock_state._set_value(Doors.LockState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1033
+ else:
1034
+ LOG_API.info('Unknown locked state %s', vehicle_status_data['overall']['locked'])
1035
+ vehicle.doors.lock_state._set_value(Doors.LockState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1036
+ if 'doors' in vehicle_status_data['overall'] and vehicle_status_data['overall']['doors'] is not None:
1037
+ if vehicle_status_data['overall']['doors'] == 'CLOSED':
1038
+ vehicle.doors.open_state._set_value(Doors.OpenState.CLOSED, measured=captured_at) # pylint: disable=protected-access
1039
+ elif vehicle_status_data['overall']['doors'] == 'OPEN':
1040
+ vehicle.doors.open_state._set_value(Doors.OpenState.OPEN, measured=captured_at) # pylint: disable=protected-access
1041
+ elif vehicle_status_data['overall']['doors'] == 'UNSUPPORTED':
1042
+ vehicle.doors.open_state._set_value(Doors.OpenState.UNSUPPORTED, measured=captured_at) # pylint: disable=protected-access
1043
+ elif vehicle_status_data['overall']['doors'] == 'UNKNOWN':
1044
+ vehicle.doors.open_state._set_value(Doors.OpenState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1045
+ else:
1046
+ LOG_API.info('Unknown doors state %s', vehicle_status_data['overall']['doors'])
1047
+ vehicle.doors.open_state._set_value(Doors.OpenState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1048
+ if 'windows' in vehicle_status_data['overall'] and vehicle_status_data['overall']['windows'] is not None:
1049
+ if vehicle_status_data['overall']['windows'] == 'CLOSED':
1050
+ vehicle.windows.open_state._set_value(Windows.OpenState.CLOSED, measured=captured_at) # pylint: disable=protected-access
1051
+ elif vehicle_status_data['overall']['windows'] == 'OPEN':
1052
+ vehicle.windows.open_state._set_value(Windows.OpenState.OPEN, measured=captured_at) # pylint: disable=protected-access
1053
+ elif vehicle_status_data['overall']['windows'] == 'UNKNOWN':
1054
+ vehicle.windows.open_state._set_value(Windows.OpenState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1055
+ elif vehicle_status_data['overall']['windows'] == 'UNSUPPORTED':
1056
+ vehicle.windows.open_state._set_value(Windows.OpenState.UNSUPPORTED, measured=captured_at) # pylint: disable=protected-access
1057
+ else:
1058
+ LOG_API.info('Unknown windows state %s', vehicle_status_data['overall']['windows'])
1059
+ vehicle.windows.open_state._set_value(Windows.OpenState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1060
+ if 'lights' in vehicle_status_data['overall'] and vehicle_status_data['overall']['lights'] is not None:
1061
+ if vehicle_status_data['overall']['lights'] == 'ON':
1062
+ vehicle.lights.light_state._set_value(Lights.LightState.ON, measured=captured_at) # pylint: disable=protected-access
1063
+ elif vehicle_status_data['overall']['lights'] == 'OFF':
1064
+ vehicle.lights.light_state._set_value(Lights.LightState.OFF, measured=captured_at) # pylint: disable=protected-access
1065
+ elif vehicle_status_data['overall']['lights'] == 'UNKNOWN':
1066
+ vehicle.lights.light_state._set_value(Lights.LightState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1067
+ else:
1068
+ LOG_API.info('Unknown lights state %s', vehicle_status_data['overall']['lights'])
1069
+ vehicle.lights.light_state._set_value(Lights.LightState.UNKNOWN, measured=captured_at) # pylint: disable=protected-access
1070
+ log_extra_keys(LOG_API, 'status overall', vehicle_status_data['overall'], {'doorsLocked',
1071
+ 'locked',
1072
+ 'doors',
1073
+ 'windows',
1074
+ 'lights'})
1075
+ log_extra_keys(LOG_API, f'/api/v2/vehicle-status/{vin}', vehicle_status_data, {'overall', 'carCapturedTimestamp'})
1076
+ return vehicle
1077
+
878
1078
  # def fetch_vehicle_status_second_api(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
879
1079
  # """
880
1080
  # Fetches the status of a vehicle from other Skoda API.
@@ -1115,3 +1315,125 @@ class Connector(BaseConnector):
1115
1315
 
1116
1316
  def get_version(self) -> str:
1117
1317
  return __version__
1318
+
1319
+ def __on_air_conditioning_target_temperature_change(self, temperature_attribute: TemperatureAttribute, target_temperature: float) -> float:
1320
+ """
1321
+ Callback for the climatization target temperature change.
1322
+
1323
+ Args:
1324
+ temperature_attribute (TemperatureAttribute): The temperature attribute that changed.
1325
+ target_temperature (float): The new target temperature.
1326
+ """
1327
+ if temperature_attribute.parent is None or temperature_attribute.parent.parent is None \
1328
+ or temperature_attribute.parent.parent.parent is None or not isinstance(temperature_attribute.parent.parent.parent, SkodaVehicle):
1329
+ raise SetterError('Object hierarchy is not as expected')
1330
+ vehicle: SkodaVehicle = temperature_attribute.parent.parent.parent
1331
+ vin: Optional[str] = vehicle.vin.value
1332
+ if vin is None:
1333
+ raise SetterError('VIN in object hierarchy missing')
1334
+ setting_dict = {}
1335
+ # Round target temperature to nearest 0.5
1336
+ setting_dict['temperatureValue'] = round(target_temperature * 2) / 2
1337
+ if temperature_attribute.unit == Temperature.C:
1338
+ setting_dict['unitInCar'] = 'CELSIUS'
1339
+ elif temperature_attribute.unit == Temperature.F:
1340
+ setting_dict['unitInCar'] = 'FAHRENHEIT'
1341
+ elif temperature_attribute.unit == Temperature.K:
1342
+ setting_dict['unitInCar'] = 'KELVIN'
1343
+ else:
1344
+ raise SetterError(f'Unknown temperature unit {temperature_attribute.unit}')
1345
+
1346
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/target-temperature'
1347
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1348
+ if settings_response.status_code != requests.codes['accepted']:
1349
+ LOG.error('Could not set target temperature (%s)', settings_response.status_code)
1350
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1351
+ return target_temperature
1352
+
1353
+ def __on_air_conditioning_at_unlock_change(self, at_unlock_attribute: BooleanAttribute, at_unlock_value: bool) -> bool:
1354
+ if at_unlock_attribute.parent is None or at_unlock_attribute.parent.parent is None \
1355
+ or at_unlock_attribute.parent.parent.parent is None or not isinstance(at_unlock_attribute.parent.parent.parent, SkodaVehicle):
1356
+ raise SetterError('Object hierarchy is not as expected')
1357
+ vehicle: SkodaVehicle = at_unlock_attribute.parent.parent.parent
1358
+ vin: Optional[str] = vehicle.vin.value
1359
+ if vin is None:
1360
+ raise SetterError('VIN in object hierarchy missing')
1361
+ setting_dict = {}
1362
+ # Round target temperature to nearest 0.5
1363
+ setting_dict['airConditioningAtUnlockEnabled'] = at_unlock_value
1364
+
1365
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/ac-at-unlock'
1366
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1367
+ if settings_response.status_code != requests.codes['accepted']:
1368
+ LOG.error('Could not set air conditioning at unlock (%s)', settings_response.status_code)
1369
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1370
+ return at_unlock_value
1371
+
1372
+ def __on_air_conditioning_window_heating_change(self, window_heating_attribute: BooleanAttribute, window_heating_value: bool) -> bool:
1373
+ if window_heating_attribute.parent is None or window_heating_attribute.parent.parent is None \
1374
+ or window_heating_attribute.parent.parent.parent is None or not isinstance(window_heating_attribute.parent.parent.parent, SkodaVehicle):
1375
+ raise SetterError('Object hierarchy is not as expected')
1376
+ vehicle: SkodaVehicle = window_heating_attribute.parent.parent.parent
1377
+ vin: Optional[str] = vehicle.vin.value
1378
+ if vin is None:
1379
+ raise SetterError('VIN in object hierarchy missing')
1380
+ setting_dict = {}
1381
+ # Round target temperature to nearest 0.5
1382
+ setting_dict['windowHeatingEnabled'] = window_heating_value
1383
+
1384
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/ac-at-unlock'
1385
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1386
+ if settings_response.status_code != requests.codes['accepted']:
1387
+ LOG.error('Could not set air conditioning window heating (%s)', settings_response.status_code)
1388
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1389
+ return window_heating_value
1390
+
1391
+ def __on_air_conditioning_start_stop(self, start_stop_command: ClimatizationStartStopCommand, command_arguments: Union[str, Dict[str, Any]]) \
1392
+ -> Union[str, Dict[str, Any]]:
1393
+ if start_stop_command.parent is None or start_stop_command.parent.parent is None \
1394
+ or start_stop_command.parent.parent.parent is None or not isinstance(start_stop_command.parent.parent.parent, SkodaVehicle):
1395
+ raise SetterError('Object hierarchy is not as expected')
1396
+ if not isinstance(command_arguments, dict):
1397
+ raise SetterError('Command arguments are not a dictionary')
1398
+ vehicle: SkodaVehicle = start_stop_command.parent.parent.parent
1399
+ vin: Optional[str] = vehicle.vin.value
1400
+ if vin is None:
1401
+ raise SetterError('VIN in object hierarchy missing')
1402
+ if 'command' not in command_arguments:
1403
+ raise SetterError('Command argument missing')
1404
+ command_dict = {}
1405
+ if command_arguments['command'] == ClimatizationStartStopCommand.Command.START:
1406
+ command_dict['heaterSource'] = 'ELECTRIC'
1407
+ command_dict['targetTemperature'] = {}
1408
+ if 'target_temperature' in command_arguments:
1409
+ # Round target temperature to nearest 0.5
1410
+ command_dict['targetTemperature']['temperatureValue'] = round(command_arguments['target_temperature'] * 2) / 2
1411
+ if 'target_temperature_unit' in command_arguments:
1412
+ if not isinstance(command_arguments['target_temperature_unit'], Temperature):
1413
+ raise SetterError('Temperature unit is not of type Temperature')
1414
+ if command_arguments['target_temperature_unit'] == Temperature.C:
1415
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1416
+ elif command_arguments['target_temperature_unit'] == Temperature.F:
1417
+ command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
1418
+ elif command_arguments['target_temperature_unit'] == Temperature.K:
1419
+ command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
1420
+ else:
1421
+ raise SetterError(f'Unknown temperature unit {command_arguments['target_temperature_unit']}')
1422
+ else:
1423
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1424
+ else:
1425
+ command_dict['targetTemperature']['temperatureValue'] = 25.0
1426
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1427
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/start'
1428
+ print(json.dumps(command_dict))
1429
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1430
+ elif command_arguments['command'] == ClimatizationStartStopCommand.Command.STOP:
1431
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/stop'
1432
+ settings_response: requests.Response = self.session.post(url, allow_redirects=True)
1433
+ else:
1434
+ raise SetterError(f'Unknown command {command_arguments["command"]}')
1435
+
1436
+ if settings_response.status_code != requests.codes['accepted']:
1437
+ LOG.error('Could not start/stop air conditioning (%s: %s)', settings_response.status_code, settings_response.text)
1438
+ raise SetterError(f'Could not start/stop air conditioning ({settings_response.status_code}: {settings_response.text})')
1439
+ return command_arguments
@@ -40,3 +40,14 @@ class Error(GenericObject):
40
40
  MAX_CHARGE_CURRENT_IS_NOT_AVAILABLE = 'MAX_CHARGE_CURRENT_IS_NOT_AVAILABLE'
41
41
  CHARGE_LIMIT_IS_NOT_AVAILABLE = 'CHARGE_LIMIT_IS_NOT_AVAILABLE'
42
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'
@@ -21,7 +21,7 @@ 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
23
  from carconnectivity.climatization import Climatization
24
- from carconnectivity.units import Speed, Power
24
+ from carconnectivity.units import Speed, Power, Length
25
25
 
26
26
  from carconnectivity_connectors.skoda.vehicle import SkodaVehicle, SkodaElectricVehicle
27
27
  from carconnectivity_connectors.skoda.charging import SkodaCharging, mapping_skoda_charging_state
@@ -468,6 +468,15 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
468
468
  if electric_drive is not None:
469
469
  charging_state: Optional[Charging.ChargingState] = vehicle.charging.state.value
470
470
  old_charging_state: Optional[Charging.ChargingState] = charging_state
471
+ if 'mode' in data['data'] and data['data']['mode'] is not None \
472
+ and vehicle.charging is not None and isinstance(vehicle.charging.settings, SkodaCharging.Settings):
473
+ if data['data']['mode'] in SkodaCharging.SkodaChargeMode:
474
+ skoda_charging_mode = SkodaCharging.SkodaChargeMode(data['data']['mode'])
475
+ else:
476
+ LOG_API.info('Unkown charging mode %s not in %s', data['data']['mode'], str(SkodaCharging.SkodaChargeMode))
477
+ skoda_charging_mode = Charging.ChargingState.UNKNOWN
478
+ # pylint: disable-next=protected-access
479
+ vehicle.charging.settings.preferred_charge_mode._set_value(value=skoda_charging_mode, measured=measured_at)
471
480
  if 'state' in data['data'] and data['data']['state'] is not None:
472
481
  if data['data']['state'] in [item.value for item in SkodaCharging.SkodaChargingState]:
473
482
  skoda_charging_state = SkodaCharging.SkodaChargingState(data['data']['state'])
@@ -488,7 +497,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
488
497
  electric_drive.level._set_value(measured=measured_at, value=data['data']['soc']) # pylint: disable=protected-access
489
498
  if 'chargedRange' in data['data'] and data['data']['chargedRange'] is not None:
490
499
  # pylint: disable-next=protected-access
491
- electric_drive.range._set_value(measured=measured_at, value=data['data']['chargedRange'])
500
+ electric_drive.range._set_value(measured=measured_at, value=data['data']['chargedRange'], unit=Length.KM)
492
501
  # If charging state changed, fetch charging again
493
502
  if old_charging_state != charging_state:
494
503
  try:
@@ -505,7 +514,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
505
514
  estimated_date_reached: Optional[datetime] = None
506
515
  # pylint: disable-next=protected-access
507
516
  vehicle.charging.estimated_date_reached._set_value(measured=measured_at, value=estimated_date_reached)
508
- log_extra_keys(LOG_API, 'data', data['data'], {'vin', 'userId', 'soc', 'chargedRange', 'timeToFinish', 'state'})
517
+ log_extra_keys(LOG_API, 'data', data['data'], {'vin', 'userId', 'soc', 'chargedRange', 'timeToFinish', 'state', 'mode'})
509
518
  LOG.debug('Received %s event for vehicle %s from user %s', data['name'], vin, user_id)
510
519
  return
511
520
  else:
@@ -544,6 +553,10 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
544
553
  """
545
554
  vin = vehicle.id
546
555
  self.delayed_access_function_timers.pop(vin)
556
+ try:
557
+ self._skoda_connector.fetch_vehicle_status(vehicle, no_cache=True)
558
+ except CarConnectivityError as e:
559
+ LOG.error('Error while fetching vehicle status: %s', e)
547
560
  if vehicle.capabilities is not None and vehicle.capabilities.enabled \
548
561
  and vehicle.capabilities.has_capability('CHARGING') and isinstance(vehicle, SkodaElectricVehicle):
549
562
  try:
@@ -568,12 +581,25 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
568
581
  self.delayed_access_function_timers[vin] = threading.Timer(2.0, delayed_access_function, kwargs={'vehicle': vehicle})
569
582
  self.delayed_access_function_timers[vin].start()
570
583
 
584
+ LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
585
+ service_event, vin, user_id, msg.payload)
586
+ return
587
+ elif service_event == 'vehicle-status/lights':
588
+ if 'name' in data and data['name'] == 'change-lights':
589
+ if 'data' in data and data['data'] is not None:
590
+ vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
591
+ if isinstance(vehicle, SkodaVehicle):
592
+ try:
593
+ self._skoda_connector.fetch_vehicle_status(vehicle, no_cache=True)
594
+ except CarConnectivityError as e:
595
+ LOG.error('Error while fetching vehicle status: %s', e)
596
+
571
597
  LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
572
598
  service_event, vin, user_id, msg.payload)
573
599
  return
574
600
  LOG_API.info('Received unknown service event %s for vehicle %s from user %s: %s', service_event, vin, user_id, msg.payload)
575
601
  return
576
- # service_events
602
+ # operation-requests
577
603
  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)
578
604
  if match:
579
605
  user_id: str = match.group('user_id')
@@ -599,6 +625,9 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
599
625
  except CarConnectivityError as e:
600
626
  LOG.error('Error while fetching air-conditioning: %s', e)
601
627
  return
628
+ elif data['status'] == 'IN_PROGRESS':
629
+ LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
630
+ return
602
631
  if operation_request == 'charging/start-stop-charging' \
603
632
  or operation_request == 'charging/update-battery-support' \
604
633
  or operation_request == 'charging/update-auto-unlock-plug' \