carconnectivity-connector-skoda 0.1a18__tar.gz → 0.1a20__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.

Files changed (37) hide show
  1. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/Makefile +1 -1
  2. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/PKG-INFO +1 -1
  3. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connector_skoda.egg-info/PKG-INFO +1 -1
  4. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/_version.py +1 -1
  5. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/charging.py +2 -2
  6. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/connector.py +201 -10
  7. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/error.py +1 -0
  8. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/mqtt_client.py +12 -6
  9. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/vehicle.py +1 -0
  10. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/.flake8 +0 -0
  11. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/.github/dependabot.yml +0 -0
  14. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/.github/workflows/build.yml +0 -0
  15. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/.github/workflows/build_and_publish.yml +0 -0
  16. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/.github/workflows/codeql-analysis.yml +0 -0
  17. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/.gitignore +0 -0
  18. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/LICENSE +0 -0
  19. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/README.md +0 -0
  20. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/doc/Config.md +0 -0
  21. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/pyproject.toml +0 -0
  22. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/setup.cfg +0 -0
  23. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/setup_requirements.txt +0 -0
  24. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connector_skoda.egg-info/SOURCES.txt +0 -0
  25. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connector_skoda.egg-info/dependency_links.txt +0 -0
  26. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connector_skoda.egg-info/requires.txt +0 -0
  27. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connector_skoda.egg-info/top_level.txt +0 -0
  28. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/__init__.py +0 -0
  29. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/auth/__init__.py +0 -0
  30. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/auth/auth_util.py +0 -0
  31. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py +0 -0
  32. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/auth/my_skoda_session.py +0 -0
  33. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/auth/openid_session.py +0 -0
  34. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/auth/session_manager.py +0 -0
  35. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/auth/skoda_web_session.py +0 -0
  36. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/capability.py +0 -0
  37. {carconnectivity_connector_skoda-0.1a18 → carconnectivity_connector_skoda-0.1a20}/src/carconnectivity_connectors/skoda/climatization.py +0 -0
@@ -6,7 +6,7 @@ test:
6
6
 
7
7
  lint:
8
8
  @echo "\n${BLUE}Running Pylint against source and test files...${NC}\n"
9
- @pylint ./examples ./src ./tests
9
+ @pylint ./src
10
10
  @echo "\n${BLUE}Running Flake8 against source and test files...${NC}\n"
11
11
  @flake8
12
12
  @echo "\n${BLUE}Running Bandit against source files...${NC}\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: carconnectivity-connector-skoda
3
- Version: 0.1a18
3
+ Version: 0.1a20
4
4
  Summary: CarConnectivity connector for Skoda services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: carconnectivity-connector-skoda
3
- Version: 0.1a18
3
+ Version: 0.1a20
4
4
  Summary: CarConnectivity connector for Skoda services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.1a18'
15
+ __version__ = version = '0.1a20'
16
16
  __version_tuple__ = version_tuple = (0, 1)
@@ -69,7 +69,7 @@ class SkodaCharging(Charging): # pylint: disable=too-many-instance-attributes
69
69
  CONNECT_CABLE = 'connectCable'
70
70
  READY_FOR_CHARGING = 'readyForCharging'
71
71
  NOT_READY_FOR_CHARGING = 'notReadyForCharging'
72
- CONSERVATION = 'conservation'
72
+ CONSERVING = 'conserving'
73
73
  CHARGE_PURPOSE_REACHED_NOT_CONSERVATION_CHARGING = 'chargePurposeReachedAndNotConservationCharging'
74
74
  CHARGE_PURPOSE_REACHED_CONSERVATION = 'chargePurposeReachedAndConservation'
75
75
  CHARGING = 'charging'
@@ -126,7 +126,7 @@ mapping_skoda_charging_state: Dict[SkodaCharging.SkodaChargingState, Charging.Ch
126
126
  SkodaCharging.SkodaChargingState.CONNECT_CABLE: Charging.ChargingState.OFF,
127
127
  SkodaCharging.SkodaChargingState.READY_FOR_CHARGING: Charging.ChargingState.READY_FOR_CHARGING,
128
128
  SkodaCharging.SkodaChargingState.NOT_READY_FOR_CHARGING: Charging.ChargingState.OFF,
129
- SkodaCharging.SkodaChargingState.CONSERVATION: Charging.ChargingState.CONSERVATION,
129
+ SkodaCharging.SkodaChargingState.CONSERVING: Charging.ChargingState.CONSERVATION,
130
130
  SkodaCharging.SkodaChargingState.CHARGE_PURPOSE_REACHED_NOT_CONSERVATION_CHARGING: Charging.ChargingState.READY_FOR_CHARGING,
131
131
  SkodaCharging.SkodaChargingState.CHARGE_PURPOSE_REACHED_CONSERVATION: Charging.ChargingState.CONSERVATION,
132
132
  SkodaCharging.SkodaChargingState.CHARGING: Charging.ChargingState.CHARGING,
@@ -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
21
  from carconnectivity.doors import Doors
19
22
  from carconnectivity.windows import Windows
20
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, ChargingStartStopCommand
27
31
 
28
32
  from carconnectivity_connectors.base.connector import BaseConnector
29
33
  from carconnectivity_connectors.skoda.auth.session_manager import SessionManager, SessionUser, Service
@@ -37,7 +41,7 @@ from carconnectivity_connectors.skoda._version import __version__
37
41
  from carconnectivity_connectors.skoda.mqtt_client import SkodaMQTTClient
38
42
 
39
43
  if TYPE_CHECKING:
40
- from typing import Dict, List, Optional, Any, Set
44
+ from typing import Dict, List, Optional, Any, Set, Union
41
45
 
42
46
  from carconnectivity.carconnectivity import CarConnectivity
43
47
 
@@ -71,20 +75,20 @@ class Connector(BaseConnector):
71
75
  # Configure logging
72
76
  if 'log_level' in config and config['log_level'] is not None:
73
77
  config['log_level'] = config['log_level'].upper()
74
- if config['log_level'] in logging.getLevelNamesMapping():
78
+ if config['log_level'] in logging._nameToLevel:
75
79
  LOG.setLevel(config['log_level'])
76
80
  self.log_level._set_value(config['log_level']) # pylint: disable=protected-access
77
81
  logging.getLogger('requests').setLevel(config['log_level'])
78
82
  logging.getLogger('urllib3').setLevel(config['log_level'])
79
83
  logging.getLogger('oauthlib').setLevel(config['log_level'])
80
84
  else:
81
- raise ConfigurationError(f'Invalid log level: "{config["log_level"]}" not in {list(logging.getLevelNamesMapping().keys())}')
85
+ raise ConfigurationError(f'Invalid log level: "{config["log_level"]}" not in {list(logging._nameToLevel.keys())}')
82
86
  if 'api_log_level' in config and config['api_log_level'] is not None:
83
87
  config['api_log_level'] = config['api_log_level'].upper()
84
- if config['api_log_level'] in logging.getLevelNamesMapping():
88
+ if config['api_log_level'] in logging._nameToLevel:
85
89
  LOG_API.setLevel(config['api_log_level'])
86
90
  else:
87
- raise ConfigurationError(f'Invalid log level: "{config["log_level"]}" not in {list(logging.getLevelNamesMapping().keys())}')
91
+ raise ConfigurationError(f'Invalid log level: "{config["log_level"]}" not in {list(logging._nameToLevel.keys())}')
88
92
  LOG.info("Loading skoda connector with config %s", config_remove_credentials(self.config))
89
93
 
90
94
  username: Optional[str] = None
@@ -171,10 +175,10 @@ class Connector(BaseConnector):
171
175
  self.update_vehicles()
172
176
  self.last_update._set_value(value=datetime.now(tz=timezone.utc)) # pylint: disable=protected-access
173
177
  if self.interval.value is not None:
174
- interval: int = self.interval.value.total_seconds()
178
+ interval: float = self.interval.value.total_seconds()
175
179
  except Exception:
176
180
  if self.interval.value is not None:
177
- interval: int = self.interval.value.total_seconds()
181
+ interval: float = self.interval.value.total_seconds()
178
182
  raise
179
183
  except TooManyRequestsError as err:
180
184
  LOG.error('Retrieval error during update. Too many requests from your account (%s). Will try again after 15 minutes', str(err))
@@ -283,7 +287,12 @@ class Connector(BaseConnector):
283
287
  else:
284
288
  vehicle.license_plate._set_value(None) # pylint: disable=protected-access
285
289
 
286
- log_extra_keys(LOG_API, 'vehicles', vehicle_dict, {'vin', 'licensePlate'})
290
+ if 'name' in vehicle_dict and vehicle_dict['name'] is not None:
291
+ vehicle.name._set_value(vehicle_dict['name']) # pylint: disable=protected-access
292
+ else:
293
+ vehicle.name._set_value(None) # pylint: disable=protected-access
294
+
295
+ log_extra_keys(LOG_API, 'vehicles', vehicle_dict, {'vin', 'licensePlate', 'name'})
287
296
 
288
297
  vehicle = self.fetch_vehicle_details(vehicle)
289
298
  else:
@@ -339,6 +348,10 @@ class Connector(BaseConnector):
339
348
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}'
340
349
  data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
341
350
  if data is not None:
351
+ start_stop_command: ChargingStartStopCommand = ChargingStartStopCommand(parent=vehicle.charging.commands)
352
+ start_stop_command._add_on_set_hook(self.__on_charging_start_stop) # pylint: disable=protected-access
353
+ start_stop_command.enabled = True
354
+ vehicle.charging.commands.add_command(start_stop_command)
342
355
  if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
343
356
  captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
344
357
  else:
@@ -604,6 +617,13 @@ class Connector(BaseConnector):
604
617
  url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}'
605
618
  data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
606
619
  if data is not None:
620
+ if vehicle.climatization is not None and vehicle.climatization.commands is not None \
621
+ and not vehicle.climatization.commands.contains_command('start-stop'):
622
+ start_stop_command = ClimatizationStartStopCommand(parent=vehicle.climatization.commands)
623
+ start_stop_command._add_on_set_hook(self.__on_air_conditioning_start_stop) # pylint: disable=protected-access
624
+ start_stop_command.enabled = True
625
+ vehicle.climatization.commands.add_command(start_stop_command)
626
+
607
627
  if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
608
628
  captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
609
629
  else:
@@ -626,6 +646,9 @@ class Connector(BaseConnector):
626
646
  else:
627
647
  vehicle.climatization.estimated_date_reached._set_value(value=None, measured=captured_at) # pylint: disable=protected-access
628
648
  if 'targetTemperature' in data and data['targetTemperature'] is not None:
649
+ # pylint: disable-next=protected-access
650
+ vehicle.climatization.settings.target_temperature._add_on_set_hook(self.__on_air_conditioning_target_temperature_change)
651
+ vehicle.climatization.settings.target_temperature._is_changeable = True # pylint: disable=protected-access
629
652
  unit: Temperature = Temperature.UNKNOWN
630
653
  if 'unitInCar' in data['targetTemperature'] and data['targetTemperature']['unitInCar'] is not None:
631
654
  if data['targetTemperature']['unitInCar'] == 'CELSIUS':
@@ -679,6 +702,9 @@ class Connector(BaseConnector):
679
702
  vehicle.outside_temperature._set_value(value=None, measured=None, unit=Temperature.UNKNOWN) # pylint: disable=protected-access
680
703
  if 'airConditioningAtUnlock' in data and data['airConditioningAtUnlock'] is not None:
681
704
  if vehicle.climatization is not None and vehicle.climatization.settings is not None:
705
+ # pylint: disable-next=protected-access
706
+ vehicle.climatization.settings.climatization_at_unlock._add_on_set_hook(self.__on_air_conditioning_at_unlock_change)
707
+ vehicle.climatization.settings.climatization_at_unlock._is_changeable = True # pylint: disable=protected-access
682
708
  if data['airConditioningAtUnlock'] is True:
683
709
  # pylint: disable-next=protected-access
684
710
  vehicle.climatization.settings.climatization_at_unlock._set_value(True, measured=captured_at)
@@ -709,6 +735,9 @@ class Connector(BaseConnector):
709
735
  vehicle.specification.steering_wheel_position._set_value(None, measured=captured_at)
710
736
  if 'windowHeatingEnabled' in data and data['windowHeatingEnabled'] is not None:
711
737
  if vehicle.climatization is not None and vehicle.climatization.settings is not None:
738
+ # pylint: disable-next=protected-access
739
+ vehicle.climatization.settings.window_heating._add_on_set_hook(self.__on_air_conditioning_window_heating_change)
740
+ vehicle.climatization.settings.window_heating._is_changeable = True # pylint: disable=protected-access
712
741
  if data['windowHeatingEnabled'] is True:
713
742
  # pylint: disable-next=protected-access
714
743
  vehicle.climatization.settings.window_heating._set_value(True, measured=captured_at)
@@ -1295,3 +1324,165 @@ class Connector(BaseConnector):
1295
1324
 
1296
1325
  def get_version(self) -> str:
1297
1326
  return __version__
1327
+
1328
+ def __on_air_conditioning_target_temperature_change(self, temperature_attribute: TemperatureAttribute, target_temperature: float) -> float:
1329
+ """
1330
+ Callback for the climatization target temperature change.
1331
+
1332
+ Args:
1333
+ temperature_attribute (TemperatureAttribute): The temperature attribute that changed.
1334
+ target_temperature (float): The new target temperature.
1335
+ """
1336
+ if temperature_attribute.parent is None or temperature_attribute.parent.parent is None \
1337
+ or temperature_attribute.parent.parent.parent is None or not isinstance(temperature_attribute.parent.parent.parent, SkodaVehicle):
1338
+ raise SetterError('Object hierarchy is not as expected')
1339
+ vehicle: SkodaVehicle = temperature_attribute.parent.parent.parent
1340
+ vin: Optional[str] = vehicle.vin.value
1341
+ if vin is None:
1342
+ raise SetterError('VIN in object hierarchy missing')
1343
+ setting_dict = {}
1344
+ # Round target temperature to nearest 0.5
1345
+ setting_dict['temperatureValue'] = round(target_temperature * 2) / 2
1346
+ if temperature_attribute.unit == Temperature.C:
1347
+ setting_dict['unitInCar'] = 'CELSIUS'
1348
+ elif temperature_attribute.unit == Temperature.F:
1349
+ setting_dict['unitInCar'] = 'FAHRENHEIT'
1350
+ elif temperature_attribute.unit == Temperature.K:
1351
+ setting_dict['unitInCar'] = 'KELVIN'
1352
+ else:
1353
+ raise SetterError(f'Unknown temperature unit {temperature_attribute.unit}')
1354
+
1355
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/target-temperature'
1356
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1357
+ if settings_response.status_code != requests.codes['accepted']:
1358
+ LOG.error('Could not set target temperature (%s)', settings_response.status_code)
1359
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1360
+ return target_temperature
1361
+
1362
+ def __on_air_conditioning_at_unlock_change(self, at_unlock_attribute: BooleanAttribute, at_unlock_value: bool) -> bool:
1363
+ if at_unlock_attribute.parent is None or at_unlock_attribute.parent.parent is None \
1364
+ or at_unlock_attribute.parent.parent.parent is None or not isinstance(at_unlock_attribute.parent.parent.parent, SkodaVehicle):
1365
+ raise SetterError('Object hierarchy is not as expected')
1366
+ vehicle: SkodaVehicle = at_unlock_attribute.parent.parent.parent
1367
+ vin: Optional[str] = vehicle.vin.value
1368
+ if vin is None:
1369
+ raise SetterError('VIN in object hierarchy missing')
1370
+ setting_dict = {}
1371
+ # Round target temperature to nearest 0.5
1372
+ setting_dict['airConditioningAtUnlockEnabled'] = at_unlock_value
1373
+
1374
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/ac-at-unlock'
1375
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1376
+ if settings_response.status_code != requests.codes['accepted']:
1377
+ LOG.error('Could not set air conditioning at unlock (%s)', settings_response.status_code)
1378
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1379
+ return at_unlock_value
1380
+
1381
+ def __on_air_conditioning_window_heating_change(self, window_heating_attribute: BooleanAttribute, window_heating_value: bool) -> bool:
1382
+ if window_heating_attribute.parent is None or window_heating_attribute.parent.parent is None \
1383
+ or window_heating_attribute.parent.parent.parent is None or not isinstance(window_heating_attribute.parent.parent.parent, SkodaVehicle):
1384
+ raise SetterError('Object hierarchy is not as expected')
1385
+ vehicle: SkodaVehicle = window_heating_attribute.parent.parent.parent
1386
+ vin: Optional[str] = vehicle.vin.value
1387
+ if vin is None:
1388
+ raise SetterError('VIN in object hierarchy missing')
1389
+ setting_dict = {}
1390
+ # Round target temperature to nearest 0.5
1391
+ setting_dict['windowHeatingEnabled'] = window_heating_value
1392
+
1393
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/ac-at-unlock'
1394
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
1395
+ if settings_response.status_code != requests.codes['accepted']:
1396
+ LOG.error('Could not set air conditioning window heating (%s)', settings_response.status_code)
1397
+ raise SetterError(f'Could not set value ({settings_response.status_code})')
1398
+ return window_heating_value
1399
+
1400
+ def __on_air_conditioning_start_stop(self, start_stop_command: ClimatizationStartStopCommand, command_arguments: Union[str, Dict[str, Any]]) \
1401
+ -> Union[str, Dict[str, Any]]:
1402
+ if start_stop_command.parent is None or start_stop_command.parent.parent is None \
1403
+ or start_stop_command.parent.parent.parent is None or not isinstance(start_stop_command.parent.parent.parent, SkodaVehicle):
1404
+ raise SetterError('Object hierarchy is not as expected')
1405
+ if not isinstance(command_arguments, dict):
1406
+ raise SetterError('Command arguments are not a dictionary')
1407
+ vehicle: SkodaVehicle = start_stop_command.parent.parent.parent
1408
+ vin: Optional[str] = vehicle.vin.value
1409
+ if vin is None:
1410
+ raise SetterError('VIN in object hierarchy missing')
1411
+ if 'command' not in command_arguments:
1412
+ raise SetterError('Command argument missing')
1413
+ command_dict = {}
1414
+ if command_arguments['command'] == ClimatizationStartStopCommand.Command.START:
1415
+ command_dict['heaterSource'] = 'ELECTRIC'
1416
+ command_dict['targetTemperature'] = {}
1417
+ if 'target_temperature' in command_arguments:
1418
+ # Round target temperature to nearest 0.5
1419
+ command_dict['targetTemperature']['temperatureValue'] = round(command_arguments['target_temperature'] * 2) / 2
1420
+ if 'target_temperature_unit' in command_arguments:
1421
+ if not isinstance(command_arguments['target_temperature_unit'], Temperature):
1422
+ raise SetterError('Temperature unit is not of type Temperature')
1423
+ if command_arguments['target_temperature_unit'] == Temperature.C:
1424
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1425
+ elif command_arguments['target_temperature_unit'] == Temperature.F:
1426
+ command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
1427
+ elif command_arguments['target_temperature_unit'] == Temperature.K:
1428
+ command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
1429
+ else:
1430
+ raise SetterError(f'Unknown temperature unit {command_arguments['target_temperature_unit']}')
1431
+ else:
1432
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1433
+ elif start_stop_command.parent is not None and (climatization := start_stop_command.parent.parent) is not None \
1434
+ and isinstance(climatization, Climatization) and climatization.settings is not None \
1435
+ and climatization.settings.target_temperature is not None and climatization.settings.target_temperature.enabled \
1436
+ and climatization.settings.target_temperature.value is not None: # pylint: disable=too-many-boolean-expressions
1437
+ # Round target temperature to nearest 0.5
1438
+ command_dict['targetTemperature']['temperatureValue'] = round(climatization.settings.target_temperature.value * 2) / 2
1439
+ if climatization.settings.target_temperature.unit == Temperature.C:
1440
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1441
+ elif climatization.settings.target_temperature.unit == Temperature.F:
1442
+ command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
1443
+ elif climatization.settings.target_temperature.unit == Temperature.K:
1444
+ command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
1445
+ else:
1446
+ raise SetterError(f'Unknown temperature unit {climatization.settings.target_temperature.unit}')
1447
+ else:
1448
+ command_dict['targetTemperature']['temperatureValue'] = 25.0
1449
+ command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
1450
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/start'
1451
+ settings_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
1452
+ elif command_arguments['command'] == ClimatizationStartStopCommand.Command.STOP:
1453
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/stop'
1454
+ settings_response: requests.Response = self.session.post(url, allow_redirects=True)
1455
+ else:
1456
+ raise SetterError(f'Unknown command {command_arguments["command"]}')
1457
+
1458
+ if settings_response.status_code != requests.codes['accepted']:
1459
+ LOG.error('Could not start/stop air conditioning (%s: %s)', settings_response.status_code, settings_response.text)
1460
+ raise SetterError(f'Could not start/stop air conditioning ({settings_response.status_code}: {settings_response.text})')
1461
+ return command_arguments
1462
+
1463
+ def __on_charging_start_stop(self, start_stop_command: ChargingStartStopCommand, command_arguments: Union[str, Dict[str, Any]]) \
1464
+ -> Union[str, Dict[str, Any]]:
1465
+ if start_stop_command.parent is None or start_stop_command.parent.parent is None \
1466
+ or start_stop_command.parent.parent.parent is None or not isinstance(start_stop_command.parent.parent.parent, SkodaVehicle):
1467
+ raise SetterError('Object hierarchy is not as expected')
1468
+ if not isinstance(command_arguments, dict):
1469
+ raise SetterError('Command arguments are not a dictionary')
1470
+ vehicle: SkodaVehicle = start_stop_command.parent.parent.parent
1471
+ vin: Optional[str] = vehicle.vin.value
1472
+ if vin is None:
1473
+ raise SetterError('VIN in object hierarchy missing')
1474
+ if 'command' not in command_arguments:
1475
+ raise SetterError('Command argument missing')
1476
+ if command_arguments['command'] == ChargingStartStopCommand.Command.START:
1477
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}/start'
1478
+ settings_response: requests.Response = self.session.post(url, allow_redirects=True)
1479
+ elif command_arguments['command'] == ChargingStartStopCommand.Command.STOP:
1480
+ url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}/stop'
1481
+ settings_response: requests.Response = self.session.post(url, allow_redirects=True)
1482
+ else:
1483
+ raise SetterError(f'Unknown command {command_arguments["command"]}')
1484
+
1485
+ if settings_response.status_code != requests.codes['accepted']:
1486
+ LOG.error('Could not start/stop charging (%s: %s)', settings_response.status_code, settings_response.text)
1487
+ raise SetterError(f'Could not start/stop charging ({settings_response.status_code}: {settings_response.text})')
1488
+ return command_arguments
@@ -49,4 +49,5 @@ class Error(GenericObject):
49
49
  This enum can be extended to include specific error codes and messages
50
50
  that correspond to different climatization issues.
51
51
  """
52
+ UNAVAILABLE_CHARGING_INFORMATION = 'UNAVAILABLE_CHARGING_INFORMATION'
52
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
@@ -497,7 +497,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
497
497
  electric_drive.level._set_value(measured=measured_at, value=data['data']['soc']) # pylint: disable=protected-access
498
498
  if 'chargedRange' in data['data'] and data['data']['chargedRange'] is not None:
499
499
  # pylint: disable-next=protected-access
500
- 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)
501
501
  # If charging state changed, fetch charging again
502
502
  if old_charging_state != charging_state:
503
503
  try:
@@ -514,7 +514,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
514
514
  estimated_date_reached: Optional[datetime] = None
515
515
  # pylint: disable-next=protected-access
516
516
  vehicle.charging.estimated_date_reached._set_value(measured=measured_at, value=estimated_date_reached)
517
- 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'})
518
518
  LOG.debug('Received %s event for vehicle %s from user %s', data['name'], vin, user_id)
519
519
  return
520
520
  else:
@@ -530,7 +530,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
530
530
  try:
531
531
  self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
532
532
  except CarConnectivityError as e:
533
- LOG.error('Error while fetching charging: %s', e)
533
+ LOG.error('Error while fetching air conditioning: %s', e)
534
534
  elif 'name' in data and data['name'] == 'climatisation-completed':
535
535
  if 'data' in data and data['data'] is not None:
536
536
  vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
@@ -599,7 +599,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
599
599
  return
600
600
  LOG_API.info('Received unknown service event %s for vehicle %s from user %s: %s', service_event, vin, user_id, msg.payload)
601
601
  return
602
- # service_events
602
+ # operation-requests
603
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)
604
604
  if match:
605
605
  user_id: str = match.group('user_id')
@@ -625,7 +625,10 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
625
625
  except CarConnectivityError as e:
626
626
  LOG.error('Error while fetching air-conditioning: %s', e)
627
627
  return
628
- if operation_request == 'charging/start-stop-charging' \
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
631
+ elif operation_request == 'charging/start-stop-charging' \
629
632
  or operation_request == 'charging/update-battery-support' \
630
633
  or operation_request == 'charging/update-auto-unlock-plug' \
631
634
  or operation_request == 'charging/update-care-mode' \
@@ -642,6 +645,9 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
642
645
  except CarConnectivityError as e:
643
646
  LOG.error('Error while fetching charging: %s', e)
644
647
  return
648
+ elif data['status'] == 'IN_PROGRESS':
649
+ LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
650
+ return
645
651
  LOG_API.info('Received unknown operation request %s for vehicle %s from user %s: %s', operation_request, vin, user_id, msg.payload)
646
652
  return
647
653
  LOG_API.info('I don\'t understand message %s: %s', msg.topic, msg.payload)
@@ -27,6 +27,7 @@ class SkodaVehicle(GenericVehicle): # pylint: disable=too-many-instance-attribu
27
27
  else:
28
28
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
29
29
  self.capabilities = Capabilities(vehicle=self)
30
+ self.manufacturer._set_value(value='Škoda') # pylint: disable=protected-access
30
31
 
31
32
  def __str__(self) -> str:
32
33
  return_string: str = f'\t{self.capabilities}\n'