rivian-python-client 2.1.0__tar.gz → 2.2.0__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.
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/PKG-INFO +29 -2
- rivian_python_client-2.2.0/README.md +52 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/pyproject.toml +15 -1
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/__version__.py +1 -1
- rivian_python_client-2.2.0/src/rivian/parallax/__init__.py +70 -0
- rivian_python_client-2.2.0/src/rivian/parallax/body.py +260 -0
- rivian_python_client-2.2.0/src/rivian/parallax/charging.py +407 -0
- rivian_python_client-2.2.0/src/rivian/parallax/comfort.py +317 -0
- rivian_python_client-2.2.0/src/rivian/parallax/core.py +163 -0
- rivian_python_client-2.2.0/src/rivian/parallax/departure.py +105 -0
- rivian_python_client-2.2.0/src/rivian/parallax/device_table.py +136 -0
- rivian_python_client-2.2.0/src/rivian/parallax/dynamics.py +288 -0
- rivian_python_client-2.2.0/src/rivian/parallax/energy.py +148 -0
- rivian_python_client-2.2.0/src/rivian/parallax/energy_edge_compute.py +193 -0
- rivian_python_client-2.2.0/src/rivian/parallax/gearguard_streaming.py +66 -0
- rivian_python_client-2.2.0/src/rivian/parallax/geofence.py +35 -0
- rivian_python_client-2.2.0/src/rivian/parallax/holiday_celebration.py +193 -0
- rivian_python_client-2.2.0/src/rivian/parallax/navigation.py +377 -0
- rivian_python_client-2.2.0/src/rivian/parallax/ota.py +242 -0
- rivian_python_client-2.2.0/src/rivian/parallax/parallax.py +33 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/__init__.py +8 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/body.proto +268 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/body_pb2.py +84 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/body_pb2.pyi +436 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/charging.proto +239 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/charging_pb2.py +86 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/charging_pb2.pyi +361 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/comfort.proto +190 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/comfort_pb2.py +84 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/comfort_pb2.pyi +287 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/departure.proto +78 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/departure_pb2.py +54 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/departure_pb2.pyi +115 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/device_table.proto +111 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/device_table_pb2.py +60 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/device_table_pb2.pyi +162 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/dynamics.proto +158 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/dynamics_pb2.py +74 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/dynamics_pb2.pyi +238 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/energy.proto +97 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/energy_edge_compute.proto +93 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/energy_edge_compute_pb2.py +53 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/energy_edge_compute_pb2.pyi +138 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/energy_pb2.py +58 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/energy_pb2.pyi +148 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/gearguard_streaming.proto +29 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/gearguard_streaming_pb2.py +42 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/gearguard_streaming_pb2.pyi +40 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/geofence.proto +21 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/geofence_pb2.py +40 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/geofence_pb2.pyi +30 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/holiday_celebration.proto +86 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/holiday_celebration_pb2.py +56 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/holiday_celebration_pb2.pyi +128 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/navigation.proto +201 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/navigation_pb2.py +76 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/navigation_pb2.pyi +312 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/ota.proto +173 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/ota_pb2.py +78 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/ota_pb2.pyi +253 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/parallax.proto +17 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/parallax_pb2.py +38 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/parallax_pb2.pyi +23 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/secure_file_transfer.proto +9 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/secure_file_transfer_pb2.py +36 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/secure_file_transfer_pb2.pyi +9 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/security.proto +131 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/security_pb2.py +68 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/security_pb2.pyi +198 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/user_passcodes.proto +17 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/user_passcodes_pb2.py +38 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/user_passcodes_pb2.pyi +21 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/vehicle.proto +165 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/vehicle_access.proto +30 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/vehicle_access_pb2.py +42 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/vehicle_access_pb2.pyi +38 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/vehicle_pb2.py +72 -0
- rivian_python_client-2.2.0/src/rivian/parallax/proto/vehicle_pb2.pyi +259 -0
- rivian_python_client-2.2.0/src/rivian/parallax/secure_file_transfer.py +19 -0
- rivian_python_client-2.2.0/src/rivian/parallax/security.py +224 -0
- rivian_python_client-2.2.0/src/rivian/parallax/user_passcodes.py +32 -0
- rivian_python_client-2.2.0/src/rivian/parallax/vehicle.py +240 -0
- rivian_python_client-2.2.0/src/rivian/parallax/vehicle_access.py +68 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/rivian.py +20 -9
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/utils.py +17 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/ws_monitor.py +31 -0
- rivian_python_client-2.1.0/README.md +0 -26
- rivian_python_client-2.1.0/src/rivian/parallax.py +0 -628
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/.gitignore +0 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/__init__.py +0 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/ble.py +0 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/const.py +0 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/exceptions.py +0 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/py.typed +0 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/schemas/charging.graphql +0 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/schemas/gateway.graphql +0 -0
- {rivian_python_client-2.1.0 → rivian_python_client-2.2.0}/src/rivian/schemas/orders.graphql +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: rivian-python-client
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Rivian API Client (Unofficial)
|
|
5
5
|
Author-email: Brian Retterer <bretterer@gmail.com>, "J.R. Gutierrez" <jrgutier@gmail.com>, Nathan Spencer <natekspencer@gmail.com>, Trevor Mack <drummer8001@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.10
|
|
|
8
8
|
Requires-Dist: aiohttp>=3.0.0
|
|
9
9
|
Requires-Dist: backports-strenum>=1.2.4; python_version < '3.11'
|
|
10
10
|
Requires-Dist: cryptography>=41.0.1
|
|
11
|
+
Requires-Dist: protobuf<8,>=6.31.1
|
|
11
12
|
Requires-Dist: typing-extensions>=4.14.1; python_version < '3.11'
|
|
12
13
|
Provides-Extra: ble
|
|
13
14
|
Requires-Dist: bleak>=0.21; extra == 'ble'
|
|
@@ -40,3 +41,29 @@ pre-commit install
|
|
|
40
41
|
```
|
|
41
42
|
uv run pytest
|
|
42
43
|
```
|
|
44
|
+
|
|
45
|
+
## Parallax
|
|
46
|
+
|
|
47
|
+
`subscribe_for_parallax_messages` streams the vehicle's Parallax RVM topics,
|
|
48
|
+
whose payloads are protobuf. `rivian.parallax` decodes them into dicts keyed
|
|
49
|
+
like the GraphQL vehicle state where a matching property exists:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from rivian import parallax
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def on_message(data):
|
|
56
|
+
if decoded := parallax.decode_parallax_subscription_message(data):
|
|
57
|
+
print(decoded)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`parallax.PARALLAX_RVMS` lists every topic with a decoder, and
|
|
61
|
+
`parallax.CHARGING_RVMS` the ones relevant to a charging session.
|
|
62
|
+
|
|
63
|
+
The schemas in `src/rivian/parallax/proto/*.proto` are reverse-engineered. After
|
|
64
|
+
editing one, regenerate its `*_pb2` modules (don't edit those by hand):
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
uv run python -m grpc_tools.protoc --proto_path=src --python_out=src \
|
|
68
|
+
--pyi_out=src src/rivian/parallax/proto/*.proto
|
|
69
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Python: Rivian API Client
|
|
2
|
+
|
|
3
|
+
Currently a Work In Progress
|
|
4
|
+
|
|
5
|
+
## Dependencies
|
|
6
|
+
|
|
7
|
+
[uv](https://docs.astral.sh/uv/)
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Install project dependencies into the uv virtual environment and run pre-commit
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
uv sync --all-extras
|
|
19
|
+
pre-commit install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Run Tests
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
uv run pytest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Parallax
|
|
29
|
+
|
|
30
|
+
`subscribe_for_parallax_messages` streams the vehicle's Parallax RVM topics,
|
|
31
|
+
whose payloads are protobuf. `rivian.parallax` decodes them into dicts keyed
|
|
32
|
+
like the GraphQL vehicle state where a matching property exists:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from rivian import parallax
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def on_message(data):
|
|
39
|
+
if decoded := parallax.decode_parallax_subscription_message(data):
|
|
40
|
+
print(decoded)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`parallax.PARALLAX_RVMS` lists every topic with a decoder, and
|
|
44
|
+
`parallax.CHARGING_RVMS` the ones relevant to a charging session.
|
|
45
|
+
|
|
46
|
+
The schemas in `src/rivian/parallax/proto/*.proto` are reverse-engineered. After
|
|
47
|
+
editing one, regenerate its `*_pb2` modules (don't edit those by hand):
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
uv run python -m grpc_tools.protoc --proto_path=src --python_out=src \
|
|
51
|
+
--pyi_out=src src/rivian/parallax/proto/*.proto
|
|
52
|
+
```
|
|
@@ -15,6 +15,7 @@ dependencies = [
|
|
|
15
15
|
"cryptography (>=41.0.1)",
|
|
16
16
|
"backports-strenum (>=1.2.4) ; python_version < '3.11'",
|
|
17
17
|
"typing_extensions (>=4.14.1) ; python_version < '3.11'",
|
|
18
|
+
"protobuf>=6.31.1,<8",
|
|
18
19
|
]
|
|
19
20
|
dynamic = ["version"]
|
|
20
21
|
|
|
@@ -33,8 +34,18 @@ dev = [
|
|
|
33
34
|
"mypy>=1.7,<3.0",
|
|
34
35
|
"ruff>=0.16,<0.17",
|
|
35
36
|
"pre-commit>=4.6.1,<5",
|
|
37
|
+
"types-protobuf>=6.32.1.20260221",
|
|
38
|
+
"grpcio-tools==1.80.0",
|
|
39
|
+
"pytest-cov>=7.0,<8",
|
|
36
40
|
]
|
|
37
41
|
|
|
42
|
+
[tool.coverage.run]
|
|
43
|
+
source = ["rivian"]
|
|
44
|
+
omit = ["*_pb2.py"]
|
|
45
|
+
|
|
46
|
+
[tool.coverage.report]
|
|
47
|
+
exclude_also = ["if TYPE_CHECKING:"]
|
|
48
|
+
|
|
38
49
|
[tool.hatch.build.hooks.version]
|
|
39
50
|
path = "src/rivian/__version__.py"
|
|
40
51
|
|
|
@@ -57,10 +68,13 @@ cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true
|
|
|
57
68
|
[tool.uv-dynamic-versioning]
|
|
58
69
|
pattern = "default-unprefixed"
|
|
59
70
|
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
extend-exclude = ["*_pb2.py", "*_pb2.pyi"]
|
|
73
|
+
|
|
60
74
|
[tool.ruff.lint.isort]
|
|
61
75
|
combine-as-imports = true
|
|
62
76
|
split-on-trailing-comma = false
|
|
63
77
|
|
|
64
78
|
[build-system]
|
|
65
79
|
requires = ["hatchling", "uv-dynamic-versioning"]
|
|
66
|
-
build-backend = "hatchling.build"
|
|
80
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Decoders for Rivian Parallax protobuf payloads.
|
|
2
|
+
|
|
3
|
+
Public API:
|
|
4
|
+
decode_parallax_message(rvm, payload, timestamp=None, **kwargs) -> dict | None
|
|
5
|
+
Adds `timestamp` (epoch milliseconds) as a UTC datetime if given.
|
|
6
|
+
decode_parallax_subscription_message(data) -> dict | None
|
|
7
|
+
Same, for a raw `subscribe_for_parallax_messages` message.
|
|
8
|
+
PARALLAX_RVMS: list[str] — every RVM with a registered decoder
|
|
9
|
+
CHARGING_RVMS: list[str] — the subset relevant to a charging session
|
|
10
|
+
|
|
11
|
+
Layout:
|
|
12
|
+
core.py - registry, payload parsing and shared helpers
|
|
13
|
+
proto/ - .proto schemas and their generated *_pb2 modules
|
|
14
|
+
<topic prefix>.py - one module per RVM topic prefix (body.*, etc.)
|
|
15
|
+
|
|
16
|
+
To add a decoder, decorate it with
|
|
17
|
+
`@RVMDecoder.register("the.rvm.topic", some_pb2.Message)` in its prefix's
|
|
18
|
+
module; a new module also needs adding to the import list below.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from . import ( # noqa: F401 (imported to register their decoders)
|
|
24
|
+
body,
|
|
25
|
+
charging,
|
|
26
|
+
comfort,
|
|
27
|
+
departure,
|
|
28
|
+
device_table,
|
|
29
|
+
dynamics,
|
|
30
|
+
energy,
|
|
31
|
+
energy_edge_compute,
|
|
32
|
+
gearguard_streaming,
|
|
33
|
+
geofence,
|
|
34
|
+
holiday_celebration,
|
|
35
|
+
navigation,
|
|
36
|
+
ota,
|
|
37
|
+
parallax,
|
|
38
|
+
secure_file_transfer,
|
|
39
|
+
security,
|
|
40
|
+
user_passcodes,
|
|
41
|
+
vehicle,
|
|
42
|
+
vehicle_access,
|
|
43
|
+
)
|
|
44
|
+
from .core import (
|
|
45
|
+
RVMDecoder,
|
|
46
|
+
decode_parallax_message,
|
|
47
|
+
decode_parallax_subscription_message,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
PARALLAX_RVMS: list[str] = list(RVMDecoder.decoders)
|
|
51
|
+
|
|
52
|
+
CHARGING_RVMS: list[str] = [
|
|
53
|
+
"charging.energy.state",
|
|
54
|
+
"charging.session.notification",
|
|
55
|
+
"charging.session.power",
|
|
56
|
+
"charging.session.remote_command",
|
|
57
|
+
"charging.session.soc_slider",
|
|
58
|
+
"charging.session.status",
|
|
59
|
+
"charging.session.time_estimation",
|
|
60
|
+
"energy.high_voltage.battery_state",
|
|
61
|
+
"energy_edge_compute.graphs.charge_session_breakdown",
|
|
62
|
+
"energy_edge_compute.graphs.charging_graph_global",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"CHARGING_RVMS",
|
|
67
|
+
"PARALLAX_RVMS",
|
|
68
|
+
"decode_parallax_message",
|
|
69
|
+
"decode_parallax_subscription_message",
|
|
70
|
+
]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Decoders for `body.*` RVM topics: closures, locks, trailer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Final
|
|
6
|
+
|
|
7
|
+
from .core import _LOGGER, RVMDecoder, _enum, _present
|
|
8
|
+
from .proto import body_pb2
|
|
9
|
+
|
|
10
|
+
_CLOSURE_MAP: Final[dict[int, str]] = {
|
|
11
|
+
body_pb2.DOOR_FRONT_LEFT: "doorFrontLeftClosed",
|
|
12
|
+
body_pb2.DOOR_FRONT_RIGHT: "doorFrontRightClosed",
|
|
13
|
+
body_pb2.DOOR_REAR_LEFT: "doorRearLeftClosed",
|
|
14
|
+
body_pb2.DOOR_REAR_RIGHT: "doorRearRightClosed",
|
|
15
|
+
body_pb2.FRUNK: "closureFrunkClosed",
|
|
16
|
+
body_pb2.TAILGATE: "closureTailgateClosed",
|
|
17
|
+
body_pb2.LIFTGATE: "closureLiftgateClosed",
|
|
18
|
+
body_pb2.SIDE_BIN_LEFT: "closureSideBinLeftClosed",
|
|
19
|
+
body_pb2.SIDE_BIN_RIGHT: "closureSideBinRightClosed",
|
|
20
|
+
body_pb2.CHARGE_PORT: "chargePortState",
|
|
21
|
+
body_pb2.TONNEAU: "closureTonneauClosed",
|
|
22
|
+
body_pb2.WINDOW_FRONT_LEFT: "windowFrontLeftClosed",
|
|
23
|
+
body_pb2.WINDOW_FRONT_RIGHT: "windowFrontRightClosed",
|
|
24
|
+
body_pb2.WINDOW_REAR_LEFT: "windowRearLeftClosed",
|
|
25
|
+
body_pb2.WINDOW_REAR_RIGHT: "windowRearRightClosed",
|
|
26
|
+
body_pb2.WINDOW_REAR: "windowRearClosed",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_CLOSURE_STATE_MAP: Final[dict[int, str]] = {
|
|
30
|
+
body_pb2.CLOSURE_STATE_OPEN: "open",
|
|
31
|
+
body_pb2.CLOSURE_STATE_CLOSED: "closed",
|
|
32
|
+
body_pb2.CLOSURE_STATE_AJAR: "ajar",
|
|
33
|
+
body_pb2.CLOSURE_STATE_OPENING: "opening",
|
|
34
|
+
body_pb2.CLOSURE_STATE_CLOSING: "closing",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _enum_names(enum: Any, prefix: str) -> dict[int, str]:
|
|
39
|
+
"""Map an enum's numbers to its lowercased names, minus `prefix`."""
|
|
40
|
+
return {
|
|
41
|
+
value.number: value.name.removeprefix(prefix).lower()
|
|
42
|
+
for value in enum.DESCRIPTOR.values
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_SIDE_BIN_NEXT_ACTIONS: Final = _enum_names(body_pb2.SideBinNextAction, "SIDE_BIN_")
|
|
47
|
+
|
|
48
|
+
# Closure id -> (next-action field, result key, value names).
|
|
49
|
+
_NEXT_ACTIONS: Final[dict[int, tuple[str, str, dict[int, str]]]] = {
|
|
50
|
+
body_pb2.FRUNK: (
|
|
51
|
+
"frunk_next_action",
|
|
52
|
+
"closureFrunkNextAction",
|
|
53
|
+
_enum_names(body_pb2.FrunkNextAction, "FRUNK_"),
|
|
54
|
+
),
|
|
55
|
+
body_pb2.LIFTGATE: (
|
|
56
|
+
"liftgate_next_action",
|
|
57
|
+
"closureLiftgateNextAction",
|
|
58
|
+
_enum_names(body_pb2.LiftgateNextAction, "LIFTGATE_"),
|
|
59
|
+
),
|
|
60
|
+
body_pb2.SIDE_BIN_LEFT: (
|
|
61
|
+
"side_bin_next_action",
|
|
62
|
+
"closureSideBinLeftNextAction",
|
|
63
|
+
_SIDE_BIN_NEXT_ACTIONS,
|
|
64
|
+
),
|
|
65
|
+
body_pb2.SIDE_BIN_RIGHT: (
|
|
66
|
+
"side_bin_next_action",
|
|
67
|
+
"closureSideBinRightNextAction",
|
|
68
|
+
_SIDE_BIN_NEXT_ACTIONS,
|
|
69
|
+
),
|
|
70
|
+
body_pb2.TAILGATE: (
|
|
71
|
+
"tailgate_next_action",
|
|
72
|
+
"closureTailgateNextAction",
|
|
73
|
+
_enum_names(body_pb2.TailgateNextAction, "TAILGATE_"),
|
|
74
|
+
),
|
|
75
|
+
body_pb2.CHARGE_PORT: (
|
|
76
|
+
"charge_port_door_next_action",
|
|
77
|
+
"closureChargePortDoorNextAction",
|
|
78
|
+
_enum_names(body_pb2.ChargePortDoorNextAction, "CHARGE_PORT_DOOR_"),
|
|
79
|
+
),
|
|
80
|
+
body_pb2.GROUP_WINDOWS: (
|
|
81
|
+
"windows_next_action",
|
|
82
|
+
"windowsNextAction",
|
|
83
|
+
_enum_names(body_pb2.WindowsNextAction, "WINDOWS_"),
|
|
84
|
+
),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_LOCK_MAP: Final[dict[int, str]] = {
|
|
88
|
+
body_pb2.LOCK_DOOR_FRONT_LEFT: "doorFrontLeftLocked",
|
|
89
|
+
body_pb2.LOCK_DOOR_FRONT_RIGHT: "doorFrontRightLocked",
|
|
90
|
+
body_pb2.LOCK_DOOR_REAR_LEFT: "doorRearLeftLocked",
|
|
91
|
+
body_pb2.LOCK_DOOR_REAR_RIGHT: "doorRearRightLocked",
|
|
92
|
+
body_pb2.LOCK_FRUNK: "closureFrunkLocked",
|
|
93
|
+
body_pb2.LOCK_TAILGATE: "closureTailgateLocked",
|
|
94
|
+
body_pb2.LOCK_LIFTGATE: "closureLiftgateLocked",
|
|
95
|
+
body_pb2.LOCK_SIDE_BIN_LEFT: "closureSideBinLeftLocked",
|
|
96
|
+
body_pb2.LOCK_SIDE_BIN_RIGHT: "closureSideBinRightLocked",
|
|
97
|
+
body_pb2.LOCK_CHARGE_PORT: "closureChargePortLocked",
|
|
98
|
+
body_pb2.LOCK_TRUNK_SECURITY: "closureTrunkSecurityLocked",
|
|
99
|
+
body_pb2.LOCK_CENTER_CONSOLE: "closureCenterConsoleLocked",
|
|
100
|
+
body_pb2.LOCK_GLOVE_BOX: "closureGloveBoxLocked",
|
|
101
|
+
body_pb2.LOCK_GEAR_GUARD: "gearGuardLocked",
|
|
102
|
+
body_pb2.LOCK_TONNEAU: "closureTonneauLocked",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_LOCK_STATE_MAP: Final[dict[int, str]] = {
|
|
106
|
+
body_pb2.LOCK_STATE_LOCKED: "locked",
|
|
107
|
+
body_pb2.LOCK_STATE_UNLOCKED: "unlocked",
|
|
108
|
+
body_pb2.LOCK_STATE_PARTIALLY_UNLOCKED: "partially_unlocked",
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_REAR_HITCH_STATUS_MAP: Final[dict[int, str]] = {
|
|
112
|
+
body_pb2.REAR_HITCH_STATUS_NOT_PRESENT: "not_present",
|
|
113
|
+
body_pb2.REAR_HITCH_STATUS_ACCESSORY: "accessory",
|
|
114
|
+
body_pb2.REAR_HITCH_STATUS_TRAILER1: "trailer1",
|
|
115
|
+
body_pb2.REAR_HITCH_STATUS_TRAILER2: "trailer2",
|
|
116
|
+
body_pb2.REAR_HITCH_STATUS_TRAILER3: "trailer3",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_WINDOW_MAP: Final[dict[int, str]] = {
|
|
120
|
+
body_pb2.WINDOW_INSTANCE_FRONT_LEFT: "windowFrontLeftCalibrated",
|
|
121
|
+
body_pb2.WINDOW_INSTANCE_FRONT_RIGHT: "windowFrontRightCalibrated",
|
|
122
|
+
body_pb2.WINDOW_INSTANCE_REAR_LEFT: "windowRearLeftCalibrated",
|
|
123
|
+
body_pb2.WINDOW_INSTANCE_REAR_RIGHT: "windowRearRightCalibrated",
|
|
124
|
+
body_pb2.WINDOW_INSTANCE_REAR: "windowRearCalibrated",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# GraphQL's windowXCalibrated values.
|
|
128
|
+
_CALIBRATION_MAP: Final[dict[int, str]] = {
|
|
129
|
+
body_pb2.CALIBRATION_STATUS_CALIBRATED: "calibrated",
|
|
130
|
+
body_pb2.CALIBRATION_STATUS_NOT_CALIBRATED: "not_calibrated",
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_TRAILER_PRESENCE_MAP: Final[dict[int, str]] = {
|
|
134
|
+
body_pb2.TRAILER_NOT_PRESENT: "trailer_not_present",
|
|
135
|
+
body_pb2.TRAILER_PRESENT: "trailer_present",
|
|
136
|
+
body_pb2.TRAILER_PRESENT_WITH_BRAKES: "trailer_present_with_brakes",
|
|
137
|
+
body_pb2.TRAILER_INVALID: "trailer_invalid",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@RVMDecoder.register("body.closures.states", body_pb2.ClosuresState)
|
|
142
|
+
def decode_closures(m: body_pb2.ClosuresState) -> dict[str, Any]:
|
|
143
|
+
"""body.closures.states — state of every door, window and closure.
|
|
144
|
+
|
|
145
|
+
Fields:
|
|
146
|
+
doorFrontLeftClosed, closureFrunkClosed, etc.: str
|
|
147
|
+
("open" | "closed" | "ajar" | "opening" | "closing")
|
|
148
|
+
closureFrunkNextAction, closureLiftgateNextAction,
|
|
149
|
+
closureTailgateNextAction, closureSideBinLeftNextAction,
|
|
150
|
+
closureSideBinRightNextAction, closureChargePortDoorNextAction,
|
|
151
|
+
windowsNextAction: str — e.g. "open_allowed" (shut),
|
|
152
|
+
"close_allowed" (open); for the charge port,
|
|
153
|
+
"close_not_available" means plugged in
|
|
154
|
+
|
|
155
|
+
`chargePortState` can lag the door (e.g. "opening" until plugged in),
|
|
156
|
+
so prefer its next action. After an app command, next actions briefly
|
|
157
|
+
drop to "sna" / "open_not_available".
|
|
158
|
+
"""
|
|
159
|
+
result: dict[str, Any] = {}
|
|
160
|
+
for s in m.closure:
|
|
161
|
+
cid = s.id
|
|
162
|
+
state_val = _present(s, "state")
|
|
163
|
+
if cid in _CLOSURE_MAP:
|
|
164
|
+
result[_CLOSURE_MAP[cid]] = _enum(
|
|
165
|
+
_CLOSURE_STATE_MAP, state_val, what="closure state"
|
|
166
|
+
)
|
|
167
|
+
elif cid != body_pb2.GROUP_WINDOWS:
|
|
168
|
+
_LOGGER.debug("Unknown closure id %s (state %s)", cid, state_val)
|
|
169
|
+
if cid in _NEXT_ACTIONS:
|
|
170
|
+
field, key, names = _NEXT_ACTIONS[cid]
|
|
171
|
+
if (next_action := _present(s, field)) is not None:
|
|
172
|
+
result[key] = _enum(names, next_action, what=f"{key} value")
|
|
173
|
+
return result
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@RVMDecoder.register("body.locks.states", body_pb2.LocksState)
|
|
177
|
+
def decode_locks(m: body_pb2.LocksState) -> dict[str, Any]:
|
|
178
|
+
"""body.locks.states — lock state of every lock.
|
|
179
|
+
|
|
180
|
+
Fields:
|
|
181
|
+
doorFrontLeftLocked, closureFrunkLocked, gearGuardLocked (the Gear
|
|
182
|
+
Guard cable), etc.: str ("locked" | "unlocked" |
|
|
183
|
+
"partially_unlocked")
|
|
184
|
+
"""
|
|
185
|
+
result: dict[str, Any] = {}
|
|
186
|
+
for s in m.lock:
|
|
187
|
+
lid = s.id
|
|
188
|
+
state_val = _present(s, "state")
|
|
189
|
+
if state_val is None:
|
|
190
|
+
continue
|
|
191
|
+
if lid in _LOCK_MAP:
|
|
192
|
+
result[_LOCK_MAP[lid]] = _enum(
|
|
193
|
+
_LOCK_STATE_MAP, state_val, what="lock state"
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
_LOGGER.debug("Unknown lock id %s (state %s)", lid, state_val)
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@RVMDecoder.register("body.windows.states", body_pb2.WindowsState)
|
|
201
|
+
def decode_windows(m: body_pb2.WindowsState) -> dict[str, Any]:
|
|
202
|
+
"""body.windows.states — window calibration.
|
|
203
|
+
|
|
204
|
+
Fields:
|
|
205
|
+
windowFrontLeftCalibrated, windowFrontRightCalibrated,
|
|
206
|
+
windowRearLeftCalibrated, windowRearRightCalibrated,
|
|
207
|
+
windowRearCalibrated: str ("calibrated" | "not_calibrated")
|
|
208
|
+
"""
|
|
209
|
+
return {
|
|
210
|
+
_WINDOW_MAP[w.instance]: _enum(
|
|
211
|
+
_CALIBRATION_MAP, w.calibration_status or None, what="window calibration"
|
|
212
|
+
)
|
|
213
|
+
for w in m.window
|
|
214
|
+
if w.instance in _WINDOW_MAP
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@RVMDecoder.register("body.trailer.state", body_pb2.TrailerState)
|
|
219
|
+
def decode_trailer_state(m: body_pb2.TrailerState) -> dict[str, Any]:
|
|
220
|
+
"""body.trailer.state — trailer presence and rear hitch status.
|
|
221
|
+
|
|
222
|
+
Fields:
|
|
223
|
+
trailerStatus: str
|
|
224
|
+
rearHitchStatus: str | None ("not_present" | "accessory" |
|
|
225
|
+
"trailer1" | "trailer2" | "trailer3")
|
|
226
|
+
"""
|
|
227
|
+
result: dict[str, Any] = {
|
|
228
|
+
"rearHitchStatus": _enum(
|
|
229
|
+
_REAR_HITCH_STATUS_MAP, m.rear_hitch_status or None, what="rear hitch"
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
presence = _enum(
|
|
233
|
+
_TRAILER_PRESENCE_MAP, _present(m, "presence"), what="trailer presence"
|
|
234
|
+
)
|
|
235
|
+
if presence is not None:
|
|
236
|
+
result["trailerStatus"] = presence
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@RVMDecoder.register("body.wipers.fluid_level", body_pb2.WiperFluidLevel)
|
|
241
|
+
def decode_wiper_fluid_level(m: body_pb2.WiperFluidLevel) -> dict[str, Any]:
|
|
242
|
+
"""body.wipers.fluid_level — washer fluid level.
|
|
243
|
+
|
|
244
|
+
The value numbering is a best guess.
|
|
245
|
+
|
|
246
|
+
Fields:
|
|
247
|
+
wiperFluidState: str | None ("normal" | "low" | "empty")
|
|
248
|
+
"""
|
|
249
|
+
return {
|
|
250
|
+
"wiperFluidState": _enum(
|
|
251
|
+
_WIPER_FLUID_STATE_MAP, m.state or None, what="wiper fluid state"
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
_WIPER_FLUID_STATE_MAP: Final[dict[int, str]] = {
|
|
257
|
+
body_pb2.WIPER_FLUID_STATE_NORMAL: "normal",
|
|
258
|
+
body_pb2.WIPER_FLUID_STATE_LOW: "low",
|
|
259
|
+
body_pb2.WIPER_FLUID_STATE_EMPTY: "empty",
|
|
260
|
+
}
|