carconnectivity-connector-skoda 0.2a2__py3-none-any.whl → 0.2a4__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.2a2
3
+ Version: 0.2a4
4
4
  Summary: CarConnectivity connector for Skoda services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -37,10 +37,11 @@ Classifier: Topic :: Software Development :: Libraries
37
37
  Requires-Python: >=3.8
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
- Requires-Dist: carconnectivity>=0.2
40
+ Requires-Dist: carconnectivity>=0.3a2
41
41
  Requires-Dist: oauthlib~=3.2.2
42
42
  Requires-Dist: requests~=2.32.3
43
43
  Requires-Dist: jwt~=1.3.1
44
+ Requires-Dist: paho-mqtt~=2.1.0
44
45
 
45
46
 
46
47
 
@@ -0,0 +1,22 @@
1
+ carconnectivity_connectors/skoda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ carconnectivity_connectors/skoda/_version.py,sha256=BvxRhPNikofS69C59rpfXi_PwIqZKOowoWGrEf19x7Y,408
3
+ carconnectivity_connectors/skoda/capability.py,sha256=vbAKK8KKre-CndLF6_5qyWLpfa4KZHk1U-hpb6nCL5w,4225
4
+ carconnectivity_connectors/skoda/charging.py,sha256=CoUOYHHUPPPldKQvv0h-qrUsoEtstR3iUx-l0IU5rNM,6798
5
+ carconnectivity_connectors/skoda/climatization.py,sha256=-Nk4tO5C5_YYNQfUIUWBL7mGgR6-J0_pOZplLK8p_ms,1627
6
+ carconnectivity_connectors/skoda/command_impl.py,sha256=WdgxWPgi82-UgmyFpiSZE-KHRtRjqn7CH-YX9N3bAoI,2875
7
+ carconnectivity_connectors/skoda/connector.py,sha256=3SiP181vatcHF-2YJEvmxQ7Gb1RUCRE2Z2sRCakcz7w,110760
8
+ carconnectivity_connectors/skoda/error.py,sha256=ffxzvjmci7vtp9Q1K4DR1kBF0kTJxN5Gluci3kDBkEI,2459
9
+ carconnectivity_connectors/skoda/mqtt_client.py,sha256=PHvMkNhmkP_FxHvVlXzFLJA6Q3vkFCK8jZHkfII_j74,38123
10
+ carconnectivity_connectors/skoda/vehicle.py,sha256=EhrhAY41A05S2yf5YoU-uvo_alAiSdjFuAyLW318DJw,3383
11
+ carconnectivity_connectors/skoda/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ carconnectivity_connectors/skoda/auth/auth_util.py,sha256=dGLUbUre0HBsTg_Ii5vW34f8DLrCykYJYCyzEvUBBEE,4434
13
+ carconnectivity_connectors/skoda/auth/my_skoda_session.py,sha256=lSh23SFJs8opjmPwHTv-KNIKDep_WY4aItSP4Zq7bT8,10396
14
+ carconnectivity_connectors/skoda/auth/openid_session.py,sha256=5JfR-gS1uKpE8DD-sx5Qvw6zv-OJhzcRlt0D-cm38-Y,16832
15
+ carconnectivity_connectors/skoda/auth/session_manager.py,sha256=Uf1vujuDBYUCAXhYToOsZkgbTtfmY3Qe0ICTfwomBpI,2899
16
+ carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=cjzMkzx473Sh-4RgZAQULeRRcxB1MboddldCVM_y5LE,10640
17
+ carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
18
+ carconnectivity_connector_skoda-0.2a4.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
19
+ carconnectivity_connector_skoda-0.2a4.dist-info/METADATA,sha256=LW_mlPJJ3bdQZJeTlAnNoITcNPGTUMVIKjMWShAn8Yg,5365
20
+ carconnectivity_connector_skoda-0.2a4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
21
+ carconnectivity_connector_skoda-0.2a4.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
22
+ carconnectivity_connector_skoda-0.2a4.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.2a2'
15
+ __version__ = version = '0.2a4'
16
16
  __version_tuple__ = version_tuple = (0, 2)
@@ -18,8 +18,8 @@ from requests.adapters import HTTPAdapter
18
18
 
19
19
  from carconnectivity.errors import AuthenticationError, RetrievalError
20
20
 
21
- from carconnectivity_connectors.volkswagen.auth.auth_util import add_bearer_auth_header
22
- from carconnectivity_connectors.volkswagen.auth.helpers.blacklist_retry import BlacklistRetry
21
+ from carconnectivity_connectors.skoda.auth.auth_util import add_bearer_auth_header
22
+ from carconnectivity_connectors.skoda.auth.helpers.blacklist_retry import BlacklistRetry
23
23
 
24
24
  if TYPE_CHECKING:
25
25
  from typing import Dict
@@ -102,7 +102,7 @@ class Capability(GenericObject):
102
102
  raise ValueError('Capability ID cannot be None')
103
103
  super().__init__(object_id=capability_id, parent=capabilities)
104
104
  self.delay_notifications = True
105
- self.capability_id = StringAttribute("id", self, capability_id)
105
+ self.capability_id = StringAttribute("id", self, capability_id, tags={'connector_custom'})
106
106
  self.statuses = list[Capability.Status]
107
107
  self.enabled = True
108
108
  self.delay_notifications = False
@@ -32,7 +32,7 @@ class SkodaCharging(Charging): # pylint: disable=too-many-instance-attributes
32
32
  super().__init__(vehicle=vehicle)
33
33
  self.settings: Charging.Settings = SkodaCharging.Settings(origin=self.settings)
34
34
  self.errors: Dict[str, Error] = {}
35
- self.is_in_saved_location: BooleanAttribute = BooleanAttribute("is_in_saved_location", parent=self)
35
+ self.is_in_saved_location: BooleanAttribute = BooleanAttribute("is_in_saved_location", parent=self, tags={'connector_custom'})
36
36
 
37
37
  class Settings(Charging.Settings):
38
38
  """
@@ -43,10 +43,10 @@ class SkodaCharging(Charging): # pylint: disable=too-many-instance-attributes
43
43
  super().__init__(origin=origin)
44
44
  else:
45
45
  super().__init__(parent=parent)
46
- self.preferred_charge_mode: EnumAttribute = EnumAttribute("preferred_charge_mode", parent=self)
47
- self.available_charge_modes: StringAttribute = StringAttribute("available_charge_modes", parent=self)
48
- self.charging_care_mode: EnumAttribute = EnumAttribute("charging_care_mode", parent=self)
49
- self.battery_support: EnumAttribute = EnumAttribute("battery_support", parent=self)
46
+ self.preferred_charge_mode: EnumAttribute = EnumAttribute("preferred_charge_mode", parent=self, tags={'connector_custom'})
47
+ self.available_charge_modes: StringAttribute = StringAttribute("available_charge_modes", parent=self, tags={'connector_custom'})
48
+ self.charging_care_mode: EnumAttribute = EnumAttribute("charging_care_mode", parent=self, tags={'connector_custom'})
49
+ self.battery_support: EnumAttribute = EnumAttribute("battery_support", parent=self, tags={'connector_custom'})
50
50
 
51
51
  class SkodaChargingState(Enum,):
52
52
  """
@@ -42,6 +42,16 @@ from carconnectivity_connectors.skoda._version import __version__
42
42
  from carconnectivity_connectors.skoda.mqtt_client import SkodaMQTTClient
43
43
  from carconnectivity_connectors.skoda.command_impl import SpinCommand
44
44
 
45
+ SUPPORT_IMAGES = False
46
+ try:
47
+ from PIL import Image
48
+ import base64
49
+ import io
50
+ SUPPORT_IMAGES = True
51
+ from carconnectivity.attributes import ImageAttribute
52
+ except ImportError:
53
+ pass
54
+
45
55
  if TYPE_CHECKING:
46
56
  from typing import Dict, List, Optional, Any, Set, Union
47
57
 
@@ -69,8 +79,8 @@ class Connector(BaseConnector):
69
79
  self._background_connect_thread: Optional[threading.Thread] = None
70
80
  self._stop_event = threading.Event()
71
81
 
72
- self.connected: BooleanAttribute = BooleanAttribute(name="connected", parent=self)
73
- self.interval: DurationAttribute = DurationAttribute(name="interval", parent=self)
82
+ self.connected: BooleanAttribute = BooleanAttribute(name="connected", parent=self, tags={'connector_custom'})
83
+ self.interval: DurationAttribute = DurationAttribute(name="interval", parent=self, tags={'connector_custom'})
74
84
  self.commands: Commands = Commands(parent=self)
75
85
 
76
86
  self.user_id: Optional[str] = None
@@ -315,6 +325,8 @@ class Connector(BaseConnector):
315
325
  log_extra_keys(LOG_API, 'vehicles', vehicle_dict, {'vin', 'licensePlate', 'name'})
316
326
 
317
327
  vehicle = self.fetch_vehicle_details(vehicle)
328
+ if SUPPORT_IMAGES:
329
+ vehicle = self.fetch_vehicle_images(vehicle)
318
330
  else:
319
331
  raise APIError('Could not parse vehicle, vin missing')
320
332
  for vin in set(garage.list_vehicle_vins()) - seen_vehicle_vins:
@@ -930,6 +942,68 @@ class Connector(BaseConnector):
930
942
  log_extra_keys(LOG_API, 'api/v2/garage/vehicles/VIN', vehicle_data, {'softwareVersion'})
931
943
  return vehicle
932
944
 
945
+ def fetch_vehicle_images(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
946
+ if SUPPORT_IMAGES:
947
+ url: str = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-information/{vehicle.vin.value}/renders'
948
+ data = self._fetch_data(url, session=self.session, allow_http_error=True)
949
+ if data is not None and 'compositeRenders' in data: # pylint: disable=too-many-nested-blocks
950
+ for image in data['compositeRenders']:
951
+ if not 'layers' in image or image['layers'] is None or len(image['layers']) == 0:
952
+ continue
953
+ image_url: Optional[str] = None
954
+ for layer in image['layers']:
955
+ if 'url' in layer and layer['url'] is not None:
956
+ image_url = layer['url']
957
+ break
958
+ if image_url is None:
959
+ continue
960
+ img = None
961
+ cache_date = None
962
+ if self.max_age is not None and self.session.cache is not None and image_url in self.session.cache:
963
+ img, cache_date_string = self.session.cache[image_url]
964
+ img = base64.b64decode(img) # pyright: ignore[reportPossiblyUnboundVariable]
965
+ img = Image.open(io.BytesIO(img)) # pyright: ignore[reportPossiblyUnboundVariable]
966
+ cache_date = datetime.fromisoformat(cache_date_string)
967
+ if img is None or self.max_age is None \
968
+ or (cache_date is not None and cache_date < (datetime.utcnow() - timedelta(seconds=self.max_age))):
969
+ try:
970
+ image_download_response = requests.get(image_url, stream=True)
971
+ if image_download_response.status_code == requests.codes['ok']:
972
+ img = Image.open(image_download_response.raw) # pyright: ignore[reportPossiblyUnboundVariable]
973
+ if self.session.cache is not None:
974
+ buffered = io.BytesIO() # pyright: ignore[reportPossiblyUnboundVariable]
975
+ img.save(buffered, format="PNG")
976
+ img_str = base64.b64encode(buffered.getvalue()).decode("utf-8") # pyright: ignore[reportPossiblyUnboundVariable]
977
+ self.session.cache[image_url] = (img_str, str(datetime.utcnow()))
978
+ elif image_download_response.status_code == requests.codes['unauthorized']:
979
+ LOG.info('Server asks for new authorization')
980
+ self.session.login()
981
+ image_download_response = self.session.get(image_url, stream=True)
982
+ if image_download_response.status_code == requests.codes['ok']:
983
+ img = Image.open(image_download_response.raw) # pyright: ignore[reportPossiblyUnboundVariable]
984
+ if self.session.cache is not None:
985
+ buffered = io.BytesIO() # pyright: ignore[reportPossiblyUnboundVariable]
986
+ img.save(buffered, format="PNG")
987
+ img_str = base64.b64encode(buffered.getvalue()).decode("utf-8") # pyright: ignore[reportPossiblyUnboundVariable]
988
+ self.session.cache[image_url] = (img_str, str(datetime.utcnow()))
989
+ except requests.exceptions.ConnectionError as connection_error:
990
+ raise RetrievalError(f'Connection error: {connection_error}') from connection_error
991
+ except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
992
+ raise RetrievalError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
993
+ except requests.exceptions.ReadTimeout as timeout_error:
994
+ raise RetrievalError(f'Timeout during read: {timeout_error}') from timeout_error
995
+ except requests.exceptions.RetryError as retry_error:
996
+ raise RetrievalError(f'Retrying failed: {retry_error}') from retry_error
997
+ if img is not None:
998
+ vehicle._car_images[image['viewType']] = img # pylint: disable=protected-access
999
+ if image['viewType'] == 'UNMODIFIED_EXTERIOR_FRONT':
1000
+ if 'car_picture' in vehicle.images.images:
1001
+ vehicle.images.images['car_picture']._set_value(img) # pylint: disable=protected-access
1002
+ else:
1003
+ vehicle.images.images['car_picture']: ImageAttribute = ImageAttribute(name="car_picture", parent=vehicle.images,
1004
+ value=img, tags={'carconnectivity'})
1005
+ return vehicle
1006
+
933
1007
  def fetch_driving_range(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
934
1008
  """
935
1009
  Fetches the driving range data for a given Skoda vehicle and updates the vehicle object accordingly.
@@ -1194,6 +1268,9 @@ class Connector(BaseConnector):
1194
1268
  def get_version(self) -> str:
1195
1269
  return __version__
1196
1270
 
1271
+ def get_type(self) -> str:
1272
+ return "carconnectivity-connector-skoda"
1273
+
1197
1274
  def __on_air_conditioning_target_temperature_change(self, temperature_attribute: TemperatureAttribute, target_temperature: float) -> float:
1198
1275
  """
1199
1276
  Callback for the climatization target temperature change.
@@ -18,8 +18,8 @@ class Error(GenericObject):
18
18
 
19
19
  def __init__(self, object_id, parent: Optional[GenericObject] = None) -> None:
20
20
  super().__init__(object_id, parent=parent)
21
- self.type: EnumAttribute = EnumAttribute("type", parent=self)
22
- self.description: StringAttribute = StringAttribute("description", parent=self)
21
+ self.type: EnumAttribute = EnumAttribute("type", parent=self, tags={'connector_custom'})
22
+ self.description: StringAttribute = StringAttribute("description", parent=self, tags={'connector_custom'})
23
23
 
24
24
  class ChargingError(Enum):
25
25
  """
@@ -179,7 +179,10 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
179
179
  vin: str = vehicle.vin.value
180
180
  # If the skoda connector is managing this vehicle
181
181
  if self._skoda_connector in vehicle.managing_connectors:
182
- account_events: Set[str] = {'privacy'}
182
+ account_events: Set[str] = {'privacy',
183
+ 'guest-user-nomination',
184
+ 'primary-user-nomination'}
185
+ vehicle_status_events: Set[str] = {'vehicle-connection-status'}
183
186
  operation_requests: Set[str] = {
184
187
  'air-conditioning/set-air-conditioning-at-unlock',
185
188
  'air-conditioning/set-air-conditioning-seats-heating',
@@ -216,6 +219,8 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
216
219
  # Compile all possible topics
217
220
  for event in account_events:
218
221
  possible_topics.add(f'{user_id}/{vin}/account-event/{event}')
222
+ for event in vehicle_status_events:
223
+ possible_topics.add(f'{user_id}/{vin}/vehicle-status/{event}')
219
224
  for event in operation_requests:
220
225
  possible_topics.add(f'{user_id}/{vin}/operation-request/{event}')
221
226
  for event in service_events:
@@ -8,8 +8,15 @@ from carconnectivity.charging import Charging
8
8
  from carconnectivity_connectors.skoda.capability import Capabilities
9
9
  from carconnectivity_connectors.skoda.charging import SkodaCharging
10
10
 
11
+ SUPPORT_IMAGES = False
12
+ try:
13
+ from PIL import Image
14
+ SUPPORT_IMAGES = True
15
+ except ImportError:
16
+ pass
17
+
11
18
  if TYPE_CHECKING:
12
- from typing import Optional
19
+ from typing import Optional, Dict
13
20
  from carconnectivity.garage import Garage
14
21
  from carconnectivity_connectors.base.connector import BaseConnector
15
22
 
@@ -24,9 +31,14 @@ class SkodaVehicle(GenericVehicle): # pylint: disable=too-many-instance-attribu
24
31
  super().__init__(origin=origin)
25
32
  self.capabilities: Capabilities = origin.capabilities
26
33
  self.capabilities.parent = self
34
+ if SUPPORT_IMAGES:
35
+ self._car_images = origin._car_images
36
+
27
37
  else:
28
38
  super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
29
39
  self.capabilities = Capabilities(vehicle=self)
40
+ if SUPPORT_IMAGES:
41
+ self._car_images: Dict[str, Image.Image] = {}
30
42
  self.manufacturer._set_value(value='Škoda') # pylint: disable=protected-access
31
43
 
32
44
 
@@ -1,22 +0,0 @@
1
- carconnectivity_connectors/skoda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- carconnectivity_connectors/skoda/_version.py,sha256=K5FRuDp7oE3vVzMAjjBq7gz8WRnAzHih0vG_csLk83E,408
3
- carconnectivity_connectors/skoda/capability.py,sha256=SnK9xVsqDA5EcWtBznzfWxe6gIpkYdjgY3UzNIc3OCY,4198
4
- carconnectivity_connectors/skoda/charging.py,sha256=rG_GoDPetjyjWCyV6l65hgEDU6ths6MkMQ0KL25rbVU,6663
5
- carconnectivity_connectors/skoda/climatization.py,sha256=-Nk4tO5C5_YYNQfUIUWBL7mGgR6-J0_pOZplLK8p_ms,1627
6
- carconnectivity_connectors/skoda/command_impl.py,sha256=WdgxWPgi82-UgmyFpiSZE-KHRtRjqn7CH-YX9N3bAoI,2875
7
- carconnectivity_connectors/skoda/connector.py,sha256=g4G9u8xE3iG6ASy7sGSXimaJ3zFV04E9GrNn5TB0ia4,105140
8
- carconnectivity_connectors/skoda/error.py,sha256=EnzzDxxJ1fswYT5QnMOVSebfoAcqoPmHKcG5i0Tqk3E,2405
9
- carconnectivity_connectors/skoda/mqtt_client.py,sha256=lfHJfKOl-FBVd5hV6cS6ZMpZ53ktXyVc4lafvQls-Tk,37748
10
- carconnectivity_connectors/skoda/vehicle.py,sha256=_ALtlBy7sKVHmqpqAhWNbMd9dto915_SdNWcRi_AqYU,3088
11
- carconnectivity_connectors/skoda/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- carconnectivity_connectors/skoda/auth/auth_util.py,sha256=dGLUbUre0HBsTg_Ii5vW34f8DLrCykYJYCyzEvUBBEE,4434
13
- carconnectivity_connectors/skoda/auth/my_skoda_session.py,sha256=lSh23SFJs8opjmPwHTv-KNIKDep_WY4aItSP4Zq7bT8,10396
14
- carconnectivity_connectors/skoda/auth/openid_session.py,sha256=LusWi2FZZIL3buodGXZKUR0naLhhqeYv0uRW4V3wI2w,16842
15
- carconnectivity_connectors/skoda/auth/session_manager.py,sha256=Uf1vujuDBYUCAXhYToOsZkgbTtfmY3Qe0ICTfwomBpI,2899
16
- carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=cjzMkzx473Sh-4RgZAQULeRRcxB1MboddldCVM_y5LE,10640
17
- carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
18
- carconnectivity_connector_skoda-0.2a2.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
19
- carconnectivity_connector_skoda-0.2a2.dist-info/METADATA,sha256=t2d6yjpItOO8uvEmSK53fb2YoxQ7RK3TLBQ3x9YmHdw,5331
20
- carconnectivity_connector_skoda-0.2a2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
21
- carconnectivity_connector_skoda-0.2a2.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
22
- carconnectivity_connector_skoda-0.2a2.dist-info/RECORD,,