carconnectivity-connector-skoda 0.1a5__tar.gz → 0.1a7__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.1a5 → carconnectivity_connector_skoda-0.1a7}/PKG-INFO +1 -1
- carconnectivity_connector_skoda-0.1a7/doc/Config.md +27 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connector_skoda.egg-info/PKG-INFO +1 -1
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connector_skoda.egg-info/SOURCES.txt +1 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/_version.py +1 -1
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/auth/my_skoda_session.py +1 -1
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/connector.py +52 -3
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/mqtt_client.py +56 -5
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/.flake8 +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/.github/dependabot.yml +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/.github/workflows/build.yml +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/.github/workflows/build_and_publish.yml +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/.github/workflows/codeql-analysis.yml +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/.gitignore +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/LICENSE +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/Makefile +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/README.md +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/pyproject.toml +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/setup.cfg +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/setup_requirements.txt +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connector_skoda.egg-info/dependency_links.txt +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connector_skoda.egg-info/requires.txt +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connector_skoda.egg-info/top_level.txt +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/__init__.py +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/auth/__init__.py +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/auth/auth_util.py +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/auth/openid_session.py +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/auth/session_manager.py +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/auth/skoda_web_session.py +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/capability.py +0 -0
- {carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/src/carconnectivity_connectors/skoda/vehicle.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# CarConnectivity Connector for MySkoda Config Options
|
|
4
|
+
The configuration for CarConnectivity is a .json file.
|
|
5
|
+
## MySkoda Connector Options
|
|
6
|
+
These are the valid options for the MySkoda Connector
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"carConnectivity": {
|
|
10
|
+
"connectors": [
|
|
11
|
+
{
|
|
12
|
+
"type": "skoda", // Definition for the MySkoda Connector
|
|
13
|
+
"config": {
|
|
14
|
+
"log_level": "error", // set the connectos log level
|
|
15
|
+
"interval": 300, // Interval in which the server is checked in seconds
|
|
16
|
+
"username": "test@test.de", // Username of your Volkswagen Account
|
|
17
|
+
"password": "testpassword123", // Username of your Volkswagen Account
|
|
18
|
+
"netrc": "~/.netr", // netrc file if to be used for passwords
|
|
19
|
+
"api_log_level": "debug", // Show debug information regarding the API
|
|
20
|
+
"max_age": 300 //Cache requests to the server vor MAX_AGE seconds
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"plugins": []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
@@ -11,6 +11,7 @@ setup_requirements.txt
|
|
|
11
11
|
.github/workflows/build.yml
|
|
12
12
|
.github/workflows/build_and_publish.yml
|
|
13
13
|
.github/workflows/codeql-analysis.yml
|
|
14
|
+
doc/Config.md
|
|
14
15
|
src/carconnectivity_connector_skoda.egg-info/PKG-INFO
|
|
15
16
|
src/carconnectivity_connector_skoda.egg-info/SOURCES.txt
|
|
16
17
|
src/carconnectivity_connector_skoda.egg-info/dependency_links.txt
|
|
@@ -146,7 +146,7 @@ class MySkodaSession(SkodaWebSession):
|
|
|
146
146
|
if 'refreshToken' in token:
|
|
147
147
|
found_tokens.add('refreshToken')
|
|
148
148
|
token['refresh_token'] = token.pop('refreshToken')
|
|
149
|
-
LOG.
|
|
149
|
+
LOG.debug(f'Found tokens in answer: {found_tokens}')
|
|
150
150
|
# generate json from fixed dict
|
|
151
151
|
fixed_token_response = to_unicode(json.dumps(token)).encode("utf-8")
|
|
152
152
|
# Let OAuthlib parse the token
|
|
@@ -14,7 +14,7 @@ from carconnectivity.vehicle import GenericVehicle
|
|
|
14
14
|
from carconnectivity.errors import AuthenticationError, TooManyRequestsError, RetrievalError, APIError, APICompatibilityError, \
|
|
15
15
|
TemporaryAuthenticationError, ConfigurationError
|
|
16
16
|
from carconnectivity.util import robust_time_parse, log_extra_keys, config_remove_credentials
|
|
17
|
-
from carconnectivity.units import Length
|
|
17
|
+
from carconnectivity.units import Length, Speed, Power
|
|
18
18
|
from carconnectivity.doors import Doors
|
|
19
19
|
from carconnectivity.windows import Windows
|
|
20
20
|
from carconnectivity.lights import Lights
|
|
@@ -267,7 +267,9 @@ class Connector(BaseConnector):
|
|
|
267
267
|
vehicle.license_plate._set_value(None) # pylint: disable=protected-access
|
|
268
268
|
|
|
269
269
|
log_extra_keys(LOG_API, 'vehicles', vehicle_dict, {'vin', 'licensePlate'})
|
|
270
|
-
self.fetch_vehicle_status(vehicle)
|
|
270
|
+
vehicle = self.fetch_vehicle_status(vehicle)
|
|
271
|
+
if isinstance(vehicle, SkodaElectricVehicle):
|
|
272
|
+
vehicle = self.fetch_charging(vehicle)
|
|
271
273
|
else:
|
|
272
274
|
raise APIError('Could not parse vehicle, vin missing')
|
|
273
275
|
for vin in set(garage.list_vehicle_vins()) - seen_vehicle_vins:
|
|
@@ -275,7 +277,53 @@ class Connector(BaseConnector):
|
|
|
275
277
|
if vehicle_to_remove is not None and vehicle_to_remove.is_managed_by_connector(self):
|
|
276
278
|
garage.remove_vehicle(vin)
|
|
277
279
|
|
|
278
|
-
def
|
|
280
|
+
def fetch_charging(self, vehicle: SkodaElectricVehicle) -> SkodaElectricVehicle:
|
|
281
|
+
"""
|
|
282
|
+
Fetches the charging information for a given Skoda electric vehicle.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
vehicle (SkodaElectricVehicle): The Skoda electric vehicle object.
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
APIError: If the VIN is missing or if the carCapturedTimestamp is missing in the response data.
|
|
289
|
+
ValueError: If the vehicle has no charging object.
|
|
290
|
+
"""
|
|
291
|
+
vin = vehicle.vin.value
|
|
292
|
+
if vin is None:
|
|
293
|
+
raise APIError('VIN is missing')
|
|
294
|
+
if vehicle.charging is None:
|
|
295
|
+
raise ValueError('Vehicle has no charging object')
|
|
296
|
+
url = f'https://mysmob.api.connect.skoda-auto.cz/api/v1/charging/{vin}'
|
|
297
|
+
data: Dict[str, Any] | None = self._fetch_data(url, session=self.session)
|
|
298
|
+
if data is not None:
|
|
299
|
+
if 'carCapturedTimestamp' in data and data['carCapturedTimestamp'] is not None:
|
|
300
|
+
captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
|
|
301
|
+
else:
|
|
302
|
+
raise APIError('Could not fetch charging, carCapturedTimestamp missing')
|
|
303
|
+
if 'status' in data and data['status'] is not None:
|
|
304
|
+
if 'chargingRateInKilometersPerHour' in data['status'] and data['status']['chargingRateInKilometersPerHour'] is not None:
|
|
305
|
+
# pylint: disable-next=protected-access
|
|
306
|
+
vehicle.charging.rate._set_value(value=data['status']['chargingRateInKilometersPerHour'], measured=captured_at, unit=Speed.KMH)
|
|
307
|
+
else:
|
|
308
|
+
vehicle.charging.rate._set_value(None, measured=captured_at, unit=Speed.KMH) # pylint: disable=protected-access
|
|
309
|
+
if 'chargePowerInKw' in data['status'] and data['status']['chargePowerInKw'] is not None:
|
|
310
|
+
# pylint: disable-next=protected-access
|
|
311
|
+
vehicle.charging.power._set_value(value=data['status']['chargePowerInKw'], measured=captured_at, unit=Power.KW)
|
|
312
|
+
else:
|
|
313
|
+
vehicle.charging.power._set_value(None, measured=captured_at, unit=Power.KW) # pylint: disable=protected-access
|
|
314
|
+
if 'remainingTimeToFullyChargedInMinutes' in data['status'] and data['status']['remainingTimeToFullyChargedInMinutes'] is not None:
|
|
315
|
+
remaining_duration: timedelta = timedelta(minutes=data['status']['remainingTimeToFullyChargedInMinutes'])
|
|
316
|
+
# pylint: disable-next=protected-access
|
|
317
|
+
vehicle.charging.remaining_duration._set_value(value=remaining_duration, measured=captured_at)
|
|
318
|
+
else:
|
|
319
|
+
vehicle.charging.remaining_duration._set_value(None, measured=captured_at) # pylint: disable=protected-access
|
|
320
|
+
log_extra_keys(LOG_API, 'status', data['status'], {'chargingRateInKilometersPerHour',
|
|
321
|
+
'chargePowerInKw',
|
|
322
|
+
'remainingTimeToFullyChargedInMinutes'})
|
|
323
|
+
log_extra_keys(LOG_API, 'charging data', data, {'carCapturedTimestamp', 'status'})
|
|
324
|
+
return vehicle
|
|
325
|
+
|
|
326
|
+
def fetch_vehicle_status(self, vehicle: SkodaVehicle) -> SkodaVehicle:
|
|
279
327
|
"""
|
|
280
328
|
Fetches the status of a vehicle from the Skoda API.
|
|
281
329
|
|
|
@@ -579,6 +627,7 @@ class Connector(BaseConnector):
|
|
|
579
627
|
vehicle.lights.lights = {}
|
|
580
628
|
log_extra_keys(LOG_API, 'lights', vehicle_status_data['lights'], {'overallStatus', 'lightsStatus'})
|
|
581
629
|
log_extra_keys(LOG_API, 'vehicles', vehicle_status_data, {'capturedAt', 'mileageInKm', 'status', 'doors', 'windows', 'lights'})
|
|
630
|
+
return vehicle
|
|
582
631
|
|
|
583
632
|
def _record_elapsed(self, elapsed: timedelta) -> None:
|
|
584
633
|
"""
|
|
@@ -2,24 +2,31 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
import logging
|
|
6
7
|
import uuid
|
|
7
8
|
import ssl
|
|
9
|
+
import json
|
|
10
|
+
from datetime import timedelta
|
|
8
11
|
|
|
9
12
|
from paho.mqtt.client import Client
|
|
10
13
|
from paho.mqtt.enums import MQTTProtocolVersion, CallbackAPIVersion, MQTTErrorCode
|
|
11
14
|
|
|
12
15
|
from carconnectivity.observable import Observable
|
|
13
|
-
from carconnectivity.vehicle import GenericVehicle
|
|
16
|
+
from carconnectivity.vehicle import GenericVehicle, ElectricVehicle
|
|
17
|
+
from carconnectivity.drive import ElectricDrive
|
|
18
|
+
from carconnectivity.util import robust_time_parse, log_extra_keys
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
if TYPE_CHECKING:
|
|
17
|
-
from typing import Set
|
|
22
|
+
from typing import Set, Dict, Any, Optional
|
|
23
|
+
from datetime import datetime
|
|
18
24
|
|
|
19
25
|
from carconnectivity_connectors.skoda.connector import Connector
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
LOG: logging.Logger = logging.getLogger("carconnectivity.connectors.skoda.mqtt")
|
|
29
|
+
LOG_API: logging.Logger = logging.getLogger("carconnectivity.connectors.skoda-api-debug")
|
|
23
30
|
|
|
24
31
|
|
|
25
32
|
class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
@@ -362,10 +369,14 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
362
369
|
self._skoda_connector.connected._set_value(value=False) # pylint: disable=protected-access
|
|
363
370
|
self._skoda_connector.car_connectivity.garage.remove_observer(observer=self._on_carconnectivity_vehicle_enabled)
|
|
364
371
|
|
|
372
|
+
self.subscribed_topics.clear()
|
|
373
|
+
|
|
365
374
|
if reason_code == 0:
|
|
366
375
|
LOG.info('Client successfully disconnected')
|
|
367
376
|
elif reason_code == 4:
|
|
368
377
|
LOG.info('Client successfully disconnected: %s', userdata)
|
|
378
|
+
elif reason_code == 128:
|
|
379
|
+
LOG.info('Client disconnected: Needs new access token, trying to reconnect')
|
|
369
380
|
elif reason_code == 137:
|
|
370
381
|
LOG.error('Client disconnected: Server busy')
|
|
371
382
|
elif reason_code == 139:
|
|
@@ -373,7 +384,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
373
384
|
elif reason_code == 160:
|
|
374
385
|
LOG.error('Client disconnected: Maximum connect time')
|
|
375
386
|
else:
|
|
376
|
-
LOG.error('Client unexpectedly disconnected (%s), trying to reconnect', reason_code)
|
|
387
|
+
LOG.error('Client unexpectedly disconnected (%d: %s), trying to reconnect', reason_code, reason_code)
|
|
377
388
|
|
|
378
389
|
def _on_subscribe_callback(self, mqttc, obj, mid, reason_codes, properties) -> None:
|
|
379
390
|
"""
|
|
@@ -418,5 +429,45 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
418
429
|
"""
|
|
419
430
|
del mqttc # unused
|
|
420
431
|
del obj # unused
|
|
421
|
-
|
|
422
|
-
|
|
432
|
+
if len(msg.payload) == 0:
|
|
433
|
+
LOG_API.debug('MQTT topic %s: ignoring empty message', msg.topic)
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
# service_events
|
|
437
|
+
match = re.match(r'^(?P<user_id>[0-9a-fA-F-]+)/(?P<vin>[A-Z0-9]+)/service-event/(?P<service_event>\w+)$', msg.topic)
|
|
438
|
+
if match:
|
|
439
|
+
user_id: str = match.group('user_id')
|
|
440
|
+
vin: str = match.group('vin')
|
|
441
|
+
service_event: str = match.group('service_event')
|
|
442
|
+
data: Dict[str, Any] = json.loads(msg.payload)
|
|
443
|
+
if data is not None:
|
|
444
|
+
if 'timestamp' in data and data['timestamp'] is not None:
|
|
445
|
+
measured_at: datetime = robust_time_parse(data['timestamp'])
|
|
446
|
+
else:
|
|
447
|
+
measured_at: datetime = datetime.now()
|
|
448
|
+
if service_event == 'charging':
|
|
449
|
+
if 'name' in data and data['name'] == 'change-charge-mode' or data['name'] == 'change-soc':
|
|
450
|
+
if 'data' in data and data['data'] is not None:
|
|
451
|
+
vehicle: Optional[GenericVehicle] = self._skoda_connector.car_connectivity.garage.get_vehicle(vin)
|
|
452
|
+
if isinstance(vehicle, ElectricVehicle):
|
|
453
|
+
electric_drive: ElectricDrive = vehicle.get_electric_drive()
|
|
454
|
+
if electric_drive is not None:
|
|
455
|
+
if 'soc' in data['data'] and data['data']['soc'] is not None:
|
|
456
|
+
electric_drive.level._set_value(measured=measured_at, value=data['data']['soc']) # pylint: disable=protected-access
|
|
457
|
+
if 'chargedRange' in data['data'] and data['data']['chargedRange'] is not None:
|
|
458
|
+
# pylint: disable-next=protected-access
|
|
459
|
+
electric_drive.range._set_value(measured=measured_at, value=data['data']['chargedRange'])
|
|
460
|
+
if 'timeToFinish' in data['data'] and data['data']['timeToFinish'] is not None \
|
|
461
|
+
and vehicle.charging is not None:
|
|
462
|
+
remaining_duration: timedelta = timedelta(minutes=data['data']['timeToFinish'])
|
|
463
|
+
# pylint: disable-next=protected-access
|
|
464
|
+
vehicle.charging.remaining_duration._set_value(measured=measured_at, value=remaining_duration)
|
|
465
|
+
log_extra_keys(LOG_API, 'data', data['data'], {'vin', 'userId', 'soc', 'chargedRange', 'timeToFinish'})
|
|
466
|
+
LOG.debug('Received %s event for vehicle %s from user %s', data['name'], vin, user_id)
|
|
467
|
+
return
|
|
468
|
+
LOG_API.info('Received event name %s service event %s for vehicle %s from user %s: %s', data['name'],
|
|
469
|
+
service_event, vin, user_id, msg.payload)
|
|
470
|
+
return
|
|
471
|
+
LOG_API.info('Received unknown service event %s for vehicle %s from user %s: %s', service_event, vin, user_id, msg.payload)
|
|
472
|
+
return
|
|
473
|
+
LOG_API.info('I don\'t understand message %s: %s', msg.topic, msg.payload)
|
|
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
|
{carconnectivity_connector_skoda-0.1a5 → carconnectivity_connector_skoda-0.1a7}/pyproject.toml
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
|