carconnectivity-connector-skoda 0.1a11__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: carconnectivity-connector-skoda
3
- Version: 0.1a11
3
+ Version: 0.8.2
4
4
  Summary: CarConnectivity connector for Skoda services
5
5
  Author: Till Steinbach
6
6
  License: MIT License
@@ -33,18 +33,21 @@ Classifier: Programming Language :: Python :: 3.10
33
33
  Classifier: Programming Language :: Python :: 3.11
34
34
  Classifier: Programming Language :: Python :: 3.12
35
35
  Classifier: Programming Language :: Python :: 3.13
36
+ Classifier: Programming Language :: Python :: 3.14
36
37
  Classifier: Topic :: Software Development :: Libraries
37
38
  Requires-Python: >=3.8
38
39
  Description-Content-Type: text/markdown
39
40
  License-File: LICENSE
40
- Requires-Dist: carconnectivity
41
- Requires-Dist: oauthlib~=3.2.2
42
- Requires-Dist: requests~=2.32.3
43
- Requires-Dist: jwt~=1.3.1
41
+ Requires-Dist: carconnectivity>=0.8.1
42
+ Requires-Dist: oauthlib~=3.3.1
43
+ Requires-Dist: requests~=2.32.5
44
+ Requires-Dist: pyjwt~=2.10
45
+ Requires-Dist: paho-mqtt~=2.1.0
46
+ Dynamic: license-file
44
47
 
45
48
 
46
49
 
47
- # CarConnectivity Connector for Volkswagen Vehicles
50
+ # CarConnectivity Connector for Skoda Vehicles
48
51
  [![GitHub sourcecode](https://img.shields.io/badge/Source-GitHub-green)](https://github.com/tillsteinbach/CarConnectivity-connector-skoda/)
49
52
  [![GitHub release (latest by date)](https://img.shields.io/github/v/release/tillsteinbach/CarConnectivity-connector-skoda)](https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/latest)
50
53
  [![GitHub](https://img.shields.io/github/license/tillsteinbach/CarConnectivity-connector-skoda)](https://github.com/tillsteinbach/CarConnectivity-connector-skoda/blob/master/LICENSE)
@@ -0,0 +1,23 @@
1
+ carconnectivity_connector_skoda-0.8.2.dist-info/licenses/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
2
+ carconnectivity_connectors/skoda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ carconnectivity_connectors/skoda/_version.py,sha256=K6dg_KQgkOH-DF8J9hcSjz3upL94O2YIVOO7FP9tPpk,704
4
+ carconnectivity_connectors/skoda/capability.py,sha256=TC8-yC23UUrf0faePdbZL0802DHXbtGDcSlt3vj5ltg,4770
5
+ carconnectivity_connectors/skoda/charging.py,sha256=7DPNiTWFhxiiEFKVnbIIU2TCmkpmcMWx_zsHXGXFpAQ,6856
6
+ carconnectivity_connectors/skoda/climatization.py,sha256=Jut468SkxjPBDTqroWFvDifVPfJBxGjsFed5pc4kZkg,1768
7
+ carconnectivity_connectors/skoda/command_impl.py,sha256=wDCI3Bka5pXlbyI4yVFS353TgFGyiBHBkERpP2g0A9w,3230
8
+ carconnectivity_connectors/skoda/connector.py,sha256=y25ZY1jCH-8IXSK847Stu4GbRK6j35xwZJKU9C7TSSU,147301
9
+ carconnectivity_connectors/skoda/error.py,sha256=ffxzvjmci7vtp9Q1K4DR1kBF0kTJxN5Gluci3kDBkEI,2459
10
+ carconnectivity_connectors/skoda/mqtt_client.py,sha256=f4fRFeI1VUCGm9ZzGj_vnctiSIiMAoolYLAwKx4apqA,40086
11
+ carconnectivity_connectors/skoda/vehicle.py,sha256=q5gwe-_yPfE_-aEc17UQ-Q0Z46IN7PCpNG5jLw5PZl0,3981
12
+ carconnectivity_connectors/skoda/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ carconnectivity_connectors/skoda/auth/auth_util.py,sha256=dGLUbUre0HBsTg_Ii5vW34f8DLrCykYJYCyzEvUBBEE,4434
14
+ carconnectivity_connectors/skoda/auth/my_skoda_session.py,sha256=5olw2TsId63BbJxmMk7i193Vww7RlBo9mpE3Ucl5fhw,11207
15
+ carconnectivity_connectors/skoda/auth/openid_session.py,sha256=CiHKXWpZqlhjzKNCKQ_Y5f3RJgw4gEKbWp_I5_fXa7Y,16984
16
+ carconnectivity_connectors/skoda/auth/session_manager.py,sha256=Uf1vujuDBYUCAXhYToOsZkgbTtfmY3Qe0ICTfwomBpI,2899
17
+ carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=tapjCRRPBu3tHrDoKmtuAlQhgxktib3oWTB8MHEzZTY,10816
18
+ carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
19
+ carconnectivity_connectors/skoda/ui/connector_ui.py,sha256=lLjwoakRaU0S80hAVwVi4JA1wtHycGHcoM2-7S9qsqI,1386
20
+ carconnectivity_connector_skoda-0.8.2.dist-info/METADATA,sha256=LP-Xq58069bnrLJeiyEC8yg0zJGgGQGLt3AIuKStJes,5434
21
+ carconnectivity_connector_skoda-0.8.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ carconnectivity_connector_skoda-0.8.2.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
23
+ carconnectivity_connector_skoda-0.8.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,16 +1,34 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
3
13
  TYPE_CHECKING = False
4
14
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
15
+ from typing import Tuple
16
+ from typing import Union
17
+
6
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
7
20
  else:
8
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
9
23
 
10
24
  version: str
11
25
  __version__: str
12
26
  __version_tuple__: VERSION_TUPLE
13
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.8.2'
32
+ __version_tuple__ = version_tuple = (0, 8, 2)
14
33
 
15
- __version__ = version = '0.1a11'
16
- __version_tuple__ = version_tuple = (0, 1)
34
+ __commit_id__ = commit_id = None
@@ -12,9 +12,11 @@ import random
12
12
  import string
13
13
 
14
14
  from urllib.parse import parse_qsl, urlparse
15
+ from urllib3.exceptions import NameResolutionError
15
16
 
16
17
  import requests
17
18
  from requests.models import CaseInsensitiveDict
19
+ from requests.exceptions import ReadTimeout, ConnectionError
18
20
 
19
21
  from oauthlib.common import add_params_to_uri, generate_nonce, to_unicode
20
22
  from oauthlib.oauth2 import InsecureTransportError
@@ -58,17 +60,24 @@ class MySkodaSession(SkodaWebSession):
58
60
  def login(self):
59
61
  super(MySkodaSession, self).login()
60
62
 
61
- verifier = "".join(random.choices(string.ascii_uppercase + string.digits, k=16))
62
- verifier_hash = hashlib.sha256(verifier.encode("utf-8")).digest()
63
- code_challenge = base64.b64encode(verifier_hash).decode("utf-8").replace("+", "-").replace("/", "_").rstrip("=")
64
- # retrieve authorization URL
65
- authorization_url = self.authorization_url(url='https://identity.vwgroup.io/oidc/v1/authorize', prompt='login', code_challenge=code_challenge,
66
- code_challenge_method='s256')
67
- # perform web authentication
68
- response = self.do_web_auth(authorization_url)
69
- # fetch tokens from web authentication response
70
- self.fetch_tokens('https://mysmob.api.connect.skoda-auto.cz/api/v1/authentication/exchange-authorization-code?tokenType=CONNECT',
71
- authorization_response=response, verifier=verifier)
63
+ try:
64
+ verifier = "".join(random.choices(string.ascii_uppercase + string.digits, k=16))
65
+ verifier_hash = hashlib.sha256(verifier.encode("utf-8")).digest()
66
+ code_challenge = base64.b64encode(verifier_hash).decode("utf-8").replace("+", "-").replace("/", "_").rstrip("=")
67
+ # retrieve authorization URL
68
+ authorization_url = self.authorization_url(url='https://identity.vwgroup.io/oidc/v1/authorize', prompt='login', code_challenge=code_challenge,
69
+ code_challenge_method='s256')
70
+ # perform web authentication
71
+ response = self.do_web_auth(authorization_url)
72
+ # fetch tokens from web authentication response
73
+ self.fetch_tokens('https://mysmob.api.connect.skoda-auto.cz/api/v1/authentication/exchange-authorization-code?tokenType=CONNECT',
74
+ authorization_response=response, verifier=verifier)
75
+ except ReadTimeout as exc:
76
+ raise TemporaryAuthenticationError('Login timed out (Read timeout)') from exc
77
+ except ConnectionError as exc:
78
+ raise TemporaryAuthenticationError('Login failed due to connection error') from exc
79
+ except NameResolutionError as exc:
80
+ raise TemporaryAuthenticationError('Token could not be refreshed due to Name resolution error, probably no internet connection') from exc
72
81
 
73
82
  def refresh(self) -> None:
74
83
  # refresh tokens from refresh endpoint
@@ -118,7 +127,7 @@ class MySkodaSession(SkodaWebSession):
118
127
  token_response = self.post(token_url, headers=request_headers, data=body, allow_redirects=False,
119
128
  access_type=AccessType.NONE) # pyright: ignore reportCallIssue
120
129
  if token_response.status_code != requests.codes['ok']:
121
- raise TemporaryAuthenticationError(f'Token could not be fetched due to temporary WeConnect failure: {token_response.status_code}')
130
+ raise TemporaryAuthenticationError(f'Token could not be fetched due to temporary MySkoda failure: {token_response.status_code}')
122
131
  # parse token from response body
123
132
  token = self.parse_from_body(token_response.text)
124
133
  return token
@@ -132,7 +141,7 @@ class MySkodaSession(SkodaWebSession):
132
141
  # Tokens are in body of response in json format
133
142
  token = json.loads(token_response)
134
143
  except json.decoder.JSONDecodeError as err:
135
- raise TemporaryAuthenticationError('Token could not be refreshed due to temporary WeConnect failure: json could not be decoded') from err
144
+ raise TemporaryAuthenticationError('Token could not be refreshed due to temporary MySkoda failure: json could not be decoded') from err
136
145
  found_tokens: Set[str] = set()
137
146
  # Fix token keys, we want access_token instead of accessToken
138
147
  if 'accessToken' in token:
@@ -222,3 +231,5 @@ class MySkodaSession(SkodaWebSession):
222
231
  except ConnectionError:
223
232
  self.login()
224
233
  return self.token
234
+ except NameResolutionError as exc:
235
+ raise TemporaryAuthenticationError('Token could not be refreshed due to Name resolution error, probably no internet connection') from exc
@@ -7,7 +7,7 @@ import time
7
7
  from datetime import datetime, timezone
8
8
  import logging
9
9
  import requests
10
- from jwt import JWT
10
+ import jwt
11
11
 
12
12
  from oauthlib.common import UNICODE_ASCII_CHARACTER_SET, generate_nonce, generate_token
13
13
  from oauthlib.oauth2.rfc6749.parameters import parse_authorization_code_response, parse_token_response, prepare_grant_uri
@@ -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
@@ -149,23 +149,24 @@ class OpenIDSession(requests.Session):
149
149
  if new_token is not None:
150
150
  # If new token e.g. after refresh is missing expires_in we assume it is the same than before
151
151
  if 'expires_in' not in new_token:
152
- if self._token is not None and 'expires_in' in self._token:
153
- new_token['expires_in'] = self._token['expires_in']
154
- else:
155
- if 'id_token' in new_token:
156
- jwt_instance = JWT()
157
- meta_data = jwt_instance.decode(new_token['id_token'], do_verify=False)
158
- if 'exp' in meta_data:
159
- new_token['expires_at'] = meta_data['exp']
160
- expires_at = datetime.fromtimestamp(meta_data['exp'], tz=timezone.utc)
161
- new_token['expires_in'] = (expires_at - datetime.now(tz=timezone.utc)).total_seconds()
162
- else:
163
- new_token['expires_in'] = 3600
152
+ if 'id_token' in new_token:
153
+ meta_data = jwt.decode(new_token['id_token'], options={"verify_signature": False})
154
+ if 'exp' in meta_data:
155
+ new_token['expires_at'] = meta_data['exp']
156
+ expires_at = datetime.fromtimestamp(meta_data['exp'], tz=timezone.utc)
157
+ new_token['expires_in'] = (expires_at - datetime.now(tz=timezone.utc)).total_seconds()
158
+ if 'expires_in' not in new_token:
159
+ if self._token is not None and 'expires_in' in self._token:
160
+ new_token['expires_in'] = self._token['expires_in']
164
161
  else:
165
162
  new_token['expires_in'] = 3600
166
163
  # If expires_in is set and expires_at is not set we calculate expires_at from expires_in using the current time
167
164
  if 'expires_in' in new_token and 'expires_at' not in new_token:
168
165
  new_token['expires_at'] = time.time() + int(new_token.get('expires_in'))
166
+ if new_token['expires_in'] > 3600:
167
+ LOG.warning('unexpected Token expires_in > 3600s (%d)', new_token['expires_in'])
168
+ if new_token['expires_at'] > (time.time() + 3600):
169
+ LOG.warning('unexpected Token expires_at after more than 3600s')
169
170
  self._token = new_token
170
171
 
171
172
  @property
@@ -121,6 +121,8 @@ class SkodaWebSession(OpenIDSession):
121
121
  raise RetrievalError('Temporary server error during login')
122
122
 
123
123
  if 'Location' not in response.headers:
124
+ if 'consent' in url:
125
+ raise AuthenticationError('Could not find Location in headers, probably due to missing consent. Try visiting: ' + url)
124
126
  raise APICompatibilityError('Forwarding without Location in headers')
125
127
 
126
128
  url = response.headers['Location']
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from enum import IntEnum
6
6
 
7
7
  from carconnectivity.objects import GenericObject
8
- from carconnectivity.attributes import StringAttribute
8
+ from carconnectivity.attributes import StringAttribute, GenericAttribute
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from typing import Dict, Optional
@@ -77,7 +77,7 @@ class Capabilities(GenericObject):
77
77
  """
78
78
  return self.__capabilities.get(capability_id)
79
79
 
80
- def has_capability(self, capability_id: str) -> bool:
80
+ def has_capability(self, capability_id: str, check_status_ok=False) -> bool:
81
81
  """
82
82
  Check if the Capabilities contains a capability with the specified ID.
83
83
 
@@ -87,13 +87,14 @@ class Capabilities(GenericObject):
87
87
  Returns:
88
88
  bool: True if the capability exists, otherwise False.
89
89
  """
90
- return capability_id in self.__capabilities
91
-
92
- def __str__(self) -> str:
93
- return_string = 'Capabilities:\n'
94
- for capability in self.__capabilities.values():
95
- return_string += f'\t{capability}\n'
96
- return return_string
90
+ if check_status_ok:
91
+ if capability_id in self.__capabilities and self.__capabilities[capability_id].enabled:
92
+ capability: Capability = self.__capabilities[capability_id]
93
+ if capability.status.enabled and capability.status.value is not None and len(capability.status.value) > 0:
94
+ return False
95
+ return True
96
+ return False
97
+ return capability_id in self.__capabilities and self.__capabilities[capability_id].enabled
97
98
 
98
99
 
99
100
  class Capability(GenericObject):
@@ -108,15 +109,11 @@ class Capability(GenericObject):
108
109
  raise ValueError('Capability ID cannot be None')
109
110
  super().__init__(object_id=capability_id, parent=capabilities)
110
111
  self.delay_notifications = True
111
- self.capability_id = StringAttribute("id", self, capability_id)
112
- self.statuses = list[Capability.Status]
112
+ self.capability_id = StringAttribute("id", self, capability_id, tags={'connector_custom'})
113
+ self.status = GenericAttribute("status", self, value=[], tags={'connector_custom'})
113
114
  self.enabled = True
114
115
  self.delay_notifications = False
115
116
 
116
- def __str__(self) -> str:
117
- return_string = f'{self.capability_id}'
118
- return return_string
119
-
120
117
  class Status(IntEnum):
121
118
  """
122
119
  Enum for capability status.
@@ -135,7 +132,7 @@ class Capability(GenericObject):
135
132
  LOCATION_DATA_DISABLED = 1013
136
133
  LICENSE_INACTIVE = 2001
137
134
  LICENSE_EXPIRED = 2002
138
- MISSING_LICENSE = 2003
135
+ LICENSE_MISSING = 2003
139
136
  USER_NOT_VERIFIED = 3001
140
137
  TERMS_AND_CONDITIONS_NOT_ACCEPTED = 3002
141
138
  INSUFFICIENT_RIGHTS = 3003
@@ -7,18 +7,47 @@ from typing import TYPE_CHECKING
7
7
  from enum import Enum
8
8
 
9
9
  from carconnectivity.charging import Charging
10
+ from carconnectivity.objects import GenericObject
11
+ from carconnectivity.vehicle import ElectricVehicle
12
+ from carconnectivity.attributes import BooleanAttribute, EnumAttribute, StringAttribute
13
+
14
+ from carconnectivity_connectors.skoda.error import Error
10
15
 
11
16
  if TYPE_CHECKING:
12
- from typing import Dict
17
+ from typing import Optional, Dict
13
18
 
14
19
 
15
20
  class SkodaCharging(Charging): # pylint: disable=too-many-instance-attributes
16
21
  """
17
22
  SkodaCharging class for handling Skoda vehicle charging information.
18
23
 
19
- This class extends the Charging class and includes an enumeration of various
24
+ This class extends the Charging class and includes an enumeration of various
20
25
  charging states specific to Skoda vehicles.
21
26
  """
27
+ def __init__(self, vehicle: ElectricVehicle | None = None, origin: Optional[Charging] = None) -> None:
28
+ if origin is not None:
29
+ super().__init__(vehicle=vehicle, origin=origin)
30
+ self.settings: Charging.Settings = SkodaCharging.Settings(parent=self, origin=origin.settings)
31
+ else:
32
+ super().__init__(vehicle=vehicle)
33
+ self.settings: Charging.Settings = SkodaCharging.Settings(parent=self, origin=self.settings)
34
+ self.errors: Dict[str, Error] = {}
35
+ self.is_in_saved_location: BooleanAttribute = BooleanAttribute("is_in_saved_location", parent=self, tags={'connector_custom'})
36
+
37
+ class Settings(Charging.Settings):
38
+ """
39
+ This class represents the settings for a skoda car charging.
40
+ """
41
+ def __init__(self, parent: Optional[GenericObject] = None, origin: Optional[Charging.Settings] = None) -> None:
42
+ if origin is not None:
43
+ super().__init__(parent=parent, origin=origin)
44
+ else:
45
+ super().__init__(parent=parent)
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
+
22
51
  class SkodaChargingState(Enum,):
23
52
  """
24
53
  Enum representing the various charging states for a Skoda vehicle.
@@ -40,7 +69,7 @@ class SkodaCharging(Charging): # pylint: disable=too-many-instance-attributes
40
69
  CONNECT_CABLE = 'connectCable'
41
70
  READY_FOR_CHARGING = 'readyForCharging'
42
71
  NOT_READY_FOR_CHARGING = 'notReadyForCharging'
43
- CONSERVATION = 'conservation'
72
+ CONSERVING = 'conserving'
44
73
  CHARGE_PURPOSE_REACHED_NOT_CONSERVATION_CHARGING = 'chargePurposeReachedAndNotConservationCharging'
45
74
  CHARGE_PURPOSE_REACHED_CONSERVATION = 'chargePurposeReachedAndConservation'
46
75
  CHARGING = 'charging'
@@ -49,6 +78,47 @@ class SkodaCharging(Charging): # pylint: disable=too-many-instance-attributes
49
78
  DISCHARGING = 'discharging'
50
79
  UNKNOWN = 'unknown charging state'
51
80
 
81
+ class SkodaChargeMode(Enum,):
82
+ """
83
+ Enum class representing different Skoda charge modes.
84
+
85
+ Attributes:
86
+ HOME_STORAGE_CHARGING (str): Charge mode for home storage charging.
87
+ IMMEDIATE_DISCHARGING (str): Charge mode for immediate discharging.
88
+ ONLY_OWN_CURRENT (str): Charge mode for using only own current.
89
+ PREFERRED_CHARGING_TIMES (str): Charge mode for preferred charging times.
90
+ TIMER_CHARGING_WITH_CLIMATISATION (str): Charge mode for timer charging with climatisation.
91
+ TIMER (str): Charge mode for timer-based charging.
92
+ MANUAL (str): Charge mode for manual charging.
93
+ OFF (str): Charge mode for turning off charging.
94
+ """
95
+ HOME_STORAGE_CHARGING = 'HOME_STORAGE_CHARGING'
96
+ IMMEDIATE_DISCHARGING = 'IMMEDIATE_DISCHARGING'
97
+ ONLY_OWN_CURRENT = 'ONLY_OWN_CURRENT'
98
+ PREFERRED_CHARGING_TIMES = 'PREFERRED_CHARGING_TIMES'
99
+ TIMER_CHARGING_WITH_CLIMATISATION = 'TIMER_CHARGING_WITH_CLIMATISATION'
100
+ TIMER = 'timer'
101
+ MANUAL = 'manual'
102
+ OFF = 'off'
103
+ UNKNOWN = 'unknown charge mode'
104
+
105
+ class SkodaChargingCareMode(Enum,):
106
+ """
107
+ Enum representing the charging care mode for Skoda vehicles.
108
+ """
109
+ ACTIVATED = 'ACTIVATED'
110
+ DEACTIVATED = 'DEACTIVATED'
111
+ UNKNOWN = 'UNKNOWN'
112
+
113
+ class SkodaBatterySupport(Enum,):
114
+ """
115
+ SkodaBatterySupport is an enumeration that represents the different states of battery support for Skoda vehicles.
116
+ """
117
+ ENABLED = 'ENABLED'
118
+ DISABLED = 'DISABLED'
119
+ NOT_ALLOWED = 'NOT_ALLOWED'
120
+ UNKNOWN = 'UNKNOWN'
121
+
52
122
 
53
123
  # Mapping of Skoda charging states to generic charging states
54
124
  mapping_skoda_charging_state: Dict[SkodaCharging.SkodaChargingState, Charging.ChargingState] = {
@@ -56,7 +126,7 @@ mapping_skoda_charging_state: Dict[SkodaCharging.SkodaChargingState, Charging.Ch
56
126
  SkodaCharging.SkodaChargingState.CONNECT_CABLE: Charging.ChargingState.OFF,
57
127
  SkodaCharging.SkodaChargingState.READY_FOR_CHARGING: Charging.ChargingState.READY_FOR_CHARGING,
58
128
  SkodaCharging.SkodaChargingState.NOT_READY_FOR_CHARGING: Charging.ChargingState.OFF,
59
- SkodaCharging.SkodaChargingState.CONSERVATION: Charging.ChargingState.CONSERVATION,
129
+ SkodaCharging.SkodaChargingState.CONSERVING: Charging.ChargingState.CONSERVATION,
60
130
  SkodaCharging.SkodaChargingState.CHARGE_PURPOSE_REACHED_NOT_CONSERVATION_CHARGING: Charging.ChargingState.READY_FOR_CHARGING,
61
131
  SkodaCharging.SkodaChargingState.CHARGE_PURPOSE_REACHED_CONSERVATION: Charging.ChargingState.CONSERVATION,
62
132
  SkodaCharging.SkodaChargingState.CHARGING: Charging.ChargingState.CHARGING,
@@ -0,0 +1,43 @@
1
+ """
2
+ Module for climatization 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 GenericVehicle
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: GenericVehicle | None = None, origin: Optional[Climatization] = None) -> None:
25
+ if origin is not None:
26
+ super().__init__(origin=origin)
27
+ if not isinstance(self.settings, SkodaClimatization.Settings):
28
+ self.settings: Climatization.Settings = SkodaClimatization.Settings(parent=self, origin=origin.settings)
29
+ self.settings.parent = self
30
+ else:
31
+ super().__init__(vehicle=vehicle)
32
+ self.settings: Climatization.Settings = SkodaClimatization.Settings(parent=self)
33
+ self.errors: Dict[str, Error] = {}
34
+
35
+ class Settings(Climatization.Settings):
36
+ """
37
+ This class represents the settings for a skoda car climatiation.
38
+ """
39
+ def __init__(self, parent: Optional[GenericObject] = None, origin: Optional[Climatization.Settings] = None) -> None:
40
+ if origin is not None:
41
+ super().__init__(parent=parent, origin=origin)
42
+ else:
43
+ super().__init__(parent=parent)
@@ -0,0 +1,78 @@
1
+ """This module defines the classes that represent attributes in the CarConnectivity system."""
2
+ from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Dict, Union
4
+
5
+ from enum import Enum
6
+ import argparse
7
+ import logging
8
+
9
+ from carconnectivity.commands import GenericCommand
10
+ from carconnectivity.objects import GenericObject
11
+ from carconnectivity.errors import SetterError
12
+ from carconnectivity.util import ThrowingArgumentParser
13
+
14
+ if TYPE_CHECKING:
15
+ from carconnectivity.objects import Optional
16
+
17
+ LOG: logging.Logger = logging.getLogger("carconnectivity.connectors.skoda")
18
+
19
+
20
+ class SpinCommand(GenericCommand):
21
+ """
22
+ SpinCommand is a command class for verifying the spin
23
+
24
+ """
25
+ def __init__(self, name: str = 'spin', parent: Optional[GenericObject] = None) -> None:
26
+ super().__init__(name=name, parent=parent)
27
+
28
+ @property
29
+ def value(self) -> Optional[Union[str, Dict]]:
30
+ return super().value
31
+
32
+ @value.setter
33
+ def value(self, new_value: Optional[Union[str, Dict]]) -> None:
34
+ # Execute early hooks before parsing the value
35
+ new_value = self._execute_on_set_hook(new_value, early_hook=True)
36
+ if isinstance(new_value, SpinCommand.Command):
37
+ newvalue_dict = {}
38
+ newvalue_dict['command'] = new_value
39
+ new_value = newvalue_dict
40
+ elif isinstance(new_value, str):
41
+ parser = ThrowingArgumentParser(prog='', add_help=False, exit_on_error=False)
42
+ parser.add_argument('command', help='Command to execute', type=SpinCommand.Command,
43
+ choices=list(SpinCommand.Command))
44
+ parser.add_argument('--spin', dest='spin', help='Spin to be used instead of spin from config or .netrc', type=str, required=False,
45
+ default=None)
46
+ try:
47
+ args = parser.parse_args(new_value.strip().split(sep=' '))
48
+ except argparse.ArgumentError as e:
49
+ raise SetterError(f'Invalid format for SpinCommand: {e.message} {parser.format_usage()}') from e
50
+
51
+ newvalue_dict = {}
52
+ newvalue_dict['command'] = args.command
53
+ if args.spin is not None:
54
+ newvalue_dict['spin'] = args.spin
55
+ new_value = newvalue_dict
56
+ elif isinstance(new_value, dict):
57
+ if 'command' in new_value and isinstance(new_value['command'], str):
58
+ if new_value['command'] in SpinCommand.Command:
59
+ new_value['command'] = SpinCommand.Command(new_value['command'])
60
+ else:
61
+ raise ValueError('Invalid value for SpinCommand. '
62
+ f'Command must be one of {SpinCommand.Command}')
63
+ if self._is_changeable:
64
+ # Execute late hooks before setting the value
65
+ new_value = self._execute_on_set_hook(new_value, early_hook=False)
66
+ self._set_value(new_value)
67
+ else:
68
+ raise TypeError('You cannot set this attribute. Attribute is not mutable.')
69
+
70
+ class Command(Enum):
71
+ """
72
+ Enum class representing different commands for SPIN.
73
+
74
+ """
75
+ VERIFY = 'verify'
76
+
77
+ def __str__(self) -> str:
78
+ return self.value