carconnectivity-connector-skoda 0.3__tar.gz → 0.4__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.
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/CHANGELOG.md +16 -1
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/PKG-INFO +2 -2
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/pyproject.toml +1 -1
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connector_skoda.egg-info/PKG-INFO +2 -2
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connector_skoda.egg-info/requires.txt +1 -1
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/_version.py +9 -4
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/auth/skoda_web_session.py +2 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/climatization.py +8 -6
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/command_impl.py +4 -2
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/connector.py +336 -84
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/mqtt_client.py +14 -3
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/vehicle.py +6 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.flake8 +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.github/dependabot.yml +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.github/workflows/build.yml +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.github/workflows/build_and_publish.yml +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.github/workflows/codeql-analysis.yml +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.gitignore +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/LICENSE +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/Makefile +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/README.md +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/doc/Config.md +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/setup.cfg +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/setup_requirements.txt +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connector_skoda.egg-info/SOURCES.txt +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connector_skoda.egg-info/dependency_links.txt +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connector_skoda.egg-info/top_level.txt +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/__init__.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/auth/__init__.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/auth/auth_util.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/auth/my_skoda_session.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/auth/openid_session.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/auth/session_manager.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/capability.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/charging.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/error.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/src/carconnectivity_connectors/skoda/ui/connector_ui.py +0 -0
- {carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/test/integration_test/carConnectivity.json +0 -0
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
- No unreleased changes so far
|
|
7
7
|
|
|
8
|
+
## [0.4] - 2025-03-02
|
|
9
|
+
### Added
|
|
10
|
+
- Added better feedback when consent is needed
|
|
11
|
+
- added better access to connection state
|
|
12
|
+
- added better access to health state
|
|
13
|
+
- added attribute for vehicle in_motion
|
|
14
|
+
- added possibility to online change interval
|
|
15
|
+
- added named threads for better debugging
|
|
16
|
+
- added vehcile connection state
|
|
17
|
+
- added global vehicle state
|
|
18
|
+
- added maintainance objects
|
|
19
|
+
- added checks for min/max values with climatization temperatures
|
|
20
|
+
- improved error handling with commands
|
|
21
|
+
|
|
8
22
|
## [0.3] - 2025-02-19
|
|
9
23
|
### Added
|
|
10
24
|
- Added support for images
|
|
@@ -19,7 +33,8 @@ All notable changes to this project will be documented in this file.
|
|
|
19
33
|
Initial release, let's go and give this to the public to try out...
|
|
20
34
|
The API is not yet implemented completely but most functions already work
|
|
21
35
|
|
|
22
|
-
[unreleased]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/compare/v0.
|
|
36
|
+
[unreleased]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/compare/v0.4...HEAD
|
|
37
|
+
[0.4]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.4
|
|
23
38
|
[0.3]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.3
|
|
24
39
|
[0.2]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.2
|
|
25
40
|
[0.1]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: carconnectivity-connector-skoda
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: CarConnectivity connector for Skoda services
|
|
5
5
|
Author: Till Steinbach
|
|
6
6
|
License: MIT License
|
|
@@ -37,7 +37,7 @@ 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.
|
|
40
|
+
Requires-Dist: carconnectivity>=0.4
|
|
41
41
|
Requires-Dist: oauthlib~=3.2.2
|
|
42
42
|
Requires-Dist: requests~=2.32.3
|
|
43
43
|
Requires-Dist: jwt~=1.3.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: carconnectivity-connector-skoda
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: CarConnectivity connector for Skoda services
|
|
5
5
|
Author: Till Steinbach
|
|
6
6
|
License: MIT License
|
|
@@ -37,7 +37,7 @@ 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.
|
|
40
|
+
Requires-Dist: carconnectivity>=0.4
|
|
41
41
|
Requires-Dist: oauthlib~=3.2.2
|
|
42
42
|
Requires-Dist: requests~=2.32.3
|
|
43
43
|
Requires-Dist: jwt~=1.3.1
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0,
|
|
20
|
+
__version__ = version = '0.4'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 4)
|
|
@@ -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']
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Module for
|
|
2
|
+
Module for climatization for skoda vehicles.
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from carconnectivity.climatization import Climatization
|
|
8
8
|
from carconnectivity.objects import GenericObject
|
|
9
|
-
from carconnectivity.vehicle import
|
|
9
|
+
from carconnectivity.vehicle import GenericVehicle
|
|
10
10
|
|
|
11
11
|
from carconnectivity_connectors.skoda.error import Error
|
|
12
12
|
|
|
@@ -21,13 +21,15 @@ class SkodaClimatization(Climatization): # pylint: disable=too-many-instance-at
|
|
|
21
21
|
This class extends the Climatization class and includes an enumeration of various
|
|
22
22
|
charging states specific to Skoda vehicles.
|
|
23
23
|
"""
|
|
24
|
-
def __init__(self, vehicle:
|
|
24
|
+
def __init__(self, vehicle: GenericVehicle | None = None, origin: Optional[Climatization] = None) -> None:
|
|
25
25
|
if origin is not None:
|
|
26
26
|
super().__init__(origin=origin)
|
|
27
|
-
self.settings
|
|
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
|
|
28
30
|
else:
|
|
29
31
|
super().__init__(vehicle=vehicle)
|
|
30
|
-
self.settings: Climatization.Settings = SkodaClimatization.Settings(
|
|
32
|
+
self.settings: Climatization.Settings = SkodaClimatization.Settings(parent=self)
|
|
31
33
|
self.errors: Dict[str, Error] = {}
|
|
32
34
|
|
|
33
35
|
class Settings(Climatization.Settings):
|
|
@@ -36,6 +38,6 @@ class SkodaClimatization(Climatization): # pylint: disable=too-many-instance-at
|
|
|
36
38
|
"""
|
|
37
39
|
def __init__(self, parent: Optional[GenericObject] = None, origin: Optional[Climatization.Settings] = None) -> None:
|
|
38
40
|
if origin is not None:
|
|
39
|
-
super().__init__(origin=origin)
|
|
41
|
+
super().__init__(parent=parent, origin=origin)
|
|
40
42
|
else:
|
|
41
43
|
super().__init__(parent=parent)
|
|
@@ -31,6 +31,8 @@ class SpinCommand(GenericCommand):
|
|
|
31
31
|
|
|
32
32
|
@value.setter
|
|
33
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)
|
|
34
36
|
if isinstance(new_value, str):
|
|
35
37
|
parser = ThrowingArgumentParser(prog='', add_help=False, exit_on_error=False)
|
|
36
38
|
parser.add_argument('command', help='Command to execute', type=SpinCommand.Command,
|
|
@@ -55,8 +57,8 @@ class SpinCommand(GenericCommand):
|
|
|
55
57
|
raise ValueError('Invalid value for SpinCommand. '
|
|
56
58
|
f'Command must be one of {SpinCommand.Command}')
|
|
57
59
|
if self._is_changeable:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
# Execute late hooks before setting the value
|
|
61
|
+
new_value = self._execute_on_set_hook(new_value, early_hook=False)
|
|
60
62
|
self._set_value(new_value)
|
|
61
63
|
else:
|
|
62
64
|
raise TypeError('You cannot set this attribute. Attribute is not mutable.')
|
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
import threading
|
|
6
6
|
import os
|
|
7
|
+
import traceback
|
|
7
8
|
import logging
|
|
8
9
|
import netrc
|
|
9
10
|
from datetime import datetime, timedelta, timezone
|
|
@@ -22,13 +23,14 @@ from carconnectivity.doors import Doors
|
|
|
22
23
|
from carconnectivity.windows import Windows
|
|
23
24
|
from carconnectivity.lights import Lights
|
|
24
25
|
from carconnectivity.drive import GenericDrive, ElectricDrive, CombustionDrive
|
|
25
|
-
from carconnectivity.attributes import BooleanAttribute, DurationAttribute, TemperatureAttribute
|
|
26
|
+
from carconnectivity.attributes import GenericAttribute, BooleanAttribute, DurationAttribute, TemperatureAttribute, EnumAttribute
|
|
26
27
|
from carconnectivity.charging import Charging
|
|
27
28
|
from carconnectivity.position import Position
|
|
28
29
|
from carconnectivity.climatization import Climatization
|
|
29
30
|
from carconnectivity.charging_connector import ChargingConnector
|
|
30
31
|
from carconnectivity.commands import Commands
|
|
31
32
|
from carconnectivity.command_impl import ClimatizationStartStopCommand, ChargingStartStopCommand, HonkAndFlashCommand, LockUnlockCommand, WakeSleepCommand
|
|
33
|
+
from carconnectivity.enums import ConnectionState
|
|
32
34
|
|
|
33
35
|
from carconnectivity_connectors.base.connector import BaseConnector
|
|
34
36
|
from carconnectivity_connectors.skoda.auth.session_manager import SessionManager, SessionUser, Service
|
|
@@ -79,8 +81,14 @@ class Connector(BaseConnector):
|
|
|
79
81
|
self._background_connect_thread: Optional[threading.Thread] = None
|
|
80
82
|
self._stop_event = threading.Event()
|
|
81
83
|
|
|
82
|
-
self.
|
|
84
|
+
self.connection_state: EnumAttribute = EnumAttribute(name="connection_state", parent=self, value_type=ConnectionState,
|
|
85
|
+
value=ConnectionState.DISCONNECTED, tags={'connector_custom'})
|
|
86
|
+
self.rest_connected: bool = False
|
|
87
|
+
self.mqtt_connected: bool = False
|
|
83
88
|
self.interval: DurationAttribute = DurationAttribute(name="interval", parent=self, tags={'connector_custom'})
|
|
89
|
+
self.interval.minimum = timedelta(seconds=180)
|
|
90
|
+
self.interval._is_changeable = True # pylint: disable=protected-access
|
|
91
|
+
|
|
84
92
|
self.commands: Commands = Commands(parent=self)
|
|
85
93
|
|
|
86
94
|
self.user_id: Optional[str] = None
|
|
@@ -154,12 +162,15 @@ class Connector(BaseConnector):
|
|
|
154
162
|
self._stop_event.clear()
|
|
155
163
|
# Start background thread for Rest API polling
|
|
156
164
|
self._background_thread = threading.Thread(target=self._background_loop, daemon=False)
|
|
165
|
+
self._background_thread.name = 'carconnectivity.connectors.skoda-background'
|
|
157
166
|
self._background_thread.start()
|
|
158
167
|
# Start background thread for MQTT connection
|
|
159
168
|
self._background_connect_thread = threading.Thread(target=self._background_connect_loop, daemon=False)
|
|
169
|
+
self._background_connect_thread.name = 'carconnectivity.connectors.skoda-background_connect'
|
|
160
170
|
self._background_connect_thread.start()
|
|
161
171
|
# Start MQTT thread
|
|
162
172
|
self._mqtt_client.loop_start()
|
|
173
|
+
self.healthy._set_value(value=True) # pylint: disable=protected-access
|
|
163
174
|
|
|
164
175
|
def _background_connect_loop(self) -> None:
|
|
165
176
|
while not self._stop_event.is_set():
|
|
@@ -173,6 +184,7 @@ class Connector(BaseConnector):
|
|
|
173
184
|
def _background_loop(self) -> None:
|
|
174
185
|
self._stop_event.clear()
|
|
175
186
|
fetch: bool = True
|
|
187
|
+
self.connection_state._set_value(value=ConnectionState.CONNECTING) # pylint: disable=protected-access
|
|
176
188
|
while not self._stop_event.is_set():
|
|
177
189
|
interval = 300
|
|
178
190
|
try:
|
|
@@ -191,21 +203,43 @@ class Connector(BaseConnector):
|
|
|
191
203
|
raise
|
|
192
204
|
except TooManyRequestsError as err:
|
|
193
205
|
LOG.error('Retrieval error during update. Too many requests from your account (%s). Will try again after 15 minutes', str(err))
|
|
206
|
+
self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
|
|
207
|
+
self.rest_connected = False
|
|
194
208
|
self._stop_event.wait(900)
|
|
195
209
|
except RetrievalError as err:
|
|
196
210
|
LOG.error('Retrieval error during update (%s). Will try again after configured interval of %ss', str(err), interval)
|
|
211
|
+
self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
|
|
212
|
+
self.rest_connected = False
|
|
197
213
|
self._stop_event.wait(interval)
|
|
198
214
|
except APIError as err:
|
|
199
215
|
LOG.error('API error during update (%s). Will try again after configured interval of %ss', str(err), interval)
|
|
216
|
+
self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
|
|
217
|
+
self.rest_connected = False
|
|
200
218
|
self._stop_event.wait(interval)
|
|
201
219
|
except APICompatibilityError as err:
|
|
202
220
|
LOG.error('API compatability error during update (%s). Will try again after configured interval of %ss', str(err), interval)
|
|
221
|
+
self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
|
|
222
|
+
self.rest_connected = False
|
|
203
223
|
self._stop_event.wait(interval)
|
|
204
224
|
except TemporaryAuthenticationError as err:
|
|
205
225
|
LOG.error('Temporary authentification error during update (%s). Will try again after configured interval of %ss', str(err), interval)
|
|
226
|
+
self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
|
|
227
|
+
self.rest_connected = False
|
|
206
228
|
self._stop_event.wait(interval)
|
|
229
|
+
except Exception as err:
|
|
230
|
+
LOG.critical('Critical error during update: %s', traceback.format_exc())
|
|
231
|
+
self.connection_state._set_value(value=ConnectionState.ERROR) # pylint: disable=protected-access
|
|
232
|
+
self.rest_connected = False
|
|
233
|
+
self.healthy._set_value(value=False) # pylint: disable=protected-access
|
|
234
|
+
raise err
|
|
207
235
|
else:
|
|
236
|
+
self.rest_connected = True
|
|
237
|
+
if self.mqtt_connected:
|
|
238
|
+
self.connection_state._set_value(value=ConnectionState.CONNECTED) # pylint: disable=protected-access
|
|
208
239
|
self._stop_event.wait(interval)
|
|
240
|
+
# When leaving the loop, set the connection state to disconnected
|
|
241
|
+
self.connection_state._set_value(value=ConnectionState.DISCONNECTED) # pylint: disable=protected-access
|
|
242
|
+
self.rest_connected = False
|
|
209
243
|
|
|
210
244
|
def persist(self) -> None:
|
|
211
245
|
"""
|
|
@@ -236,12 +270,12 @@ class Connector(BaseConnector):
|
|
|
236
270
|
self.car_connectivity.garage.remove_vehicle(vehicle.id)
|
|
237
271
|
vehicle.enabled = False
|
|
238
272
|
self._stop_event.set()
|
|
273
|
+
self.session.close()
|
|
239
274
|
if self._background_thread is not None:
|
|
240
275
|
self._background_thread.join()
|
|
241
276
|
if self._background_connect_thread is not None:
|
|
242
277
|
self._background_connect_thread.join()
|
|
243
278
|
self.persist()
|
|
244
|
-
self.session.close()
|
|
245
279
|
return super().shutdown()
|
|
246
280
|
|
|
247
281
|
def fetch_all(self) -> None:
|
|
@@ -339,12 +373,38 @@ class Connector(BaseConnector):
|
|
|
339
373
|
vehicle_to_update = self.fetch_vehicle_status(vehicle_to_update)
|
|
340
374
|
vehicle_to_update = self.fetch_driving_range(vehicle_to_update)
|
|
341
375
|
if vehicle_to_update.capabilities is not None and vehicle_to_update.capabilities.enabled:
|
|
376
|
+
if vehicle_to_update.capabilities.has_capability('READINESS'):
|
|
377
|
+
vehicle_to_update = self.fetch_connection_status(vehicle_to_update)
|
|
342
378
|
if vehicle_to_update.capabilities.has_capability('PARKING_POSITION'):
|
|
343
379
|
vehicle_to_update = self.fetch_position(vehicle_to_update)
|
|
344
380
|
if vehicle_to_update.capabilities.has_capability('CHARGING') and isinstance(vehicle_to_update, SkodaElectricVehicle):
|
|
345
381
|
vehicle_to_update = self.fetch_charging(vehicle_to_update)
|
|
346
382
|
if vehicle_to_update.capabilities.has_capability('AIR_CONDITIONING'):
|
|
347
383
|
vehicle_to_update = self.fetch_air_conditioning(vehicle_to_update)
|
|
384
|
+
if vehicle_to_update.capabilities.has_capability('VEHICLE_HEALTH_INSPECTION'):
|
|
385
|
+
vehicle_to_update = self.fetch_maintenance(vehicle_to_update)
|
|
386
|
+
vehicle_to_update = self.decide_state(vehicle_to_update)
|
|
387
|
+
self.car_connectivity.transaction_end()
|
|
388
|
+
|
|
389
|
+
def decide_state(self, vehicle: SkodaVehicle) -> SkodaVehicle:
|
|
390
|
+
"""
|
|
391
|
+
Decides the state of the vehicle based on the current data.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
vehicle (SkodaVehicle): The Skoda vehicle object.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
SkodaVehicle: The Skoda vehicle object with the updated state.
|
|
398
|
+
"""
|
|
399
|
+
if vehicle is not None:
|
|
400
|
+
if vehicle.in_motion is not None and vehicle.in_motion.enabled and vehicle.in_motion.value:
|
|
401
|
+
vehicle.state._set_value(GenericVehicle.State.IGNITION_ON) # pylint: disable=protected-access
|
|
402
|
+
elif vehicle.position is not None and vehicle.position.enabled and vehicle.position.position_type is not None \
|
|
403
|
+
and vehicle.position.position_type.enabled and vehicle.position.position_type.value == Position.PositionType.PARKING:
|
|
404
|
+
vehicle.state._set_value(GenericVehicle.State.PARKED) # pylint: disable=protected-access
|
|
405
|
+
else:
|
|
406
|
+
vehicle.state._set_value(None) # pylint: disable=protected-access
|
|
407
|
+
return vehicle
|
|
348
408
|
|
|
349
409
|
def fetch_charging(self, vehicle: SkodaElectricVehicle, no_cache: bool = False) -> SkodaElectricVehicle:
|
|
350
410
|
"""
|
|
@@ -553,6 +613,41 @@ class Connector(BaseConnector):
|
|
|
553
613
|
vehicle.charging.errors.clear()
|
|
554
614
|
log_extra_keys(LOG_API, 'charging data', data, {'carCapturedTimestamp', 'status', 'isVehicleInSavedLocation', 'errors', 'settings'})
|
|
555
615
|
return vehicle
|
|
616
|
+
|
|
617
|
+
def fetch_connection_status(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
|
|
618
|
+
"""
|
|
619
|
+
Fetches the connection status of the given Skoda vehicle and updates its connection attributes.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
vehicle (SkodaVehicle): The Skoda vehicle object containing the VIN and connection attributes.
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
SkodaVehicle: The updated Skoda vehicle object with the fetched connection data.
|
|
626
|
+
|
|
627
|
+
Raises:
|
|
628
|
+
APIError: If the VIN is missing.
|
|
629
|
+
ValueError: If the vehicle has no connection object.
|
|
630
|
+
"""
|
|
631
|
+
vin = vehicle.vin.value
|
|
632
|
+
if vin is None:
|
|
633
|
+
raise APIError('VIN is missing')
|
|
634
|
+
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/connection-status/{vin}/readiness'
|
|
635
|
+
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
|
636
|
+
# {'unreachable': False, 'inMotion': False, 'batteryProtectionLimitOn': False}
|
|
637
|
+
if data is not None:
|
|
638
|
+
if 'unreachable' in data and data['unreachable'] is not None:
|
|
639
|
+
if data['unreachable']:
|
|
640
|
+
vehicle.connection_state._set_value(vehicle.ConnectionState.OFFLINE) # pylint: disable=protected-access
|
|
641
|
+
else:
|
|
642
|
+
vehicle.connection_state._set_value(vehicle.ConnectionState.REACHABLE) # pylint: disable=protected-access
|
|
643
|
+
else:
|
|
644
|
+
vehicle.connection_state._set_value(None) # pylint: disable=protected-access
|
|
645
|
+
if 'inMotion' in data and data['inMotion'] is not None:
|
|
646
|
+
vehicle.in_motion._set_value(data['inMotion']) # pylint: disable=protected-access
|
|
647
|
+
else:
|
|
648
|
+
vehicle.in_motion._set_value(None) # pylint: disable=protected-access
|
|
649
|
+
log_extra_keys(LOG_API, 'connection status', data, {'unreachable'})
|
|
650
|
+
return vehicle
|
|
556
651
|
|
|
557
652
|
def fetch_position(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
|
|
558
653
|
"""
|
|
@@ -574,7 +669,7 @@ class Connector(BaseConnector):
|
|
|
574
669
|
if vehicle.position is None:
|
|
575
670
|
raise ValueError('Vehicle has no charging object')
|
|
576
671
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/maps/positions?vin={vin}'
|
|
577
|
-
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
|
672
|
+
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache, allow_empty=True)
|
|
578
673
|
if data is not None:
|
|
579
674
|
if 'positions' in data and data['positions'] is not None:
|
|
580
675
|
for position_dict in data['positions']:
|
|
@@ -608,6 +703,53 @@ class Connector(BaseConnector):
|
|
|
608
703
|
vehicle.position.position_type._set_value(None) # pylint: disable=protected-access
|
|
609
704
|
return vehicle
|
|
610
705
|
|
|
706
|
+
def fetch_maintenance(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
|
|
707
|
+
"""
|
|
708
|
+
Fetches the maintenance information for a given Skoda vehicle.
|
|
709
|
+
|
|
710
|
+
Args:
|
|
711
|
+
vehicle (SkodaVehicle): The vehicle object for which maintenance information is to be fetched.
|
|
712
|
+
no_cache (bool, optional): If True, bypasses the cache and fetches fresh data. Defaults to False.
|
|
713
|
+
|
|
714
|
+
Returns:
|
|
715
|
+
SkodaVehicle: The vehicle object with updated maintenance information.
|
|
716
|
+
|
|
717
|
+
Raises:
|
|
718
|
+
APIError: If the VIN is missing or if the 'capturedAt' field is missing in the fetched data.
|
|
719
|
+
ValueError: If the vehicle has no charging object.
|
|
720
|
+
"""
|
|
721
|
+
vin = vehicle.vin.value
|
|
722
|
+
if vin is None:
|
|
723
|
+
raise APIError('VIN is missing')
|
|
724
|
+
if vehicle.position is None:
|
|
725
|
+
raise ValueError('Vehicle has no charging object')
|
|
726
|
+
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v3/vehicle-maintenance/vehicles/{vin}/report'
|
|
727
|
+
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
|
728
|
+
#{'capturedAt': '2025-02-24T19:54:32.728Z', 'inspectionDueInDays': 620, 'mileageInKm': 2512}
|
|
729
|
+
if data is not None:
|
|
730
|
+
if 'capturedAt' in data and data['capturedAt'] is not None:
|
|
731
|
+
captured_at: datetime = robust_time_parse(data['capturedAt'])
|
|
732
|
+
else:
|
|
733
|
+
raise APIError('Could not fetch maintenance, capturedAt missing')
|
|
734
|
+
if 'mileageInKm' in data and data['mileageInKm'] is not None:
|
|
735
|
+
vehicle.odometer._set_value(value=data['mileageInKm'], measured=captured_at, unit=Length.KM) # pylint: disable=protected-access
|
|
736
|
+
else:
|
|
737
|
+
vehicle.odometer._set_value(None) # pylint: disable=protected-access
|
|
738
|
+
if 'inspectionDueInDays' in data and data['inspectionDueInDays'] is not None:
|
|
739
|
+
inspection_due: timedelta = timedelta(days=data['inspectionDueInDays'])
|
|
740
|
+
inspection_date: datetime = captured_at + inspection_due
|
|
741
|
+
inspection_date = inspection_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
742
|
+
# pylint: disable-next=protected-access
|
|
743
|
+
vehicle.maintenance.inspection_due_at._set_value(value=inspection_date, measured=captured_at)
|
|
744
|
+
else:
|
|
745
|
+
vehicle.maintenance.inspection_due_at._set_value(None) # pylint: disable=protected-access
|
|
746
|
+
log_extra_keys(LOG_API, 'maintenance', data, {'capturedAt', 'mileageInKm', 'inspectionDueInDays'})
|
|
747
|
+
|
|
748
|
+
#url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-health-report/warning-lights/{vin}'
|
|
749
|
+
#data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
|
750
|
+
#{'capturedAt': '2025-02-24T15:32:35.032Z', 'mileageInKm': 2512, 'warningLights': [{'category': 'ASSISTANCE', 'defects': []}, {'category': 'COMFORT', 'defects': []}, {'category': 'BRAKE', 'defects': []}, {'category': 'ELECTRIC_ENGINE', 'defects': []}, {'category': 'LIGHTING', 'defects': []}, {'category': 'TIRE', 'defects': []}, {'category': 'OTHER', 'defects': []}]}
|
|
751
|
+
return vehicle
|
|
752
|
+
|
|
611
753
|
def fetch_air_conditioning(self, vehicle: SkodaVehicle, no_cache: bool = False) -> SkodaVehicle:
|
|
612
754
|
"""
|
|
613
755
|
Fetches the air conditioning data for a given Skoda vehicle and updates the vehicle object with the retrieved data.
|
|
@@ -667,12 +809,19 @@ class Connector(BaseConnector):
|
|
|
667
809
|
# pylint: disable-next=protected-access
|
|
668
810
|
vehicle.climatization.settings.target_temperature._add_on_set_hook(self.__on_air_conditioning_target_temperature_change)
|
|
669
811
|
vehicle.climatization.settings.target_temperature._is_changeable = True # pylint: disable=protected-access
|
|
812
|
+
precision: float = 0.5
|
|
813
|
+
min_temperature: Optional[float] = None
|
|
814
|
+
max_temperature: Optional[float] = None
|
|
670
815
|
unit: Temperature = Temperature.UNKNOWN
|
|
671
816
|
if 'unitInCar' in data['targetTemperature'] and data['targetTemperature']['unitInCar'] is not None:
|
|
672
817
|
if data['targetTemperature']['unitInCar'] == 'CELSIUS':
|
|
673
818
|
unit = Temperature.C
|
|
819
|
+
min_temperature: Optional[float] = 16
|
|
820
|
+
max_temperature: Optional[float] = 29.5
|
|
674
821
|
elif data['targetTemperature']['unitInCar'] == 'FAHRENHEIT':
|
|
675
822
|
unit = Temperature.F
|
|
823
|
+
min_temperature: Optional[float] = 61
|
|
824
|
+
max_temperature: Optional[float] = 85
|
|
676
825
|
elif data['targetTemperature']['unitInCar'] == 'KELVIN':
|
|
677
826
|
unit = Temperature.K
|
|
678
827
|
else:
|
|
@@ -682,6 +831,10 @@ class Connector(BaseConnector):
|
|
|
682
831
|
vehicle.climatization.settings.target_temperature._set_value(value=data['targetTemperature']['temperatureValue'],
|
|
683
832
|
measured=captured_at,
|
|
684
833
|
unit=unit)
|
|
834
|
+
vehicle.climatization.settings.target_temperature.precision = precision
|
|
835
|
+
vehicle.climatization.settings.target_temperature.minimum = min_temperature
|
|
836
|
+
vehicle.climatization.settings.target_temperature.maximum = max_temperature
|
|
837
|
+
|
|
685
838
|
else:
|
|
686
839
|
# pylint: disable-next=protected-access
|
|
687
840
|
vehicle.climatization.settings.target_temperature._set_value(value=None, measured=captured_at, unit=unit)
|
|
@@ -1221,6 +1374,8 @@ class Connector(BaseConnector):
|
|
|
1221
1374
|
data = status_response.json()
|
|
1222
1375
|
if session.cache is not None:
|
|
1223
1376
|
session.cache[url] = (data, str(datetime.utcnow()))
|
|
1377
|
+
elif status_response.status_code == requests.codes['no_content'] and allow_empty:
|
|
1378
|
+
data = None
|
|
1224
1379
|
elif status_response.status_code == requests.codes['too_many_requests']:
|
|
1225
1380
|
raise TooManyRequestsError('Could not fetch data due to too many requests from your account. '
|
|
1226
1381
|
f'Status Code was: {status_response.status_code}')
|
|
@@ -1287,10 +1442,20 @@ class Connector(BaseConnector):
|
|
|
1287
1442
|
raise SetterError(f'Unknown temperature unit {temperature_attribute.unit}')
|
|
1288
1443
|
|
|
1289
1444
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/target-temperature'
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1445
|
+
try:
|
|
1446
|
+
settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
|
|
1447
|
+
if settings_response.status_code != requests.codes['accepted']:
|
|
1448
|
+
LOG.error('Could not set target temperature (%s)', settings_response.status_code)
|
|
1449
|
+
raise SetterError(f'Could not set value ({settings_response.status_code})')
|
|
1450
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1451
|
+
raise SetterError(f'Connection error: {connection_error}.'
|
|
1452
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1453
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1454
|
+
raise SetterError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1455
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1456
|
+
raise SetterError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1457
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1458
|
+
raise SetterError(f'Retrying failed: {retry_error}') from retry_error
|
|
1294
1459
|
return target_temperature
|
|
1295
1460
|
|
|
1296
1461
|
def __on_air_conditioning_at_unlock_change(self, at_unlock_attribute: BooleanAttribute, at_unlock_value: bool) -> bool:
|
|
@@ -1306,10 +1471,20 @@ class Connector(BaseConnector):
|
|
|
1306
1471
|
setting_dict['airConditioningAtUnlockEnabled'] = at_unlock_value
|
|
1307
1472
|
|
|
1308
1473
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/ac-at-unlock'
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1474
|
+
try:
|
|
1475
|
+
settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
|
|
1476
|
+
if settings_response.status_code != requests.codes['accepted']:
|
|
1477
|
+
LOG.error('Could not set air conditioning at unlock (%s)', settings_response.status_code)
|
|
1478
|
+
raise SetterError(f'Could not set value ({settings_response.status_code})')
|
|
1479
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1480
|
+
raise SetterError(f'Connection error: {connection_error}.'
|
|
1481
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1482
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1483
|
+
raise SetterError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1484
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1485
|
+
raise SetterError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1486
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1487
|
+
raise SetterError(f'Retrying failed: {retry_error}') from retry_error
|
|
1313
1488
|
return at_unlock_value
|
|
1314
1489
|
|
|
1315
1490
|
def __on_air_conditioning_window_heating_change(self, window_heating_attribute: BooleanAttribute, window_heating_value: bool) -> bool:
|
|
@@ -1325,10 +1500,20 @@ class Connector(BaseConnector):
|
|
|
1325
1500
|
setting_dict['windowHeatingEnabled'] = window_heating_value
|
|
1326
1501
|
|
|
1327
1502
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/settings/ac-at-unlock'
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1503
|
+
try:
|
|
1504
|
+
settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
|
|
1505
|
+
if settings_response.status_code != requests.codes['accepted']:
|
|
1506
|
+
LOG.error('Could not set air conditioning window heating (%s)', settings_response.status_code)
|
|
1507
|
+
raise SetterError(f'Could not set value ({settings_response.status_code})')
|
|
1508
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1509
|
+
raise SetterError(f'Connection error: {connection_error}.'
|
|
1510
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1511
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1512
|
+
raise SetterError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1513
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1514
|
+
raise SetterError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1515
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1516
|
+
raise SetterError(f'Retrying failed: {retry_error}') from retry_error
|
|
1332
1517
|
return window_heating_value
|
|
1333
1518
|
|
|
1334
1519
|
def __on_air_conditioning_start_stop(self, start_stop_command: ClimatizationStartStopCommand, command_arguments: Union[str, Dict[str, Any]]) \
|
|
@@ -1345,53 +1530,66 @@ class Connector(BaseConnector):
|
|
|
1345
1530
|
if 'command' not in command_arguments:
|
|
1346
1531
|
raise CommandError('Command argument missing')
|
|
1347
1532
|
command_dict = {}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1533
|
+
try:
|
|
1534
|
+
if command_arguments['command'] == ClimatizationStartStopCommand.Command.START:
|
|
1535
|
+
command_dict['heaterSource'] = 'ELECTRIC'
|
|
1536
|
+
command_dict['targetTemperature'] = {}
|
|
1537
|
+
precision: float = 0.5
|
|
1538
|
+
if 'target_temperature' in command_arguments:
|
|
1539
|
+
# Round target temperature to nearest 0.5
|
|
1540
|
+
command_dict['targetTemperature']['temperatureValue'] = round(command_arguments['target_temperature'] / precision) * precision
|
|
1541
|
+
if 'target_temperature_unit' in command_arguments:
|
|
1542
|
+
if not isinstance(command_arguments['target_temperature_unit'], Temperature):
|
|
1543
|
+
raise CommandError('Temperature unit is not of type Temperature')
|
|
1544
|
+
if command_arguments['target_temperature_unit'] == Temperature.C:
|
|
1545
|
+
command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
|
|
1546
|
+
elif command_arguments['target_temperature_unit'] == Temperature.F:
|
|
1547
|
+
command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
|
|
1548
|
+
elif command_arguments['target_temperature_unit'] == Temperature.K:
|
|
1549
|
+
command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
|
|
1550
|
+
else:
|
|
1551
|
+
raise CommandError(f'Unknown temperature unit {command_arguments["target_temperature_unit"]}')
|
|
1552
|
+
else:
|
|
1358
1553
|
command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
|
|
1359
|
-
|
|
1554
|
+
elif start_stop_command.parent is not None and (climatization := start_stop_command.parent.parent) is not None \
|
|
1555
|
+
and isinstance(climatization, Climatization) and climatization.settings is not None \
|
|
1556
|
+
and climatization.settings.target_temperature is not None and climatization.settings.target_temperature.enabled \
|
|
1557
|
+
and climatization.settings.target_temperature.value is not None: # pylint: disable=too-many-boolean-expressions
|
|
1558
|
+
if climatization.settings.target_temperature.precision is not None:
|
|
1559
|
+
precision = climatization.settings.target_temperature.precision
|
|
1560
|
+
# Round target temperature to nearest 0.5
|
|
1561
|
+
command_dict['targetTemperature']['temperatureValue'] = round(climatization.settings.target_temperature.value / precision) * precision
|
|
1562
|
+
if climatization.settings.target_temperature.unit == Temperature.C:
|
|
1563
|
+
command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
|
|
1564
|
+
elif climatization.settings.target_temperature.unit == Temperature.F:
|
|
1360
1565
|
command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
|
|
1361
|
-
elif
|
|
1566
|
+
elif climatization.settings.target_temperature.unit == Temperature.K:
|
|
1362
1567
|
command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
|
|
1363
1568
|
else:
|
|
1364
|
-
raise CommandError(f'Unknown temperature unit {
|
|
1569
|
+
raise CommandError(f'Unknown temperature unit {climatization.settings.target_temperature.unit}')
|
|
1365
1570
|
else:
|
|
1571
|
+
command_dict['targetTemperature']['temperatureValue'] = 25.0
|
|
1366
1572
|
command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
command_dict['targetTemperature']['temperatureValue'] = round(climatization.settings.target_temperature.value * 2) / 2
|
|
1373
|
-
if climatization.settings.target_temperature.unit == Temperature.C:
|
|
1374
|
-
command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
|
|
1375
|
-
elif climatization.settings.target_temperature.unit == Temperature.F:
|
|
1376
|
-
command_dict['targetTemperature']['unitInCar'] = 'FAHRENHEIT'
|
|
1377
|
-
elif climatization.settings.target_temperature.unit == Temperature.K:
|
|
1378
|
-
command_dict['targetTemperature']['unitInCar'] = 'KELVIN'
|
|
1379
|
-
else:
|
|
1380
|
-
raise CommandError(f'Unknown temperature unit {climatization.settings.target_temperature.unit}')
|
|
1573
|
+
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/start'
|
|
1574
|
+
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
|
1575
|
+
elif command_arguments['command'] == ClimatizationStartStopCommand.Command.STOP:
|
|
1576
|
+
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/stop'
|
|
1577
|
+
command_response: requests.Response = self.session.post(url, allow_redirects=True)
|
|
1381
1578
|
else:
|
|
1382
|
-
|
|
1383
|
-
command_dict['targetTemperature']['unitInCar'] = 'CELSIUS'
|
|
1384
|
-
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/start'
|
|
1385
|
-
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
|
1386
|
-
elif command_arguments['command'] == ClimatizationStartStopCommand.Command.STOP:
|
|
1387
|
-
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/air-conditioning/{vin}/stop'
|
|
1388
|
-
command_response: requests.Response = self.session.post(url, allow_redirects=True)
|
|
1389
|
-
else:
|
|
1390
|
-
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
|
1579
|
+
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
|
1391
1580
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1581
|
+
if command_response.status_code != requests.codes['accepted']:
|
|
1582
|
+
LOG.error('Could not start/stop air conditioning (%s: %s)', command_response.status_code, command_response.text)
|
|
1583
|
+
raise CommandError(f'Could not start/stop air conditioning ({command_response.status_code}: {command_response.text})')
|
|
1584
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1585
|
+
raise CommandError(f'Connection error: {connection_error}.'
|
|
1586
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1587
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1588
|
+
raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1589
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1590
|
+
raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1591
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1592
|
+
raise CommandError(f'Retrying failed: {retry_error}') from retry_error
|
|
1395
1593
|
return command_arguments
|
|
1396
1594
|
|
|
1397
1595
|
def __on_charging_start_stop(self, start_stop_command: ChargingStartStopCommand, command_arguments: Union[str, Dict[str, Any]]) \
|
|
@@ -1407,18 +1605,29 @@ class Connector(BaseConnector):
|
|
|
1407
1605
|
raise CommandError('VIN in object hierarchy missing')
|
|
1408
1606
|
if 'command' not in command_arguments:
|
|
1409
1607
|
raise CommandError('Command argument missing')
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1608
|
+
try:
|
|
1609
|
+
if command_arguments['command'] == ChargingStartStopCommand.Command.START:
|
|
1610
|
+
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}/start'
|
|
1611
|
+
command_response: requests.Response = self.session.post(url, allow_redirects=True)
|
|
1612
|
+
elif command_arguments['command'] == ChargingStartStopCommand.Command.STOP:
|
|
1613
|
+
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}/stop'
|
|
1614
|
+
|
|
1615
|
+
command_response: requests.Response = self.session.post(url, allow_redirects=True)
|
|
1616
|
+
else:
|
|
1617
|
+
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
|
1418
1618
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1619
|
+
if command_response.status_code != requests.codes['accepted']:
|
|
1620
|
+
LOG.error('Could not start/stop charging (%s: %s)', command_response.status_code, command_response.text)
|
|
1621
|
+
raise CommandError(f'Could not start/stop charging ({command_response.status_code}: {command_response.text})')
|
|
1622
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1623
|
+
raise CommandError(f'Connection error: {connection_error}.'
|
|
1624
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1625
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1626
|
+
raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1627
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1628
|
+
raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1629
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1630
|
+
raise CommandError(f'Retrying failed: {retry_error}') from retry_error
|
|
1422
1631
|
return command_arguments
|
|
1423
1632
|
|
|
1424
1633
|
def __on_honk_flash(self, honk_flash_command: HonkAndFlashCommand, command_arguments: Union[str, Dict[str, Any]]) \
|
|
@@ -1448,10 +1657,20 @@ class Connector(BaseConnector):
|
|
|
1448
1657
|
command_dict['vehiclePosition']['longitude'] = vehicle.position.longitude.value
|
|
1449
1658
|
|
|
1450
1659
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-access/{vin}/honk-and-flash'
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1660
|
+
try:
|
|
1661
|
+
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
|
1662
|
+
if command_response.status_code != requests.codes['accepted']:
|
|
1663
|
+
LOG.error('Could not execute honk or flash command (%s: %s)', command_response.status_code, command_response.text)
|
|
1664
|
+
raise CommandError(f'Could not execute honk or flash command ({command_response.status_code}: {command_response.text})')
|
|
1665
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1666
|
+
raise CommandError(f'Connection error: {connection_error}.'
|
|
1667
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1668
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1669
|
+
raise SetterError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1670
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1671
|
+
raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1672
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1673
|
+
raise CommandError(f'Retrying failed: {retry_error}') from retry_error
|
|
1455
1674
|
else:
|
|
1456
1675
|
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
|
1457
1676
|
return command_arguments
|
|
@@ -1482,10 +1701,20 @@ class Connector(BaseConnector):
|
|
|
1482
1701
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-access/{vin}/unlock'
|
|
1483
1702
|
else:
|
|
1484
1703
|
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1704
|
+
try:
|
|
1705
|
+
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
|
1706
|
+
if command_response.status_code != requests.codes['accepted']:
|
|
1707
|
+
LOG.error('Could not execute locking command (%s: %s)', command_response.status_code, command_response.text)
|
|
1708
|
+
raise CommandError(f'Could not execute locking command ({command_response.status_code}: {command_response.text})')
|
|
1709
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1710
|
+
raise CommandError(f'Connection error: {connection_error}.'
|
|
1711
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1712
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1713
|
+
raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1714
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1715
|
+
raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1716
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1717
|
+
raise CommandError(f'Retrying failed: {retry_error}') from retry_error
|
|
1489
1718
|
return command_arguments
|
|
1490
1719
|
|
|
1491
1720
|
def __on_wake_sleep(self, wake_sleep_command: WakeSleepCommand, command_arguments: Union[str, Dict[str, Any]]) \
|
|
@@ -1504,10 +1733,20 @@ class Connector(BaseConnector):
|
|
|
1504
1733
|
if command_arguments['command'] == WakeSleepCommand.Command.WAKE:
|
|
1505
1734
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/vehicle-wakeup/{vin}?applyRequestLimiter=true'
|
|
1506
1735
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1736
|
+
try:
|
|
1737
|
+
command_response: requests.Response = self.session.post(url, data='{}', allow_redirects=True)
|
|
1738
|
+
if command_response.status_code != requests.codes['accepted']:
|
|
1739
|
+
LOG.error('Could not execute wake command (%s: %s)', command_response.status_code, command_response.text)
|
|
1740
|
+
raise CommandError(f'Could not execute wake command ({command_response.status_code}: {command_response.text})')
|
|
1741
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1742
|
+
raise CommandError(f'Connection error: {connection_error}.'
|
|
1743
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1744
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1745
|
+
raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1746
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1747
|
+
raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1748
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1749
|
+
raise CommandError(f'Retrying failed: {retry_error}') from retry_error
|
|
1511
1750
|
elif command_arguments['command'] == WakeSleepCommand.Command.SLEEP:
|
|
1512
1751
|
raise CommandError('Sleep command not supported by vehicle. Vehicle will put itself to sleep')
|
|
1513
1752
|
else:
|
|
@@ -1534,10 +1773,23 @@ class Connector(BaseConnector):
|
|
|
1534
1773
|
url = 'https://mysmob.api.connect.skoda-auto.cz/api/v1/spin/verify'
|
|
1535
1774
|
else:
|
|
1536
1775
|
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1776
|
+
try:
|
|
1777
|
+
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
|
1778
|
+
if command_response.status_code != requests.codes['ok']:
|
|
1779
|
+
LOG.error('Could not execute spin command (%s: %s)', command_response.status_code, command_response.text)
|
|
1780
|
+
raise CommandError(f'Could not execute spin command ({command_response.status_code}: {command_response.text})')
|
|
1781
|
+
else:
|
|
1782
|
+
LOG.info('Spin verify command executed successfully')
|
|
1783
|
+
except requests.exceptions.ConnectionError as connection_error:
|
|
1784
|
+
raise CommandError(f'Connection error: {connection_error}.'
|
|
1785
|
+
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
1786
|
+
except requests.exceptions.ChunkedEncodingError as chunked_encoding_error:
|
|
1787
|
+
raise CommandError(f'Error: {chunked_encoding_error}') from chunked_encoding_error
|
|
1788
|
+
except requests.exceptions.ReadTimeout as timeout_error:
|
|
1789
|
+
raise CommandError(f'Timeout during read: {timeout_error}') from timeout_error
|
|
1790
|
+
except requests.exceptions.RetryError as retry_error:
|
|
1791
|
+
raise CommandError(f'Retrying failed: {retry_error}') from retry_error
|
|
1543
1792
|
return command_arguments
|
|
1793
|
+
|
|
1794
|
+
def get_name(self) -> str:
|
|
1795
|
+
return "Skoda Connector"
|
|
@@ -22,6 +22,7 @@ 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
24
|
from carconnectivity.units import Speed, Power, Length
|
|
25
|
+
from carconnectivity.enums import ConnectionState
|
|
25
26
|
|
|
26
27
|
from carconnectivity_connectors.skoda.vehicle import SkodaVehicle, SkodaElectricVehicle
|
|
27
28
|
from carconnectivity_connectors.skoda.charging import SkodaCharging, mapping_skoda_charging_state
|
|
@@ -77,6 +78,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
77
78
|
Returns:
|
|
78
79
|
MQTTErrorCode: The result of the connection attempt.
|
|
79
80
|
"""
|
|
81
|
+
self._skoda_connector.connection_state._set_value(value=ConnectionState.CONNECTING) # pylint: disable=protected-access
|
|
80
82
|
return super().connect(*args, host='mqtt.messagehub.de', port=8883, keepalive=60, **kwargs)
|
|
81
83
|
|
|
82
84
|
def _on_pre_connect_callback(self, client: Client, userdata: Any) -> None:
|
|
@@ -312,7 +314,9 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
312
314
|
# reason_code 0 means success
|
|
313
315
|
if reason_code == 0:
|
|
314
316
|
LOG.info('Connected to Skoda MQTT server')
|
|
315
|
-
self._skoda_connector.
|
|
317
|
+
if self._skoda_connector.rest_connected:
|
|
318
|
+
self._skoda_connector.connection_state._set_value(value=ConnectionState.CONNECTED) # pylint: disable=protected-access
|
|
319
|
+
self._skoda_connector.mqtt_connected = True
|
|
316
320
|
observer_flags: Observable.ObserverEvent = Observable.ObserverEvent.ENABLED | Observable.ObserverEvent.DISABLED
|
|
317
321
|
self._skoda_connector.car_connectivity.garage.add_observer(observer=self._on_carconnectivity_vehicle_enabled,
|
|
318
322
|
flag=observer_flags,
|
|
@@ -385,7 +389,8 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
385
389
|
del properties
|
|
386
390
|
del flags
|
|
387
391
|
|
|
388
|
-
self._skoda_connector.
|
|
392
|
+
self._skoda_connector.connection_state._set_value(value=ConnectionState.DISCONNECTED) # pylint: disable=protected-access
|
|
393
|
+
self._skoda_connector.mqtt_connected = False
|
|
389
394
|
self._skoda_connector.car_connectivity.garage.remove_observer(observer=self._on_carconnectivity_vehicle_enabled)
|
|
390
395
|
|
|
391
396
|
self.subscribed_topics.clear()
|
|
@@ -469,7 +474,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
469
474
|
if 'data' in data and data['data'] is not None:
|
|
470
475
|
vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
|
|
471
476
|
if isinstance(vehicle, SkodaElectricVehicle):
|
|
472
|
-
electric_drive: ElectricDrive = vehicle.get_electric_drive()
|
|
477
|
+
electric_drive: Optional[ElectricDrive] = vehicle.get_electric_drive()
|
|
473
478
|
if electric_drive is not None:
|
|
474
479
|
charging_state: Optional[Charging.ChargingState] = vehicle.charging.state.value
|
|
475
480
|
old_charging_state: Optional[Charging.ChargingState] = charging_state
|
|
@@ -509,6 +514,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
509
514
|
if old_charging_state != charging_state:
|
|
510
515
|
try:
|
|
511
516
|
self._skoda_connector.fetch_charging(vehicle, no_cache=True)
|
|
517
|
+
self._skoda_connector.car_connectivity.transaction_end()
|
|
512
518
|
except CarConnectivityError as e:
|
|
513
519
|
LOG.error('Error while fetching charging: %s', e)
|
|
514
520
|
if 'timeToFinish' in data['data'] and data['data']['timeToFinish'] is not None \
|
|
@@ -536,6 +542,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
536
542
|
if isinstance(vehicle, SkodaVehicle):
|
|
537
543
|
try:
|
|
538
544
|
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
|
|
545
|
+
self._skoda_connector.car_connectivity.transaction_end()
|
|
539
546
|
except CarConnectivityError as e:
|
|
540
547
|
LOG.error('Error while fetching air conditioning: %s', e)
|
|
541
548
|
elif 'name' in data and data['name'] == 'climatisation-completed':
|
|
@@ -582,6 +589,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
582
589
|
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
|
|
583
590
|
except CarConnectivityError as e:
|
|
584
591
|
LOG.error('Error while fetching air conditioning: %s', e)
|
|
592
|
+
self._skoda_connector.car_connectivity.transaction_end()
|
|
585
593
|
|
|
586
594
|
if vin in self.delayed_access_function_timers:
|
|
587
595
|
self.delayed_access_function_timers[vin].cancel()
|
|
@@ -598,6 +606,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
598
606
|
if isinstance(vehicle, SkodaVehicle):
|
|
599
607
|
try:
|
|
600
608
|
self._skoda_connector.fetch_vehicle_status(vehicle, no_cache=True)
|
|
609
|
+
self._skoda_connector.car_connectivity.transaction_end()
|
|
601
610
|
except CarConnectivityError as e:
|
|
602
611
|
LOG.error('Error while fetching vehicle status: %s', e)
|
|
603
612
|
|
|
@@ -629,6 +638,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
629
638
|
LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
|
|
630
639
|
try:
|
|
631
640
|
self._skoda_connector.fetch_air_conditioning(vehicle, no_cache=True)
|
|
641
|
+
self._skoda_connector.car_connectivity.transaction_end()
|
|
632
642
|
except CarConnectivityError as e:
|
|
633
643
|
LOG.error('Error while fetching air-conditioning: %s', e)
|
|
634
644
|
return
|
|
@@ -649,6 +659,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
649
659
|
LOG.debug('Received %s operation request for vehicle %s from user %s', operation_request, vin, user_id)
|
|
650
660
|
try:
|
|
651
661
|
self._skoda_connector.fetch_charging(vehicle, no_cache=True)
|
|
662
|
+
self._skoda_connector.car_connectivity.transaction_end()
|
|
652
663
|
except CarConnectivityError as e:
|
|
653
664
|
LOG.error('Error while fetching charging: %s', e)
|
|
654
665
|
return
|
|
@@ -4,9 +4,11 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from carconnectivity.vehicle import GenericVehicle, ElectricVehicle, CombustionVehicle, HybridVehicle
|
|
6
6
|
from carconnectivity.charging import Charging
|
|
7
|
+
from carconnectivity.attributes import BooleanAttribute
|
|
7
8
|
|
|
8
9
|
from carconnectivity_connectors.skoda.capability import Capabilities
|
|
9
10
|
from carconnectivity_connectors.skoda.charging import SkodaCharging
|
|
11
|
+
from carconnectivity_connectors.skoda.climatization import SkodaClimatization
|
|
10
12
|
|
|
11
13
|
SUPPORT_IMAGES = False
|
|
12
14
|
try:
|
|
@@ -31,12 +33,16 @@ class SkodaVehicle(GenericVehicle): # pylint: disable=too-many-instance-attribu
|
|
|
31
33
|
super().__init__(origin=origin)
|
|
32
34
|
self.capabilities: Capabilities = origin.capabilities
|
|
33
35
|
self.capabilities.parent = self
|
|
36
|
+
self.in_motion: BooleanAttribute = origin.in_motion
|
|
37
|
+
self.in_motion.parent = self
|
|
34
38
|
if SUPPORT_IMAGES:
|
|
35
39
|
self._car_images = origin._car_images
|
|
36
40
|
|
|
37
41
|
else:
|
|
38
42
|
super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
|
|
43
|
+
self.climatization = SkodaClimatization(vehicle=self, origin=self.climatization)
|
|
39
44
|
self.capabilities = Capabilities(vehicle=self)
|
|
45
|
+
self.in_motion = BooleanAttribute(name='in_motion', parent=self, tags={'connector_custom'})
|
|
40
46
|
if SUPPORT_IMAGES:
|
|
41
47
|
self._car_images: Dict[str, Image.Image] = {}
|
|
42
48
|
self.manufacturer._set_value(value='Škoda') # pylint: disable=protected-access
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/.github/dependabot.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{carconnectivity_connector_skoda-0.3 → carconnectivity_connector_skoda-0.4}/setup_requirements.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|