rivian-python-client 2.1.0b0__tar.gz → 2.2.0b1__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.0b0 → rivian_python_client-2.2.0b1}/PKG-INFO +3 -2
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/pyproject.toml +7 -1
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/__version__.py +1 -1
- rivian_python_client-2.2.0b1/src/rivian/parallax/__init__.py +70 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/body.py +179 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/charging.py +246 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/comfort.py +270 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/core.py +163 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/departure.py +105 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/device_table.py +59 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/dynamics.py +245 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/energy.py +93 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/energy_edge_compute.py +188 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/gearguard_streaming.py +68 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/geofence.py +30 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/holiday_celebration.py +72 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/navigation.py +335 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/ota.py +172 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/parallax.py +33 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/__init__.py +8 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/body.proto +163 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/body_pb2.py +64 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/body_pb2.pyi +255 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/charging.proto +144 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/charging_pb2.py +72 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/charging_pb2.pyi +209 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/comfort.proto +151 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/comfort_pb2.py +78 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/comfort_pb2.pyi +220 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/departure.proto +78 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/departure_pb2.py +54 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/departure_pb2.pyi +115 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/device_table.proto +44 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/device_table_pb2.py +46 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/device_table_pb2.pyi +61 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/dynamics.proto +149 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/dynamics_pb2.py +72 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/dynamics_pb2.pyi +225 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/energy.proto +58 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/energy_edge_compute.proto +77 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/energy_edge_compute_pb2.py +49 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/energy_edge_compute_pb2.pyi +111 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/energy_pb2.py +48 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/energy_pb2.pyi +83 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/gearguard_streaming.proto +31 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/gearguard_streaming_pb2.py +42 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/gearguard_streaming_pb2.pyi +44 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/geofence.proto +15 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/geofence_pb2.py +38 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/geofence_pb2.pyi +20 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/holiday_celebration.proto +51 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/holiday_celebration_pb2.py +44 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/holiday_celebration_pb2.pyi +73 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/navigation.proto +186 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/navigation_pb2.py +78 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/navigation_pb2.pyi +251 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/ota.proto +111 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/ota_pb2.py +64 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/ota_pb2.pyi +149 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/parallax.proto +17 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/parallax_pb2.py +38 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/parallax_pb2.pyi +23 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/secure_file_transfer.proto +9 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/secure_file_transfer_pb2.py +36 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/secure_file_transfer_pb2.pyi +9 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/security.proto +119 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/security_pb2.py +66 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/security_pb2.pyi +177 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/user_passcodes.proto +17 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/user_passcodes_pb2.py +38 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/user_passcodes_pb2.pyi +21 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/vehicle.proto +116 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/vehicle_access.proto +8 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/vehicle_access_pb2.py +36 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/vehicle_access_pb2.pyi +9 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/vehicle_pb2.py +62 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/proto/vehicle_pb2.pyi +180 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/secure_file_transfer.py +19 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/security.py +210 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/user_passcodes.py +32 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/vehicle.py +187 -0
- rivian_python_client-2.2.0b1/src/rivian/parallax/vehicle_access.py +18 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/rivian.py +20 -9
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/utils.py +17 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/ws_monitor.py +31 -0
- rivian_python_client-2.1.0b0/src/rivian/parallax.py +0 -624
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/.gitignore +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/README.md +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/__init__.py +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/ble.py +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/const.py +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/exceptions.py +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/py.typed +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/schemas/charging.graphql +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/src/rivian/schemas/gateway.graphql +0 -0
- {rivian_python_client-2.1.0b0 → rivian_python_client-2.2.0b1}/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.0b1
|
|
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,>=7.35.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'
|
|
@@ -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>=7.35.1,<8",
|
|
18
19
|
]
|
|
19
20
|
dynamic = ["version"]
|
|
20
21
|
|
|
@@ -33,6 +34,8 @@ 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>=7.35.1.20260906",
|
|
38
|
+
"grpcio-tools==1.84.0",
|
|
36
39
|
]
|
|
37
40
|
|
|
38
41
|
[tool.hatch.build.hooks.version]
|
|
@@ -57,10 +60,13 @@ cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true
|
|
|
57
60
|
[tool.uv-dynamic-versioning]
|
|
58
61
|
pattern = "default-unprefixed"
|
|
59
62
|
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
extend-exclude = ["*_pb2.py", "*_pb2.pyi"]
|
|
65
|
+
|
|
60
66
|
[tool.ruff.lint.isort]
|
|
61
67
|
combine-as-imports = true
|
|
62
68
|
split-on-trailing-comma = false
|
|
63
69
|
|
|
64
70
|
[build-system]
|
|
65
71
|
requires = ["hatchling", "uv-dynamic-versioning"]
|
|
66
|
-
build-backend = "hatchling.build"
|
|
72
|
+
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,179 @@
|
|
|
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
|
+
1: "doorFrontLeftClosed",
|
|
12
|
+
2: "doorFrontRightClosed",
|
|
13
|
+
3: "doorRearLeftClosed",
|
|
14
|
+
4: "doorRearRightClosed",
|
|
15
|
+
5: "closureFrunkClosed",
|
|
16
|
+
6: "closureTailgateClosed",
|
|
17
|
+
7: "closureLiftgateClosed",
|
|
18
|
+
8: "closureSideBinLeftClosed",
|
|
19
|
+
9: "closureSideBinRightClosed",
|
|
20
|
+
10: "chargePortState",
|
|
21
|
+
11: "closureTonneauClosed",
|
|
22
|
+
12: "windowFrontLeftClosed",
|
|
23
|
+
13: "windowFrontRightClosed",
|
|
24
|
+
14: "windowRearLeftClosed",
|
|
25
|
+
15: "windowRearRightClosed",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_CLOSURE_STATE_MAP: Final[dict[int, str]] = {
|
|
29
|
+
1: "open",
|
|
30
|
+
2: "closed",
|
|
31
|
+
4: "opening",
|
|
32
|
+
5: "closing",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _enum_names(enum: Any, prefix: str) -> dict[int, str]:
|
|
37
|
+
"""Map an enum's numbers to its lowercased names, minus `prefix`."""
|
|
38
|
+
return {
|
|
39
|
+
value.number: value.name.removeprefix(prefix).lower()
|
|
40
|
+
for value in enum.DESCRIPTOR.values
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Other side-bin values are unconfirmed, so they pass through as raw ints.
|
|
45
|
+
_SIDE_BIN_NEXT_ACTIONS: Final[dict[int, str]] = {
|
|
46
|
+
body_pb2.SIDE_BIN_SNA: "sna",
|
|
47
|
+
body_pb2.SIDE_BIN_OPEN_ALLOWED: "open_allowed",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Closure id -> (next-action field, result key, value names).
|
|
51
|
+
_NEXT_ACTIONS: Final[dict[int, tuple[str, str, dict[int, str]]]] = {
|
|
52
|
+
body_pb2.FRUNK: (
|
|
53
|
+
"frunk_next_action",
|
|
54
|
+
"closureFrunkNextAction",
|
|
55
|
+
_enum_names(body_pb2.FrunkNextAction, "FRUNK_"),
|
|
56
|
+
),
|
|
57
|
+
body_pb2.SIDE_BIN_LEFT: (
|
|
58
|
+
"side_bin_next_action",
|
|
59
|
+
"closureSideBinLeftNextAction",
|
|
60
|
+
_SIDE_BIN_NEXT_ACTIONS,
|
|
61
|
+
),
|
|
62
|
+
body_pb2.SIDE_BIN_RIGHT: (
|
|
63
|
+
"side_bin_next_action",
|
|
64
|
+
"closureSideBinRightNextAction",
|
|
65
|
+
_SIDE_BIN_NEXT_ACTIONS,
|
|
66
|
+
),
|
|
67
|
+
body_pb2.TAILGATE: (
|
|
68
|
+
"tailgate_next_action",
|
|
69
|
+
"closureTailgateNextAction",
|
|
70
|
+
_enum_names(body_pb2.TailgateNextAction, "TAILGATE_"),
|
|
71
|
+
),
|
|
72
|
+
body_pb2.CHARGE_PORT: (
|
|
73
|
+
"charge_port_door_next_action",
|
|
74
|
+
"closureChargePortDoorNextAction",
|
|
75
|
+
_enum_names(body_pb2.ChargePortDoorNextAction, "CHARGE_PORT_DOOR_"),
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_LOCK_MAP: Final[dict[int, str]] = {
|
|
80
|
+
1: "doorFrontLeftLocked",
|
|
81
|
+
2: "doorFrontRightLocked",
|
|
82
|
+
3: "doorRearLeftLocked",
|
|
83
|
+
4: "doorRearRightLocked",
|
|
84
|
+
5: "closureFrunkLocked",
|
|
85
|
+
6: "closureTailgateLocked",
|
|
86
|
+
7: "closureLiftgateLocked",
|
|
87
|
+
8: "closureSideBinLeftLocked",
|
|
88
|
+
9: "closureSideBinRightLocked",
|
|
89
|
+
15: "closureTonneauLocked",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_TRAILER_PRESENCE_MAP: Final[dict[int, str]] = {
|
|
93
|
+
1: "trailer_not_present",
|
|
94
|
+
2: "trailer_present",
|
|
95
|
+
3: "trailer_present_with_brakes",
|
|
96
|
+
4: "trailer_invalid",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@RVMDecoder.register("body.closures.states", body_pb2.ClosuresState)
|
|
101
|
+
def decode_closures(m: body_pb2.ClosuresState) -> dict[str, Any]:
|
|
102
|
+
"""body.closures.states — state of every door, window and closure.
|
|
103
|
+
|
|
104
|
+
Fields:
|
|
105
|
+
doorFrontLeftClosed, closureFrunkClosed, etc.: str
|
|
106
|
+
("open" | "closed" | "opening" | "closing")
|
|
107
|
+
closureFrunkNextAction, closureTailgateNextAction,
|
|
108
|
+
closureSideBinLeftNextAction, closureSideBinRightNextAction,
|
|
109
|
+
closureChargePortDoorNextAction: str — e.g. "open_allowed" (shut),
|
|
110
|
+
"close_allowed" (open); for the charge port,
|
|
111
|
+
"close_not_available" means plugged in
|
|
112
|
+
|
|
113
|
+
`chargePortState` can lag the door (e.g. "opening" until plugged in),
|
|
114
|
+
so prefer its next action. After an app command, next actions briefly
|
|
115
|
+
drop to "sna" / "open_not_available".
|
|
116
|
+
"""
|
|
117
|
+
result: dict[str, Any] = {}
|
|
118
|
+
for s in m.closure:
|
|
119
|
+
cid = s.id
|
|
120
|
+
state_val = _present(s, "state")
|
|
121
|
+
if cid in _CLOSURE_MAP:
|
|
122
|
+
result[_CLOSURE_MAP[cid]] = _enum(
|
|
123
|
+
_CLOSURE_STATE_MAP, state_val, what="closure state"
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
_LOGGER.debug("Unknown closure id %s (state %s)", cid, state_val)
|
|
127
|
+
if cid in _NEXT_ACTIONS:
|
|
128
|
+
field, key, names = _NEXT_ACTIONS[cid]
|
|
129
|
+
if (next_action := _present(s, field)) is not None:
|
|
130
|
+
result[key] = _enum(names, next_action, what=f"{key} value")
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@RVMDecoder.register("body.locks.states", body_pb2.LocksState)
|
|
135
|
+
def decode_locks(m: body_pb2.LocksState) -> dict[str, Any]:
|
|
136
|
+
"""body.locks.states — lock state of every lockable closure.
|
|
137
|
+
|
|
138
|
+
Fields:
|
|
139
|
+
doorFrontLeftLocked, closureFrunkLocked, etc.: str ("locked" | "unlocked")
|
|
140
|
+
"""
|
|
141
|
+
result: dict[str, Any] = {}
|
|
142
|
+
for s in m.lock:
|
|
143
|
+
lid = s.id
|
|
144
|
+
state_val = _present(s, "state")
|
|
145
|
+
if state_val is None:
|
|
146
|
+
continue
|
|
147
|
+
if lid in _LOCK_MAP:
|
|
148
|
+
result[_LOCK_MAP[lid]] = "locked" if state_val == 1 else "unlocked"
|
|
149
|
+
else:
|
|
150
|
+
_LOGGER.debug("Unknown lock id %s (state %s)", lid, state_val)
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@RVMDecoder.register("body.trailer.state", body_pb2.TrailerState)
|
|
155
|
+
def decode_trailer_state(m: body_pb2.TrailerState) -> dict[str, Any]:
|
|
156
|
+
"""body.trailer.state — trailer presence, with or without brakes.
|
|
157
|
+
|
|
158
|
+
Fields:
|
|
159
|
+
trailerStatus: str
|
|
160
|
+
"""
|
|
161
|
+
result: dict[str, Any] = {}
|
|
162
|
+
presence = _enum(
|
|
163
|
+
_TRAILER_PRESENCE_MAP, _present(m, "presence"), what="trailer presence"
|
|
164
|
+
)
|
|
165
|
+
if presence is not None:
|
|
166
|
+
result["trailerStatus"] = presence
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@RVMDecoder.register("body.wipers.fluid_level", body_pb2.WiperFluidLevel)
|
|
171
|
+
def decode_wiper_fluid_level(m: body_pb2.WiperFluidLevel) -> dict[str, Any]:
|
|
172
|
+
"""body.wipers.fluid_level — washer fluid level.
|
|
173
|
+
|
|
174
|
+
Fields:
|
|
175
|
+
_wiperFluidLevel: int — raw (1 so far, presumably normal)
|
|
176
|
+
"""
|
|
177
|
+
if (v := _present(m, "field_2")) is None:
|
|
178
|
+
return {}
|
|
179
|
+
return {"_wiperFluidLevel": v}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Decoders for `charging.*` RVM topics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Final
|
|
6
|
+
|
|
7
|
+
from .core import RVMDecoder, _enum, _present
|
|
8
|
+
from .proto import charging_pb2
|
|
9
|
+
|
|
10
|
+
_DAY_OF_WEEK_MAP: Final[dict[int, str]] = {
|
|
11
|
+
1: "sunday",
|
|
12
|
+
2: "monday",
|
|
13
|
+
3: "tuesday",
|
|
14
|
+
4: "wednesday",
|
|
15
|
+
5: "thursday",
|
|
16
|
+
6: "friday",
|
|
17
|
+
7: "saturday",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@RVMDecoder.register("charging.schedule.time_window", charging_pb2.ScheduleTimeWindow)
|
|
22
|
+
def decode_schedule_time_window(m: charging_pb2.ScheduleTimeWindow) -> dict[str, Any]:
|
|
23
|
+
"""charging.schedule.time_window — the scheduled charging window.
|
|
24
|
+
|
|
25
|
+
Fields:
|
|
26
|
+
chargingScheduleEnabled: bool — False when no schedule is set
|
|
27
|
+
chargingScheduleStartDay, chargingScheduleEndDay: str
|
|
28
|
+
("sunday" ... "saturday")
|
|
29
|
+
chargingScheduleStartTime, chargingScheduleEndTime: int — minutes
|
|
30
|
+
after local midnight on that day (1440 = end of day)
|
|
31
|
+
chargingScheduleDuration: int — minutes (10080 for all week)
|
|
32
|
+
chargingScheduleAmps: int — current limit (A)
|
|
33
|
+
chargingScheduleLocation: {"latitude": float, "longitude": float}
|
|
34
|
+
|
|
35
|
+
The days are the window's current/next occurrence; its daily/weekdays
|
|
36
|
+
recurrence isn't sent.
|
|
37
|
+
"""
|
|
38
|
+
result: dict[str, Any] = {"chargingScheduleEnabled": m.enabled}
|
|
39
|
+
if not m.HasField("window"):
|
|
40
|
+
return result
|
|
41
|
+
window = m.window
|
|
42
|
+
result["chargingScheduleStartDay"] = _enum(
|
|
43
|
+
_DAY_OF_WEEK_MAP, window.start_day or None, what="schedule start day"
|
|
44
|
+
)
|
|
45
|
+
result["chargingScheduleEndDay"] = _enum(
|
|
46
|
+
_DAY_OF_WEEK_MAP, window.end_day or None, what="schedule end day"
|
|
47
|
+
)
|
|
48
|
+
result["chargingScheduleStartTime"] = window.start_time
|
|
49
|
+
result["chargingScheduleEndTime"] = window.end_time
|
|
50
|
+
result["chargingScheduleDuration"] = window.duration
|
|
51
|
+
if window.HasField("amps"):
|
|
52
|
+
result["chargingScheduleAmps"] = window.amps
|
|
53
|
+
if window.HasField("location"):
|
|
54
|
+
result["chargingScheduleLocation"] = {
|
|
55
|
+
"latitude": round(window.location.latitude, 6),
|
|
56
|
+
"longitude": round(window.location.longitude, 6),
|
|
57
|
+
}
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@RVMDecoder.register("charging.session.notification", charging_pb2.SessionNotification)
|
|
62
|
+
def decode_session_notification(m: charging_pb2.SessionNotification) -> dict[str, Any]:
|
|
63
|
+
"""charging.session.notification — unmapped; field 1 as `_field1`."""
|
|
64
|
+
return {"_field1": m.field1} if m.HasField("field1") else {}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
_REMOTE_COMMAND_MAP: Final[dict[int, str]] = {
|
|
68
|
+
1: "start",
|
|
69
|
+
2: "stop",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@RVMDecoder.register(
|
|
74
|
+
"charging.session.remote_command", charging_pb2.SessionRemoteCommand
|
|
75
|
+
)
|
|
76
|
+
def decode_session_remote_command(
|
|
77
|
+
m: charging_pb2.SessionRemoteCommand,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
"""charging.session.remote_command — the last start/stop command.
|
|
80
|
+
|
|
81
|
+
Fields:
|
|
82
|
+
chargingRemoteCommand: str ("start" | "stop")
|
|
83
|
+
|
|
84
|
+
Also sent when the vehicle holds ("stop") or resumes ("start")
|
|
85
|
+
charging around a schedule or limit change.
|
|
86
|
+
"""
|
|
87
|
+
if not m.HasField("command"):
|
|
88
|
+
return {}
|
|
89
|
+
return {
|
|
90
|
+
"chargingRemoteCommand": _enum(
|
|
91
|
+
_REMOTE_COMMAND_MAP, m.command, what="charging remote command"
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@RVMDecoder.register("charging.session.soc_slider", charging_pb2.SocSlider)
|
|
97
|
+
def decode_soc_slider(m: charging_pb2.SocSlider) -> dict[str, Any]:
|
|
98
|
+
"""charging.session.soc_slider — the charge limit.
|
|
99
|
+
|
|
100
|
+
Fields:
|
|
101
|
+
batteryLimit: int (percent, 0-100)
|
|
102
|
+
"""
|
|
103
|
+
if not m.HasField("soc_limit"):
|
|
104
|
+
return {}
|
|
105
|
+
return {"batteryLimit": m.soc_limit}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@RVMDecoder.register("charging.session.trip_target", charging_pb2.TripTarget)
|
|
109
|
+
def decode_trip_target(m: charging_pb2.TripTarget) -> dict[str, Any]:
|
|
110
|
+
"""charging.session.trip_target — unmapped; field 2 as `_field2`.
|
|
111
|
+
|
|
112
|
+
0xFFFF seems to mean "not set".
|
|
113
|
+
"""
|
|
114
|
+
return {"_field2": m.field2} if m.HasField("field2") else {}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@RVMDecoder.register(
|
|
118
|
+
"charging.smart_charging.settings", charging_pb2.SmartChargingSettings
|
|
119
|
+
)
|
|
120
|
+
def decode_smart_charging_settings(
|
|
121
|
+
_m: charging_pb2.SmartChargingSettings,
|
|
122
|
+
) -> dict[str, Any]:
|
|
123
|
+
"""charging.smart_charging.settings — unmapped."""
|
|
124
|
+
return {}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@RVMDecoder.register(
|
|
128
|
+
"charging.smart_charging.smart_charging_info", charging_pb2.SmartChargingInfo
|
|
129
|
+
)
|
|
130
|
+
def decode_smart_charging_info(_m: charging_pb2.SmartChargingInfo) -> dict[str, Any]:
|
|
131
|
+
"""charging.smart_charging.smart_charging_info — unmapped."""
|
|
132
|
+
return {}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@RVMDecoder.register(
|
|
136
|
+
"charging.smart_charging.weighted_charging_forecast",
|
|
137
|
+
charging_pb2.WeightedChargingForecast,
|
|
138
|
+
)
|
|
139
|
+
def decode_weighted_charging_forecast(
|
|
140
|
+
_m: charging_pb2.WeightedChargingForecast,
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
"""charging.smart_charging.weighted_charging_forecast — unmapped."""
|
|
143
|
+
return {}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
_CONNECTION_STATE_MAP: Final[dict[int, str]] = {
|
|
147
|
+
1: "disconnected",
|
|
148
|
+
2: "connected",
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
_CHARGING_STATE_MAP: Final[dict[int, str]] = {
|
|
152
|
+
1: "charging_ready",
|
|
153
|
+
2: "charging_connecting",
|
|
154
|
+
3: "charging_active",
|
|
155
|
+
4: "charging_complete",
|
|
156
|
+
5: "charging_scheduled",
|
|
157
|
+
6: "charging_vehicle_error",
|
|
158
|
+
7: "charging_station_error",
|
|
159
|
+
8: "charging_stopped_by_user",
|
|
160
|
+
9: "charging_stopped_by_station",
|
|
161
|
+
10: "charging_payment_error",
|
|
162
|
+
11: "charging_cert_error",
|
|
163
|
+
12: "charging_tls_error",
|
|
164
|
+
13: "charging_error_ac_adapter_used_on_dc",
|
|
165
|
+
14: "charging_error_dc_adapter_used_on_ac",
|
|
166
|
+
15: "charging_error_incompatible_charger",
|
|
167
|
+
16: "charging_sd_compensation",
|
|
168
|
+
17: "waiting_on_charger",
|
|
169
|
+
18: "charging_error_not_ready_or_incompatible_charger",
|
|
170
|
+
19: "charging_vehicle_stopped",
|
|
171
|
+
20: "charging_payment_error_start_rivian_app",
|
|
172
|
+
21: "charging_tls_error_unknown_charger",
|
|
173
|
+
22: "charging_tls_error_unexpected_fail",
|
|
174
|
+
23: "charging_tls_error_start_rivian_app",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@RVMDecoder.register("charging.session.status", charging_pb2.SessionStatus)
|
|
179
|
+
def decode_charging_status(m: charging_pb2.SessionStatus) -> dict[str, Any]:
|
|
180
|
+
"""charging.session.status — plug and charging state.
|
|
181
|
+
|
|
182
|
+
Fields:
|
|
183
|
+
connectionState: str ("connected" | "disconnected")
|
|
184
|
+
chargerState: str — GraphQL chargerState values, e.g.
|
|
185
|
+
"charging_ready" (no session, or unplugged),
|
|
186
|
+
"charging_connecting", "charging_active", "charging_complete",
|
|
187
|
+
"charging_scheduled", "charging_stopped_by_user"
|
|
188
|
+
isActive: bool — true from a session's start until it ends
|
|
189
|
+
"""
|
|
190
|
+
result: dict[str, Any] = {}
|
|
191
|
+
if m.HasField("connection_state"):
|
|
192
|
+
result["connectionState"] = _enum(
|
|
193
|
+
_CONNECTION_STATE_MAP, m.connection_state, what="charging connection state"
|
|
194
|
+
)
|
|
195
|
+
if m.HasField("charging_state"):
|
|
196
|
+
result["chargerState"] = _enum(
|
|
197
|
+
_CHARGING_STATE_MAP, m.charging_state, what="charging state"
|
|
198
|
+
)
|
|
199
|
+
result["isActive"] = m.is_active
|
|
200
|
+
return result
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@RVMDecoder.register("charging.session.time_estimation", charging_pb2.TimeEstimation)
|
|
204
|
+
def decode_time_estimation(m: charging_pb2.TimeEstimation) -> dict[str, Any]:
|
|
205
|
+
"""charging.session.time_estimation — time to the charge limit.
|
|
206
|
+
|
|
207
|
+
Fields:
|
|
208
|
+
timeToEndOfCharge: int (minutes; 0 when not charging)
|
|
209
|
+
"""
|
|
210
|
+
return {"timeToEndOfCharge": m.estimated_time_remaining}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
_CHARGER_STATUS_MAP: Final[dict[int, str]] = {
|
|
214
|
+
1: "chrgr_sts_not_connected",
|
|
215
|
+
2: "chrgr_sts_connected_no_chrg",
|
|
216
|
+
3: "chrgr_sts_connected_charging",
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@RVMDecoder.register("charging.energy.state", charging_pb2.EnergyState)
|
|
221
|
+
def decode_energy_state(m: charging_pb2.EnergyState) -> dict[str, Any]:
|
|
222
|
+
"""charging.energy.state — charger connection status.
|
|
223
|
+
|
|
224
|
+
Fields:
|
|
225
|
+
chargerStatus: str — GraphQL chargerStatus values
|
|
226
|
+
("chrgr_sts_not_connected" | "chrgr_sts_connected_no_chrg" |
|
|
227
|
+
"chrgr_sts_connected_charging"); inferred
|
|
228
|
+
_field1, _field3, _field11: int — raw
|
|
229
|
+
"""
|
|
230
|
+
result: dict[str, Any] = {}
|
|
231
|
+
if (v := _present(m, "charger_status")) is not None:
|
|
232
|
+
result["chargerStatus"] = _enum(_CHARGER_STATUS_MAP, v, what="charger status")
|
|
233
|
+
for num in (1, 3, 11):
|
|
234
|
+
if (v := _present(m, f"field_{num}")) is not None:
|
|
235
|
+
result[f"_field{num}"] = v
|
|
236
|
+
return result
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@RVMDecoder.register("charging.session.power", charging_pb2.SessionPower)
|
|
240
|
+
def decode_session_power(m: charging_pb2.SessionPower) -> dict[str, Any]:
|
|
241
|
+
"""charging.session.power — live charging power, about every 5 seconds.
|
|
242
|
+
|
|
243
|
+
Fields:
|
|
244
|
+
power: float (kW; 0 when not charging)
|
|
245
|
+
"""
|
|
246
|
+
return {"power": round(m.power, 2)}
|