carconnectivity-connector-skoda 0.6__tar.gz → 0.8.1__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.6 → carconnectivity_connector_skoda-0.8.1}/.github/workflows/build.yml +3 -3
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/.github/workflows/build_and_publish.yml +2 -2
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/.github/workflows/codeql-analysis.yml +4 -4
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/CHANGELOG.md +47 -1
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/PKG-INFO +6 -5
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/doc/Config.md +2 -1
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/pyproject.toml +6 -5
- carconnectivity_connector_skoda-0.8.1/setup_requirements.txt +6 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connector_skoda.egg-info/PKG-INFO +6 -5
- carconnectivity_connector_skoda-0.8.1/src/carconnectivity_connector_skoda.egg-info/requires.txt +5 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/_version.py +16 -3
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/auth/my_skoda_session.py +19 -13
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/auth/openid_session.py +6 -3
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/capability.py +1 -1
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/command_impl.py +1 -1
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/connector.py +33 -6
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/mqtt_client.py +20 -3
- carconnectivity_connector_skoda-0.6/setup_requirements.txt +0 -6
- carconnectivity_connector_skoda-0.6/src/carconnectivity_connector_skoda.egg-info/requires.txt +0 -5
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/.flake8 +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/.github/dependabot.yml +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/.gitignore +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/LICENSE +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/Makefile +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/README.md +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/setup.cfg +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connector_skoda.egg-info/SOURCES.txt +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connector_skoda.egg-info/dependency_links.txt +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connector_skoda.egg-info/top_level.txt +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/__init__.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/auth/__init__.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/auth/auth_util.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/auth/helpers/blacklist_retry.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/auth/session_manager.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/auth/skoda_web_session.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/charging.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/climatization.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/error.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/ui/connector_ui.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/src/carconnectivity_connectors/skoda/vehicle.py +0 -0
- {carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/test/integration_test/carConnectivity.json +0 -0
|
@@ -22,12 +22,12 @@ jobs:
|
|
|
22
22
|
runs-on: ubuntu-latest
|
|
23
23
|
strategy:
|
|
24
24
|
matrix:
|
|
25
|
-
python-version: ['3.
|
|
25
|
+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
|
|
26
26
|
|
|
27
27
|
steps:
|
|
28
|
-
- uses: actions/checkout@
|
|
28
|
+
- uses: actions/checkout@v5
|
|
29
29
|
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
-
uses: actions/setup-python@
|
|
30
|
+
uses: actions/setup-python@v6
|
|
31
31
|
with:
|
|
32
32
|
python-version: ${{ matrix.python-version }}
|
|
33
33
|
- name: Install dependencies
|
|
@@ -15,11 +15,11 @@ jobs:
|
|
|
15
15
|
id-token: write
|
|
16
16
|
|
|
17
17
|
steps:
|
|
18
|
-
- uses: actions/checkout@
|
|
18
|
+
- uses: actions/checkout@v5
|
|
19
19
|
with:
|
|
20
20
|
fetch-depth: 0
|
|
21
21
|
- name: Set up Python
|
|
22
|
-
uses: actions/setup-python@
|
|
22
|
+
uses: actions/setup-python@v6
|
|
23
23
|
with:
|
|
24
24
|
python-version: "3.x"
|
|
25
25
|
- name: Install dependencies
|
|
@@ -26,11 +26,11 @@ jobs:
|
|
|
26
26
|
|
|
27
27
|
steps:
|
|
28
28
|
- name: Checkout repository
|
|
29
|
-
uses: actions/checkout@
|
|
29
|
+
uses: actions/checkout@v5
|
|
30
30
|
|
|
31
31
|
# Initializes the CodeQL tools for scanning.
|
|
32
32
|
- name: Initialize CodeQL
|
|
33
|
-
uses: github/codeql-action/init@
|
|
33
|
+
uses: github/codeql-action/init@v4
|
|
34
34
|
with:
|
|
35
35
|
languages: ${{ matrix.language }}
|
|
36
36
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
42
42
|
# If this step fails, then you should remove it and run the build manually (see below)
|
|
43
43
|
- name: Autobuild
|
|
44
|
-
uses: github/codeql-action/autobuild@
|
|
44
|
+
uses: github/codeql-action/autobuild@v4
|
|
45
45
|
|
|
46
46
|
# ℹ️ Command-line programs to run using the OS shell.
|
|
47
47
|
# 📚 https://git.io/JvXDl
|
|
@@ -55,4 +55,4 @@ jobs:
|
|
|
55
55
|
# make release
|
|
56
56
|
|
|
57
57
|
- name: Perform CodeQL Analysis
|
|
58
|
-
uses: github/codeql-action/analyze@
|
|
58
|
+
uses: github/codeql-action/analyze@v4
|
|
@@ -5,6 +5,46 @@ 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.8.1] - 2025-11-02
|
|
9
|
+
### Fixed
|
|
10
|
+
- Relogin when MQTT client receives a bad username or password error
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Warning when connector detects suspicious token expiry
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Updated some dependencies
|
|
17
|
+
|
|
18
|
+
## [0.8] - 2025-06-27
|
|
19
|
+
### Added
|
|
20
|
+
- Support for adblue range
|
|
21
|
+
|
|
22
|
+
## [0.7.3] - 2025-06-20
|
|
23
|
+
### Fixed
|
|
24
|
+
- Fixes bug that registers hooks several times, causing multiple calls to the servers
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Updated dependencies
|
|
28
|
+
|
|
29
|
+
## [0.7.2] - 2025-04-19
|
|
30
|
+
### Fixed
|
|
31
|
+
- Fix for problems introduced with PyJWT
|
|
32
|
+
|
|
33
|
+
## [0.7.1] - 2025-04-18
|
|
34
|
+
### Fixed
|
|
35
|
+
- Fixed problem where driving range was not fetched when measurements capability had an expired license. Now it will be also fetched when charging license is active.
|
|
36
|
+
|
|
37
|
+
## [0.7] - 2025-04-17
|
|
38
|
+
### Fixed
|
|
39
|
+
- Bug in mode attribute that caused the connector to crash
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
- Updated dependencies
|
|
43
|
+
- stripping of leading and trailing spaces in commands
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- Precision for all attributes is now used when displaying values
|
|
47
|
+
|
|
8
48
|
## [0.6] - 2025-04-02
|
|
9
49
|
### Fixed
|
|
10
50
|
- Only fetch range when measurement capability is available (fix for older cars)
|
|
@@ -51,7 +91,13 @@ All notable changes to this project will be documented in this file.
|
|
|
51
91
|
Initial release, let's go and give this to the public to try out...
|
|
52
92
|
The API is not yet implemented completely but most functions already work
|
|
53
93
|
|
|
54
|
-
[unreleased]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/compare/v0.
|
|
94
|
+
[unreleased]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/compare/v0.8.1...HEAD
|
|
95
|
+
[0.8.1]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.8.1
|
|
96
|
+
[0.8]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.8
|
|
97
|
+
[0.7.3]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.7.3
|
|
98
|
+
[0.7.2]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.7.2
|
|
99
|
+
[0.7.1]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.7.1
|
|
100
|
+
[0.7]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.7
|
|
55
101
|
[0.6]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.6
|
|
56
102
|
[0.5]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.5
|
|
57
103
|
[0.4.1]: https://github.com/tillsteinbach/CarConnectivity-connector-skoda/releases/tag/v0.4.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: carconnectivity-connector-skoda
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: CarConnectivity connector for Skoda services
|
|
5
5
|
Author: Till Steinbach
|
|
6
6
|
License: MIT License
|
|
@@ -33,14 +33,15 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
33
33
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
34
|
Classifier: Programming Language :: Python :: 3.12
|
|
35
35
|
Classifier: Programming Language :: Python :: 3.13
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
36
37
|
Classifier: Topic :: Software Development :: Libraries
|
|
37
38
|
Requires-Python: >=3.8
|
|
38
39
|
Description-Content-Type: text/markdown
|
|
39
40
|
License-File: LICENSE
|
|
40
|
-
Requires-Dist: carconnectivity>=0.
|
|
41
|
-
Requires-Dist: oauthlib~=3.
|
|
42
|
-
Requires-Dist: requests~=2.32.
|
|
43
|
-
Requires-Dist:
|
|
41
|
+
Requires-Dist: carconnectivity>=0.8.1
|
|
42
|
+
Requires-Dist: oauthlib~=3.3.1
|
|
43
|
+
Requires-Dist: requests~=2.32.5
|
|
44
|
+
Requires-Dist: pyjwt~=2.10
|
|
44
45
|
Requires-Dist: paho-mqtt~=2.1.0
|
|
45
46
|
Dynamic: license-file
|
|
46
47
|
|
|
@@ -19,7 +19,8 @@ These are the valid options for the MySkoda Connector
|
|
|
19
19
|
"spin": 1234, //S-Pin used for some special commands like locking/unlocking
|
|
20
20
|
"netrc": "~/.netr", // netrc file if to be used for passwords
|
|
21
21
|
"api_log_level": "debug", // Show debug information regarding the API
|
|
22
|
-
"max_age": 300 //Cache requests to the server vor MAX_AGE seconds
|
|
22
|
+
"max_age": 300, //Cache requests to the server vor MAX_AGE seconds
|
|
23
|
+
"hide_vins": ["19XFB2F90CE040211", "1G2ZH35N074252067"] // Don't fetch these vins
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
],
|
{carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/pyproject.toml
RENAMED
|
@@ -14,10 +14,10 @@ authors = [
|
|
|
14
14
|
{ name = "Till Steinbach" }
|
|
15
15
|
]
|
|
16
16
|
dependencies = [
|
|
17
|
-
"carconnectivity>=0.
|
|
18
|
-
"oauthlib~=3.
|
|
19
|
-
"requests~=2.32.
|
|
20
|
-
"
|
|
17
|
+
"carconnectivity>=0.8.1",
|
|
18
|
+
"oauthlib~=3.3.1",
|
|
19
|
+
"requests~=2.32.5",
|
|
20
|
+
"pyjwt~=2.10",
|
|
21
21
|
"paho-mqtt~=2.1.0",
|
|
22
22
|
]
|
|
23
23
|
readme = "README.md"
|
|
@@ -31,6 +31,7 @@ classifiers = [
|
|
|
31
31
|
"Programming Language :: Python :: 3.11",
|
|
32
32
|
"Programming Language :: Python :: 3.12",
|
|
33
33
|
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Programming Language :: Python :: 3.14",
|
|
34
35
|
"Topic :: Software Development :: Libraries"
|
|
35
36
|
]
|
|
36
37
|
|
|
@@ -44,4 +45,4 @@ max-line-length = 160
|
|
|
44
45
|
ignore-patterns= "_version.py"
|
|
45
46
|
|
|
46
47
|
[tool.bandit]
|
|
47
|
-
targets = "carconnectivity_cli"
|
|
48
|
+
targets = "carconnectivity_cli"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: carconnectivity-connector-skoda
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: CarConnectivity connector for Skoda services
|
|
5
5
|
Author: Till Steinbach
|
|
6
6
|
License: MIT License
|
|
@@ -33,14 +33,15 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
33
33
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
34
|
Classifier: Programming Language :: Python :: 3.12
|
|
35
35
|
Classifier: Programming Language :: Python :: 3.13
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
36
37
|
Classifier: Topic :: Software Development :: Libraries
|
|
37
38
|
Requires-Python: >=3.8
|
|
38
39
|
Description-Content-Type: text/markdown
|
|
39
40
|
License-File: LICENSE
|
|
40
|
-
Requires-Dist: carconnectivity>=0.
|
|
41
|
-
Requires-Dist: oauthlib~=3.
|
|
42
|
-
Requires-Dist: requests~=2.32.
|
|
43
|
-
Requires-Dist:
|
|
41
|
+
Requires-Dist: carconnectivity>=0.8.1
|
|
42
|
+
Requires-Dist: oauthlib~=3.3.1
|
|
43
|
+
Requires-Dist: requests~=2.32.5
|
|
44
|
+
Requires-Dist: pyjwt~=2.10
|
|
44
45
|
Requires-Dist: paho-mqtt~=2.1.0
|
|
45
46
|
Dynamic: license-file
|
|
46
47
|
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '0.
|
|
21
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.8.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 8, 1)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = 'g5123c7f6f'
|
|
@@ -15,6 +15,7 @@ from urllib.parse import parse_qsl, urlparse
|
|
|
15
15
|
|
|
16
16
|
import requests
|
|
17
17
|
from requests.models import CaseInsensitiveDict
|
|
18
|
+
from requests.exceptions import ReadTimeout, ConnectionError
|
|
18
19
|
|
|
19
20
|
from oauthlib.common import add_params_to_uri, generate_nonce, to_unicode
|
|
20
21
|
from oauthlib.oauth2 import InsecureTransportError
|
|
@@ -58,17 +59,22 @@ class MySkodaSession(SkodaWebSession):
|
|
|
58
59
|
def login(self):
|
|
59
60
|
super(MySkodaSession, self).login()
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
try:
|
|
63
|
+
verifier = "".join(random.choices(string.ascii_uppercase + string.digits, k=16))
|
|
64
|
+
verifier_hash = hashlib.sha256(verifier.encode("utf-8")).digest()
|
|
65
|
+
code_challenge = base64.b64encode(verifier_hash).decode("utf-8").replace("+", "-").replace("/", "_").rstrip("=")
|
|
66
|
+
# retrieve authorization URL
|
|
67
|
+
authorization_url = self.authorization_url(url='https://identity.vwgroup.io/oidc/v1/authorize', prompt='login', code_challenge=code_challenge,
|
|
68
|
+
code_challenge_method='s256')
|
|
69
|
+
# perform web authentication
|
|
70
|
+
response = self.do_web_auth(authorization_url)
|
|
71
|
+
# fetch tokens from web authentication response
|
|
72
|
+
self.fetch_tokens('https://mysmob.api.connect.skoda-auto.cz/api/v1/authentication/exchange-authorization-code?tokenType=CONNECT',
|
|
73
|
+
authorization_response=response, verifier=verifier)
|
|
74
|
+
except ReadTimeout as exc:
|
|
75
|
+
raise TemporaryAuthenticationError('Login timed out (Read timeout)') from exc
|
|
76
|
+
except ConnectionError as exc:
|
|
77
|
+
raise TemporaryAuthenticationError('Login failed due to connection error') from exc
|
|
72
78
|
|
|
73
79
|
def refresh(self) -> None:
|
|
74
80
|
# refresh tokens from refresh endpoint
|
|
@@ -118,7 +124,7 @@ class MySkodaSession(SkodaWebSession):
|
|
|
118
124
|
token_response = self.post(token_url, headers=request_headers, data=body, allow_redirects=False,
|
|
119
125
|
access_type=AccessType.NONE) # pyright: ignore reportCallIssue
|
|
120
126
|
if token_response.status_code != requests.codes['ok']:
|
|
121
|
-
raise TemporaryAuthenticationError(f'Token could not be fetched due to temporary
|
|
127
|
+
raise TemporaryAuthenticationError(f'Token could not be fetched due to temporary MySkoda failure: {token_response.status_code}')
|
|
122
128
|
# parse token from response body
|
|
123
129
|
token = self.parse_from_body(token_response.text)
|
|
124
130
|
return token
|
|
@@ -132,7 +138,7 @@ class MySkodaSession(SkodaWebSession):
|
|
|
132
138
|
# Tokens are in body of response in json format
|
|
133
139
|
token = json.loads(token_response)
|
|
134
140
|
except json.decoder.JSONDecodeError as err:
|
|
135
|
-
raise TemporaryAuthenticationError('Token could not be refreshed due to temporary
|
|
141
|
+
raise TemporaryAuthenticationError('Token could not be refreshed due to temporary MySkoda failure: json could not be decoded') from err
|
|
136
142
|
found_tokens: Set[str] = set()
|
|
137
143
|
# Fix token keys, we want access_token instead of accessToken
|
|
138
144
|
if 'accessToken' in token:
|
|
@@ -7,7 +7,7 @@ import time
|
|
|
7
7
|
from datetime import datetime, timezone
|
|
8
8
|
import logging
|
|
9
9
|
import requests
|
|
10
|
-
|
|
10
|
+
import jwt
|
|
11
11
|
|
|
12
12
|
from oauthlib.common import UNICODE_ASCII_CHARACTER_SET, generate_nonce, generate_token
|
|
13
13
|
from oauthlib.oauth2.rfc6749.parameters import parse_authorization_code_response, parse_token_response, prepare_grant_uri
|
|
@@ -153,8 +153,7 @@ class OpenIDSession(requests.Session):
|
|
|
153
153
|
new_token['expires_in'] = self._token['expires_in']
|
|
154
154
|
else:
|
|
155
155
|
if 'id_token' in new_token:
|
|
156
|
-
|
|
157
|
-
meta_data = jwt_instance.decode(new_token['id_token'], do_verify=False)
|
|
156
|
+
meta_data = jwt.decode(new_token['id_token'], options={"verify_signature": False})
|
|
158
157
|
if 'exp' in meta_data:
|
|
159
158
|
new_token['expires_at'] = meta_data['exp']
|
|
160
159
|
expires_at = datetime.fromtimestamp(meta_data['exp'], tz=timezone.utc)
|
|
@@ -166,6 +165,10 @@ class OpenIDSession(requests.Session):
|
|
|
166
165
|
# If expires_in is set and expires_at is not set we calculate expires_at from expires_in using the current time
|
|
167
166
|
if 'expires_in' in new_token and 'expires_at' not in new_token:
|
|
168
167
|
new_token['expires_at'] = time.time() + int(new_token.get('expires_in'))
|
|
168
|
+
if new_token['expires_in'] > 3600:
|
|
169
|
+
LOG.warning('unexpected Token expires_in > 3600s (%d)', new_token['expires_in'])
|
|
170
|
+
if new_token['expires_at'] > (time.time() + 3600):
|
|
171
|
+
LOG.warning('unexpected Token expires_at after more than 3600s')
|
|
169
172
|
self._token = new_token
|
|
170
173
|
|
|
171
174
|
@property
|
|
@@ -132,7 +132,7 @@ class Capability(GenericObject):
|
|
|
132
132
|
LOCATION_DATA_DISABLED = 1013
|
|
133
133
|
LICENSE_INACTIVE = 2001
|
|
134
134
|
LICENSE_EXPIRED = 2002
|
|
135
|
-
|
|
135
|
+
LICENSE_MISSING = 2003
|
|
136
136
|
USER_NOT_VERIFIED = 3001
|
|
137
137
|
TERMS_AND_CONDITIONS_NOT_ACCEPTED = 3002
|
|
138
138
|
INSUFFICIENT_RIGHTS = 3003
|
|
@@ -44,7 +44,7 @@ class SpinCommand(GenericCommand):
|
|
|
44
44
|
parser.add_argument('--spin', dest='spin', help='Spin to be used instead of spin from config or .netrc', type=str, required=False,
|
|
45
45
|
default=None)
|
|
46
46
|
try:
|
|
47
|
-
args = parser.parse_args(new_value.split(sep=' '))
|
|
47
|
+
args = parser.parse_args(new_value.strip().split(sep=' '))
|
|
48
48
|
except argparse.ArgumentError as e:
|
|
49
49
|
raise SetterError(f'Invalid format for SpinCommand: {e.message} {parser.format_usage()}') from e
|
|
50
50
|
|
|
@@ -22,7 +22,7 @@ from carconnectivity.units import Length, Speed, Power, Temperature
|
|
|
22
22
|
from carconnectivity.doors import Doors
|
|
23
23
|
from carconnectivity.windows import Windows
|
|
24
24
|
from carconnectivity.lights import Lights
|
|
25
|
-
from carconnectivity.drive import GenericDrive, ElectricDrive, CombustionDrive
|
|
25
|
+
from carconnectivity.drive import GenericDrive, ElectricDrive, CombustionDrive, DieselDrive
|
|
26
26
|
from carconnectivity.attributes import BooleanAttribute, DurationAttribute, TemperatureAttribute, EnumAttribute, LevelAttribute, \
|
|
27
27
|
CurrentAttribute
|
|
28
28
|
from carconnectivity.charging import Charging
|
|
@@ -378,7 +378,8 @@ class Connector(BaseConnector):
|
|
|
378
378
|
if vehicle_to_update is not None and isinstance(vehicle_to_update, SkodaVehicle) and vehicle_to_update.is_managed_by_connector(self):
|
|
379
379
|
vehicle_to_update = self.fetch_vehicle_status(vehicle_to_update)
|
|
380
380
|
if vehicle_to_update.capabilities is not None and vehicle_to_update.capabilities.enabled:
|
|
381
|
-
if vehicle_to_update.capabilities.has_capability('MEASUREMENTS', check_status_ok=True)
|
|
381
|
+
if vehicle_to_update.capabilities.has_capability('MEASUREMENTS', check_status_ok=True) or \
|
|
382
|
+
vehicle_to_update.capabilities.has_capability('CHARGING', check_status_ok=True):
|
|
382
383
|
vehicle_to_update = self.fetch_driving_range(vehicle_to_update)
|
|
383
384
|
if vehicle_to_update.capabilities.has_capability('READINESS', check_status_ok=True):
|
|
384
385
|
vehicle_to_update = self.fetch_connection_status(vehicle_to_update)
|
|
@@ -404,13 +405,16 @@ class Connector(BaseConnector):
|
|
|
404
405
|
SkodaVehicle: The Skoda vehicle object with the updated state.
|
|
405
406
|
"""
|
|
406
407
|
if vehicle is not None:
|
|
407
|
-
if vehicle.
|
|
408
|
+
if vehicle.connection_state is not None and vehicle.connection_state.enabled \
|
|
409
|
+
and vehicle.connection_state.value == GenericVehicle.ConnectionState.OFFLINE:
|
|
410
|
+
vehicle.state._set_value(GenericVehicle.State.OFFLINE) # pylint: disable=protected-access
|
|
411
|
+
elif vehicle.in_motion is not None and vehicle.in_motion.enabled and vehicle.in_motion.value:
|
|
408
412
|
vehicle.state._set_value(GenericVehicle.State.IGNITION_ON) # pylint: disable=protected-access
|
|
409
413
|
elif vehicle.position is not None and vehicle.position.enabled and vehicle.position.position_type is not None \
|
|
410
414
|
and vehicle.position.position_type.enabled and vehicle.position.position_type.value == Position.PositionType.PARKING:
|
|
411
415
|
vehicle.state._set_value(GenericVehicle.State.PARKED) # pylint: disable=protected-access
|
|
412
416
|
else:
|
|
413
|
-
vehicle.state._set_value(
|
|
417
|
+
vehicle.state._set_value(GenericVehicle.State.UNKNOWN) # pylint: disable=protected-access
|
|
414
418
|
return vehicle
|
|
415
419
|
|
|
416
420
|
def fetch_charging(self, vehicle: SkodaElectricVehicle, no_cache: bool = False) -> SkodaElectricVehicle:
|
|
@@ -498,10 +502,12 @@ class Connector(BaseConnector):
|
|
|
498
502
|
cruising_range_in_km: float = data['status']['battery']['remainingCruisingRangeInMeters'] / 1000
|
|
499
503
|
# pylint: disable-next=protected-access
|
|
500
504
|
drive.range._set_value(value=cruising_range_in_km, measured=captured_at, unit=Length.KM)
|
|
505
|
+
drive.range.precision = 1
|
|
501
506
|
if 'stateOfChargeInPercent' in data['status']['battery'] \
|
|
502
507
|
and data['status']['battery']['stateOfChargeInPercent'] is not None:
|
|
503
508
|
# pylint: disable-next=protected-access
|
|
504
509
|
drive.level._set_value(value=data['status']['battery']['stateOfChargeInPercent'], measured=captured_at)
|
|
510
|
+
drive.level.precision = 1
|
|
505
511
|
log_extra_keys(LOG_API, 'status', data['status']['battery'], {'remainingCruisingRangeInMeters',
|
|
506
512
|
'stateOfChargeInPercent'})
|
|
507
513
|
break
|
|
@@ -703,7 +709,9 @@ class Connector(BaseConnector):
|
|
|
703
709
|
else:
|
|
704
710
|
longitude = None
|
|
705
711
|
vehicle.position.latitude._set_value(latitude) # pylint: disable=protected-access
|
|
712
|
+
vehicle.position.latitude.precision = 0.000001
|
|
706
713
|
vehicle.position.longitude._set_value(longitude) # pylint: disable=protected-access
|
|
714
|
+
vehicle.position.longitude.precision = 0.000001
|
|
707
715
|
vehicle.position.position_type._set_value(Position.PositionType.PARKING) # pylint: disable=protected-access
|
|
708
716
|
else:
|
|
709
717
|
vehicle.position.latitude._set_value(None) # pylint: disable=protected-access
|
|
@@ -756,6 +764,7 @@ class Connector(BaseConnector):
|
|
|
756
764
|
raise APIError('Could not fetch maintenance, capturedAt missing')
|
|
757
765
|
if 'mileageInKm' in data and data['mileageInKm'] is not None:
|
|
758
766
|
vehicle.odometer._set_value(value=data['mileageInKm'], measured=captured_at, unit=Length.KM) # pylint: disable=protected-access
|
|
767
|
+
vehicle.odometer.precision = 1
|
|
759
768
|
else:
|
|
760
769
|
vehicle.odometer._set_value(None) # pylint: disable=protected-access
|
|
761
770
|
if 'inspectionDueInDays' in data and data['inspectionDueInDays'] is not None:
|
|
@@ -1274,6 +1283,7 @@ class Connector(BaseConnector):
|
|
|
1274
1283
|
if 'totalRangeInKm' in range_data and range_data['totalRangeInKm'] is not None:
|
|
1275
1284
|
# pylint: disable-next=protected-access
|
|
1276
1285
|
vehicle.drives.total_range._set_value(value=range_data['totalRangeInKm'], measured=captured_at, unit=Length.KM)
|
|
1286
|
+
vehicle.drives.total_range.precision = 1
|
|
1277
1287
|
else:
|
|
1278
1288
|
vehicle.drives.total_range._set_value(None, measured=captured_at, unit=Length.KM) # pylint: disable=protected-access
|
|
1279
1289
|
|
|
@@ -1291,10 +1301,11 @@ class Connector(BaseConnector):
|
|
|
1291
1301
|
else:
|
|
1292
1302
|
if engine_type == GenericDrive.Type.ELECTRIC:
|
|
1293
1303
|
drive = ElectricDrive(drive_id=drive_id, drives=vehicle.drives)
|
|
1304
|
+
elif engine_type == GenericDrive.Type.DIESEL:
|
|
1305
|
+
drive = DieselDrive(drive_id=drive_id, drives=vehicle.drives)
|
|
1294
1306
|
elif engine_type in [GenericDrive.Type.FUEL,
|
|
1295
1307
|
GenericDrive.Type.GASOLINE,
|
|
1296
1308
|
GenericDrive.Type.PETROL,
|
|
1297
|
-
GenericDrive.Type.DIESEL,
|
|
1298
1309
|
GenericDrive.Type.CNG,
|
|
1299
1310
|
GenericDrive.Type.LPG]:
|
|
1300
1311
|
drive = CombustionDrive(drive_id=drive_id, drives=vehicle.drives)
|
|
@@ -1306,15 +1317,18 @@ class Connector(BaseConnector):
|
|
|
1306
1317
|
and range_data[f'{drive_id}EngineRange']['currentSoCInPercent'] is not None:
|
|
1307
1318
|
# pylint: disable-next=protected-access
|
|
1308
1319
|
drive.level._set_value(value=range_data[f'{drive_id}EngineRange']['currentSoCInPercent'], measured=captured_at)
|
|
1320
|
+
drive.level.precision = 1
|
|
1309
1321
|
elif 'currentFuelLevelInPercent' in range_data[f'{drive_id}EngineRange'] \
|
|
1310
1322
|
and range_data[f'{drive_id}EngineRange']['currentFuelLevelInPercent'] is not None:
|
|
1311
1323
|
# pylint: disable-next=protected-access
|
|
1312
1324
|
drive.level._set_value(value=range_data[f'{drive_id}EngineRange']['currentFuelLevelInPercent'], measured=captured_at)
|
|
1325
|
+
drive.level.precision = 1
|
|
1313
1326
|
else:
|
|
1314
1327
|
drive.level._set_value(None, measured=captured_at) # pylint: disable=protected-access
|
|
1315
1328
|
if 'remainingRangeInKm' in range_data[f'{drive_id}EngineRange'] and range_data[f'{drive_id}EngineRange']['remainingRangeInKm'] is not None:
|
|
1316
1329
|
# pylint: disable-next=protected-access
|
|
1317
1330
|
drive.range._set_value(value=range_data[f'{drive_id}EngineRange']['remainingRangeInKm'], measured=captured_at, unit=Length.KM)
|
|
1331
|
+
drive.range.precision = 1
|
|
1318
1332
|
else:
|
|
1319
1333
|
drive.range._set_value(None, measured=captured_at, unit=Length.KM) # pylint: disable=protected-access
|
|
1320
1334
|
|
|
@@ -1322,9 +1336,22 @@ class Connector(BaseConnector):
|
|
|
1322
1336
|
'currentSoCInPercent',
|
|
1323
1337
|
'currentFuelLevelInPercent',
|
|
1324
1338
|
'remainingRangeInKm'})
|
|
1339
|
+
if 'adBlueRange' in range_data and range_data['adBlueRange'] is not None:
|
|
1340
|
+
# pylint: disable-next=protected-access
|
|
1341
|
+
for drive in vehicle.drives.drives.values():
|
|
1342
|
+
if isinstance(drive, DieselDrive):
|
|
1343
|
+
# pylint: disable-next=protected-access
|
|
1344
|
+
drive.adblue_range._set_value(value=range_data['adBlueRange'], measured=captured_at, unit=Length.KM)
|
|
1345
|
+
drive.adblue_range.precision = 1
|
|
1346
|
+
else:
|
|
1347
|
+
for drive in vehicle.drives.drives.values():
|
|
1348
|
+
if isinstance(drive, DieselDrive):
|
|
1349
|
+
# pylint: disable-next=protected-access
|
|
1350
|
+
drive.adblue_range._set_value(value=None, measured=captured_at, unit=Length.KM)
|
|
1325
1351
|
log_extra_keys(LOG_API, '/api/v2/vehicle-status/{vin}/driving-range', range_data, {'carCapturedTimestamp',
|
|
1326
1352
|
'carType',
|
|
1327
1353
|
'totalRangeInKm',
|
|
1354
|
+
'adBlueRange',
|
|
1328
1355
|
'primaryEngineRange',
|
|
1329
1356
|
'secondaryEngineRange'})
|
|
1330
1357
|
return vehicle
|
|
@@ -1468,7 +1495,7 @@ class Connector(BaseConnector):
|
|
|
1468
1495
|
elif not allow_http_error or (allowed_errors is not None and status_response.status_code not in allowed_errors):
|
|
1469
1496
|
raise RetrievalError(f'Could not fetch data even after re-authorization. Status Code was: {status_response.status_code}')
|
|
1470
1497
|
elif not allow_http_error or (allowed_errors is not None and status_response.status_code not in allowed_errors):
|
|
1471
|
-
raise RetrievalError(f'Could not fetch data. Status Code was: {status_response.status_code}')
|
|
1498
|
+
raise RetrievalError(f'Could not fetch data for {url}. Status Code was: {status_response.status_code}')
|
|
1472
1499
|
except requests.exceptions.ConnectionError as connection_error:
|
|
1473
1500
|
raise RetrievalError(f'Connection error: {connection_error}.'
|
|
1474
1501
|
' If this happens frequently, please check if other applications communicate with the Skoda server.') from connection_error
|
|
@@ -13,7 +13,7 @@ from datetime import timedelta, timezone
|
|
|
13
13
|
from paho.mqtt.client import Client
|
|
14
14
|
from paho.mqtt.enums import MQTTProtocolVersion, CallbackAPIVersion, MQTTErrorCode
|
|
15
15
|
|
|
16
|
-
from carconnectivity.errors import CarConnectivityError
|
|
16
|
+
from carconnectivity.errors import CarConnectivityError, TemporaryAuthenticationError
|
|
17
17
|
from carconnectivity.observable import Observable
|
|
18
18
|
from carconnectivity.vehicle import GenericVehicle
|
|
19
19
|
|
|
@@ -70,6 +70,8 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
70
70
|
self.delayed_access_function_timers: Dict[str, threading.Timer] = {}
|
|
71
71
|
|
|
72
72
|
self.tls_set(cert_reqs=ssl.CERT_NONE)
|
|
73
|
+
|
|
74
|
+
self._retry_refresh_login_once = True
|
|
73
75
|
|
|
74
76
|
def connect(self, *args, **kwargs) -> MQTTErrorCode:
|
|
75
77
|
"""
|
|
@@ -98,7 +100,12 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
98
100
|
del userdata
|
|
99
101
|
|
|
100
102
|
if self._skoda_connector.session.expired or self._skoda_connector.session.access_token is None:
|
|
101
|
-
|
|
103
|
+
try:
|
|
104
|
+
self._skoda_connector.session.refresh()
|
|
105
|
+
except ConnectionError as exc:
|
|
106
|
+
LOG.error('Token refresh failed due to connection error: %s', exc)
|
|
107
|
+
except TemporaryAuthenticationError as exc:
|
|
108
|
+
LOG.error('Token refresh failed due to temporary MySkoda error: %s', exc)
|
|
102
109
|
if not self._skoda_connector.session.expired and self._skoda_connector.session.access_token is not None:
|
|
103
110
|
# pylint: disable-next=attribute-defined-outside-init # this is a false positive, password has a setter in super class
|
|
104
111
|
self._password = self._skoda_connector.session.access_token # This is a bit hacky but if password attribute is used here there is an Exception
|
|
@@ -321,6 +328,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
321
328
|
self._skoda_connector.car_connectivity.garage.add_observer(observer=self._on_carconnectivity_vehicle_enabled,
|
|
322
329
|
flag=observer_flags,
|
|
323
330
|
priority=Observable.ObserverPriority.USER_MID)
|
|
331
|
+
self._retry_refresh_login_once = True
|
|
324
332
|
self._subscribe_vehicles()
|
|
325
333
|
|
|
326
334
|
# Handle different reason codes
|
|
@@ -338,6 +346,15 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
338
346
|
LOG.error('Could not connect (%s): Client identifier not valid', reason_code)
|
|
339
347
|
elif reason_code == 134:
|
|
340
348
|
LOG.error('Could not connect (%s): Bad user name or password', reason_code)
|
|
349
|
+
if self._retry_refresh_login_once == True:
|
|
350
|
+
self._retry_refresh_login_once = False
|
|
351
|
+
LOG.info('trying a relogin once to resolve the error')
|
|
352
|
+
try:
|
|
353
|
+
self._skoda_connector.session.login()
|
|
354
|
+
except TemporaryAuthenticationError as exc:
|
|
355
|
+
LOG.error('Login failed due to temporary MySkoda error: %s', exc)
|
|
356
|
+
except ConnectionError as exc:
|
|
357
|
+
LOG.error('Login failed due to connection error: %s', exc)
|
|
341
358
|
elif reason_code == 135:
|
|
342
359
|
LOG.error('Could not connect (%s): Not authorized', reason_code)
|
|
343
360
|
elif reason_code == 136:
|
|
@@ -480,7 +497,7 @@ class SkodaMQTTClient(Client): # pylint: disable=too-many-instance-attributes
|
|
|
480
497
|
old_charging_state: Optional[Charging.ChargingState] = charging_state
|
|
481
498
|
if 'mode' in data['data'] and data['data']['mode'] is not None \
|
|
482
499
|
and vehicle.charging is not None and isinstance(vehicle.charging.settings, SkodaCharging.Settings):
|
|
483
|
-
if data['data']['mode'] in SkodaCharging.SkodaChargeMode:
|
|
500
|
+
if data['data']['mode'] in [item.value for item in SkodaCharging.SkodaChargeMode]:
|
|
484
501
|
skoda_charging_mode = SkodaCharging.SkodaChargeMode(data['data']['mode'])
|
|
485
502
|
else:
|
|
486
503
|
LOG_API.info('Unkown charging mode %s not in %s', data['data']['mode'], str(SkodaCharging.SkodaChargeMode))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{carconnectivity_connector_skoda-0.6 → carconnectivity_connector_skoda-0.8.1}/.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
|
|
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
|