rivian-python-client 2.2.0b11__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rivian/__version__.py +1 -1
- rivian/const.py +1 -27
- rivian/parallax/__init__.py +13 -0
- rivian/parallax/body.py +3 -3
- rivian/parallax/charging.py +3 -1
- rivian/parallax/comfort.py +22 -16
- rivian/parallax/departure.py +21 -21
- rivian/parallax/device_table.py +24 -6
- rivian/parallax/dynamics.py +14 -8
- rivian/parallax/holiday_celebration.py +42 -9
- rivian/parallax/navigation.py +4 -4
- rivian/parallax/operation.py +39 -0
- rivian/parallax/ota.py +17 -18
- rivian/parallax/proto/body.proto +10 -9
- rivian/parallax/proto/body_pb2.py +49 -49
- rivian/parallax/proto/charging.proto +5 -0
- rivian/parallax/proto/charging_pb2.py +22 -22
- rivian/parallax/proto/charging_pb2.pyi +4 -2
- rivian/parallax/proto/comfort.proto +1 -1
- rivian/parallax/proto/device_table.proto +43 -8
- rivian/parallax/proto/device_table_pb2.py +21 -13
- rivian/parallax/proto/device_table_pb2.pyi +54 -4
- rivian/parallax/proto/dynamics.proto +1 -1
- rivian/parallax/proto/dynamics_pb2.py +27 -27
- rivian/parallax/proto/dynamics_pb2.pyi +4 -4
- rivian/parallax/proto/holiday_celebration.proto +19 -6
- rivian/parallax/proto/holiday_celebration_pb2.py +22 -20
- rivian/parallax/proto/holiday_celebration_pb2.pyi +19 -4
- rivian/parallax/proto/navigation.proto +2 -2
- rivian/parallax/proto/navigation_pb2.py +1 -1
- rivian/parallax/proto/navigation_pb2.pyi +4 -4
- rivian/parallax/proto/operation.proto +31 -0
- rivian/parallax/proto/operation_pb2.py +44 -0
- rivian/parallax/proto/operation_pb2.pyi +45 -0
- rivian/parallax/proto/ota.proto +4 -4
- rivian/parallax/proto/ota_pb2.py +41 -41
- rivian/parallax/proto/ota_pb2.pyi +6 -6
- rivian/parallax/proto/vehicle.proto +2 -2
- rivian/rivian.py +261 -63
- rivian/schemas/charging.graphql +0 -21
- rivian/utils.py +2 -2
- rivian/ws_monitor.py +37 -22
- {rivian_python_client-2.2.0b11.dist-info → rivian_python_client-2.3.0.dist-info}/METADATA +28 -4
- {rivian_python_client-2.2.0b11.dist-info → rivian_python_client-2.3.0.dist-info}/RECORD +45 -41
- {rivian_python_client-2.2.0b11.dist-info → rivian_python_client-2.3.0.dist-info}/WHEEL +0 -0
rivian/__version__.py
CHANGED
rivian/const.py
CHANGED
|
@@ -2,34 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
from enum import StrEnum
|
|
6
6
|
from typing import Final
|
|
7
7
|
|
|
8
|
-
if sys.version_info >= (3, 11):
|
|
9
|
-
from enum import StrEnum
|
|
10
|
-
else:
|
|
11
|
-
from backports.strenum import StrEnum
|
|
12
|
-
|
|
13
|
-
LIVE_SESSION_PROPERTIES: Final[set[str]] = {
|
|
14
|
-
"chargerId",
|
|
15
|
-
"current",
|
|
16
|
-
"currentCurrency",
|
|
17
|
-
"currentMiles",
|
|
18
|
-
"currentPrice",
|
|
19
|
-
"isFreeSession",
|
|
20
|
-
"isRivianCharger",
|
|
21
|
-
"kilometersChargedPerHour",
|
|
22
|
-
"locationId",
|
|
23
|
-
"power",
|
|
24
|
-
"rangeAddedThisSession",
|
|
25
|
-
"soc",
|
|
26
|
-
"startTime",
|
|
27
|
-
"timeElapsed",
|
|
28
|
-
"timeRemaining",
|
|
29
|
-
"totalChargedEnergy",
|
|
30
|
-
"vehicleChargerState",
|
|
31
|
-
}
|
|
32
|
-
|
|
33
8
|
VEHICLE_STATE_PROPERTIES: Final[set[str]] = {
|
|
34
9
|
# VehicleCloudConnection
|
|
35
10
|
"cloudConnection",
|
|
@@ -38,7 +13,6 @@ VEHICLE_STATE_PROPERTIES: Final[set[str]] = {
|
|
|
38
13
|
"gnssLocation",
|
|
39
14
|
"gnssError",
|
|
40
15
|
# TimeStamped(String|[Nullable]Float|Int)
|
|
41
|
-
"activeDriverName",
|
|
42
16
|
"alarmSoundStatus",
|
|
43
17
|
"batteryCapacity",
|
|
44
18
|
"batteryCellType",
|
rivian/parallax/__init__.py
CHANGED
|
@@ -7,6 +7,9 @@ Public API:
|
|
|
7
7
|
Same, for a raw `subscribe_for_parallax_messages` message.
|
|
8
8
|
PARALLAX_RVMS: list[str] — every RVM with a registered decoder
|
|
9
9
|
CHARGING_RVMS: list[str] — the subset relevant to a charging session
|
|
10
|
+
encode_vehicle_operation(rvm, payload=b"", ...) -> str
|
|
11
|
+
Builds a `send_vehicle_operation` request; an empty payload reads
|
|
12
|
+
the RVM, anything else (e.g. `encode_climate_hold_setting`) writes it.
|
|
10
13
|
|
|
11
14
|
Layout:
|
|
12
15
|
core.py - registry, payload parsing and shared helpers
|
|
@@ -41,11 +44,17 @@ from . import ( # noqa: F401 (imported to register their decoders)
|
|
|
41
44
|
vehicle,
|
|
42
45
|
vehicle_access,
|
|
43
46
|
)
|
|
47
|
+
from .comfort import (
|
|
48
|
+
CLIMATE_HOLD_SETTING_RVM,
|
|
49
|
+
CLIMATE_HOLD_STATUS_RVM,
|
|
50
|
+
encode_climate_hold_setting,
|
|
51
|
+
)
|
|
44
52
|
from .core import (
|
|
45
53
|
RVMDecoder,
|
|
46
54
|
decode_parallax_message,
|
|
47
55
|
decode_parallax_subscription_message,
|
|
48
56
|
)
|
|
57
|
+
from .operation import encode_vehicle_operation
|
|
49
58
|
|
|
50
59
|
PARALLAX_RVMS: list[str] = list(RVMDecoder.decoders)
|
|
51
60
|
|
|
@@ -64,7 +73,11 @@ CHARGING_RVMS: list[str] = [
|
|
|
64
73
|
|
|
65
74
|
__all__ = [
|
|
66
75
|
"CHARGING_RVMS",
|
|
76
|
+
"CLIMATE_HOLD_SETTING_RVM",
|
|
77
|
+
"CLIMATE_HOLD_STATUS_RVM",
|
|
67
78
|
"PARALLAX_RVMS",
|
|
68
79
|
"decode_parallax_message",
|
|
69
80
|
"decode_parallax_subscription_message",
|
|
81
|
+
"encode_climate_hold_setting",
|
|
82
|
+
"encode_vehicle_operation",
|
|
70
83
|
]
|
rivian/parallax/body.py
CHANGED
|
@@ -126,8 +126,8 @@ _WINDOW_MAP: Final[dict[int, str]] = {
|
|
|
126
126
|
|
|
127
127
|
# GraphQL's windowXCalibrated values.
|
|
128
128
|
_CALIBRATION_MAP: Final[dict[int, str]] = {
|
|
129
|
-
body_pb2.CALIBRATION_STATUS_CALIBRATED: "
|
|
130
|
-
body_pb2.CALIBRATION_STATUS_NOT_CALIBRATED: "
|
|
129
|
+
body_pb2.CALIBRATION_STATUS_CALIBRATED: "calibrated",
|
|
130
|
+
body_pb2.CALIBRATION_STATUS_NOT_CALIBRATED: "not_calibrated",
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
_TRAILER_PRESENCE_MAP: Final[dict[int, str]] = {
|
|
@@ -204,7 +204,7 @@ def decode_windows(m: body_pb2.WindowsState) -> dict[str, Any]:
|
|
|
204
204
|
Fields:
|
|
205
205
|
windowFrontLeftCalibrated, windowFrontRightCalibrated,
|
|
206
206
|
windowRearLeftCalibrated, windowRearRightCalibrated,
|
|
207
|
-
windowRearCalibrated: str ("
|
|
207
|
+
windowRearCalibrated: str ("calibrated" | "not_calibrated")
|
|
208
208
|
"""
|
|
209
209
|
return {
|
|
210
210
|
_WINDOW_MAP[w.instance]: _enum(
|
rivian/parallax/charging.py
CHANGED
|
@@ -374,7 +374,7 @@ def decode_energy_state(m: charging_pb2.EnergyState) -> dict[str, Any]:
|
|
|
374
374
|
energyChargerState, energyConnectionState: str — as
|
|
375
375
|
charging.session.status's chargerState / connectionState, which
|
|
376
376
|
these can lag
|
|
377
|
-
_field3: int — raw
|
|
377
|
+
_field3, _field8: int — raw
|
|
378
378
|
"""
|
|
379
379
|
result: dict[str, Any] = {}
|
|
380
380
|
if (v := _present(m, "charging_state")) is not None:
|
|
@@ -389,6 +389,8 @@ def decode_energy_state(m: charging_pb2.EnergyState) -> dict[str, Any]:
|
|
|
389
389
|
result["chargerStatus"] = _enum(_CHARGER_STATUS_MAP, v, what="charger status")
|
|
390
390
|
if (v := _present(m, "field_3")) is not None:
|
|
391
391
|
result["_field3"] = v
|
|
392
|
+
if (v := _present(m, "field_8")) is not None:
|
|
393
|
+
result["_field8"] = v
|
|
392
394
|
return result
|
|
393
395
|
|
|
394
396
|
|
rivian/parallax/comfort.py
CHANGED
|
@@ -7,6 +7,9 @@ from typing import Any, Final
|
|
|
7
7
|
from .core import _LOGGER, RVMDecoder, _enum, _present
|
|
8
8
|
from .proto import comfort_pb2
|
|
9
9
|
|
|
10
|
+
CLIMATE_HOLD_SETTING_RVM: Final = "comfort.cabin.climate_hold_setting"
|
|
11
|
+
CLIMATE_HOLD_STATUS_RVM: Final = "comfort.cabin.climate_hold_status"
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
@RVMDecoder.register(
|
|
12
15
|
"comfort.cabin.hvac_settings_status", comfort_pb2.HvacSettingsStatus
|
|
@@ -63,10 +66,10 @@ _CLIMATE_HOLD_UNAVAILABILITY_REASON_MAP: Final[dict[int, str]] = {
|
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
_DEFROST_MAP: Final[dict[int, str]] = {
|
|
66
|
-
comfort_pb2.DEFROST_DEFOG: "
|
|
67
|
-
comfort_pb2.DEFROST_ACTIVE: "
|
|
68
|
-
comfort_pb2.DEFROST_DEFOG_DEFROST: "
|
|
69
|
-
comfort_pb2.DEFROST_OFF: "
|
|
69
|
+
comfort_pb2.DEFROST_DEFOG: "defog",
|
|
70
|
+
comfort_pb2.DEFROST_ACTIVE: "defrost",
|
|
71
|
+
comfort_pb2.DEFROST_DEFOG_DEFROST: "defog_defrost",
|
|
72
|
+
comfort_pb2.DEFROST_OFF: "off",
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
# GraphQL's cabinPreconditioningStatus / cabinPreconditioningType values.
|
|
@@ -132,10 +135,10 @@ _CABIN_SURFACE_MAP: Final[dict[int, str]] = {
|
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
_CONDITIONING_LEVEL_MAP: Final[dict[int | None, str]] = {
|
|
135
|
-
None: "
|
|
136
|
-
comfort_pb2.CONDITIONING_LEVEL_1: "
|
|
137
|
-
comfort_pb2.CONDITIONING_LEVEL_2: "
|
|
138
|
-
comfort_pb2.CONDITIONING_LEVEL_3: "
|
|
138
|
+
None: "off",
|
|
139
|
+
comfort_pb2.CONDITIONING_LEVEL_1: "level_1",
|
|
140
|
+
comfort_pb2.CONDITIONING_LEVEL_2: "level_2",
|
|
141
|
+
comfort_pb2.CONDITIONING_LEVEL_3: "level_3",
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
_CONDITIONING_TYPE_MAP: Final[dict[int, str]] = {
|
|
@@ -200,9 +203,12 @@ def decode_cabin_ventilation(m: comfort_pb2.CabinVentilationSetting) -> dict[str
|
|
|
200
203
|
return {"cabinVentilationEnabled": m.auto_cabin_ventilation_enabled}
|
|
201
204
|
|
|
202
205
|
|
|
203
|
-
|
|
204
|
-
"comfort.cabin.climate_hold_setting
|
|
205
|
-
)
|
|
206
|
+
def encode_climate_hold_setting(duration_seconds: int) -> bytes:
|
|
207
|
+
"""Encode a comfort.cabin.climate_hold_setting payload."""
|
|
208
|
+
return comfort_pb2.ClimateHoldSetting(duration=duration_seconds).SerializeToString()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@RVMDecoder.register(CLIMATE_HOLD_SETTING_RVM, comfort_pb2.ClimateHoldSetting)
|
|
206
212
|
def decode_climate_hold_setting(m: comfort_pb2.ClimateHoldSetting) -> dict[str, Any]:
|
|
207
213
|
"""comfort.cabin.climate_hold_setting — configured climate-hold duration.
|
|
208
214
|
|
|
@@ -212,7 +218,7 @@ def decode_climate_hold_setting(m: comfort_pb2.ClimateHoldSetting) -> dict[str,
|
|
|
212
218
|
return {"climateHoldDuration": m.duration}
|
|
213
219
|
|
|
214
220
|
|
|
215
|
-
@RVMDecoder.register(
|
|
221
|
+
@RVMDecoder.register(CLIMATE_HOLD_STATUS_RVM, comfort_pb2.ClimateHoldStatus)
|
|
216
222
|
def decode_climate_hold_status(m: comfort_pb2.ClimateHoldStatus) -> dict[str, Any]:
|
|
217
223
|
"""comfort.cabin.climate_hold_status — current climate-hold status.
|
|
218
224
|
|
|
@@ -248,8 +254,8 @@ def decode_defrost_status(m: comfort_pb2.DefrostDefogStatus) -> dict[str, Any]:
|
|
|
248
254
|
"""comfort.cabin.defrost_defog_status — windshield defrost state.
|
|
249
255
|
|
|
250
256
|
Fields:
|
|
251
|
-
defrostDefogStatus: str | None ("
|
|
252
|
-
| "
|
|
257
|
+
defrostDefogStatus: str | None ("defog" | "defrost" | "defog_defrost"
|
|
258
|
+
| "off")
|
|
253
259
|
"""
|
|
254
260
|
return {
|
|
255
261
|
"defrostDefogStatus": _enum(
|
|
@@ -293,8 +299,8 @@ def decode_seat_conditioning(m: comfort_pb2.SeatConditioningStatus) -> dict[str,
|
|
|
293
299
|
seatFrontLeftHeat, seatFrontLeftVent, seatFrontRightHeat,
|
|
294
300
|
seatFrontRightVent, seatRearLeftHeat, seatRearRightHeat,
|
|
295
301
|
seatThirdRowLeftHeat, seatThirdRowRightHeat: str
|
|
296
|
-
("
|
|
297
|
-
steeringWheelHeat: str ("
|
|
302
|
+
("off" | "level_1" | "level_2" | "level_3")
|
|
303
|
+
steeringWheelHeat: str ("off" | "level_1")
|
|
298
304
|
plus <surface>Heat/Vent for any other surface sent (middle seats,
|
|
299
305
|
rearGlass, frontGlass, sideviewMirrors, wiperArea)
|
|
300
306
|
|
rivian/parallax/departure.py
CHANGED
|
@@ -12,28 +12,28 @@ _Schedules = departure_pb2.DepartureSchedules
|
|
|
12
12
|
|
|
13
13
|
# Days field -> gateway WeekDay value.
|
|
14
14
|
_WEEKDAYS = (
|
|
15
|
-
("monday", "
|
|
16
|
-
("tuesday", "
|
|
17
|
-
("wednesday", "
|
|
18
|
-
("thursday", "
|
|
19
|
-
("friday", "
|
|
20
|
-
("saturday", "
|
|
21
|
-
("sunday", "
|
|
15
|
+
("monday", "monday"),
|
|
16
|
+
("tuesday", "tuesday"),
|
|
17
|
+
("wednesday", "wednesday"),
|
|
18
|
+
("thursday", "thursday"),
|
|
19
|
+
("friday", "friday"),
|
|
20
|
+
("saturday", "saturday"),
|
|
21
|
+
("sunday", "sunday"),
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
_DEFOG_DEFROST = {
|
|
25
|
-
departure_pb2.DEFOG_DEFROST_OFF: "
|
|
26
|
-
departure_pb2.DEFOG_DEFROST_DEFOG: "
|
|
27
|
-
departure_pb2.DEFOG_DEFROST_DEFROST: "
|
|
25
|
+
departure_pb2.DEFOG_DEFROST_OFF: "off",
|
|
26
|
+
departure_pb2.DEFOG_DEFROST_DEFOG: "defog",
|
|
27
|
+
departure_pb2.DEFOG_DEFROST_DEFROST: "defrost",
|
|
28
28
|
}
|
|
29
29
|
_SURFACE_LEVEL = {
|
|
30
|
-
departure_pb2.SURFACE_OFF: "
|
|
31
|
-
departure_pb2.SURFACE_HEAT_1: "
|
|
32
|
-
departure_pb2.SURFACE_HEAT_2: "
|
|
33
|
-
departure_pb2.SURFACE_HEAT_3: "
|
|
34
|
-
departure_pb2.SURFACE_VENT_1: "
|
|
35
|
-
departure_pb2.SURFACE_VENT_2: "
|
|
36
|
-
departure_pb2.SURFACE_VENT_3: "
|
|
30
|
+
departure_pb2.SURFACE_OFF: "off",
|
|
31
|
+
departure_pb2.SURFACE_HEAT_1: "heat_1",
|
|
32
|
+
departure_pb2.SURFACE_HEAT_2: "heat_2",
|
|
33
|
+
departure_pb2.SURFACE_HEAT_3: "heat_3",
|
|
34
|
+
departure_pb2.SURFACE_VENT_1: "vent_1",
|
|
35
|
+
departure_pb2.SURFACE_VENT_2: "vent_2",
|
|
36
|
+
departure_pb2.SURFACE_VENT_3: "vent_3",
|
|
37
37
|
}
|
|
38
38
|
_SURFACES = (
|
|
39
39
|
("front_left_seat", "frontLeftSeat"),
|
|
@@ -86,14 +86,14 @@ def decode_departure_schedules(m: departure_pb2.DepartureSchedules) -> dict[str,
|
|
|
86
86
|
Fields:
|
|
87
87
|
departureSchedules: list[dict] — the gateway's DepartureSchedule shape:
|
|
88
88
|
id: str, name: str, isEnabled: bool
|
|
89
|
-
occurrence: {"days": list[str] ("
|
|
89
|
+
occurrence: {"days": list[str] ("monday" ... "sunday"; order
|
|
90
90
|
inferred), "startsAtMin": int (minutes after local midnight)}
|
|
91
91
|
departureSettings: {"shouldOverrideChargeSchedule": bool,
|
|
92
92
|
"comfortSettings": {"cabinTempCelsius": float,
|
|
93
|
-
"frontDefogDefrost": "
|
|
93
|
+
"frontDefogDefrost": "off" | "defog" | "defrost",
|
|
94
94
|
"surfaceHeatVentLevels": {frontLeftSeat, frontRightSeat,
|
|
95
|
-
rearLeftSeat, rearRightSeat, steeringWheel: "
|
|
96
|
-
"
|
|
95
|
+
rearLeftSeat, rearRightSeat, steeringWheel: "off" |
|
|
96
|
+
"heat_1" ... "vent_3"}}}
|
|
97
97
|
departureSchedulesUpdatedAt: datetime
|
|
98
98
|
"""
|
|
99
99
|
result: dict[str, Any] = {
|
rivian/parallax/device_table.py
CHANGED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import uuid
|
|
6
6
|
from typing import Any, Final
|
|
7
7
|
|
|
8
|
+
from ..utils import from_epoch
|
|
8
9
|
from .core import RVMDecoder, _enum, _present
|
|
9
10
|
from .proto import device_table_pb2
|
|
10
11
|
|
|
@@ -59,9 +60,14 @@ def decode_vas_keyper_devices(m: device_table_pb2.VasKeyperDevices) -> dict[str,
|
|
|
59
60
|
infoC: int — 1 while active, 2147483647 while inactive, else the
|
|
60
61
|
pairing attempt number
|
|
61
62
|
infoA, infoB: int — unknown
|
|
63
|
+
keyPermissions: int — raw
|
|
64
|
+
keyCreatedAt, keyExpiresAt: datetime — when sent and non-zero
|
|
65
|
+
keyMaterial: str — which key material is set ("owner" | "friend" |
|
|
66
|
+
"card" | "fob" | "phone" | "fob2")
|
|
62
67
|
deviceId: str — hex; for a key card, the GraphQL `devices[].id`
|
|
68
|
+
nfcDeviceId: str — hex; fob2's NFC id (deviceId is its BLE id)
|
|
63
69
|
phoneId: str — the phone's UUID; phone keys
|
|
64
|
-
credentialHex: str — hex;
|
|
70
|
+
credentialHex: str — hex; the wrapped shared secret
|
|
65
71
|
deviceOem: str ("unknown" | "rivian" | "apple" | "google" |
|
|
66
72
|
"samsung" | "vw") — the device maker
|
|
67
73
|
"""
|
|
@@ -100,17 +106,29 @@ def decode_vas_keyper_devices(m: device_table_pb2.VasKeyperDevices) -> dict[str,
|
|
|
100
106
|
result[key] = v
|
|
101
107
|
if (v := _present(credentials.info, "status")) is not None:
|
|
102
108
|
result["keyStatus"] = _enum(_KEY_STATUS_MAP, v, what="key status")
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
if (v := _present(credentials.info, "permissions")) is not None:
|
|
110
|
+
result["keyPermissions"] = v
|
|
111
|
+
for field, key in (
|
|
112
|
+
("created_at", "keyCreatedAt"),
|
|
113
|
+
("expires_at", "keyExpiresAt"),
|
|
114
|
+
):
|
|
115
|
+
if v := _present(credentials.info, field):
|
|
116
|
+
result[key] = from_epoch(v)
|
|
117
|
+
if (field := credentials.WhichOneof("material")) is not None:
|
|
118
|
+
result["keyMaterial"] = field
|
|
106
119
|
material = getattr(credentials, field)
|
|
107
|
-
|
|
120
|
+
id_field, cred_field = (
|
|
121
|
+
("ble_id", "ble_credential") if field == "fob2" else ("id", "credential")
|
|
122
|
+
)
|
|
123
|
+
if (ident := _present(material, id_field)) is not None:
|
|
108
124
|
if field == "phone" and len(ident) == 16:
|
|
109
125
|
result["phoneId"] = str(uuid.UUID(bytes=ident))
|
|
110
126
|
else:
|
|
111
127
|
result["deviceId"] = ident.hex()
|
|
112
|
-
if (cred := _present(material,
|
|
128
|
+
if (cred := _present(material, cred_field)) is not None:
|
|
113
129
|
result["credentialHex"] = cred.hex()
|
|
130
|
+
if field == "fob2" and (ident := _present(material, "nfc_id")) is not None:
|
|
131
|
+
result["nfcDeviceId"] = ident.hex()
|
|
114
132
|
if (v := _present(credentials, "key_type")) is not None:
|
|
115
133
|
result["keyType"] = _enum(_KEY_TYPE_MAP, v, what="key type")
|
|
116
134
|
if (v := _present(credentials, "device_oem")) is not None:
|
rivian/parallax/dynamics.py
CHANGED
|
@@ -53,9 +53,8 @@ _TEMPERATURE_IMPACT_MAP: Final[dict[int, str]] = {
|
|
|
53
53
|
dynamics_pb2.TEMPERATURE_COLD_IMPACT: "cold_impact",
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
# "OK" is the GraphQL value for normal.
|
|
57
56
|
_TIRE_PRESSURE_STATUS_MAP: Final[dict[int, str]] = {
|
|
58
|
-
dynamics_pb2.TIRE_PRESSURE_STATUS_NORMAL: "
|
|
57
|
+
dynamics_pb2.TIRE_PRESSURE_STATUS_NORMAL: "ok",
|
|
59
58
|
dynamics_pb2.TIRE_PRESSURE_STATUS_WARNING_HARD: "warning_hard",
|
|
60
59
|
dynamics_pb2.TIRE_PRESSURE_STATUS_WARNING_SOFT: "warning_soft",
|
|
61
60
|
dynamics_pb2.TIRE_PRESSURE_STATUS_WARNING_PUNCTURE: "warning_puncture",
|
|
@@ -195,15 +194,21 @@ def decode_range(m: dynamics_pb2.Range) -> dict[str, Any]:
|
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
|
|
197
|
+
# The app's enum class for tire validity was stripped; only 0 and 1 are known.
|
|
198
|
+
_TIRE_VALID: Final = 0
|
|
199
|
+
_TIRE_VALIDITY_MAP: Final[dict[int, str]] = {_TIRE_VALID: "valid", 1: "invalid"}
|
|
200
|
+
|
|
201
|
+
|
|
198
202
|
@RVMDecoder.register("dynamics.tires.state", dynamics_pb2.TiresState)
|
|
199
203
|
def decode_tires(m: dynamics_pb2.TiresState) -> dict[str, Any]:
|
|
200
204
|
"""dynamics.tires.state — per-tire pressure, status and validity.
|
|
201
205
|
|
|
202
206
|
Fields:
|
|
203
207
|
tirePressureFrontLeft, tirePressureFrontRight, etc.: float (bar)
|
|
204
|
-
tirePressureStatusFrontLeft, etc.: str ("
|
|
208
|
+
tirePressureStatusFrontLeft, etc.: str ("ok" | "warning_hard" |
|
|
205
209
|
"warning_soft" | "warning_puncture")
|
|
206
|
-
tirePressureStatusValidFrontLeft, etc.: str ("valid" | "invalid")
|
|
210
|
+
tirePressureStatusValidFrontLeft, etc.: str ("valid" | "invalid"),
|
|
211
|
+
or the raw int for an unmapped validity
|
|
207
212
|
"""
|
|
208
213
|
result: dict[str, Any] = {}
|
|
209
214
|
for s in m.tire:
|
|
@@ -212,10 +217,11 @@ def decode_tires(m: dynamics_pb2.TiresState) -> dict[str, Any]:
|
|
|
212
217
|
continue
|
|
213
218
|
suffix = _TIRE_POSITION_MAP[s.pos]
|
|
214
219
|
|
|
215
|
-
# Pressure and status are stale
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
220
|
+
# Pressure and status are stale unless valid.
|
|
221
|
+
result[f"tirePressureStatusValid{suffix}"] = _enum(
|
|
222
|
+
_TIRE_VALIDITY_MAP, s.validity, what="tire validity"
|
|
223
|
+
)
|
|
224
|
+
if s.validity != _TIRE_VALID:
|
|
219
225
|
continue
|
|
220
226
|
|
|
221
227
|
if (pressure := _present(s, "pressure")) is not None:
|
|
@@ -14,11 +14,34 @@ from .proto import holiday_celebration_pb2
|
|
|
14
14
|
holiday_celebration_pb2.HolidayCelebrationEnabled,
|
|
15
15
|
)
|
|
16
16
|
def decode_holiday_celebration_enabled(
|
|
17
|
-
|
|
17
|
+
m: holiday_celebration_pb2.HolidayCelebrationEnabled,
|
|
18
18
|
) -> dict[str, Any]:
|
|
19
|
-
"""holiday_celebration.car_costume.holiday_celebration_enabled
|
|
20
|
-
return {}
|
|
19
|
+
"""holiday_celebration.car_costume.holiday_celebration_enabled.
|
|
21
20
|
|
|
21
|
+
The names are inferred; the app has no class for this topic.
|
|
22
|
+
|
|
23
|
+
Fields:
|
|
24
|
+
holidayCelebrationAvailable: bool — the (seasonal) feature is offered
|
|
25
|
+
holidayCelebrationEnabled: bool — the user's on/off switch
|
|
26
|
+
"""
|
|
27
|
+
return {
|
|
28
|
+
"holidayCelebrationAvailable": m.available,
|
|
29
|
+
"holidayCelebrationEnabled": m.enabled,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Best guess; 2 hasn't been seen.
|
|
34
|
+
_CAR_COSTUME_AVAILABILITY_MAP: Final[dict[int, str]] = {
|
|
35
|
+
1: "available",
|
|
36
|
+
3: "unavailable",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_COSTUME_THEME_MAP: Final[dict[int, str]] = {
|
|
41
|
+
holiday_celebration_pb2.CAR_COSTUME_THEME_NONE: "none",
|
|
42
|
+
holiday_celebration_pb2.CAR_COSTUME_THEME_GHOSTBUSTERS: "ghostbusters",
|
|
43
|
+
holiday_celebration_pb2.CAR_COSTUME_THEME_GHOSTBUSTERS_DISPLAY: "ghostbusters_display",
|
|
44
|
+
}
|
|
22
45
|
|
|
23
46
|
_COSTUME_EFFECT_TRIGGER_MAP: Final[dict[int, str]] = {
|
|
24
47
|
holiday_celebration_pb2.COSTUME_EFFECT_TRIGGER_MANUAL: "manual",
|
|
@@ -45,7 +68,7 @@ def decode_car_costume_settings(
|
|
|
45
68
|
costumeInteriorLightShowEnabled: bool
|
|
46
69
|
costumeInteriorOverheadLightsEnabled: bool
|
|
47
70
|
costumeLightsColor: int
|
|
48
|
-
costumeEffect: int
|
|
71
|
+
costumeEffect: int (1 none, 3 the display show; inferred)
|
|
49
72
|
costumeEffectTrigger: str ("manual" | "motion"), None when unset
|
|
50
73
|
"""
|
|
51
74
|
return {
|
|
@@ -80,15 +103,25 @@ def decode_car_costume_state(
|
|
|
80
103
|
Names come from the app; the int values are unmapped enums.
|
|
81
104
|
|
|
82
105
|
Fields:
|
|
83
|
-
carCostumeAvailability: int
|
|
84
|
-
|
|
106
|
+
carCostumeAvailability: str | int ("available" | "unavailable"; a
|
|
107
|
+
best guess), the raw int when unmapped
|
|
108
|
+
costumeTheme: str | None ("none" | "ghostbusters" |
|
|
109
|
+
"ghostbusters_display"; inferred), None when unset
|
|
85
110
|
costumeMotionTriggerDetected: bool
|
|
86
|
-
activeCostumeEffect: int
|
|
111
|
+
activeCostumeEffect: int (1 none, 3 the display show; inferred)
|
|
87
112
|
costumeStartTime: datetime, only when sent
|
|
88
113
|
"""
|
|
89
114
|
result: dict[str, Any] = {
|
|
90
|
-
"carCostumeAvailability":
|
|
91
|
-
|
|
115
|
+
"carCostumeAvailability": _enum(
|
|
116
|
+
_CAR_COSTUME_AVAILABILITY_MAP,
|
|
117
|
+
m.car_costume_availability,
|
|
118
|
+
what="car costume availability",
|
|
119
|
+
),
|
|
120
|
+
"costumeTheme": (
|
|
121
|
+
_enum(_COSTUME_THEME_MAP, m.costume_theme, what="costume theme")
|
|
122
|
+
if m.costume_theme
|
|
123
|
+
else None
|
|
124
|
+
),
|
|
92
125
|
"costumeMotionTriggerDetected": m.motion_trigger_detected,
|
|
93
126
|
"activeCostumeEffect": m.active_costume_effect,
|
|
94
127
|
}
|
rivian/parallax/navigation.py
CHANGED
|
@@ -85,7 +85,7 @@ def _decode_origin(origin: navigation_pb2.GpsFix) -> dict[str, Any]:
|
|
|
85
85
|
result = _decode_geocoordinate(
|
|
86
86
|
_present(origin, "location"), "originLatitude", "originLongitude"
|
|
87
87
|
)
|
|
88
|
-
result["
|
|
88
|
+
result["originBearing"] = origin.bearing
|
|
89
89
|
if (origin_epoch := _present(origin, "time")) is not None:
|
|
90
90
|
result["originTime"] = from_epoch(origin_epoch)
|
|
91
91
|
return result
|
|
@@ -282,7 +282,7 @@ def decode_trip_info(m: navigation_pb2.TripInfo) -> dict[str, Any]:
|
|
|
282
282
|
tripId: str — changes on every route computation, so it can't
|
|
283
283
|
correlate messages
|
|
284
284
|
originLatitude, originLongitude: float
|
|
285
|
-
|
|
285
|
+
originBearing: float (degrees)
|
|
286
286
|
originTime: datetime
|
|
287
287
|
originStateOfCharge: float (percent)
|
|
288
288
|
distance, duration: float — driving totals (meters, seconds),
|
|
@@ -354,7 +354,7 @@ def decode_trip_progress(m: navigation_pb2.TripProgress) -> dict[str, Any]:
|
|
|
354
354
|
(meters, seconds)
|
|
355
355
|
latitude, longitude: float
|
|
356
356
|
speed: float (m/s)
|
|
357
|
-
|
|
357
|
+
bearing: float (degrees)
|
|
358
358
|
locationTime: datetime — time of the position fix
|
|
359
359
|
"""
|
|
360
360
|
result: dict[str, Any] = {}
|
|
@@ -371,7 +371,7 @@ def decode_trip_progress(m: navigation_pb2.TripProgress) -> dict[str, Any]:
|
|
|
371
371
|
_decode_geocoordinate(_present(fix, "location"), "latitude", "longitude")
|
|
372
372
|
)
|
|
373
373
|
result["speed"] = fix.speed
|
|
374
|
-
result["
|
|
374
|
+
result["bearing"] = fix.bearing
|
|
375
375
|
if (fix_epoch := _present(fix, "time")) is not None:
|
|
376
376
|
result["locationTime"] = from_epoch(fix_epoch)
|
|
377
377
|
return result
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Encoder for `sendVehicleOperation` requests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from .proto import operation_pb2
|
|
9
|
+
|
|
10
|
+
_Request = operation_pb2.VehicleOperationRequest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def encode_vehicle_operation(
|
|
14
|
+
rvm: str,
|
|
15
|
+
payload: bytes = b"",
|
|
16
|
+
phone_id: bytes = bytes(16),
|
|
17
|
+
request_id: str | None = None,
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Encode a `sendVehicleOperation` request as a base64 string.
|
|
20
|
+
|
|
21
|
+
An empty payload asks the vehicle to publish the current value of the RVM,
|
|
22
|
+
anything else writes it. The vehicle accepts an all-zero phone id.
|
|
23
|
+
"""
|
|
24
|
+
request = _Request(
|
|
25
|
+
metadata=_Request.Metadata(
|
|
26
|
+
phone_info=_Request.Metadata.PhoneInfo(field_1=1, phone_id=phone_id),
|
|
27
|
+
request_id=request_id or str(uuid.uuid4()),
|
|
28
|
+
),
|
|
29
|
+
operation=_Request.Operation(
|
|
30
|
+
rvm=rvm,
|
|
31
|
+
type=(
|
|
32
|
+
operation_pb2.OPERATION_WRITE
|
|
33
|
+
if payload
|
|
34
|
+
else operation_pb2.OPERATION_READ
|
|
35
|
+
),
|
|
36
|
+
payload=payload,
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
return base64.b64encode(request.SerializeToString()).decode("utf-8")
|
rivian/parallax/ota.py
CHANGED
|
@@ -27,24 +27,23 @@ _OTA_STATUS_MAP: Final[dict[int, str]] = {
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
_Deployment = ota_pb2.DeploymentState
|
|
30
|
-
# The app's GraphQL-style strings.
|
|
31
30
|
_CURRENT_STATUS_MAP: Final[dict[int, str]] = {
|
|
32
|
-
_Deployment.CURRENT_STATUS_INSTALL_SUCCESS: "
|
|
33
|
-
_Deployment.CURRENT_STATUS_INSTALL_FAILED: "
|
|
34
|
-
_Deployment.CURRENT_STATUS_INSTALL_UNABLE_TO_START: "
|
|
31
|
+
_Deployment.CURRENT_STATUS_INSTALL_SUCCESS: "install_success",
|
|
32
|
+
_Deployment.CURRENT_STATUS_INSTALL_FAILED: "install_failed",
|
|
33
|
+
_Deployment.CURRENT_STATUS_INSTALL_UNABLE_TO_START: "install_unable_to_start",
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
_DEPLOYMENT_INTENT_MAP: Final[dict[int, str]] = {
|
|
38
|
-
_Deployment.DEPLOYMENT_INTENT_PERFORMANCE_UPGRADE: "
|
|
39
|
-
_Deployment.DEPLOYMENT_INTENT_BUG_FIX: "
|
|
40
|
-
_Deployment.DEPLOYMENT_INTENT_SECURITY_UPDATE: "
|
|
41
|
-
_Deployment.DEPLOYMENT_INTENT_FEATURE_ADDITION: "
|
|
37
|
+
_Deployment.DEPLOYMENT_INTENT_PERFORMANCE_UPGRADE: "performance_upgrade",
|
|
38
|
+
_Deployment.DEPLOYMENT_INTENT_BUG_FIX: "bug_fix",
|
|
39
|
+
_Deployment.DEPLOYMENT_INTENT_SECURITY_UPDATE: "security_update",
|
|
40
|
+
_Deployment.DEPLOYMENT_INTENT_FEATURE_ADDITION: "feature_addition",
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
_SOFTWARE_CATEGORY_MAP: Final[dict[int, str]] = {
|
|
45
|
-
_Deployment.SOFTWARE_CATEGORY_FIRMWARE: "
|
|
46
|
-
_Deployment.SOFTWARE_CATEGORY_HD_MAPS: "
|
|
47
|
-
_Deployment.SOFTWARE_CATEGORY_VEHICLE_CONFIG: "
|
|
44
|
+
_Deployment.SOFTWARE_CATEGORY_FIRMWARE: "firmware",
|
|
45
|
+
_Deployment.SOFTWARE_CATEGORY_HD_MAPS: "hd_maps",
|
|
46
|
+
_Deployment.SOFTWARE_CATEGORY_VEHICLE_CONFIG: "vehicle_config",
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
|
|
@@ -56,8 +55,8 @@ def _decode_version(
|
|
|
56
55
|
for field, key in (
|
|
57
56
|
("version_string", "Version"),
|
|
58
57
|
("version_year", "VersionYear"),
|
|
59
|
-
("
|
|
60
|
-
("
|
|
58
|
+
("version_week", "VersionWeek"),
|
|
59
|
+
("git_hash", "VersionGitHash"),
|
|
61
60
|
):
|
|
62
61
|
if (v := _present(version, field)) is not None:
|
|
63
62
|
result[f"{prefix}{key}"] = v
|
|
@@ -80,7 +79,7 @@ def decode_deployment_state(m: ota_pb2.DeploymentState) -> dict[str, Any]:
|
|
|
80
79
|
otaCurrentVersionYear, otaCurrentVersionWeek,
|
|
81
80
|
otaCurrentVersionNumber: int
|
|
82
81
|
otaCurrentVersionGitHash: str
|
|
83
|
-
otaSoftwareCategory: str ("
|
|
82
|
+
otaSoftwareCategory: str ("firmware" | "hd_maps" | "vehicle_config")
|
|
84
83
|
otaUpdateInProgress: bool
|
|
85
84
|
otaDeploymentId: str — UUID, only while an update is in flight
|
|
86
85
|
otaAvailableVersion, otaAvailableVersionYear,
|
|
@@ -94,13 +93,13 @@ def decode_deployment_state(m: ota_pb2.DeploymentState) -> dict[str, Any]:
|
|
|
94
93
|
otaDownloadProgress, otaInstallProgress: int (0-100)
|
|
95
94
|
otaTimeRemaining: int (seconds; counts down during
|
|
96
95
|
"install_countdown", a static default otherwise)
|
|
97
|
-
otaCurrentStatus: str | None ("
|
|
98
|
-
"
|
|
96
|
+
otaCurrentStatus: str | None ("install_success" | "install_failed" |
|
|
97
|
+
"install_unable_to_start") — the last install's result
|
|
99
98
|
otaInstallReady: str ("ota_available" | "ota_not_available")
|
|
100
99
|
otaInstallDuration: int (minutes)
|
|
101
100
|
otaInstallTimeOfDay: int (minutes after local midnight), when set
|
|
102
|
-
otaDeploymentIntent: str | None ("
|
|
103
|
-
"
|
|
101
|
+
otaDeploymentIntent: str | None ("performance_upgrade" | "bug_fix" |
|
|
102
|
+
"security_update" | "feature_addition")
|
|
104
103
|
otaSkipAllowed: bool, otaSkipCount: int
|
|
105
104
|
otaPendingReasons: list[str] — what's blocking an install, e.g.
|
|
106
105
|
"not_parked", "unplugged", "lv_batt"
|
rivian/parallax/proto/body.proto
CHANGED
|
@@ -12,16 +12,17 @@ message ClosuresState {
|
|
|
12
12
|
int32 open_position_percent = 3;
|
|
13
13
|
// Unset on doors and windows; briefly GENERAL while re-reporting.
|
|
14
14
|
ClosureFault fault = 4;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// One field per closure type. Briefly SNA / not-available while the
|
|
15
|
+
// One member per closure type. Briefly SNA / not-available while the
|
|
18
16
|
// vehicle re-reports after an app command.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
oneof next_action {
|
|
18
|
+
WindowsNextAction windows_next_action = 5; // on GROUP_WINDOWS
|
|
19
|
+
FrunkNextAction frunk_next_action = 6;
|
|
20
|
+
LiftgateNextAction liftgate_next_action = 7;
|
|
21
|
+
SideBinNextAction side_bin_next_action = 8;
|
|
22
|
+
TailgateNextAction tailgate_next_action = 9;
|
|
23
|
+
ChargePortDoorNextAction charge_port_door_next_action = 10;
|
|
24
|
+
int32 tonneau_next_action = 11; // the app's enum (class stripped)
|
|
25
|
+
}
|
|
25
26
|
}
|
|
26
27
|
repeated Closure closure = 1;
|
|
27
28
|
}
|