carconnectivity-connector-skoda 0.1a2__py3-none-any.whl → 0.1a4__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.
- {carconnectivity_connector_skoda-0.1a2.dist-info → carconnectivity_connector_skoda-0.1a4.dist-info}/METADATA +2 -1
- {carconnectivity_connector_skoda-0.1a2.dist-info → carconnectivity_connector_skoda-0.1a4.dist-info}/RECORD +11 -10
- carconnectivity_connectors/skoda/_version.py +1 -1
- carconnectivity_connectors/skoda/auth/my_skoda_session.py +35 -31
- carconnectivity_connectors/skoda/auth/openid_session.py +14 -2
- carconnectivity_connectors/skoda/auth/session_manager.py +7 -6
- carconnectivity_connectors/skoda/connector.py +55 -11
- carconnectivity_connectors/skoda/mqtt_client.py +422 -0
- {carconnectivity_connector_skoda-0.1a2.dist-info → carconnectivity_connector_skoda-0.1a4.dist-info}/LICENSE +0 -0
- {carconnectivity_connector_skoda-0.1a2.dist-info → carconnectivity_connector_skoda-0.1a4.dist-info}/WHEEL +0 -0
- {carconnectivity_connector_skoda-0.1a2.dist-info → carconnectivity_connector_skoda-0.1a4.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: carconnectivity-connector-skoda
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1a4
|
|
4
4
|
Summary: CarConnectivity connector for Skoda services
|
|
5
5
|
Author: Till Steinbach
|
|
6
6
|
License: MIT License
|
|
@@ -40,6 +40,7 @@ License-File: LICENSE
|
|
|
40
40
|
Requires-Dist: carconnectivity
|
|
41
41
|
Requires-Dist: oauthlib~=3.2.2
|
|
42
42
|
Requires-Dist: requests~=2.32.3
|
|
43
|
+
Requires-Dist: jwt~=1.3.1
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
carconnectivity_connectors/skoda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
carconnectivity_connectors/skoda/_version.py,sha256=
|
|
2
|
+
carconnectivity_connectors/skoda/_version.py,sha256=k2_F9qhiZ-QdrUSPYbgP7u-QuYMDZVq_7Ev7i0J23L0,408
|
|
3
3
|
carconnectivity_connectors/skoda/capability.py,sha256=JlNEaisVYF8qWv0wNDHTaas36uIpTIQ3NVR69wesiYQ,4513
|
|
4
|
-
carconnectivity_connectors/skoda/connector.py,sha256=
|
|
4
|
+
carconnectivity_connectors/skoda/connector.py,sha256=uAf1WcSQ68fkMF_R2zPjLP5vJYjk1QzGcg-ZdX4mPZo,42473
|
|
5
|
+
carconnectivity_connectors/skoda/mqtt_client.py,sha256=7Hn-TqBl7VsN7e7DoPLvXZQ2UsSaOl1P0bMhbQQPX7k,18933
|
|
5
6
|
carconnectivity_connectors/skoda/vehicle.py,sha256=H3GRDNimMghFwFi--y9BsgoSK3pMibNf_l6SsDN6gvQ,2759
|
|
6
7
|
carconnectivity_connectors/skoda/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
8
|
carconnectivity_connectors/skoda/auth/auth_util.py,sha256=dGLUbUre0HBsTg_Ii5vW34f8DLrCykYJYCyzEvUBBEE,4434
|
|
8
|
-
carconnectivity_connectors/skoda/auth/my_skoda_session.py,sha256=
|
|
9
|
-
carconnectivity_connectors/skoda/auth/openid_session.py,sha256=
|
|
10
|
-
carconnectivity_connectors/skoda/auth/session_manager.py,sha256=
|
|
9
|
+
carconnectivity_connectors/skoda/auth/my_skoda_session.py,sha256=sKO73OjQzvZ70n0VLBj2vxkuLYiXabS11fh2XvvboGw,10306
|
|
10
|
+
carconnectivity_connectors/skoda/auth/openid_session.py,sha256=PLWSSKw9Dg7hBbhzJ_nEycNrqiG6GiEM15h2wduL8jI,16592
|
|
11
|
+
carconnectivity_connectors/skoda/auth/session_manager.py,sha256=Uf1vujuDBYUCAXhYToOsZkgbTtfmY3Qe0ICTfwomBpI,2899
|
|
11
12
|
carconnectivity_connectors/skoda/auth/skoda_web_session.py,sha256=cjzMkzx473Sh-4RgZAQULeRRcxB1MboddldCVM_y5LE,10640
|
|
12
13
|
carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
|
|
13
|
-
carconnectivity_connector_skoda-0.
|
|
14
|
-
carconnectivity_connector_skoda-0.
|
|
15
|
-
carconnectivity_connector_skoda-0.
|
|
16
|
-
carconnectivity_connector_skoda-0.
|
|
17
|
-
carconnectivity_connector_skoda-0.
|
|
14
|
+
carconnectivity_connector_skoda-0.1a4.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
|
|
15
|
+
carconnectivity_connector_skoda-0.1a4.dist-info/METADATA,sha256=uAZPqSaCZIMMbLZLSkOBAA0XJyinzwcDNzrjmtKZy18,5326
|
|
16
|
+
carconnectivity_connector_skoda-0.1a4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
17
|
+
carconnectivity_connector_skoda-0.1a4.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
|
|
18
|
+
carconnectivity_connector_skoda-0.1a4.dist-info/RECORD,,
|
|
@@ -26,10 +26,10 @@ from carconnectivity_connectors.skoda.auth.openid_session import AccessType
|
|
|
26
26
|
from carconnectivity_connectors.skoda.auth.skoda_web_session import SkodaWebSession
|
|
27
27
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
29
|
-
from typing import
|
|
29
|
+
from typing import Set
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
LOG: logging.Logger = logging.getLogger("carconnectivity
|
|
32
|
+
LOG: logging.Logger = logging.getLogger("carconnectivity.connectors.skoda.auth")
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class MySkodaSession(SkodaWebSession):
|
|
@@ -38,7 +38,7 @@ class MySkodaSession(SkodaWebSession):
|
|
|
38
38
|
"""
|
|
39
39
|
def __init__(self, session_user, **kwargs) -> None:
|
|
40
40
|
super(MySkodaSession, self).__init__(client_id='7f045eee-7003-4379-9968-9355ed2adb06@apps_vw-dilab_com',
|
|
41
|
-
refresh_url='https://
|
|
41
|
+
refresh_url='https://mysmob.api.connect.skoda-auto.cz/api/v1/authentication/refresh-token?tokenType=CONNECT',
|
|
42
42
|
scope='address badge birthdate cars driversLicense dealers email mileage mbb nationalIdentifier openid phone profession profile vin',
|
|
43
43
|
redirect_uri='myskoda://redirect/login/',
|
|
44
44
|
session_user=session_user,
|
|
@@ -73,7 +73,7 @@ class MySkodaSession(SkodaWebSession):
|
|
|
73
73
|
def refresh(self) -> None:
|
|
74
74
|
# refresh tokens from refresh endpoint
|
|
75
75
|
self.refresh_tokens(
|
|
76
|
-
'https://
|
|
76
|
+
'https://mysmob.api.connect.skoda-auto.cz/api/v1/authentication/refresh-token?tokenType=CONNECT',
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
def fetch_tokens(
|
|
@@ -133,15 +133,20 @@ class MySkodaSession(SkodaWebSession):
|
|
|
133
133
|
token = json.loads(token_response)
|
|
134
134
|
except json.decoder.JSONDecodeError as err:
|
|
135
135
|
raise TemporaryAuthenticationError('Token could not be refreshed due to temporary WeConnect failure: json could not be decoded') from err
|
|
136
|
+
found_tokens: Set[str] = set()
|
|
136
137
|
# Fix token keys, we want access_token instead of accessToken
|
|
137
138
|
if 'accessToken' in token:
|
|
139
|
+
found_tokens.add('accessToken')
|
|
138
140
|
token['access_token'] = token.pop('accessToken')
|
|
139
141
|
# Fix token keys, we want id_token instead of idToken
|
|
140
142
|
if 'idToken' in token:
|
|
143
|
+
found_tokens.add('idToken')
|
|
141
144
|
token['id_token'] = token.pop('idToken')
|
|
142
145
|
# Fix token keys, we want refresh_token instead of refreshToken
|
|
143
146
|
if 'refreshToken' in token:
|
|
147
|
+
found_tokens.add('refreshToken')
|
|
144
148
|
token['refresh_token'] = token.pop('refreshToken')
|
|
149
|
+
LOG.info(f'Found tokens in answer: {found_tokens}')
|
|
145
150
|
# generate json from fixed dict
|
|
146
151
|
fixed_token_response = to_unicode(json.dumps(token)).encode("utf-8")
|
|
147
152
|
# Let OAuthlib parse the token
|
|
@@ -185,33 +190,32 @@ class MySkodaSession(SkodaWebSession):
|
|
|
185
190
|
if not is_secure_transport(token_url):
|
|
186
191
|
raise InsecureTransportError()
|
|
187
192
|
|
|
188
|
-
# Store old refresh token in case no new one is given
|
|
189
193
|
refresh_token = refresh_token or self.refresh_token
|
|
190
194
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
195
|
+
# Generate json body for token request
|
|
196
|
+
body: str = json.dumps(
|
|
197
|
+
{
|
|
198
|
+
'token': refresh_token,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
request_headers: CaseInsensitiveDict = self.headers # pyright: ignore reportAssignmentType
|
|
202
|
+
request_headers['accept'] = 'application/json'
|
|
203
|
+
request_headers['content-type'] = 'application/json'
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
# request tokens from token_url
|
|
207
|
+
token_response = self.post(token_url, headers=request_headers, data=body, allow_redirects=False,
|
|
208
|
+
access_type=AccessType.NONE) # pyright: ignore reportCallIssue
|
|
209
|
+
if token_response.status_code == requests.codes['ok']:
|
|
210
|
+
# parse token from response body
|
|
211
|
+
token = self.parse_from_body(token_response.text)
|
|
212
|
+
return token
|
|
213
|
+
elif token_response.status_code == requests.codes['unauthorized']:
|
|
214
|
+
LOG.info('Refreshing tokens failed: Server requests new authorization, will login now')
|
|
215
|
+
self.login()
|
|
216
|
+
return self.token
|
|
217
|
+
else:
|
|
218
|
+
raise TemporaryAuthenticationError(f'Token could not be fetched due to temporary MySkoda failure: {token_response.status_code}')
|
|
219
|
+
except ConnectionError:
|
|
220
|
+
self.login()
|
|
215
221
|
return self.token
|
|
216
|
-
else:
|
|
217
|
-
raise RetrievalError(f'Status Code from WeConnect while refreshing tokens was: {token_response.status_code}')
|
|
@@ -4,8 +4,10 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from enum import Enum, auto
|
|
6
6
|
import time
|
|
7
|
+
from datetime import datetime, timezone
|
|
7
8
|
import logging
|
|
8
9
|
import requests
|
|
10
|
+
from jwt import JWT
|
|
9
11
|
|
|
10
12
|
from oauthlib.common import UNICODE_ASCII_CHARACTER_SET, generate_nonce, generate_token
|
|
11
13
|
from oauthlib.oauth2.rfc6749.parameters import parse_authorization_code_response, parse_token_response, prepare_grant_uri
|
|
@@ -22,7 +24,7 @@ from carconnectivity_connectors.volkswagen.auth.helpers.blacklist_retry import B
|
|
|
22
24
|
if TYPE_CHECKING:
|
|
23
25
|
from typing import Dict
|
|
24
26
|
|
|
25
|
-
LOG = logging.getLogger("carconnectivity
|
|
27
|
+
LOG = logging.getLogger("carconnectivity.connectors.skoda.auth")
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
class AccessType(Enum):
|
|
@@ -146,7 +148,17 @@ class OpenIDSession(requests.Session):
|
|
|
146
148
|
if self._token is not None and 'expires_in' in self._token:
|
|
147
149
|
new_token['expires_in'] = self._token['expires_in']
|
|
148
150
|
else:
|
|
149
|
-
|
|
151
|
+
if 'id_token' in new_token:
|
|
152
|
+
jwt_instance = JWT()
|
|
153
|
+
meta_data = jwt_instance.decode(new_token['id_token'], do_verify=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
|
+
else:
|
|
159
|
+
new_token['expires_in'] = 3600
|
|
160
|
+
else:
|
|
161
|
+
new_token['expires_in'] = 3600
|
|
150
162
|
# If expires_in is set and expires_at is not set we calculate expires_at from expires_in using the current time
|
|
151
163
|
if 'expires_in' in new_token and 'expires_at' not in new_token:
|
|
152
164
|
new_token['expires_at'] = time.time() + int(new_token.get('expires_in'))
|
|
@@ -14,7 +14,7 @@ from requests import Session
|
|
|
14
14
|
|
|
15
15
|
from carconnectivity_connectors.skoda.auth.my_skoda_session import MySkodaSession
|
|
16
16
|
|
|
17
|
-
LOG = logging.getLogger("
|
|
17
|
+
LOG = logging.getLogger("carconnectivity.connectors.skoda.auth")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class SessionUser():
|
|
@@ -74,8 +74,9 @@ class SessionManager():
|
|
|
74
74
|
|
|
75
75
|
def persist(self) -> None:
|
|
76
76
|
for (service, user), session in self.sessions.items():
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
if session.token is not None:
|
|
78
|
+
identifier: str = SessionManager.generate_identifier(service, user)
|
|
79
|
+
self.tokenstore[identifier] = {}
|
|
80
|
+
self.tokenstore[identifier]['token'] = session.token
|
|
81
|
+
self.tokenstore[identifier]['metadata'] = session.metadata
|
|
82
|
+
self.cache[identifier] = session.cache
|
|
@@ -23,15 +23,15 @@ from carconnectivity.attributes import BooleanAttribute, DurationAttribute
|
|
|
23
23
|
|
|
24
24
|
from carconnectivity_connectors.base.connector import BaseConnector
|
|
25
25
|
from carconnectivity_connectors.skoda.auth.session_manager import SessionManager, SessionUser, Service
|
|
26
|
+
from carconnectivity_connectors.skoda.auth.my_skoda_session import MySkodaSession
|
|
26
27
|
from carconnectivity_connectors.skoda.vehicle import SkodaVehicle, SkodaElectricVehicle, SkodaCombustionVehicle, SkodaHybridVehicle
|
|
27
28
|
from carconnectivity_connectors.skoda.capability import Capability
|
|
28
29
|
from carconnectivity_connectors.skoda._version import __version__
|
|
30
|
+
from carconnectivity_connectors.skoda.mqtt_client import SkodaMQTTClient
|
|
29
31
|
|
|
30
32
|
if TYPE_CHECKING:
|
|
31
33
|
from typing import Dict, List, Optional, Any
|
|
32
34
|
|
|
33
|
-
from requests import Session
|
|
34
|
-
|
|
35
35
|
from carconnectivity.carconnectivity import CarConnectivity
|
|
36
36
|
|
|
37
37
|
LOG: logging.Logger = logging.getLogger("carconnectivity.connectors.skoda")
|
|
@@ -50,12 +50,17 @@ class Connector(BaseConnector):
|
|
|
50
50
|
def __init__(self, connector_id: str, car_connectivity: CarConnectivity, config: Dict) -> None:
|
|
51
51
|
BaseConnector.__init__(self, connector_id=connector_id, car_connectivity=car_connectivity, config=config)
|
|
52
52
|
|
|
53
|
+
self._mqtt_client: SkodaMQTTClient = SkodaMQTTClient(skoda_connector=self)
|
|
54
|
+
|
|
53
55
|
self._background_thread: Optional[threading.Thread] = None
|
|
56
|
+
self._background_connect_thread: Optional[threading.Thread] = None
|
|
54
57
|
self._stop_event = threading.Event()
|
|
55
58
|
|
|
56
59
|
self.connected: BooleanAttribute = BooleanAttribute(name="connected", parent=self)
|
|
57
60
|
self.interval: DurationAttribute = DurationAttribute(name="interval", parent=self)
|
|
58
61
|
|
|
62
|
+
self.user_id: Optional[str] = None
|
|
63
|
+
|
|
59
64
|
# Configure logging
|
|
60
65
|
if 'log_level' in config and config['log_level'] is not None:
|
|
61
66
|
config['log_level'] = config['log_level'].upper()
|
|
@@ -115,13 +120,33 @@ class Connector(BaseConnector):
|
|
|
115
120
|
raise AuthenticationError('Username or password not provided')
|
|
116
121
|
|
|
117
122
|
self._manager: SessionManager = SessionManager(tokenstore=car_connectivity.get_tokenstore(), cache=car_connectivity.get_cache())
|
|
118
|
-
|
|
123
|
+
session: requests.Session = self._manager.get_session(Service.MY_SKODA, SessionUser(username=username, password=password))
|
|
124
|
+
if not isinstance(session, MySkodaSession):
|
|
125
|
+
raise AuthenticationError('Could not create session')
|
|
126
|
+
self.session: MySkodaSession = session
|
|
127
|
+
self.session.refresh()
|
|
119
128
|
|
|
120
129
|
self._elapsed: List[timedelta] = []
|
|
121
130
|
|
|
122
131
|
def startup(self) -> None:
|
|
132
|
+
self._stop_event.clear()
|
|
133
|
+
# Start background thread for Rest API polling
|
|
123
134
|
self._background_thread = threading.Thread(target=self._background_loop, daemon=False)
|
|
124
135
|
self._background_thread.start()
|
|
136
|
+
# Start background thread for MQTT connection
|
|
137
|
+
self._background_connect_thread = threading.Thread(target=self._background_connect_loop, daemon=False)
|
|
138
|
+
self._background_connect_thread.start()
|
|
139
|
+
# Start MQTT thread
|
|
140
|
+
self._mqtt_client.loop_start()
|
|
141
|
+
|
|
142
|
+
def _background_connect_loop(self) -> None:
|
|
143
|
+
while not self._stop_event.is_set():
|
|
144
|
+
try:
|
|
145
|
+
self._mqtt_client.connect()
|
|
146
|
+
break
|
|
147
|
+
except ConnectionRefusedError as e:
|
|
148
|
+
LOG.error('Could not connect to MQTT-Server: %s, will retry in 10 seconds', e)
|
|
149
|
+
self._stop_event.wait(10)
|
|
125
150
|
|
|
126
151
|
def _background_loop(self) -> None:
|
|
127
152
|
self._stop_event.clear()
|
|
@@ -134,7 +159,6 @@ class Connector(BaseConnector):
|
|
|
134
159
|
if self.interval.value is not None:
|
|
135
160
|
interval: int = self.interval.value.total_seconds()
|
|
136
161
|
except Exception:
|
|
137
|
-
self.connected._set_value(value=False) # pylint: disable=protected-access
|
|
138
162
|
if self.interval.value is not None:
|
|
139
163
|
interval: int = self.interval.value.total_seconds()
|
|
140
164
|
raise
|
|
@@ -151,7 +175,6 @@ class Connector(BaseConnector):
|
|
|
151
175
|
LOG.error('Temporary authentification error during update (%s). Will try again after configured interval of %ss', str(err), interval)
|
|
152
176
|
self._stop_event.wait(interval)
|
|
153
177
|
else:
|
|
154
|
-
self.connected._set_value(value=True) # pylint: disable=protected-access
|
|
155
178
|
self._stop_event.wait(interval)
|
|
156
179
|
|
|
157
180
|
def persist(self) -> None:
|
|
@@ -174,6 +197,9 @@ class Connector(BaseConnector):
|
|
|
174
197
|
3. Sets the session and manager to None.
|
|
175
198
|
4. Calls the shutdown method of the base connector.
|
|
176
199
|
"""
|
|
200
|
+
self._mqtt_client.disconnect()
|
|
201
|
+
# Stop MQTT thread
|
|
202
|
+
self._mqtt_client.loop_stop()
|
|
177
203
|
# Disable and remove all vehicles managed soley by this connector
|
|
178
204
|
for vehicle in self.car_connectivity.garage.list_vehicles():
|
|
179
205
|
if len(vehicle.managing_connectors) == 1 and self in vehicle.managing_connectors:
|
|
@@ -182,8 +208,10 @@ class Connector(BaseConnector):
|
|
|
182
208
|
self._stop_event.set()
|
|
183
209
|
if self._background_thread is not None:
|
|
184
210
|
self._background_thread.join()
|
|
211
|
+
if self._background_connect_thread is not None:
|
|
212
|
+
self._background_connect_thread.join()
|
|
185
213
|
self.persist()
|
|
186
|
-
self.
|
|
214
|
+
self.session.close()
|
|
187
215
|
return super().shutdown()
|
|
188
216
|
|
|
189
217
|
def fetch_all(self) -> None:
|
|
@@ -195,6 +223,21 @@ class Connector(BaseConnector):
|
|
|
195
223
|
self.fetch_vehicles()
|
|
196
224
|
self.car_connectivity.transaction_end()
|
|
197
225
|
|
|
226
|
+
def fetch_user(self) -> None:
|
|
227
|
+
"""
|
|
228
|
+
Fetches the user data from the Skoda Connect API.
|
|
229
|
+
|
|
230
|
+
This method sends a request to the Skoda Connect API to retrieve the user data associated with the user's account.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
None
|
|
234
|
+
"""
|
|
235
|
+
url = 'https://mysmob.api.connect.skoda-auto.cz/api/v1/users'
|
|
236
|
+
data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
|
|
237
|
+
if data:
|
|
238
|
+
if 'id' in data and data['id'] is not None:
|
|
239
|
+
self.user_id = data['id']
|
|
240
|
+
|
|
198
241
|
def fetch_vehicles(self) -> None:
|
|
199
242
|
"""
|
|
200
243
|
Fetches the list of vehicles from the Skoda Connect API and updates the garage with new vehicles.
|
|
@@ -206,7 +249,7 @@ class Connector(BaseConnector):
|
|
|
206
249
|
"""
|
|
207
250
|
garage: Garage = self.car_connectivity.garage
|
|
208
251
|
url = 'https://mysmob.api.connect.skoda-auto.cz/api/v2/garage'
|
|
209
|
-
data: Dict[str, Any] | None = self._fetch_data(url, session=self.
|
|
252
|
+
data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
|
|
210
253
|
seen_vehicle_vins: set[str] = set()
|
|
211
254
|
if data is not None:
|
|
212
255
|
if 'vehicles' in data and data['vehicles'] is not None:
|
|
@@ -215,7 +258,7 @@ class Connector(BaseConnector):
|
|
|
215
258
|
seen_vehicle_vins.add(vehicle_dict['vin'])
|
|
216
259
|
vehicle: Optional[SkodaVehicle] = garage.get_vehicle(vehicle_dict['vin']) # pyright: ignore[reportAssignmentType]
|
|
217
260
|
if not vehicle:
|
|
218
|
-
vehicle = SkodaVehicle(vin=vehicle_dict['vin'], garage=garage)
|
|
261
|
+
vehicle = SkodaVehicle(vin=vehicle_dict['vin'], garage=garage, managing_connector=self)
|
|
219
262
|
garage.add_vehicle(vehicle_dict['vin'], vehicle)
|
|
220
263
|
|
|
221
264
|
if 'licensePlate' in vehicle_dict and vehicle_dict['licensePlate'] is not None:
|
|
@@ -247,7 +290,7 @@ class Connector(BaseConnector):
|
|
|
247
290
|
raise APIError('VIN is missing')
|
|
248
291
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/garage/vehicles/{vin}?' \
|
|
249
292
|
'connectivityGenerations=MOD1&connectivityGenerations=MOD2&connectivityGenerations=MOD3&connectivityGenerations=MOD4'
|
|
250
|
-
vehicle_data: Dict[str, Any] | None = self._fetch_data(url, self.
|
|
293
|
+
vehicle_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
|
|
251
294
|
if vehicle_data:
|
|
252
295
|
if 'softwareVersion' in vehicle_data and vehicle_data['softwareVersion'] is not None:
|
|
253
296
|
vehicle.software.version._set_value(vehicle_data['softwareVersion']) # pylint: disable=protected-access
|
|
@@ -285,7 +328,7 @@ class Connector(BaseConnector):
|
|
|
285
328
|
log_extra_keys(LOG_API, 'api/v2/garage/vehicles/VIN', vehicle_data, {'softwareVersion'})
|
|
286
329
|
|
|
287
330
|
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}/driving-range'
|
|
288
|
-
range_data: Dict[str, Any] | None = self._fetch_data(url, self.
|
|
331
|
+
range_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
|
|
289
332
|
if range_data:
|
|
290
333
|
captured_at: datetime = robust_time_parse(range_data['carCapturedTimestamp'])
|
|
291
334
|
# Check vehicle type and if it does not match the current vehicle type, create a new vehicle object using copy constructor
|
|
@@ -363,6 +406,7 @@ class Connector(BaseConnector):
|
|
|
363
406
|
|
|
364
407
|
log_extra_keys(LOG_API, f'{drive_id}EngineRange', range_data[f'{drive_id}EngineRange'], {'engineType',
|
|
365
408
|
'currentSoCInPercent',
|
|
409
|
+
'currentFuelLevelInPercent',
|
|
366
410
|
'remainingRangeInKm'})
|
|
367
411
|
log_extra_keys(LOG_API, '/api/v2/vehicle-status/{vin}/driving-range', range_data, {'carCapturedTimestamp',
|
|
368
412
|
'carType',
|
|
@@ -371,7 +415,7 @@ class Connector(BaseConnector):
|
|
|
371
415
|
'secondaryEngineRange'})
|
|
372
416
|
|
|
373
417
|
url = f'https://api.connect.skoda-auto.cz/api/v2/vehicle-status/{vin}'
|
|
374
|
-
vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url, self.
|
|
418
|
+
vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url, self.session)
|
|
375
419
|
if vehicle_status_data:
|
|
376
420
|
if 'remote' in vehicle_status_data and vehicle_status_data['remote'] is not None:
|
|
377
421
|
vehicle_status_data = vehicle_status_data['remote']
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""Module implements the MQTT client."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import uuid
|
|
7
|
+
import ssl
|
|
8
|
+
|
|
9
|
+
from paho.mqtt.client import Client
|
|
10
|
+
from paho.mqtt.enums import MQTTProtocolVersion, CallbackAPIVersion, MQTTErrorCode
|
|
11
|
+
|
|
12
|
+
from carconnectivity.observable import Observable
|
|
13
|
+
from carconnectivity.vehicle import GenericVehicle
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from typing import Set
|
|
18
|
+
|
|
19
|
+
from carconnectivity_connectors.skoda.connector import Connector
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
LOG: logging.Logger = logging.getLogger("carconnectivity.connectors.skoda.mqtt")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
26
|
+
"""
|
|
27
|
+
MQTT client for the myskoda event push service.
|
|
28
|
+
"""
|
|
29
|
+
def __init__(self, skoda_connector: Connector) -> None:
|
|
30
|
+
super().__init__(callback_api_version=CallbackAPIVersion.VERSION2,
|
|
31
|
+
client_id="Id" + str(uuid.uuid4()) + "#" + str(uuid.uuid4()),
|
|
32
|
+
transport="tcp",
|
|
33
|
+
protocol=MQTTProtocolVersion.MQTTv311,
|
|
34
|
+
reconnect_on_failure=True,
|
|
35
|
+
clean_session=True)
|
|
36
|
+
self._skoda_connector: Connector = skoda_connector
|
|
37
|
+
|
|
38
|
+
self.username = 'android-app'
|
|
39
|
+
|
|
40
|
+
self.on_pre_connect = self._on_pre_connect_callback
|
|
41
|
+
self.on_connect = self._on_connect_callback
|
|
42
|
+
self.on_message = self._on_message_callback
|
|
43
|
+
self.on_disconnect = self._on_disconnect_callback
|
|
44
|
+
self.on_subscribe = self._on_subscribe_callback
|
|
45
|
+
self.subscribed_topics: Set[str] = set()
|
|
46
|
+
|
|
47
|
+
self.tls_set(cert_reqs=ssl.CERT_NONE)
|
|
48
|
+
|
|
49
|
+
def connect(self, *args, **kwargs) -> MQTTErrorCode:
|
|
50
|
+
"""
|
|
51
|
+
Connects the MQTT client to the skoda server.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
MQTTErrorCode: The result of the connection attempt.
|
|
55
|
+
"""
|
|
56
|
+
return super().connect(*args, host='mqtt.messagehub.de', port=8883, keepalive=60, **kwargs)
|
|
57
|
+
|
|
58
|
+
def _on_pre_connect_callback(self, client, userdata) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Callback function that is called before the MQTT client connects to the broker.
|
|
61
|
+
|
|
62
|
+
Sets the client's password to the access token.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
client: The MQTT client instance (unused).
|
|
66
|
+
userdata: The user data passed to the callback (unused).
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
None
|
|
70
|
+
"""
|
|
71
|
+
del client
|
|
72
|
+
del userdata
|
|
73
|
+
|
|
74
|
+
if self._skoda_connector.session.expired or self._skoda_connector.session.access_token is None:
|
|
75
|
+
self._skoda_connector.session.refresh()
|
|
76
|
+
if not self._skoda_connector.session.expired and self._skoda_connector.session.access_token is not None:
|
|
77
|
+
# pylint: disable-next=attribute-defined-outside-init # this is a false positive, password has a setter in super class
|
|
78
|
+
self._password = self._skoda_connector.session.access_token # This is a bit hacky but if password attribute is used here there is an Exception
|
|
79
|
+
|
|
80
|
+
def _on_carconnectivity_vehicle_enabled(self, element, flags):
|
|
81
|
+
"""
|
|
82
|
+
Handles the event when a vehicle is enabled or disabled in the car connectivity system.
|
|
83
|
+
|
|
84
|
+
This method is triggered when the state of a vehicle changes. It subscribes to the vehicle
|
|
85
|
+
if it is enabled and unsubscribes if it is disabled.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
element: The element whose state has changed.
|
|
89
|
+
flags (Observable.ObserverEvent): The event flags indicating the state change.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
None
|
|
93
|
+
"""
|
|
94
|
+
if (flags & Observable.ObserverEvent.ENABLED) and isinstance(element, GenericVehicle):
|
|
95
|
+
self._subscribe_vehicle(element)
|
|
96
|
+
elif (flags & Observable.ObserverEvent.DISABLED) and isinstance(element, GenericVehicle):
|
|
97
|
+
self._subscribe_vehicle(element)
|
|
98
|
+
|
|
99
|
+
def _subscribe_vehicles(self) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Subscribes to all vehicles the connector is responsible for.
|
|
102
|
+
|
|
103
|
+
This method iterates through the list of vehicles in the carconnectivity
|
|
104
|
+
garage and subscribes to eliable vehicles by calling the _subscribe_vehicle method.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
None
|
|
108
|
+
"""
|
|
109
|
+
for vehicle in self._skoda_connector.car_connectivity.garage.list_vehicles():
|
|
110
|
+
self._subscribe_vehicle(vehicle)
|
|
111
|
+
|
|
112
|
+
def _unsubscribe_vehicles(self) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Unsubscribes from all vehicles the client is subscribed for.
|
|
115
|
+
|
|
116
|
+
This method iterates through the list of vehicles in the garage and
|
|
117
|
+
unsubscribes from each one by calling the _unsubscribe_vehicle method.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
None
|
|
121
|
+
"""
|
|
122
|
+
for vehicle in self._skoda_connector.car_connectivity.garage.list_vehicles():
|
|
123
|
+
self._unsubscribe_vehicle(vehicle)
|
|
124
|
+
|
|
125
|
+
def _subscribe_vehicle(self, vehicle: GenericVehicle) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Subscribes to MQTT topics for a given vehicle.
|
|
128
|
+
|
|
129
|
+
This method subscribes to various MQTT topics related to the vehicle's
|
|
130
|
+
account events, operation requests, and service events. It ensures that
|
|
131
|
+
the user ID is fetched if not already available and checks if the vehicle
|
|
132
|
+
has a valid VIN before subscribing.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
vehicle (GenericVehicle): The vehicle object containing VIN and other
|
|
136
|
+
relevant information.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
None
|
|
140
|
+
|
|
141
|
+
Logs:
|
|
142
|
+
- Warnings if the vehicle does not have a VIN.
|
|
143
|
+
- Info messages upon successful subscription to a topic.
|
|
144
|
+
- Error messages if subscription to a topic fails.
|
|
145
|
+
"""
|
|
146
|
+
# to subscribe the user_id must be known
|
|
147
|
+
if self._skoda_connector.user_id is None:
|
|
148
|
+
self._skoda_connector.fetch_user()
|
|
149
|
+
# Can only subscribe with user_id
|
|
150
|
+
if self._skoda_connector.user_id is not None:
|
|
151
|
+
user_id: str = self._skoda_connector.user_id
|
|
152
|
+
if not vehicle.vin.enabled or vehicle.vin.value is None:
|
|
153
|
+
LOG.warning('Could not subscribe to vehicle without vin')
|
|
154
|
+
else:
|
|
155
|
+
vin: str = vehicle.vin.value
|
|
156
|
+
# If the skoda connector is managing this vehicle
|
|
157
|
+
if self._skoda_connector in vehicle.managing_connectors:
|
|
158
|
+
account_events: Set[str] = {'privacy'}
|
|
159
|
+
operation_requests: Set[str] = {
|
|
160
|
+
'air-conditioning/set-air-conditioning-at-unlock',
|
|
161
|
+
'air-conditioning/set-air-conditioning-seats-heating',
|
|
162
|
+
'air-conditioning/set-air-conditioning-timers',
|
|
163
|
+
'air-conditioning/set-air-conditioning-without-external-power',
|
|
164
|
+
'air-conditioning/set-target-temperature',
|
|
165
|
+
'air-conditioning/start-stop-air-conditioning',
|
|
166
|
+
'auxiliary-heating/start-stop-auxiliary-heating',
|
|
167
|
+
'air-conditioning/start-stop-window-heating',
|
|
168
|
+
'air-conditioning/windows-heating',
|
|
169
|
+
'charging/start-stop-charging',
|
|
170
|
+
'charging/update-battery-support',
|
|
171
|
+
'charging/update-auto-unlock-plug',
|
|
172
|
+
'charging/update-care-mode',
|
|
173
|
+
'charging/update-charge-limit',
|
|
174
|
+
'charging/update-charge-mode',
|
|
175
|
+
'charging/update-charging-profiles',
|
|
176
|
+
'charging/update-charging-current',
|
|
177
|
+
'departure/update-departure-timers',
|
|
178
|
+
'departure/update-minimal-soc',
|
|
179
|
+
'vehicle-access/honk-and-flash',
|
|
180
|
+
'vehicle-access/lock-vehicle',
|
|
181
|
+
'vehicle-services-backup/apply-backup',
|
|
182
|
+
'vehicle-wakeup/wakeup'
|
|
183
|
+
}
|
|
184
|
+
service_events: Set[str] = {
|
|
185
|
+
'air-conditioning',
|
|
186
|
+
'charging',
|
|
187
|
+
'departure',
|
|
188
|
+
'vehicle-status/access',
|
|
189
|
+
'vehicle-status/lights'
|
|
190
|
+
}
|
|
191
|
+
possible_topics: Set[str] = set()
|
|
192
|
+
# Compile all possible topics
|
|
193
|
+
for event in account_events:
|
|
194
|
+
possible_topics.add(f'{user_id}/{vin}/account-event/{event}')
|
|
195
|
+
for event in operation_requests:
|
|
196
|
+
possible_topics.add(f'{user_id}/{vin}/operation-request/{event}')
|
|
197
|
+
for event in service_events:
|
|
198
|
+
possible_topics.add(f'{user_id}/{vin}/service-event/{event}')
|
|
199
|
+
|
|
200
|
+
# Subscribe to all topics
|
|
201
|
+
for topic in possible_topics:
|
|
202
|
+
if topic not in self.subscribed_topics:
|
|
203
|
+
mqtt_err, mid = self.subscribe(topic)
|
|
204
|
+
if mqtt_err == MQTTErrorCode.MQTT_ERR_SUCCESS:
|
|
205
|
+
self.subscribed_topics.add(topic)
|
|
206
|
+
LOG.debug('Subscribe to topic %s with %d', topic, mid)
|
|
207
|
+
else:
|
|
208
|
+
LOG.error('Could not subscribe to topic %s (%s)', topic, mqtt_err)
|
|
209
|
+
else:
|
|
210
|
+
LOG.warning('Could not subscribe to vehicle without user_id')
|
|
211
|
+
|
|
212
|
+
def _unsubscribe_vehicle(self, vehicle: GenericVehicle) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Unsubscribe from all MQTT topics related to a specific vehicle.
|
|
215
|
+
|
|
216
|
+
This method checks if the vehicle's VIN (Vehicle Identification Number) is enabled and not None.
|
|
217
|
+
If the VIN is valid, it iterates through the list of subscribed topics and unsubscribes from
|
|
218
|
+
any topic that contains the VIN. It also removes the topic from the list of subscribed topics
|
|
219
|
+
and logs the unsubscription.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
vehicle (GenericVehicle): The vehicle object containing the VIN information.
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
None
|
|
226
|
+
|
|
227
|
+
Logs:
|
|
228
|
+
- Warning if the vehicle's VIN is not enabled or is None.
|
|
229
|
+
- Info for each topic successfully unsubscribed.
|
|
230
|
+
"""
|
|
231
|
+
if not vehicle.vin.enabled or vehicle.vin.value is None:
|
|
232
|
+
LOG.warning('Could not unsubscribe to vehicle without vin')
|
|
233
|
+
else:
|
|
234
|
+
vin: str = vehicle.vin.value
|
|
235
|
+
for topic in self.subscribed_topics:
|
|
236
|
+
if vin in topic:
|
|
237
|
+
self.unsubscribe(topic)
|
|
238
|
+
self.subscribed_topics.remove(topic)
|
|
239
|
+
LOG.debug('Unsubscribed from topic %s', topic)
|
|
240
|
+
|
|
241
|
+
def _on_connect_callback(self, mqttc, obj, flags, reason_code, properties) -> None:
|
|
242
|
+
"""
|
|
243
|
+
Callback function that is called when the MQTT client connects to the broker.
|
|
244
|
+
|
|
245
|
+
It registers a callback to observe new vehicles being added and subscribes MQTT topics for all vehicles
|
|
246
|
+
handled by this connector.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
mqttc: The MQTT client instance (unused).
|
|
250
|
+
obj: User-defined object passed to the callback (unused).
|
|
251
|
+
flags: Response flags sent by the broker (unused).
|
|
252
|
+
reason_code: The connection result code.
|
|
253
|
+
properties: MQTT v5 properties (unused).
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
None
|
|
257
|
+
|
|
258
|
+
The function logs the connection status and handles different reason codes:
|
|
259
|
+
- 0: Connection successful.
|
|
260
|
+
- 128: Unspecified error.
|
|
261
|
+
- 129: Malformed packet.
|
|
262
|
+
- 130: Protocol error.
|
|
263
|
+
- 131: Implementation specific error.
|
|
264
|
+
- 132: Unsupported protocol version.
|
|
265
|
+
- 133: Client identifier not valid.
|
|
266
|
+
- 134: Bad user name or password.
|
|
267
|
+
- 135: Not authorized.
|
|
268
|
+
- 136: Server unavailable.
|
|
269
|
+
- 137: Server busy. Retrying.
|
|
270
|
+
- 138: Banned.
|
|
271
|
+
- 140: Bad authentication method.
|
|
272
|
+
- 144: Topic name invalid.
|
|
273
|
+
- 149: Packet too large.
|
|
274
|
+
- 151: Quota exceeded.
|
|
275
|
+
- 154: Retain not supported.
|
|
276
|
+
- 155: QoS not supported.
|
|
277
|
+
- 156: Use another server.
|
|
278
|
+
- 157: Server move.
|
|
279
|
+
- 159: Connection rate exceeded.
|
|
280
|
+
- Other: Generic connection error.
|
|
281
|
+
"""
|
|
282
|
+
del mqttc # unused
|
|
283
|
+
del obj # unused
|
|
284
|
+
del flags # unused
|
|
285
|
+
del properties
|
|
286
|
+
# reason_code 0 means success
|
|
287
|
+
if reason_code == 0:
|
|
288
|
+
LOG.info('Connected to Skoda MQTT server')
|
|
289
|
+
self._skoda_connector.connected._set_value(value=True) # pylint: disable=protected-access
|
|
290
|
+
observer_flags: Observable.ObserverEvent = Observable.ObserverEvent.ENABLED | Observable.ObserverEvent.DISABLED
|
|
291
|
+
self._skoda_connector.car_connectivity.garage.add_observer(observer=self._on_carconnectivity_vehicle_enabled,
|
|
292
|
+
flag=observer_flags,
|
|
293
|
+
priority=Observable.ObserverPriority.USER_MID)
|
|
294
|
+
self._subscribe_vehicles()
|
|
295
|
+
|
|
296
|
+
# Handle different reason codes
|
|
297
|
+
elif reason_code == 128:
|
|
298
|
+
LOG.error('Could not connect (%s): Unspecified error', reason_code)
|
|
299
|
+
elif reason_code == 129:
|
|
300
|
+
LOG.error('Could not connect (%s): Malformed packet', reason_code)
|
|
301
|
+
elif reason_code == 130:
|
|
302
|
+
LOG.error('Could not connect (%s): Protocol error', reason_code)
|
|
303
|
+
elif reason_code == 131:
|
|
304
|
+
LOG.error('Could not connect (%s): Implementation specific error', reason_code)
|
|
305
|
+
elif reason_code == 132:
|
|
306
|
+
LOG.error('Could not connect (%s): Unsupported protocol version', reason_code)
|
|
307
|
+
elif reason_code == 133:
|
|
308
|
+
LOG.error('Could not connect (%s): Client identifier not valid', reason_code)
|
|
309
|
+
elif reason_code == 134:
|
|
310
|
+
LOG.error('Could not connect (%s): Bad user name or password', reason_code)
|
|
311
|
+
elif reason_code == 135:
|
|
312
|
+
LOG.error('Could not connect (%s): Not authorized', reason_code)
|
|
313
|
+
elif reason_code == 136:
|
|
314
|
+
LOG.error('Could not connect (%s): Server unavailable', reason_code)
|
|
315
|
+
elif reason_code == 137:
|
|
316
|
+
LOG.error('Could not connect (%s): Server busy. Retrying', reason_code)
|
|
317
|
+
elif reason_code == 138:
|
|
318
|
+
LOG.error('Could not connect (%s): Banned', reason_code)
|
|
319
|
+
elif reason_code == 140:
|
|
320
|
+
LOG.error('Could not connect (%s): Bad authentication method', reason_code)
|
|
321
|
+
elif reason_code == 144:
|
|
322
|
+
LOG.error('Could not connect (%s): Topic name invalid', reason_code)
|
|
323
|
+
elif reason_code == 149:
|
|
324
|
+
LOG.error('Could not connect (%s): Packet too large', reason_code)
|
|
325
|
+
elif reason_code == 151:
|
|
326
|
+
LOG.error('Could not connect (%s): Quota exceeded', reason_code)
|
|
327
|
+
elif reason_code == 154:
|
|
328
|
+
LOG.error('Could not connect (%s): Retain not supported', reason_code)
|
|
329
|
+
elif reason_code == 155:
|
|
330
|
+
LOG.error('Could not connect (%s): QoS not supported', reason_code)
|
|
331
|
+
elif reason_code == 156:
|
|
332
|
+
LOG.error('Could not connect (%s): Use another server', reason_code)
|
|
333
|
+
elif reason_code == 157:
|
|
334
|
+
LOG.error('Could not connect (%s): Server move', reason_code)
|
|
335
|
+
elif reason_code == 159:
|
|
336
|
+
LOG.error('Could not connect (%s): Connection rate exceeded', reason_code)
|
|
337
|
+
else:
|
|
338
|
+
LOG.error('Could not connect (%s)', reason_code)
|
|
339
|
+
|
|
340
|
+
def _on_disconnect_callback(self, client, userdata, flags, reason_code, properties) -> None:
|
|
341
|
+
"""
|
|
342
|
+
Callback function that is called when the MQTT client disconnects.
|
|
343
|
+
|
|
344
|
+
This function handles the disconnection of the MQTT client and logs the appropriate
|
|
345
|
+
messages based on the reason code for the disconnection. It also removes the observer
|
|
346
|
+
from the garage to not get any notifications for vehicles being added or removed.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
client: The MQTT client instance that disconnected.
|
|
350
|
+
userdata: The private user data as set in Client() or userdata_set().
|
|
351
|
+
flags: Response flags sent by the broker.
|
|
352
|
+
reason_code: The reason code for the disconnection.
|
|
353
|
+
properties: The properties associated with the disconnection.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
None
|
|
357
|
+
"""
|
|
358
|
+
del client
|
|
359
|
+
del properties
|
|
360
|
+
del flags
|
|
361
|
+
|
|
362
|
+
self._skoda_connector.connected._set_value(value=False) # pylint: disable=protected-access
|
|
363
|
+
self._skoda_connector.car_connectivity.garage.remove_observer(observer=self._on_carconnectivity_vehicle_enabled)
|
|
364
|
+
|
|
365
|
+
if reason_code == 0:
|
|
366
|
+
LOG.info('Client successfully disconnected')
|
|
367
|
+
elif reason_code == 4:
|
|
368
|
+
LOG.info('Client successfully disconnected: %s', userdata)
|
|
369
|
+
elif reason_code == 137:
|
|
370
|
+
LOG.error('Client disconnected: Server busy')
|
|
371
|
+
elif reason_code == 139:
|
|
372
|
+
LOG.error('Client disconnected: Server shutting down')
|
|
373
|
+
elif reason_code == 160:
|
|
374
|
+
LOG.error('Client disconnected: Maximum connect time')
|
|
375
|
+
else:
|
|
376
|
+
LOG.error('Client unexpectedly disconnected (%s), trying to reconnect', reason_code)
|
|
377
|
+
|
|
378
|
+
def _on_subscribe_callback(self, mqttc, obj, mid, reason_codes, properties) -> None:
|
|
379
|
+
"""
|
|
380
|
+
Callback function for MQTT subscription.
|
|
381
|
+
|
|
382
|
+
This method is called when the client receives a SUBACK response from the server.
|
|
383
|
+
It checks the reason codes to determine if the subscription was successful.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
mqttc: The MQTT client instance (unused).
|
|
387
|
+
obj: User-defined data of any type (unused).
|
|
388
|
+
mid: The message ID of the subscribe request.
|
|
389
|
+
reason_codes: A list of reason codes indicating the result of the subscription.
|
|
390
|
+
properties: MQTT v5.0 properties (unused).
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
None
|
|
394
|
+
"""
|
|
395
|
+
del mqttc # unused
|
|
396
|
+
del obj # unused
|
|
397
|
+
del properties # unused
|
|
398
|
+
if any(x in [0, 1, 2] for x in reason_codes):
|
|
399
|
+
LOG.debug('sucessfully subscribed to topic of mid %d', mid)
|
|
400
|
+
else:
|
|
401
|
+
LOG.error('Subscribe was not successfull (%s)', ', '.join(reason_codes))
|
|
402
|
+
|
|
403
|
+
def _on_message_callback(self, mqttc, obj, msg) -> None: # noqa: C901
|
|
404
|
+
"""
|
|
405
|
+
Callback function for handling incoming MQTT messages.
|
|
406
|
+
|
|
407
|
+
This function is called when a message is received on a subscribed topic.
|
|
408
|
+
It logs an error message indicating that the message is not understood.
|
|
409
|
+
In the next step this needs to be implemented with real behaviour.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
mqttc: The MQTT client instance (unused).
|
|
413
|
+
obj: The user data (unused).
|
|
414
|
+
msg: The MQTT message instance containing topic and payload.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
None
|
|
418
|
+
"""
|
|
419
|
+
del mqttc # unused
|
|
420
|
+
del obj # unused
|
|
421
|
+
error_message = f'I don\'t understand message {msg.topic}: {msg.payload}'
|
|
422
|
+
LOG.info(error_message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|