rivian-python-client 2.3.0__tar.gz → 2.4.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.
Files changed (106) hide show
  1. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/PKG-INFO +42 -1
  2. rivian_python_client-2.4.0/README.md +93 -0
  3. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/pyproject.toml +4 -1
  4. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/__version__.py +1 -1
  5. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/ble.py +2 -2
  6. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/const.py +2 -2
  7. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/__init__.py +18 -2
  8. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/body.py +13 -0
  9. rivian_python_client-2.4.0/src/rivian/parallax/command.py +168 -0
  10. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/core.py +11 -6
  11. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/departure.py +16 -6
  12. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/navigation.py +7 -3
  13. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/body.proto +11 -0
  14. rivian_python_client-2.4.0/src/rivian/parallax/proto/body_pb2.py +88 -0
  15. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/body_pb2.pyi +12 -0
  16. rivian_python_client-2.4.0/src/rivian/parallax/proto/command.proto +108 -0
  17. rivian_python_client-2.4.0/src/rivian/parallax/proto/command_pb2.py +62 -0
  18. rivian_python_client-2.4.0/src/rivian/parallax/proto/command_pb2.pyi +151 -0
  19. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/departure.proto +12 -0
  20. rivian_python_client-2.4.0/src/rivian/parallax/proto/departure_pb2.py +58 -0
  21. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/departure_pb2.pyi +16 -2
  22. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/rivian.py +233 -71
  23. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/utils.py +32 -3
  24. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/ws_monitor.py +179 -33
  25. rivian_python_client-2.3.0/README.md +0 -52
  26. rivian_python_client-2.3.0/src/rivian/parallax/proto/body_pb2.py +0 -84
  27. rivian_python_client-2.3.0/src/rivian/parallax/proto/departure_pb2.py +0 -54
  28. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/.gitignore +0 -0
  29. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/__init__.py +0 -0
  30. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/exceptions.py +0 -0
  31. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/charging.py +0 -0
  32. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/comfort.py +0 -0
  33. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/device_table.py +0 -0
  34. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/dynamics.py +0 -0
  35. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/energy.py +0 -0
  36. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/energy_edge_compute.py +0 -0
  37. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/gearguard_streaming.py +0 -0
  38. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/geofence.py +0 -0
  39. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/holiday_celebration.py +0 -0
  40. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/operation.py +0 -0
  41. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/ota.py +0 -0
  42. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/parallax.py +0 -0
  43. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/__init__.py +0 -0
  44. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/charging.proto +0 -0
  45. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/charging_pb2.py +0 -0
  46. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/charging_pb2.pyi +0 -0
  47. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/comfort.proto +0 -0
  48. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/comfort_pb2.py +0 -0
  49. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/comfort_pb2.pyi +0 -0
  50. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/device_table.proto +0 -0
  51. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/device_table_pb2.py +0 -0
  52. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/device_table_pb2.pyi +0 -0
  53. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/dynamics.proto +0 -0
  54. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/dynamics_pb2.py +0 -0
  55. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/dynamics_pb2.pyi +0 -0
  56. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/energy.proto +0 -0
  57. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/energy_edge_compute.proto +0 -0
  58. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/energy_edge_compute_pb2.py +0 -0
  59. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/energy_edge_compute_pb2.pyi +0 -0
  60. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/energy_pb2.py +0 -0
  61. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/energy_pb2.pyi +0 -0
  62. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/gearguard_streaming.proto +0 -0
  63. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/gearguard_streaming_pb2.py +0 -0
  64. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/gearguard_streaming_pb2.pyi +0 -0
  65. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/geofence.proto +0 -0
  66. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/geofence_pb2.py +0 -0
  67. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/geofence_pb2.pyi +0 -0
  68. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/holiday_celebration.proto +0 -0
  69. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/holiday_celebration_pb2.py +0 -0
  70. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/holiday_celebration_pb2.pyi +0 -0
  71. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/navigation.proto +0 -0
  72. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/navigation_pb2.py +0 -0
  73. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/navigation_pb2.pyi +0 -0
  74. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/operation.proto +0 -0
  75. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/operation_pb2.py +0 -0
  76. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/operation_pb2.pyi +0 -0
  77. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/ota.proto +0 -0
  78. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/ota_pb2.py +0 -0
  79. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/ota_pb2.pyi +0 -0
  80. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/parallax.proto +0 -0
  81. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/parallax_pb2.py +0 -0
  82. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/parallax_pb2.pyi +0 -0
  83. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/secure_file_transfer.proto +0 -0
  84. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/secure_file_transfer_pb2.py +0 -0
  85. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/secure_file_transfer_pb2.pyi +0 -0
  86. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/security.proto +0 -0
  87. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/security_pb2.py +0 -0
  88. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/security_pb2.pyi +0 -0
  89. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/user_passcodes.proto +0 -0
  90. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/user_passcodes_pb2.py +0 -0
  91. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/user_passcodes_pb2.pyi +0 -0
  92. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/vehicle.proto +0 -0
  93. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/vehicle_access.proto +0 -0
  94. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/vehicle_access_pb2.py +0 -0
  95. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/vehicle_access_pb2.pyi +0 -0
  96. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/vehicle_pb2.py +0 -0
  97. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/proto/vehicle_pb2.pyi +0 -0
  98. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/secure_file_transfer.py +0 -0
  99. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/security.py +0 -0
  100. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/user_passcodes.py +0 -0
  101. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/vehicle.py +0 -0
  102. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/parallax/vehicle_access.py +0 -0
  103. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/py.typed +0 -0
  104. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/schemas/charging.graphql +0 -0
  105. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/schemas/gateway.graphql +0 -0
  106. {rivian_python_client-2.3.0 → rivian_python_client-2.4.0}/src/rivian/schemas/orders.graphql +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rivian-python-client
3
- Version: 2.3.0
3
+ Version: 2.4.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
@@ -65,3 +65,44 @@ editing one, regenerate its `*_pb2` modules (don't edit those by hand):
65
65
  uv run python -m grpc_tools.protoc --proto_path=src --python_out=src \
66
66
  --pyi_out=src src/rivian/parallax/proto/*.proto
67
67
  ```
68
+
69
+ ### Commands
70
+
71
+ `send_parallax_command` sends a signed command, such as unlocking
72
+ (`parallax.UNLOCK_RVM` with `parallax.encode_unlock()`; `LOCK_RVM` and
73
+ `encode_lock()` to lock), and `subscribe_for_parallax_command_state` follows it.
74
+ Subscribe first, then send:
75
+
76
+ ```python
77
+ import uuid
78
+
79
+ from rivian import parallax
80
+
81
+
82
+ def on_state(data):
83
+ if state := parallax.decode_command_state_message(data):
84
+ print(state["progress"], state["status"], state["error"])
85
+
86
+
87
+ command_id = str(uuid.uuid4())
88
+ unsubscribe = await rivian.subscribe_for_parallax_command_state(
89
+ vehicle_id, command_id, on_state
90
+ )
91
+ if unsubscribe:
92
+ await rivian.send_parallax_command(
93
+ vehicle_id,
94
+ parallax.UNLOCK_RVM,
95
+ parallax.encode_unlock(),
96
+ identity_id=identity_id,
97
+ vehicle_key=vehicle_key,
98
+ private_key=private_key,
99
+ command_id=command_id,
100
+ )
101
+ ... # wait for the progress "complete", or give up
102
+ await unsubscribe()
103
+ ```
104
+
105
+ The keys are those of `send_vehicle_command`. The command is done when the progress
106
+ is `complete`, the status `success` and there is no `error`. If you never see that,
107
+ check the vehicle before sending it again. This unlocks a real vehicle, so keep the
108
+ keys secret.
@@ -0,0 +1,93 @@
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
+ ```
53
+
54
+ ### Commands
55
+
56
+ `send_parallax_command` sends a signed command, such as unlocking
57
+ (`parallax.UNLOCK_RVM` with `parallax.encode_unlock()`; `LOCK_RVM` and
58
+ `encode_lock()` to lock), and `subscribe_for_parallax_command_state` follows it.
59
+ Subscribe first, then send:
60
+
61
+ ```python
62
+ import uuid
63
+
64
+ from rivian import parallax
65
+
66
+
67
+ def on_state(data):
68
+ if state := parallax.decode_command_state_message(data):
69
+ print(state["progress"], state["status"], state["error"])
70
+
71
+
72
+ command_id = str(uuid.uuid4())
73
+ unsubscribe = await rivian.subscribe_for_parallax_command_state(
74
+ vehicle_id, command_id, on_state
75
+ )
76
+ if unsubscribe:
77
+ await rivian.send_parallax_command(
78
+ vehicle_id,
79
+ parallax.UNLOCK_RVM,
80
+ parallax.encode_unlock(),
81
+ identity_id=identity_id,
82
+ vehicle_key=vehicle_key,
83
+ private_key=private_key,
84
+ command_id=command_id,
85
+ )
86
+ ... # wait for the progress "complete", or give up
87
+ await unsubscribe()
88
+ ```
89
+
90
+ The keys are those of `send_vehicle_command`. The command is done when the progress
91
+ is `complete`, the status `success` and there is no `error`. If you never see that,
92
+ check the vehicle before sending it again. This unlocks a real vehicle, so keep the
93
+ keys secret.
@@ -30,9 +30,12 @@ dev = [
30
30
  "python-dotenv>=1.0,<1.3",
31
31
  "aresponses>=3.0,<4.0",
32
32
  "mypy>=1.7,<3.0",
33
- "ruff>=0.16,<0.17",
33
+ "ruff>=0.16,<0.18",
34
34
  "pre-commit>=4.6.1,<5",
35
35
  "types-protobuf>=6.32.1.20260221",
36
+ # Generates the committed *_pb2 modules. Newer releases emit protobuf 7.x
37
+ # gencode, which won't import on the protobuf 6.32 that Home Assistant
38
+ # 2026.9 pins, so bump this only with the minimum supported HA version.
36
39
  "grpcio-tools==1.80.0",
37
40
  "pytest-cov>=7.0,<8",
38
41
  ]
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '2.3.0'
4
+ __version__ = VERSION = '2.4.0'
@@ -7,7 +7,7 @@ import logging
7
7
  import platform
8
8
  import secrets
9
9
 
10
- from .utils import generate_ble_command_hmac
10
+ from .utils import generate_command_hmac
11
11
 
12
12
  _LOGGER = logging.getLogger(__name__)
13
13
 
@@ -96,7 +96,7 @@ async def pair_phone(
96
96
 
97
97
  _LOGGER.debug("Exchanging nonce")
98
98
  phone_nonce = secrets.token_bytes(16)
99
- hmac = generate_ble_command_hmac(phone_nonce, vehicle_key, private_key)
99
+ hmac = generate_command_hmac(phone_nonce, vehicle_key, private_key)
100
100
  await client.write_gatt_char(
101
101
  PHONE_NONCE_VEHICLE_NONCE_UUID, phone_nonce + hmac
102
102
  )
@@ -185,8 +185,8 @@ class VehicleCommand(StrEnum):
185
185
  VEHICLE_CABIN_PRECONDITION_DISABLE = "VEHICLE_CABIN_PRECONDITION_DISABLE"
186
186
  VEHICLE_CABIN_PRECONDITION_ENABLE = "VEHICLE_CABIN_PRECONDITION_ENABLE"
187
187
  # Gen2 HVAC Controls
188
- CABIN_HVAC_THIRD_ROW_LEFT_SEAT_HEAT = "CABIN_HVAC_THIRD_ROW_LEFT_SEAT_HEAT"
189
- CABIN_HVAC_THIRD_ROW_RIGHT_SEAT_HEAT = "CABIN_HVAC_THIRD_ROW_RIGHT_SEAT_HEAT"
188
+ CABIN_HVAC_3RD_ROW_REAR_LEFT_SEAT_HEAT = "CABIN_HVAC_3RD_ROW_REAR_LEFT_SEAT_HEAT"
189
+ CABIN_HVAC_3RD_ROW_REAR_RIGHT_SEAT_HEAT = "CABIN_HVAC_3RD_ROW_REAR_RIGHT_SEAT_HEAT"
190
190
 
191
191
  # Closures
192
192
  LOCK_ALL_CLOSURES_FEEDBACK = "LOCK_ALL_CLOSURES_FEEDBACK"
@@ -10,10 +10,18 @@ Public API:
10
10
  encode_vehicle_operation(rvm, payload=b"", ...) -> str
11
11
  Builds a `send_vehicle_operation` request; an empty payload reads
12
12
  the RVM, anything else (e.g. `encode_climate_hold_setting`) writes it.
13
+ encode_command(vehicle_id, rvm, payload, ...) -> str
14
+ Builds and signs a `send_parallax_command` request, with a payload
15
+ such as `encode_unlock()`.
16
+ LOCK_RVM / encode_lock(), UNLOCK_RVM / encode_unlock()
17
+ The RVM and payload of a signed lock or unlock command.
18
+ decode_command_state_message(data) -> dict | None
19
+ Reads a raw `subscribe_for_parallax_command_state` message.
13
20
 
14
21
  Layout:
15
- core.py - registry, payload parsing and shared helpers
16
- proto/ - .proto schemas and their generated *_pb2 modules
22
+ core.py - registry, payload parsing and shared helpers
23
+ command.py - signed remote commands and their state messages
24
+ proto/ - .proto schemas and their generated *_pb2 modules
17
25
  <topic prefix>.py - one module per RVM topic prefix (body.*, etc.)
18
26
 
19
27
  To add a decoder, decorate it with
@@ -44,11 +52,13 @@ from . import ( # noqa: F401 (imported to register their decoders)
44
52
  vehicle,
45
53
  vehicle_access,
46
54
  )
55
+ from .body import LOCK_RVM, UNLOCK_RVM, encode_lock, encode_unlock
47
56
  from .comfort import (
48
57
  CLIMATE_HOLD_SETTING_RVM,
49
58
  CLIMATE_HOLD_STATUS_RVM,
50
59
  encode_climate_hold_setting,
51
60
  )
61
+ from .command import decode_command_state_message, encode_command
52
62
  from .core import (
53
63
  RVMDecoder,
54
64
  decode_parallax_message,
@@ -75,9 +85,15 @@ __all__ = [
75
85
  "CHARGING_RVMS",
76
86
  "CLIMATE_HOLD_SETTING_RVM",
77
87
  "CLIMATE_HOLD_STATUS_RVM",
88
+ "LOCK_RVM",
78
89
  "PARALLAX_RVMS",
90
+ "UNLOCK_RVM",
91
+ "decode_command_state_message",
79
92
  "decode_parallax_message",
80
93
  "decode_parallax_subscription_message",
81
94
  "encode_climate_hold_setting",
95
+ "encode_command",
96
+ "encode_lock",
97
+ "encode_unlock",
82
98
  "encode_vehicle_operation",
83
99
  ]
@@ -7,6 +7,9 @@ from typing import Any, Final
7
7
  from .core import _LOGGER, RVMDecoder, _enum, _present
8
8
  from .proto import body_pb2
9
9
 
10
+ LOCK_RVM: Final = "body.locks.lock"
11
+ UNLOCK_RVM: Final = "body.locks.unlock"
12
+
10
13
  _CLOSURE_MAP: Final[dict[int, str]] = {
11
14
  body_pb2.DOOR_FRONT_LEFT: "doorFrontLeftClosed",
12
15
  body_pb2.DOOR_FRONT_RIGHT: "doorFrontRightClosed",
@@ -173,6 +176,16 @@ def decode_closures(m: body_pb2.ClosuresState) -> dict[str, Any]:
173
176
  return result
174
177
 
175
178
 
179
+ def encode_lock() -> bytes:
180
+ """Encode a body.locks.lock payload, which has no fields."""
181
+ return body_pb2.LockCommand().SerializeToString()
182
+
183
+
184
+ def encode_unlock() -> bytes:
185
+ """Encode a body.locks.unlock payload that unlocks everything."""
186
+ return body_pb2.UnlockCommand(all=True).SerializeToString()
187
+
188
+
176
189
  @RVMDecoder.register("body.locks.states", body_pb2.LocksState)
177
190
  def decode_locks(m: body_pb2.LocksState) -> dict[str, Any]:
178
191
  """body.locks.states — lock state of every lock.
@@ -0,0 +1,168 @@
1
+ """Encoder and decoder for signed Parallax remote commands.
2
+
3
+ A command such as `body.locks.unlock` is sent with the `sendParallaxPayload`
4
+ mutation (`Rivian.send_parallax_command`). Its payload is an `Operation` in an
5
+ `Envelope`, signed with the secret that `Rivian.send_vehicle_command` derives from
6
+ the phone's key and the vehicle's, but over the operation's bytes. In testing,
7
+ Rivian woke a sleeping vehicle for it.
8
+
9
+ The `vehicleCommandStatePx` subscription
10
+ (`Rivian.subscribe_for_parallax_command_state`) reports on the command as it
11
+ goes: the vehicle waking up, validating and executing it, then the result.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import base64
17
+ import time
18
+ import uuid
19
+ from typing import Any, Final
20
+
21
+ from google.protobuf.message import DecodeError
22
+
23
+ from ..utils import generate_command_hmac
24
+ from .core import _LOGGER, _debug_message, _enum, _present
25
+ from .proto import command_pb2
26
+
27
+ _PROGRESS_MAP: Final[dict[int, str]] = {
28
+ command_pb2.VEHICLE_MODEL_OP_PROGRESS_UNSPECIFIED: "unspecified",
29
+ command_pb2.VEHICLE_MODEL_OP_PROGRESS_VEHICLE_WAKING_UP: "waking_up",
30
+ command_pb2.VEHICLE_MODEL_OP_PROGRESS_VEHICLE_VALIDATING: "validating",
31
+ command_pb2.VEHICLE_MODEL_OP_PROGRESS_VEHICLE_EXECUTING: "executing",
32
+ command_pb2.VEHICLE_MODEL_OP_PROGRESS_VEHICLE_COMPLETE: "complete",
33
+ }
34
+
35
+ _STATUS_MAP: Final[dict[int, str]] = {
36
+ command_pb2.RESPONSE_STATUS_UNDEFINED: "undefined",
37
+ command_pb2.RESPONSE_STATUS_SUCCESS: "success",
38
+ command_pb2.RESPONSE_STATUS_FAIL: "failed",
39
+ command_pb2.RESPONSE_STATUS_INVALID_REQUEST: "invalid_request",
40
+ command_pb2.RESPONSE_STATUS_NOT_SUPPORTED: "not_supported",
41
+ command_pb2.RESPONSE_STATUS_TIMEOUT: "timeout",
42
+ }
43
+
44
+
45
+ def encode_command(
46
+ vehicle_id: str,
47
+ rvm: str,
48
+ payload: bytes,
49
+ *,
50
+ identity_id: str,
51
+ vehicle_key: str,
52
+ private_key: str,
53
+ command_id: str | None = None,
54
+ timestamp: float | None = None,
55
+ ) -> str:
56
+ """Encode and sign a command, as the payload of `sendParallaxPayload`.
57
+
58
+ `payload` is the RVM's message, e.g. `encode_unlock()` for `UNLOCK_RVM`;
59
+ nothing checks that they go together. `identity_id`, `vehicle_key` and
60
+ `private_key` are the ones `send_vehicle_command` takes. The command id, a
61
+ UUID, is a new one unless `command_id` is given (`ValueError` if it isn't
62
+ one); `timestamp` is epoch milliseconds, now unless given.
63
+
64
+ The result is signed and can be replayed until the vehicle rejects its
65
+ timestamp, so treat it as a secret.
66
+ """
67
+ command_id_bytes = (
68
+ uuid.uuid4().bytes if command_id is None else uuid.UUID(command_id).bytes
69
+ )
70
+ timestamp_ms = int(time.time() * 1000 if timestamp is None else timestamp)
71
+ stamp = command_pb2.Timestamp(
72
+ seconds=timestamp_ms // 1000, nanos=timestamp_ms % 1000 * 1_000_000
73
+ )
74
+ operation = command_pb2.Operation(
75
+ rvm=rvm,
76
+ type=command_pb2.PARALLAX_OPERATION_REQUEST,
77
+ command_id=command_id_bytes,
78
+ payload=payload,
79
+ created_at=stamp,
80
+ vehicle_id=vehicle_id,
81
+ ).SerializeToString()
82
+ envelope = command_pb2.Envelope(
83
+ composite=command_pb2.Composite(bytes_val=operation),
84
+ last_update=stamp,
85
+ correlation_id=command_id_bytes,
86
+ security_info=command_pb2.SecurityInfo(
87
+ signature=command_pb2.Signature(
88
+ type=command_pb2.DATA_SIGNATURE_TYPE_HMAC_SHA256,
89
+ # The raw HMAC-SHA256 of the data, as BLE commands use too
90
+ value=generate_command_hmac(operation, vehicle_key, private_key),
91
+ ),
92
+ mapped_identity=identity_id,
93
+ ),
94
+ )
95
+ return base64.b64encode(envelope.SerializeToString()).decode("utf-8")
96
+
97
+
98
+ def decode_command_state(payload: str) -> dict[str, Any] | None:
99
+ """Decode the payload of a `vehicleCommandStatePx` message.
100
+
101
+ Returns None if the payload is empty or can't be read.
102
+
103
+ Fields:
104
+ commandId: str | None — the command's UUID
105
+ rvm: str
106
+ progress: str | int — "unspecified" | "waking_up" | "validating" |
107
+ "executing" | "complete". Any of the ones before "complete" can be
108
+ skipped, and "waking_up" has come more than once for one command.
109
+ status: str | int | None — how the vehicle answered ("undefined" |
110
+ "success" | "failed" | "invalid_request" | "not_supported" |
111
+ "timeout"); None if the message has no result
112
+ error: dict | None — {"code": int, "message": str}, from the result or,
113
+ if the whole operation failed, from the operation error (which has
114
+ no status)
115
+
116
+ A value this library doesn't know comes back as the number it was sent as.
117
+
118
+ The vehicle has done the command when the progress is "complete", the status
119
+ "success" and there is no error. "failed", "invalid_request" and
120
+ "not_supported" mean it has not. With "timeout" or "undefined", or if no
121
+ "complete" message comes at all, it isn't known, and the vehicle may do it
122
+ later.
123
+ """
124
+ try:
125
+ data = base64.b64decode(payload)
126
+ message = command_pb2.CommandState.FromString(data)
127
+ except (TypeError, ValueError, DecodeError):
128
+ _LOGGER.debug("Failed to decode command state", exc_info=True)
129
+ return None
130
+ if not data:
131
+ return None
132
+ _debug_message("command state", message)
133
+
134
+ status: str | int | None = None
135
+ error: dict[str, Any] | None = None
136
+ if (result := _present(message, "data")) is not None:
137
+ status = _enum(_STATUS_MAP, result.response_status, what="command status")
138
+ if (result_error := _present(result, "error")) is not None:
139
+ error = {"code": result_error.code, "message": result_error.message}
140
+ elif (operation_error := _present(message, "operation_error")) is not None:
141
+ error = {"code": operation_error.error, "message": operation_error.message}
142
+ return {
143
+ "commandId": (
144
+ str(uuid.UUID(bytes=message.command_id))
145
+ if len(message.command_id) == 16
146
+ else None
147
+ ),
148
+ "rvm": message.rvm,
149
+ "progress": _enum(_PROGRESS_MAP, message.progress, what="command progress"),
150
+ "status": status,
151
+ "error": error,
152
+ }
153
+
154
+
155
+ def decode_command_state_message(data: dict[str, Any]) -> dict[str, Any] | None:
156
+ """Decode a raw `subscribe_for_parallax_command_state` message.
157
+
158
+ Unwraps `payload.data.vehicleCommandStatePx.payload` for
159
+ `decode_command_state`; returns None if it's missing or can't be read. That
160
+ includes a message that carries GraphQL errors (`data["payload"]["errors"]`)
161
+ instead of a state.
162
+ """
163
+ payload: Any = data
164
+ for key in ("payload", "data", "vehicleCommandStatePx", "payload"):
165
+ if not isinstance(payload, dict):
166
+ return None
167
+ payload = payload.get(key)
168
+ return decode_command_state(payload)
@@ -76,6 +76,16 @@ def _describe_unknowns(message: Message, path: str = "") -> list[str]:
76
76
  return found
77
77
 
78
78
 
79
+ def _debug_message(label: str, message: Message) -> None:
80
+ """Debug-log a parsed `message` and anything its schema doesn't define."""
81
+ if _LOGGER.isEnabledFor(logging.DEBUG):
82
+ _LOGGER.debug(
83
+ "%s: %s", label, text_format.MessageToString(message, as_one_line=True)
84
+ )
85
+ for unknown in _describe_unknowns(message):
86
+ _LOGGER.debug("%s: %s", label, unknown)
87
+
88
+
79
89
  class RVMDecoder:
80
90
  """Registry of RVM topic -> (message class, decoder).
81
91
 
@@ -124,12 +134,7 @@ def _decode_rvm_payload(payload: str | None, rvm: str) -> dict[str, Any]:
124
134
  except Exception:
125
135
  _LOGGER.debug("Failed to decode %s payload", rvm, exc_info=True)
126
136
  return {}
127
- if _LOGGER.isEnabledFor(logging.DEBUG):
128
- _LOGGER.debug(
129
- "%s: %s", rvm, text_format.MessageToString(message, as_one_line=True)
130
- )
131
- for unknown in _describe_unknowns(message):
132
- _LOGGER.debug("%s: %s", rvm, unknown)
137
+ _debug_message(rvm, message)
133
138
  return result
134
139
 
135
140
 
@@ -53,11 +53,19 @@ def _decode_schedule(schedule: _Schedules.Schedule) -> dict[str, Any]:
53
53
  entry["name"] = v
54
54
  entry["isEnabled"] = schedule.is_enabled
55
55
 
56
- occurrence = schedule.occurrence
57
- entry["occurrence"] = {
58
- "days": [day for field, day in _WEEKDAYS if getattr(occurrence.days, field)],
59
- "startsAtMin": occurrence.starts_at,
60
- }
56
+ if schedule.HasField("single_occurrence"):
57
+ starts_at = schedule.single_occurrence.starts_at
58
+ entry["occurrence"] = {
59
+ "startsAt": from_epoch(starts_at.seconds + starts_at.nanos / 1e9)
60
+ }
61
+ else:
62
+ occurrence = schedule.occurrence
63
+ entry["occurrence"] = {
64
+ "days": [
65
+ day for field, day in _WEEKDAYS if getattr(occurrence.days, field)
66
+ ],
67
+ "startsAtMin": occurrence.starts_at,
68
+ }
61
69
 
62
70
  settings = schedule.departure_settings
63
71
  comfort = settings.comfort_settings
@@ -87,7 +95,9 @@ def decode_departure_schedules(m: departure_pb2.DepartureSchedules) -> dict[str,
87
95
  departureSchedules: list[dict] — the gateway's DepartureSchedule shape:
88
96
  id: str, name: str, isEnabled: bool
89
97
  occurrence: {"days": list[str] ("monday" ... "sunday"; order
90
- inferred), "startsAtMin": int (minutes after local midnight)}
98
+ inferred), "startsAtMin": int (minutes after local midnight)},
99
+ or {"startsAt": datetime} for the one-off "s-" schedule a
100
+ departNow adds (departing 10 minutes after the call)
91
101
  departureSettings: {"shouldOverrideChargeSchedule": bool,
92
102
  "comfortSettings": {"cabinTempCelsius": float,
93
103
  "frontDefogDefrost": "off" | "defog" | "defrost",
@@ -310,7 +310,9 @@ def decode_trip_info(m: navigation_pb2.TripInfo) -> dict[str, Any]:
310
310
  and incidents ({firstIndex, lastIndex, type})
311
311
  overviewPolyline: str — Google-encoded, whole trip
312
312
  nextWaypointDepartureTime: datetime — the next stop's departure if
313
- it's a charging stop, else its arrival
313
+ it's a charging stop, else its arrival. It stops updating once
314
+ the first stop is reached, keeping that stop's time for the
315
+ rest of the trip; use waypoints[0] instead
314
316
  finalStateOfCharge, finalRangeRemaining: float — the last
315
317
  waypoint's (percent, meters)
316
318
  socIsBelowLimit: bool
@@ -346,9 +348,11 @@ def decode_trip_progress(m: navigation_pb2.TripProgress) -> dict[str, Any]:
346
348
  """navigation.navigation_service.trip_progress — live progress on the active trip.
347
349
 
348
350
  Fields:
349
- nextWaypointArrivalTime: datetime — live ETA
351
+ nextWaypointArrivalTime: datetime — live ETA, to the minute
350
352
  finalDestinationArrivalTime: datetime — live ETA for the last
351
- waypoint; missing for one message after the waypoints change
353
+ waypoint, to the minute; missing while the route is
354
+ recalculating and on the stale progress the vehicle keeps
355
+ resending after navigation ends
352
356
  nextStopIndex: int
353
357
  distanceRemaining, durationRemaining: float — to the next waypoint
354
358
  (meters, seconds)
@@ -266,3 +266,14 @@ enum WiperFluidState {
266
266
  WIPER_FLUID_STATE_LOW = 2;
267
267
  WIPER_FLUID_STATE_EMPTY = 3;
268
268
  }
269
+
270
+ // body.locks.unlock, the payload of a signed command (see ../command.py)
271
+ message UnlockCommand {
272
+ oneof target {
273
+ bool all = 1; // the app always sets this
274
+ bool user_preference = 2; // in the app's schema; its button never sends it
275
+ }
276
+ }
277
+
278
+ // body.locks.lock, the payload of a signed command. It has no fields.
279
+ message LockCommand {}
@@ -0,0 +1,88 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: rivian/parallax/proto/body.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'rivian/parallax/proto/body.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n rivian/parallax/proto/body.proto\x12\x14rivian.parallax.body\"\x89\x06\n\rClosuresState\x12<\n\x07\x63losure\x18\x01 \x03(\x0b\x32+.rivian.parallax.body.ClosuresState.Closure\x1a\xb9\x05\n\x07\x43losure\x12+\n\x02id\x18\x01 \x01(\x0e\x32\x1f.rivian.parallax.body.ClosureId\x12\x36\n\x05state\x18\x02 \x01(\x0e\x32\".rivian.parallax.body.ClosureStateH\x01\x88\x01\x01\x12\x1d\n\x15open_position_percent\x18\x03 \x01(\x05\x12\x31\n\x05\x66\x61ult\x18\x04 \x01(\x0e\x32\".rivian.parallax.body.ClosureFault\x12\x46\n\x13windows_next_action\x18\x05 \x01(\x0e\x32\'.rivian.parallax.body.WindowsNextActionH\x00\x12\x42\n\x11\x66runk_next_action\x18\x06 \x01(\x0e\x32%.rivian.parallax.body.FrunkNextActionH\x00\x12H\n\x14liftgate_next_action\x18\x07 \x01(\x0e\x32(.rivian.parallax.body.LiftgateNextActionH\x00\x12G\n\x14side_bin_next_action\x18\x08 \x01(\x0e\x32\'.rivian.parallax.body.SideBinNextActionH\x00\x12H\n\x14tailgate_next_action\x18\t \x01(\x0e\x32(.rivian.parallax.body.TailgateNextActionH\x00\x12V\n\x1c\x63harge_port_door_next_action\x18\n \x01(\x0e\x32..rivian.parallax.body.ChargePortDoorNextActionH\x00\x12\x1d\n\x13tonneau_next_action\x18\x0b \x01(\x05H\x00\x42\r\n\x0bnext_actionB\x08\n\x06_state\"\xe3\x01\n\nLocksState\x12\x33\n\x04lock\x18\x01 \x03(\x0b\x32%.rivian.parallax.body.LocksState.Lock\x1a\x9f\x01\n\x04Lock\x12(\n\x02id\x18\x01 \x01(\x0e\x32\x1c.rivian.parallax.body.LockId\x12\x33\n\x05state\x18\x02 \x01(\x0e\x32\x1f.rivian.parallax.body.LockStateH\x00\x88\x01\x01\x12.\n\x05\x66\x61ult\x18\x03 \x01(\x0e\x32\x1f.rivian.parallax.body.LockFaultB\x08\n\x06_state\"\xd1\x01\n\x0cWindowsState\x12\x39\n\x06window\x18\x01 \x03(\x0b\x32).rivian.parallax.body.WindowsState.Window\x1a\x85\x01\n\x06Window\x12\x36\n\x08instance\x18\x01 \x01(\x0e\x32$.rivian.parallax.body.WindowInstance\x12\x43\n\x12\x63\x61libration_status\x18\x02 \x01(\x0e\x32\'.rivian.parallax.body.CalibrationStatus\"\x9b\x01\n\x0cTrailerState\x12<\n\x08presence\x18\x01 \x01(\x0e\x32%.rivian.parallax.body.TrailerPresenceH\x00\x88\x01\x01\x12@\n\x11rear_hitch_status\x18\x02 \x01(\x0e\x32%.rivian.parallax.body.RearHitchStatusB\x0b\n\t_presence\"G\n\x0fWiperFluidLevel\x12\x34\n\x05state\x18\x02 \x01(\x0e\x32%.rivian.parallax.body.WiperFluidState\"C\n\rUnlockCommand\x12\r\n\x03\x61ll\x18\x01 \x01(\x08H\x00\x12\x19\n\x0fuser_preference\x18\x02 \x01(\x08H\x00\x42\x08\n\x06target\"\r\n\x0bLockCommand*\xe8\x02\n\tClosureId\x12\x1a\n\x16\x43LOSURE_ID_UNSPECIFIED\x10\x00\x12\x13\n\x0f\x44OOR_FRONT_LEFT\x10\x01\x12\x14\n\x10\x44OOR_FRONT_RIGHT\x10\x02\x12\x12\n\x0e\x44OOR_REAR_LEFT\x10\x03\x12\x13\n\x0f\x44OOR_REAR_RIGHT\x10\x04\x12\t\n\x05\x46RUNK\x10\x05\x12\x0c\n\x08TAILGATE\x10\x06\x12\x0c\n\x08LIFTGATE\x10\x07\x12\x11\n\rSIDE_BIN_LEFT\x10\x08\x12\x12\n\x0eSIDE_BIN_RIGHT\x10\t\x12\x0f\n\x0b\x43HARGE_PORT\x10\n\x12\x0b\n\x07TONNEAU\x10\x0b\x12\x15\n\x11WINDOW_FRONT_LEFT\x10\x0c\x12\x16\n\x12WINDOW_FRONT_RIGHT\x10\r\x12\x14\n\x10WINDOW_REAR_LEFT\x10\x0e\x12\x15\n\x11WINDOW_REAR_RIGHT\x10\x0f\x12\x0f\n\x0bWINDOW_REAR\x10\x10\x12\x12\n\rGROUP_WINDOWS\x10\x90N*\xad\x01\n\x0c\x43losureState\x12\x1d\n\x19\x43LOSURE_STATE_UNSPECIFIED\x10\x00\x12\x16\n\x12\x43LOSURE_STATE_OPEN\x10\x01\x12\x18\n\x14\x43LOSURE_STATE_CLOSED\x10\x02\x12\x16\n\x12\x43LOSURE_STATE_AJAR\x10\x03\x12\x19\n\x15\x43LOSURE_STATE_OPENING\x10\x04\x12\x19\n\x15\x43LOSURE_STATE_CLOSING\x10\x05*\xf3\x02\n\x06LockId\x12\x17\n\x13LOCK_ID_UNSPECIFIED\x10\x00\x12\x18\n\x14LOCK_DOOR_FRONT_LEFT\x10\x01\x12\x19\n\x15LOCK_DOOR_FRONT_RIGHT\x10\x02\x12\x17\n\x13LOCK_DOOR_REAR_LEFT\x10\x03\x12\x18\n\x14LOCK_DOOR_REAR_RIGHT\x10\x04\x12\x0e\n\nLOCK_FRUNK\x10\x05\x12\x11\n\rLOCK_TAILGATE\x10\x06\x12\x11\n\rLOCK_LIFTGATE\x10\x07\x12\x16\n\x12LOCK_SIDE_BIN_LEFT\x10\x08\x12\x17\n\x13LOCK_SIDE_BIN_RIGHT\x10\t\x12\x14\n\x10LOCK_CHARGE_PORT\x10\n\x12\x17\n\x13LOCK_TRUNK_SECURITY\x10\x0b\x12\x17\n\x13LOCK_CENTER_CONSOLE\x10\x0c\x12\x12\n\x0eLOCK_GLOVE_BOX\x10\r\x12\x13\n\x0fLOCK_GEAR_GUARD\x10\x0e\x12\x10\n\x0cLOCK_TONNEAU\x10\x0f*z\n\tLockState\x12\x1a\n\x16LOCK_STATE_UNSPECIFIED\x10\x00\x12\x15\n\x11LOCK_STATE_LOCKED\x10\x01\x12\x17\n\x13LOCK_STATE_UNLOCKED\x10\x02\x12!\n\x1dLOCK_STATE_PARTIALLY_UNLOCKED\x10\x03*?\n\tLockFault\x12\x1a\n\x16LOCK_FAULT_UNSPECIFIED\x10\x00\x12\x16\n\x12LOCK_FAULT_FAULTED\x10\x01*\xcb\x01\n\x0eWindowInstance\x12\x1f\n\x1bWINDOW_INSTANCE_UNSPECIFIED\x10\x00\x12\x1e\n\x1aWINDOW_INSTANCE_FRONT_LEFT\x10\x01\x12\x1f\n\x1bWINDOW_INSTANCE_FRONT_RIGHT\x10\x02\x12\x1d\n\x19WINDOW_INSTANCE_REAR_LEFT\x10\x03\x12\x1e\n\x1aWINDOW_INSTANCE_REAR_RIGHT\x10\x04\x12\x18\n\x14WINDOW_INSTANCE_REAR\x10\x05*\x81\x01\n\x11\x43\x61librationStatus\x12\"\n\x1e\x43\x41LIBRATION_STATUS_UNSPECIFIED\x10\x00\x12!\n\x1d\x43\x41LIBRATION_STATUS_CALIBRATED\x10\x01\x12%\n!CALIBRATION_STATUS_NOT_CALIBRATED\x10\x02*\xd8\x01\n\x0fRearHitchStatus\x12!\n\x1dREAR_HITCH_STATUS_UNSPECIFIED\x10\x00\x12!\n\x1dREAR_HITCH_STATUS_NOT_PRESENT\x10\x01\x12\x1f\n\x1bREAR_HITCH_STATUS_ACCESSORY\x10\x02\x12\x1e\n\x1aREAR_HITCH_STATUS_TRAILER1\x10\x03\x12\x1e\n\x1aREAR_HITCH_STATUS_TRAILER2\x10\x04\x12\x1e\n\x1aREAR_HITCH_STATUS_TRAILER3\x10\x05*\xaa\x01\n\x0c\x43losureFault\x12\x1d\n\x19\x43LOSURE_FAULT_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x43LOSURE_FAULT_NO_FAULT\x10\x01\x12\x19\n\x15\x43LOSURE_FAULT_GENERAL\x10\x02\x12&\n\"CLOSURE_FAULT_NO_POWERED_OPERATION\x10\x03\x12\x1c\n\x18\x43LOSURE_FAULT_OBSTRUCTED\x10\x04*\x97\x01\n\x0fTrailerPresence\x12 \n\x1cTRAILER_PRESENCE_UNSPECIFIED\x10\x00\x12\x17\n\x13TRAILER_NOT_PRESENT\x10\x01\x12\x13\n\x0fTRAILER_PRESENT\x10\x02\x12\x1f\n\x1bTRAILER_PRESENT_WITH_BRAKES\x10\x03\x12\x13\n\x0fTRAILER_INVALID\x10\x04*\x9f\x04\n\x18\x43hargePortDoorNextAction\x12\x18\n\x14\x43HARGE_PORT_DOOR_SNA\x10\x00\x12!\n\x1d\x43HARGE_PORT_DOOR_OPEN_ALLOWED\x10\x01\x12\'\n#CHARGE_PORT_DOOR_OPEN_NOT_AVAILABLE\x10\x02\x12-\n)CHARGE_PORT_DOOR_OPEN_NOT_ALLOWED_FAULTED\x10\x03\x12\x1c\n\x18\x43HARGE_PORT_DOOR_OPENING\x10\x04\x12\"\n\x1e\x43HARGE_PORT_DOOR_CLOSE_ALLOWED\x10\x05\x12;\n7CHARGE_PORT_DOOR_OBSTRUCTED_WHILE_OPENING_CLOSE_ALLOWED\x10\x06\x12:\n6CHARGE_PORT_DOOR_OBSTRUCTED_WHILE_OPENING_OPEN_ALLOWED\x10\x07\x12;\n7CHARGE_PORT_DOOR_OBSTRUCTED_WHILE_CLOSING_CLOSE_ALLOWED\x10\x08\x12(\n$CHARGE_PORT_DOOR_CLOSE_NOT_AVAILABLE\x10\t\x12.\n*CHARGE_PORT_DOOR_CLOSE_NOT_ALLOWED_FAULTED\x10\n\x12\x1c\n\x18\x43HARGE_PORT_DOOR_CLOSING\x10\x0b*\xb0\x03\n\x0f\x46runkNextAction\x12\r\n\tFRUNK_SNA\x10\x00\x12\x16\n\x12\x46RUNK_OPEN_ALLOWED\x10\x01\x12\x17\n\x13\x46RUNK_CLOSE_ALLOWED\x10\x02\x12\x11\n\rFRUNK_OPENING\x10\x03\x12\x11\n\rFRUNK_CLOSING\x10\x04\x12\x1c\n\x18\x46RUNK_OPEN_NOT_AVAILABLE\x10\x05\x12\x1d\n\x19\x46RUNK_CLOSE_NOT_AVAILABLE\x10\x06\x12\"\n\x1e\x46RUNK_OPEN_NOT_ALLOWED_FAULTED\x10\x07\x12#\n\x1f\x46RUNK_CLOSE_NOT_ALLOWED_FAULTED\x10\x08\x12)\n%FRUNK_OPEN_ALLOWED_NO_POWER_OPERATION\x10\t\x12.\n*FRUNK_CLOSE_NOT_ALLOWED_NO_POWER_OPERATION\x10\n\x12*\n&FRUNK_OBSTRUCTED_OPENING_CLOSE_ALLOWED\x10\x0b\x12*\n&FRUNK_OBSTRUCTED_CLOSING_CLOSE_ALLOWED\x10\x0c*\xa7\x06\n\x12LiftgateNextAction\x12\x10\n\x0cLIFTGATE_SNA\x10\x00\x12\x19\n\x15LIFTGATE_OPEN_ALLOWED\x10\x01\x12\x1a\n\x16LIFTGATE_CLOSE_ALLOWED\x10\x02\x12\x14\n\x10LIFTGATE_OPENING\x10\x03\x12\x14\n\x10LIFTGATE_CLOSING\x10\x04\x12\x1f\n\x1bLIFTGATE_OPEN_NOT_AVAILABLE\x10\x05\x12 \n\x1cLIFTGATE_CLOSE_NOT_AVAILABLE\x10\x06\x12%\n!LIFTGATE_OPEN_NOT_ALLOWED_FAULTED\x10\x07\x12&\n\"LIFTGATE_CLOSE_NOT_ALLOWED_FAULTED\x10\x08\x12,\n(LIFTGATE_OPEN_ALLOWED_NO_POWER_OPERATION\x10\t\x12\x31\n-LIFTGATE_CLOSE_NOT_ALLOWED_NO_POWER_OPERATION\x10\n\x12-\n)LIFTGATE_OBSTRUCTED_OPENING_CLOSE_ALLOWED\x10\x0b\x12-\n)LIFTGATE_OBSTRUCTED_CLOSING_CLOSE_ALLOWED\x10\x0c\x12.\n*LIFTGATE_LOWER_GATE_OPEN_CLOSE_NOT_ALLOWED\x10\r\x12&\n\"LIFTGATE_OPENING_PAUSE_NOT_ALLOWED\x10\x0e\x12&\n\"LIFTGATE_CLOSING_PAUSE_NOT_ALLOWED\x10\x0f\x12+\n\'LIFTGATE_OPEN_ALLOWED_OBSTACLE_DETECTED\x10\x10\x12*\n&LIFTGATE_OPEN_ALLOWED_TRAILER_DETECTED\x10\x11\x12,\n(LIFTGATE_CLOSE_ALLOWED_OBSTACLE_DETECTED\x10\x12\x12+\n\'LIFTGATE_CLOSE_ALLOWED_TRAILER_DETECTED\x10\x13\x12\x17\n\x13LIFTGATE_PROCESSING\x10\x14*\xb2\x02\n\x11SideBinNextAction\x12\x10\n\x0cSIDE_BIN_SNA\x10\x00\x12\x19\n\x15SIDE_BIN_OPEN_ALLOWED\x10\x01\x12\x14\n\x10SIDE_BIN_OPENING\x10\x02\x12\x1f\n\x1bSIDE_BIN_OPEN_NOT_AVAILABLE\x10\x03\x12%\n!SIDE_BIN_OPEN_NOT_ALLOWED_FAULTED\x10\x04\x12\x32\n.SIDE_BIN_STUCK_AJAR_WHILE_OPENING_OPEN_ALLOWED\x10\x05\x12-\n)SIDE_BIN_OPEN_ALREADY_NO_ACTION_AVAILABLE\x10\x06\x12/\n+SIDE_BIN_OPEN_ALLOWED_CONFIRM_VEHICLE_ANGLE\x10\x07*\x8c\x03\n\x12TailgateNextAction\x12\x10\n\x0cTAILGATE_SNA\x10\x00\x12\x19\n\x15TAILGATE_OPEN_ALLOWED\x10\x01\x12\x14\n\x10TAILGATE_OPENING\x10\x02\x12\x1f\n\x1bTAILGATE_OPEN_NOT_AVAILABLE\x10\x03\x12%\n!TAILGATE_OPEN_NOT_ALLOWED_FAULTED\x10\x04\x12\x32\n.TAILGATE_STUCK_AJAR_WHILE_OPENING_OPEN_ALLOWED\x10\x05\x12-\n)TAILGATE_OPEN_ALREADY_NO_ACTION_AVAILABLE\x10\x06\x12/\n+TAILGATE_OPEN_ALLOWED_CONFIRM_VEHICLE_ANGLE\x10\x07\x12+\n\'TAILGATE_OPEN_ALLOWED_OBSTACLE_DETECTED\x10\x08\x12*\n&TAILGATE_OPEN_ALLOWED_TRAILER_DETECTED\x10\t*\xb0\x03\n\x11WindowsNextAction\x12\x0f\n\x0bWINDOWS_SNA\x10\x00\x12\x18\n\x14WINDOWS_OPEN_ALLOWED\x10\x01\x12\x19\n\x15WINDOWS_CLOSE_ALLOWED\x10\x02\x12\x13\n\x0fWINDOWS_OPENING\x10\x03\x12\x13\n\x0fWINDOWS_CLOSING\x10\x04\x12\x12\n\x0eWINDOWS_MOVING\x10\x05\x12\x1e\n\x1aWINDOWS_OPEN_NOT_AVAILABLE\x10\x06\x12\x1f\n\x1bWINDOWS_CLOSE_NOT_AVAILABLE\x10\x07\x12$\n WINDOWS_OPEN_NOT_ALLOWED_FAULTED\x10\x08\x12%\n!WINDOWS_CLOSE_NOT_ALLOWED_FAULTED\x10\t\x12\x32\n.WINDOWS_OBSTRUCTED_WHILE_CLOSING_CLOSE_ALLOWED\x10\n\x12*\n&WINDOWS_CLOSE_NOT_ALLOWED_UNCALIBRATED\x10\x0b\x12)\n%WINDOWS_OPEN_NOT_ALLOWED_UNCALIBRATED\x10\x0c*\x8a\x01\n\x0fWiperFluidState\x12!\n\x1dWIPER_FLUID_STATE_UNSPECIFIED\x10\x00\x12\x1c\n\x18WIPER_FLUID_STATE_NORMAL\x10\x01\x12\x19\n\x15WIPER_FLUID_STATE_LOW\x10\x02\x12\x1b\n\x17WIPER_FLUID_STATE_EMPTY\x10\x03\x62\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'rivian.parallax.proto.body_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ DESCRIPTOR._loaded_options = None
34
+ _globals['_CLOSUREID']._serialized_start=1596
35
+ _globals['_CLOSUREID']._serialized_end=1956
36
+ _globals['_CLOSURESTATE']._serialized_start=1959
37
+ _globals['_CLOSURESTATE']._serialized_end=2132
38
+ _globals['_LOCKID']._serialized_start=2135
39
+ _globals['_LOCKID']._serialized_end=2506
40
+ _globals['_LOCKSTATE']._serialized_start=2508
41
+ _globals['_LOCKSTATE']._serialized_end=2630
42
+ _globals['_LOCKFAULT']._serialized_start=2632
43
+ _globals['_LOCKFAULT']._serialized_end=2695
44
+ _globals['_WINDOWINSTANCE']._serialized_start=2698
45
+ _globals['_WINDOWINSTANCE']._serialized_end=2901
46
+ _globals['_CALIBRATIONSTATUS']._serialized_start=2904
47
+ _globals['_CALIBRATIONSTATUS']._serialized_end=3033
48
+ _globals['_REARHITCHSTATUS']._serialized_start=3036
49
+ _globals['_REARHITCHSTATUS']._serialized_end=3252
50
+ _globals['_CLOSUREFAULT']._serialized_start=3255
51
+ _globals['_CLOSUREFAULT']._serialized_end=3425
52
+ _globals['_TRAILERPRESENCE']._serialized_start=3428
53
+ _globals['_TRAILERPRESENCE']._serialized_end=3579
54
+ _globals['_CHARGEPORTDOORNEXTACTION']._serialized_start=3582
55
+ _globals['_CHARGEPORTDOORNEXTACTION']._serialized_end=4125
56
+ _globals['_FRUNKNEXTACTION']._serialized_start=4128
57
+ _globals['_FRUNKNEXTACTION']._serialized_end=4560
58
+ _globals['_LIFTGATENEXTACTION']._serialized_start=4563
59
+ _globals['_LIFTGATENEXTACTION']._serialized_end=5370
60
+ _globals['_SIDEBINNEXTACTION']._serialized_start=5373
61
+ _globals['_SIDEBINNEXTACTION']._serialized_end=5679
62
+ _globals['_TAILGATENEXTACTION']._serialized_start=5682
63
+ _globals['_TAILGATENEXTACTION']._serialized_end=6078
64
+ _globals['_WINDOWSNEXTACTION']._serialized_start=6081
65
+ _globals['_WINDOWSNEXTACTION']._serialized_end=6513
66
+ _globals['_WIPERFLUIDSTATE']._serialized_start=6516
67
+ _globals['_WIPERFLUIDSTATE']._serialized_end=6654
68
+ _globals['_CLOSURESSTATE']._serialized_start=59
69
+ _globals['_CLOSURESSTATE']._serialized_end=836
70
+ _globals['_CLOSURESSTATE_CLOSURE']._serialized_start=139
71
+ _globals['_CLOSURESSTATE_CLOSURE']._serialized_end=836
72
+ _globals['_LOCKSSTATE']._serialized_start=839
73
+ _globals['_LOCKSSTATE']._serialized_end=1066
74
+ _globals['_LOCKSSTATE_LOCK']._serialized_start=907
75
+ _globals['_LOCKSSTATE_LOCK']._serialized_end=1066
76
+ _globals['_WINDOWSSTATE']._serialized_start=1069
77
+ _globals['_WINDOWSSTATE']._serialized_end=1278
78
+ _globals['_WINDOWSSTATE_WINDOW']._serialized_start=1145
79
+ _globals['_WINDOWSSTATE_WINDOW']._serialized_end=1278
80
+ _globals['_TRAILERSTATE']._serialized_start=1281
81
+ _globals['_TRAILERSTATE']._serialized_end=1436
82
+ _globals['_WIPERFLUIDLEVEL']._serialized_start=1438
83
+ _globals['_WIPERFLUIDLEVEL']._serialized_end=1509
84
+ _globals['_UNLOCKCOMMAND']._serialized_start=1511
85
+ _globals['_UNLOCKCOMMAND']._serialized_end=1578
86
+ _globals['_LOCKCOMMAND']._serialized_start=1580
87
+ _globals['_LOCKCOMMAND']._serialized_end=1593
88
+ # @@protoc_insertion_point(module_scope)