rivian-python-client 2.2.0b6__py3-none-any.whl → 2.2.0b8__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.
Files changed (65) hide show
  1. rivian/__version__.py +1 -1
  2. rivian/parallax/body.py +45 -5
  3. rivian/parallax/charging.py +166 -27
  4. rivian/parallax/comfort.py +85 -37
  5. rivian/parallax/device_table.py +2 -0
  6. rivian/parallax/dynamics.py +1 -1
  7. rivian/parallax/energy.py +67 -7
  8. rivian/parallax/energy_edge_compute.py +10 -6
  9. rivian/parallax/gearguard_streaming.py +3 -5
  10. rivian/parallax/geofence.py +2 -3
  11. rivian/parallax/holiday_celebration.py +59 -9
  12. rivian/parallax/navigation.py +109 -47
  13. rivian/parallax/ota.py +79 -25
  14. rivian/parallax/proto/body.proto +36 -3
  15. rivian/parallax/proto/body_pb2.py +47 -39
  16. rivian/parallax/proto/body_pb2.pyi +60 -8
  17. rivian/parallax/proto/charging.proto +102 -41
  18. rivian/parallax/proto/charging_pb2.py +45 -33
  19. rivian/parallax/proto/charging_pb2.pyi +174 -69
  20. rivian/parallax/proto/comfort.proto +49 -17
  21. rivian/parallax/proto/comfort_pb2.py +49 -43
  22. rivian/parallax/proto/comfort_pb2.pyi +87 -34
  23. rivian/parallax/proto/device_table.proto +1 -0
  24. rivian/parallax/proto/device_table_pb2.py +13 -13
  25. rivian/parallax/proto/device_table_pb2.pyi +4 -2
  26. rivian/parallax/proto/dynamics.proto +3 -3
  27. rivian/parallax/proto/dynamics_pb2.py +26 -26
  28. rivian/parallax/proto/dynamics_pb2.pyi +7 -7
  29. rivian/parallax/proto/energy.proto +51 -12
  30. rivian/parallax/proto/energy_edge_compute.proto +19 -3
  31. rivian/parallax/proto/energy_edge_compute_pb2.py +18 -14
  32. rivian/parallax/proto/energy_edge_compute_pb2.pyi +39 -12
  33. rivian/parallax/proto/energy_pb2.py +24 -14
  34. rivian/parallax/proto/energy_pb2.pyi +87 -22
  35. rivian/parallax/proto/gearguard_streaming.proto +6 -8
  36. rivian/parallax/proto/gearguard_streaming_pb2.py +5 -5
  37. rivian/parallax/proto/gearguard_streaming_pb2.pyi +6 -10
  38. rivian/parallax/proto/geofence.proto +2 -3
  39. rivian/parallax/proto/geofence_pb2.py +5 -5
  40. rivian/parallax/proto/geofence_pb2.pyi +2 -4
  41. rivian/parallax/proto/holiday_celebration.proto +27 -16
  42. rivian/parallax/proto/holiday_celebration_pb2.py +18 -14
  43. rivian/parallax/proto/holiday_celebration_pb2.pyi +49 -28
  44. rivian/parallax/proto/navigation.proto +112 -97
  45. rivian/parallax/proto/navigation_pb2.py +39 -41
  46. rivian/parallax/proto/navigation_pb2.pyi +197 -136
  47. rivian/parallax/proto/ota.proto +67 -15
  48. rivian/parallax/proto/ota_pb2.py +43 -31
  49. rivian/parallax/proto/ota_pb2.pyi +117 -32
  50. rivian/parallax/proto/security.proto +15 -4
  51. rivian/parallax/proto/security_pb2.py +32 -30
  52. rivian/parallax/proto/security_pb2.pyi +28 -9
  53. rivian/parallax/proto/vehicle.proto +51 -9
  54. rivian/parallax/proto/vehicle_access.proto +13 -6
  55. rivian/parallax/proto/vehicle_access_pb2.py +8 -6
  56. rivian/parallax/proto/vehicle_access_pb2.pyi +15 -6
  57. rivian/parallax/proto/vehicle_pb2.py +28 -20
  58. rivian/parallax/proto/vehicle_pb2.pyi +81 -11
  59. rivian/parallax/security.py +17 -4
  60. rivian/parallax/vehicle.py +38 -5
  61. rivian/parallax/vehicle_access.py +23 -5
  62. {rivian_python_client-2.2.0b6.dist-info → rivian_python_client-2.2.0b8.dist-info}/METADATA +1 -1
  63. rivian_python_client-2.2.0b8.dist-info/RECORD +94 -0
  64. rivian_python_client-2.2.0b6.dist-info/RECORD +0 -94
  65. {rivian_python_client-2.2.0b6.dist-info → rivian_python_client-2.2.0b8.dist-info}/WHEEL +0 -0
rivian/__version__.py CHANGED
@@ -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.2.0b6'
4
+ __version__ = VERSION = '2.2.0b8'
rivian/parallax/body.py CHANGED
@@ -108,6 +108,28 @@ _LOCK_STATE_MAP: Final[dict[int, str]] = {
108
108
  body_pb2.LOCK_STATE_PARTIALLY_UNLOCKED: "partially_unlocked",
109
109
  }
110
110
 
111
+ _REAR_HITCH_STATUS_MAP: Final[dict[int, str]] = {
112
+ body_pb2.REAR_HITCH_STATUS_NOT_PRESENT: "not_present",
113
+ body_pb2.REAR_HITCH_STATUS_ACCESSORY: "accessory",
114
+ body_pb2.REAR_HITCH_STATUS_TRAILER1: "trailer1",
115
+ body_pb2.REAR_HITCH_STATUS_TRAILER2: "trailer2",
116
+ body_pb2.REAR_HITCH_STATUS_TRAILER3: "trailer3",
117
+ }
118
+
119
+ _WINDOW_MAP: Final[dict[int, str]] = {
120
+ body_pb2.WINDOW_INSTANCE_FRONT_LEFT: "windowFrontLeftCalibrated",
121
+ body_pb2.WINDOW_INSTANCE_FRONT_RIGHT: "windowFrontRightCalibrated",
122
+ body_pb2.WINDOW_INSTANCE_REAR_LEFT: "windowRearLeftCalibrated",
123
+ body_pb2.WINDOW_INSTANCE_REAR_RIGHT: "windowRearRightCalibrated",
124
+ body_pb2.WINDOW_INSTANCE_REAR: "windowRearCalibrated",
125
+ }
126
+
127
+ # GraphQL's windowXCalibrated values.
128
+ _CALIBRATION_MAP: Final[dict[int, str]] = {
129
+ body_pb2.CALIBRATION_STATUS_CALIBRATED: "Calibrated",
130
+ body_pb2.CALIBRATION_STATUS_NOT_CALIBRATED: "Not_Calibrated",
131
+ }
132
+
111
133
  _TRAILER_PRESENCE_MAP: Final[dict[int, str]] = {
112
134
  body_pb2.TRAILER_NOT_PRESENT: "trailer_not_present",
113
135
  body_pb2.TRAILER_PRESENT: "trailer_present",
@@ -176,19 +198,37 @@ def decode_locks(m: body_pb2.LocksState) -> dict[str, Any]:
176
198
 
177
199
 
178
200
  @RVMDecoder.register("body.windows.states", body_pb2.WindowsState)
179
- def decode_windows(_m: body_pb2.WindowsState) -> dict[str, Any]:
180
- """body.windows.states — unmapped."""
181
- return {}
201
+ def decode_windows(m: body_pb2.WindowsState) -> dict[str, Any]:
202
+ """body.windows.states — window calibration.
203
+
204
+ Fields:
205
+ windowFrontLeftCalibrated, windowFrontRightCalibrated,
206
+ windowRearLeftCalibrated, windowRearRightCalibrated,
207
+ windowRearCalibrated: str ("Calibrated" | "Not_Calibrated")
208
+ """
209
+ return {
210
+ _WINDOW_MAP[w.instance]: _enum(
211
+ _CALIBRATION_MAP, w.calibration_status or None, what="window calibration"
212
+ )
213
+ for w in m.window
214
+ if w.instance in _WINDOW_MAP
215
+ }
182
216
 
183
217
 
184
218
  @RVMDecoder.register("body.trailer.state", body_pb2.TrailerState)
185
219
  def decode_trailer_state(m: body_pb2.TrailerState) -> dict[str, Any]:
186
- """body.trailer.state — trailer presence, with or without brakes.
220
+ """body.trailer.state — trailer presence and rear hitch status.
187
221
 
188
222
  Fields:
189
223
  trailerStatus: str
224
+ rearHitchStatus: str | None ("not_present" | "accessory" |
225
+ "trailer1" | "trailer2" | "trailer3")
190
226
  """
191
- result: dict[str, Any] = {}
227
+ result: dict[str, Any] = {
228
+ "rearHitchStatus": _enum(
229
+ _REAR_HITCH_STATUS_MAP, m.rear_hitch_status or None, what="rear hitch"
230
+ )
231
+ }
192
232
  presence = _enum(
193
233
  _TRAILER_PRESENCE_MAP, _present(m, "presence"), what="trailer presence"
194
234
  )
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any, Final
6
6
 
7
+ from ..utils import from_epoch
7
8
  from .core import RVMDecoder, _enum, _present
8
9
  from .proto import charging_pb2
9
10
 
@@ -58,15 +59,75 @@ def decode_schedule_time_window(m: charging_pb2.ScheduleTimeWindow) -> dict[str,
58
59
  return result
59
60
 
60
61
 
62
+ _DERATE_STATUS_MAP: Final[dict[int, str]] = {
63
+ charging_pb2.DERATE_STATUS_NONE: "none",
64
+ charging_pb2.DERATE_STATUS_WARM_ADAPTER: "warm_adapter",
65
+ charging_pb2.DERATE_STATUS_DC_WARM_PLUG: "dc_warm_plug",
66
+ charging_pb2.DERATE_STATUS_AC_WARM_PLUG: "ac_warm_plug",
67
+ charging_pb2.DERATE_STATUS_EVSE_DERATING: "evse_derating",
68
+ charging_pb2.DERATE_STATUS_NEARING_TOC: "nearing_toc",
69
+ charging_pb2.DERATE_STATUS_NEAR_TOC_LFP_BATT_CALIBRATING: "near_toc_lfp_batt_calibrating",
70
+ charging_pb2.DERATE_STATUS_HVAC_PRIORITIZED: "hvac_prioritized",
71
+ charging_pb2.DERATE_STATUS_BATTERY_HEATING: "battery_heating",
72
+ charging_pb2.DERATE_STATUS_BATTERY_COOLING: "battery_cooling",
73
+ charging_pb2.DERATE_STATUS_CELL_THERMAL_LIM_COLD_NO_CURRENT: "cell_thermal_lim_cold_no_current",
74
+ charging_pb2.DERATE_STATUS_CELL_THERMAL_LIM_HOT_NO_CURRENT: "cell_thermal_lim_hot_no_current",
75
+ charging_pb2.DERATE_STATUS_CELL_THERMAL_LIM_COLD: "cell_thermal_lim_cold",
76
+ charging_pb2.DERATE_STATUS_CELL_THERMAL_LIM_HOT: "cell_thermal_lim_hot",
77
+ charging_pb2.DERATE_STATUS_PACK_HARDWARE_THERMAL_LIM: "pack_hardware_thermal_lim",
78
+ charging_pb2.DERATE_STATUS_HIGH_SOC_SIGMA: "high_soc_sigma",
79
+ charging_pb2.DERATE_STATUS_HV_BATTERY_FAULT: "hv_battery_fault",
80
+ charging_pb2.DERATE_STATUS_DCAC_EXPORT: "dcac_export",
81
+ }
82
+
83
+ _FAULT_CHIME_MAP: Final[dict[int, str]] = {
84
+ charging_pb2.FAULT_CHIME_NONE: "none",
85
+ charging_pb2.FAULT_CHIME_CHARGING_DISABLED_ALL: "charging_disabled_all",
86
+ charging_pb2.FAULT_CHIME_CHARGING_DISABLED_DC: "charging_disabled_dc",
87
+ charging_pb2.FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_DC: "charging_disabled_pin_temp_dc",
88
+ charging_pb2.FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_GRADIENT_DC: "charging_disabled_pin_temp_gradient_dc",
89
+ charging_pb2.FAULT_CHIME_CHARGING_DEGRADED_DC: "charging_degraded_dc",
90
+ charging_pb2.FAULT_CHIME_CHARGING_DISABLED_AC: "charging_disabled_ac",
91
+ charging_pb2.FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_AC: "charging_disabled_pin_temp_ac",
92
+ charging_pb2.FAULT_CHIME_CHARGING_DEGRADED_AC: "charging_degraded_ac",
93
+ charging_pb2.FAULT_CHIME_CHARGING_DISABLED_PARTIAL_CONNECTION: "charging_disabled_partial_connection",
94
+ charging_pb2.FAULT_CHIME_CHARGING_DISABLED_NOT_PARKED: "charging_disabled_not_parked",
95
+ }
96
+
97
+
61
98
  @RVMDecoder.register("charging.session.notification", charging_pb2.SessionNotification)
62
99
  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 {}
100
+ """charging.session.notification — charging derate and fault notices.
101
+
102
+ Fields:
103
+ chargerDerateStatus: str ("none" | "warm_adapter" | "nearing_toc" |
104
+ "battery_heating" | ...)
105
+ chargingFaultChime: str ("none" | "charging_disabled_all" |
106
+ "charging_disabled_dc" | ...)
107
+ _unexpectedStopReason: int — raw
108
+ """
109
+ return {
110
+ "chargerDerateStatus": _enum(
111
+ _DERATE_STATUS_MAP, m.derate_status, what="charger derate status"
112
+ ),
113
+ "chargingFaultChime": _enum(
114
+ _FAULT_CHIME_MAP, m.fault_chime, what="charging fault chime"
115
+ ),
116
+ "_unexpectedStopReason": m.unexpected_stop_reason,
117
+ }
65
118
 
66
119
 
67
- _REMOTE_COMMAND_MAP: Final[dict[int, str]] = {
68
- charging_pb2.REMOTE_COMMAND_START: "start",
69
- charging_pb2.REMOTE_COMMAND_STOP: "stop",
120
+ # The same "true"/"false"/"signal_not_available" vocabulary as the alarm.
121
+ _START_AVAILABILITY_MAP: Final[dict[int, str]] = {
122
+ charging_pb2.START_AVAILABILITY_SNA: "signal_not_available",
123
+ charging_pb2.START_AVAILABILITY_FALSE: "false",
124
+ charging_pb2.START_AVAILABILITY_TRUE: "true",
125
+ }
126
+
127
+ _TIME_ESTIMATION_VALIDITY_MAP: Final[dict[int, str]] = {
128
+ charging_pb2.TIME_ESTIMATION_VALIDITY_VALID: "valid",
129
+ charging_pb2.TIME_ESTIMATION_VALIDITY_INVALID: "invalid",
130
+ charging_pb2.TIME_ESTIMATION_VALIDITY_PACK_DISCHARGING: "pack_discharging",
70
131
  }
71
132
 
72
133
 
@@ -76,19 +137,16 @@ _REMOTE_COMMAND_MAP: Final[dict[int, str]] = {
76
137
  def decode_session_remote_command(
77
138
  m: charging_pb2.SessionRemoteCommand,
78
139
  ) -> dict[str, Any]:
79
- """charging.session.remote_command — the last start/stop command.
140
+ """charging.session.remote_command — whether a remote start is available.
80
141
 
81
142
  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.
143
+ remoteChargingAvailable: str ("true" | "false" |
144
+ "signal_not_available"); true while charging is held (e.g. by a
145
+ schedule), so "charge now" can start it
86
146
  """
87
- if not m.HasField("command"):
88
- return {}
89
147
  return {
90
- "chargingRemoteCommand": _enum(
91
- _REMOTE_COMMAND_MAP, m.command, what="charging remote command"
148
+ "remoteChargingAvailable": _enum(
149
+ _START_AVAILABILITY_MAP, m.start_available, what="remote charging"
92
150
  )
93
151
  }
94
152
 
@@ -107,29 +165,74 @@ def decode_soc_slider(m: charging_pb2.SocSlider) -> dict[str, Any]:
107
165
 
108
166
  @RVMDecoder.register("charging.session.trip_target", charging_pb2.TripTarget)
109
167
  def decode_trip_target(m: charging_pb2.TripTarget) -> dict[str, Any]:
110
- """charging.session.trip_target — unmapped; field 2 as `_field2`.
168
+ """charging.session.trip_target — the trip's target state of charge.
111
169
 
112
- 0xFFFF seems to mean "not set".
170
+ Fields:
171
+ chargingTripTargetSoc: int | None (percent; None when not set)
113
172
  """
114
- return {"_field2": m.field2} if m.HasField("field2") else {}
173
+ soc = m.soc_limit
174
+ return {"chargingTripTargetSoc": None if soc in (0, 0xFFFF) else soc}
115
175
 
116
176
 
117
177
  @RVMDecoder.register(
118
178
  "charging.smart_charging.settings", charging_pb2.SmartChargingSettings
119
179
  )
120
180
  def decode_smart_charging_settings(
121
- _m: charging_pb2.SmartChargingSettings,
181
+ m: charging_pb2.SmartChargingSettings,
122
182
  ) -> dict[str, Any]:
123
- """charging.smart_charging.settings — unmapped."""
124
- return {}
183
+ """charging.smart_charging.settings — smart charging schedule.
184
+
185
+ Not seen yet; the schema comes from the app.
186
+
187
+ Fields:
188
+ smartChargingCleanEnergyEnabled: bool
189
+ smartChargingReadyByTimes: list of {"hours": int, "minutes": int,
190
+ "days": list[str]} ("monday" … "sunday")
191
+ """
192
+ return {
193
+ "smartChargingCleanEnergyEnabled": m.clean_energy_enabled,
194
+ "smartChargingReadyByTimes": [
195
+ {
196
+ "hours": t.hours,
197
+ "minutes": t.minutes,
198
+ "days": [
199
+ _enum(_SMART_CHARGING_DAY_MAP, d, what="smart charging day")
200
+ for d in t.days
201
+ ],
202
+ }
203
+ for t in m.ready_by_times
204
+ ],
205
+ }
125
206
 
126
207
 
127
208
  @RVMDecoder.register(
128
209
  "charging.smart_charging.smart_charging_info", charging_pb2.SmartChargingInfo
129
210
  )
130
- def decode_smart_charging_info(_m: charging_pb2.SmartChargingInfo) -> dict[str, Any]:
131
- """charging.smart_charging.smart_charging_info — unmapped."""
132
- return {}
211
+ def decode_smart_charging_info(m: charging_pb2.SmartChargingInfo) -> dict[str, Any]:
212
+ """charging.smart_charging.smart_charging_info — smart charging status.
213
+
214
+ Not seen yet; the schema comes from the app.
215
+
216
+ Fields:
217
+ smartChargingNotification: str ("signal_not_available" |
218
+ "charging_with_clean_energy" | "charging_paused" |
219
+ "clean_energy_not_enough_time" |
220
+ "clean_energy_forecast_unavailable_or_incomplete")
221
+ smartChargingResumeTime: datetime, only when sent
222
+ _smartChargingScheduleType: int (raw; unmapped)
223
+ """
224
+ result: dict[str, Any] = {
225
+ "smartChargingNotification": _enum(
226
+ _SMART_CHARGING_NOTIFICATION_MAP,
227
+ m.notification,
228
+ what="smart charging notification",
229
+ ),
230
+ "_smartChargingScheduleType": m.schedule_type,
231
+ }
232
+ if m.HasField("resume_time"):
233
+ t = m.resume_time
234
+ result["smartChargingResumeTime"] = from_epoch(t.seconds + t.nanos / 1e9)
235
+ return result
133
236
 
134
237
 
135
238
  @RVMDecoder.register(
@@ -143,9 +246,35 @@ def decode_weighted_charging_forecast(
143
246
  return {}
144
247
 
145
248
 
249
+ _SMART_CHARGING_DAY_MAP: Final[dict[int, str]] = {
250
+ charging_pb2.SMART_CHARGING_DAY_MONDAY: "monday",
251
+ charging_pb2.SMART_CHARGING_DAY_TUESDAY: "tuesday",
252
+ charging_pb2.SMART_CHARGING_DAY_WEDNESDAY: "wednesday",
253
+ charging_pb2.SMART_CHARGING_DAY_THURSDAY: "thursday",
254
+ charging_pb2.SMART_CHARGING_DAY_FRIDAY: "friday",
255
+ charging_pb2.SMART_CHARGING_DAY_SATURDAY: "saturday",
256
+ charging_pb2.SMART_CHARGING_DAY_SUNDAY: "sunday",
257
+ }
258
+
259
+ _SMART_CHARGING_NOTIFICATION_MAP: Final[dict[int, str]] = {
260
+ charging_pb2.SMART_CHARGING_NOTIFICATION_SNA: "signal_not_available",
261
+ charging_pb2.SMART_CHARGING_NOTIFICATION_CHARGING_WITH_CLEAN_ENERGY: (
262
+ "charging_with_clean_energy"
263
+ ),
264
+ charging_pb2.SMART_CHARGING_NOTIFICATION_CHARGING_PAUSED: "charging_paused",
265
+ charging_pb2.SMART_CHARGING_NOTIFICATION_CLEAN_ENERGY_NOT_ENOUGH_TIME: (
266
+ "clean_energy_not_enough_time"
267
+ ),
268
+ charging_pb2.SMART_CHARGING_NOTIFICATION_CLEAN_ENERGY_FORECAST_UNAVAILABLE_OR_INCOMPLETE: (
269
+ "clean_energy_forecast_unavailable_or_incomplete"
270
+ ),
271
+ }
272
+
146
273
  _CONNECTION_STATE_MAP: Final[dict[int, str]] = {
147
274
  charging_pb2.CONNECTION_STATE_DISCONNECTED: "disconnected",
148
275
  charging_pb2.CONNECTION_STATE_CONNECTED: "connected",
276
+ charging_pb2.CONNECTION_STATE_ERROR: "error",
277
+ charging_pb2.CONNECTION_STATE_V2L_CONNECTED: "v2l_connected",
149
278
  }
150
279
 
151
280
  _CHARGING_STATE_MAP: Final[dict[int, str]] = {
@@ -182,12 +311,13 @@ def decode_charging_status(m: charging_pb2.SessionStatus) -> dict[str, Any]:
182
311
  """charging.session.status — plug and charging state.
183
312
 
184
313
  Fields:
185
- connectionState: str ("connected" | "disconnected")
314
+ connectionState: str ("connected" | "disconnected" | "error" |
315
+ "v2l_connected")
186
316
  chargerState: str — GraphQL chargerState values, e.g.
187
317
  "charging_ready" (no session, or unplugged),
188
318
  "charging_connecting", "charging_active", "charging_complete",
189
319
  "charging_scheduled", "charging_stopped_by_user"
190
- isActive: bool — true from a session's start until it ends
320
+ _evseType: int — raw; 1 while connected to a charger
191
321
  """
192
322
  result: dict[str, Any] = {}
193
323
  if m.HasField("connection_state"):
@@ -198,7 +328,7 @@ def decode_charging_status(m: charging_pb2.SessionStatus) -> dict[str, Any]:
198
328
  result["chargerState"] = _enum(
199
329
  _CHARGING_STATE_MAP, m.charging_state, what="charging state"
200
330
  )
201
- result["isActive"] = m.is_active
331
+ result["_evseType"] = m.evse_type
202
332
  return result
203
333
 
204
334
 
@@ -208,8 +338,17 @@ def decode_time_estimation(m: charging_pb2.TimeEstimation) -> dict[str, Any]:
208
338
 
209
339
  Fields:
210
340
  timeToEndOfCharge: int (minutes; 0 when not charging)
341
+ chargingTimeEstimationValidity: str | None ("valid" | "invalid" |
342
+ "pack_discharging")
211
343
  """
212
- return {"timeToEndOfCharge": m.estimated_time_remaining}
344
+ return {
345
+ "timeToEndOfCharge": m.estimated_time_remaining,
346
+ "chargingTimeEstimationValidity": _enum(
347
+ _TIME_ESTIMATION_VALIDITY_MAP,
348
+ m.validity or None,
349
+ what="time estimation validity",
350
+ ),
351
+ }
213
352
 
214
353
 
215
354
  _CHARGER_STATUS_MAP: Final[dict[int, str]] = {
@@ -22,13 +22,22 @@ def decode_hvac_settings_status(m: comfort_pb2.HvacSettingsStatus) -> dict[str,
22
22
 
23
23
  @RVMDecoder.register("comfort.user_modes.state", comfort_pb2.UserModesState)
24
24
  def decode_user_modes_state(m: comfort_pb2.UserModesState) -> dict[str, Any]:
25
- """comfort.user_modes.state — unmapped; fields 4 and 7 as `_field4`/`_field7`."""
26
- result: dict[str, Any] = {}
27
- if (v := _present(m, "field4")) is not None:
28
- result["_field4"] = v
29
- if (v := _present(m, "field7")) is not None:
30
- result["_field7"] = v
31
- return result
25
+ """comfort.user_modes.state — user modes.
26
+
27
+ Fields:
28
+ serviceMode, carWashMode: str ("on" | "off")
29
+ _petMode, _campMode, _transportMode, _climateKeep, _factoryMode:
30
+ int — raw; enum types unknown
31
+ """
32
+ return {
33
+ "serviceMode": "on" if m.in_service else "off",
34
+ "carWashMode": "on" if m.car_wash_mode else "off",
35
+ "_petMode": m.pet_mode,
36
+ "_campMode": m.camp_mode,
37
+ "_transportMode": m.transport_mode,
38
+ "_climateKeep": m.climate_keep,
39
+ "_factoryMode": m.factory_mode,
40
+ }
32
41
 
33
42
 
34
43
  _CLIMATE_HOLD_AVAILABILITY_MAP: Final[dict[int, str]] = {
@@ -50,6 +59,43 @@ _CLIMATE_HOLD_UNAVAILABILITY_REASON_MAP: Final[dict[int, str]] = {
50
59
  comfort_pb2.CLIMATE_HOLD_UNAVAILABILITY_UNSPECIFIED: "unspecified",
51
60
  comfort_pb2.CLIMATE_HOLD_UNAVAILABILITY_UNKNOWN: "unknown",
52
61
  comfort_pb2.CLIMATE_HOLD_UNAVAILABILITY_LOW_SOC: "low_soc",
62
+ comfort_pb2.CLIMATE_HOLD_UNAVAILABILITY_FAULT: "fault",
63
+ }
64
+
65
+ _DEFROST_MAP: Final[dict[int, str]] = {
66
+ comfort_pb2.DEFROST_DEFOG: "Defog",
67
+ comfort_pb2.DEFROST_ACTIVE: "Defrost",
68
+ comfort_pb2.DEFROST_DEFOG_DEFROST: "Defog_Defrost",
69
+ comfort_pb2.DEFROST_OFF: "Off",
70
+ }
71
+
72
+ # GraphQL's cabinPreconditioningStatus / cabinPreconditioningType values.
73
+ _PRECONDITIONING_STATUS_MAP: Final[dict[int, str]] = {
74
+ comfort_pb2.PRECONDITIONING_STATUS_UNSPECIFIED: "undefined",
75
+ comfort_pb2.PRECONDITIONING_INITIATE: "initiate",
76
+ comfort_pb2.PRECONDITIONING_ACTIVE: "active",
77
+ comfort_pb2.PRECONDITIONING_ACTIVE_WARNING: "active_warning",
78
+ comfort_pb2.PRECONDITIONING_COMPLETE_MAINTAIN: "complete_maintain",
79
+ comfort_pb2.PRECONDITIONING_TIMEOUT_TEMP_NOT_ACHIEVED: (
80
+ "timeout_temperature_not_achieved"
81
+ ),
82
+ comfort_pb2.PRECONDITIONING_ERROR_SOC_LOW: "error_soc_low",
83
+ comfort_pb2.PRECONDITIONING_ERROR_SYSTEM_FAULT: "error_system_fault",
84
+ comfort_pb2.PRECONDITIONING_UNAVAILABLE: "unavailable",
85
+ comfort_pb2.PRECONDITIONING_TIMEOUT_COMPLETE: "timeout_complete",
86
+ }
87
+
88
+ _PRECONDITIONING_TYPE_MAP: Final[dict[int, str]] = {
89
+ comfort_pb2.PRECONDITIONING_TYPE_USER_SELECTED: "user_selected",
90
+ comfort_pb2.PRECONDITIONING_TYPE_SCREEN_PROTECTION: "screen_protection",
91
+ comfort_pb2.PRECONDITIONING_TYPE_SCHEDULED: "scheduled",
92
+ comfort_pb2.PRECONDITIONING_TYPE_AUTO_CABIN_VENTILATION: "auto_cabin_ventilation",
93
+ }
94
+
95
+ _PET_MODE_CABIN_CLIMATE_MAP: Final[dict[int, str]] = {
96
+ comfort_pb2.PET_MODE_CABIN_CLIMATE_COMFORTABLE: "comfortable",
97
+ comfort_pb2.PET_MODE_CABIN_CLIMATE_COLD: "cold",
98
+ comfort_pb2.PET_MODE_CABIN_CLIMATE_HOT: "hot",
53
99
  }
54
100
 
55
101
  _PET_MODE_STATE_MAP: Final[dict[int, str]] = {
@@ -105,20 +151,20 @@ def decode_preconditioning(m: comfort_pb2.CabinPreconditioningStatus) -> dict[st
105
151
  """comfort.cabin.cabin_preconditioning_status — cabin preconditioning state.
106
152
 
107
153
  Fields:
108
- cabinPreconditioningStatus: str ("active" | "initiate" | "off");
109
- pet comfort counts as "active"
154
+ cabinPreconditioningStatus: str ("undefined" | "initiate" | "active" |
155
+ "complete_maintain" | "unavailable" | ...); "unavailable" while
156
+ driving or in pet comfort
157
+ cabinPreconditioningType: str | None ("user_selected" |
158
+ "screen_protection" | "scheduled" | "auto_cabin_ventilation")
110
159
  """
111
- if m.status in (
112
- comfort_pb2.PRECONDITIONING_ACTIVE,
113
- comfort_pb2.PRECONDITIONING_PET_COMFORT,
114
- ):
115
- return {"cabinPreconditioningStatus": "active"}
116
- if m.status in (
117
- comfort_pb2.PRECONDITIONING_INITIATE_1,
118
- comfort_pb2.PRECONDITIONING_INITIATE_2,
119
- ):
120
- return {"cabinPreconditioningStatus": "initiate"}
121
- return {"cabinPreconditioningStatus": "off"}
160
+ return {
161
+ "cabinPreconditioningStatus": _enum(
162
+ _PRECONDITIONING_STATUS_MAP, m.status, what="preconditioning status"
163
+ ),
164
+ "cabinPreconditioningType": _enum(
165
+ _PRECONDITIONING_TYPE_MAP, m.type or None, what="preconditioning type"
166
+ ),
167
+ }
122
168
 
123
169
 
124
170
  @RVMDecoder.register("comfort.cabin.cabin_temperatures", comfort_pb2.CabinTemperatures)
@@ -129,33 +175,27 @@ def decode_cabin_temperatures(m: comfort_pb2.CabinTemperatures) -> dict[str, Any
129
175
  cabinClimateInteriorTemperature: float (°C)
130
176
  cabinClimateDriverTemperature: float (°C) — set point, same as
131
177
  `hvacTargetTemperature`
178
+ cabinClimateExteriorTemperature: float (°C), when sent
132
179
  """
133
- return {
180
+ result: dict[str, Any] = {
134
181
  "cabinClimateInteriorTemperature": round(m.interior_temperature, 1),
135
182
  "cabinClimateDriverTemperature": round(m.driver_set_point, 1),
136
183
  }
184
+ if (v := _present(m, "exterior_temperature")) is not None:
185
+ result["cabinClimateExteriorTemperature"] = round(v, 1)
186
+ return result
137
187
 
138
188
 
139
189
  @RVMDecoder.register(
140
190
  "comfort.cabin.cabin_ventilation_setting", comfort_pb2.CabinVentilationSetting
141
191
  )
142
192
  def decode_cabin_ventilation(m: comfort_pb2.CabinVentilationSetting) -> dict[str, Any]:
143
- """comfort.cabin.cabin_ventilation_setting — passive ventilation settings.
193
+ """comfort.cabin.cabin_ventilation_setting — auto cabin ventilation.
144
194
 
145
195
  Fields:
146
196
  cabinVentilationEnabled: bool
147
- cabinVentilationMode: str | None ("AUTO" | "MANUAL" | "OFF")
148
- cabinVentilationWindowsPosition: int (percent open)
149
- cabinVentilationSunroofPosition: int (percent open)
150
- cabinVentilationDuration: int (minutes)
151
197
  """
152
- return {
153
- "cabinVentilationEnabled": m.enabled,
154
- "cabinVentilationMode": m.mode or None,
155
- "cabinVentilationWindowsPosition": m.windows_position,
156
- "cabinVentilationSunroofPosition": m.sunroof_position,
157
- "cabinVentilationDuration": m.duration,
158
- }
198
+ return {"cabinVentilationEnabled": m.auto_cabin_ventilation_enabled}
159
199
 
160
200
 
161
201
  @RVMDecoder.register(
@@ -206,10 +246,14 @@ def decode_defrost_status(m: comfort_pb2.DefrostDefogStatus) -> dict[str, Any]:
206
246
  """comfort.cabin.defrost_defog_status — windshield defrost state.
207
247
 
208
248
  Fields:
209
- defrostDefogStatus: str ("Defrost" | "Off")
249
+ defrostDefogStatus: str | None ("Defog" | "Defrost" | "Defog_Defrost"
250
+ | "Off")
210
251
  """
211
- active = m.status == comfort_pb2.DEFROST_ACTIVE
212
- return {"defrostDefogStatus": "Defrost" if active else "Off"}
252
+ return {
253
+ "defrostDefogStatus": _enum(
254
+ _DEFROST_MAP, m.status or None, what="defrost defog status"
255
+ )
256
+ }
213
257
 
214
258
 
215
259
  @RVMDecoder.register("comfort.cabin.pet_mode_status", comfort_pb2.PetModeStatus)
@@ -220,8 +264,9 @@ def decode_pet_mode_status(m: comfort_pb2.PetModeStatus) -> dict[str, Any]:
220
264
  petModeStatus: str — "disabled" while cabin climate is off, "off"
221
265
  while it runs without pet mode
222
266
  petModeTemperatureStatus: str
267
+ petModeCabinClimate: str ("comfortable" | "cold" | "hot")
223
268
 
224
- Absent fields are their zero values ("off" / "default").
269
+ Absent fields are their zero values ("off" / "default" / "comfortable").
225
270
  """
226
271
  return {
227
272
  "petModeStatus": _enum(_PET_MODE_STATE_MAP, m.status, what="pet mode status"),
@@ -230,6 +275,9 @@ def decode_pet_mode_status(m: comfort_pb2.PetModeStatus) -> dict[str, Any]:
230
275
  m.temperature_status,
231
276
  what="pet mode temperature status",
232
277
  ),
278
+ "petModeCabinClimate": _enum(
279
+ _PET_MODE_CABIN_CLIMATE_MAP, m.cabin_climate, what="pet mode cabin climate"
280
+ ),
233
281
  }
234
282
 
235
283
 
@@ -41,6 +41,7 @@ def decode_vas_keyper_devices(m: device_table_pb2.VasKeyperDevices) -> dict[str,
41
41
  vehicle.profiles.active_user)
42
42
  publicKey: str — hex; phone keys
43
43
  keyRevision: int — key table revision when the entry was last written
44
+ vehicleResponseRequired: bool
44
45
  keyStatus: str ("active" | "inactive" | "waiting_to_pair" |
45
46
  "pairing"); inactive covers unpaired, no longer paired and
46
47
  deleted keys
@@ -65,6 +66,7 @@ def decode_vas_keyper_devices(m: device_table_pb2.VasKeyperDevices) -> dict[str,
65
66
  ("profile_id", "profileId"),
66
67
  ("public_key", "publicKey"),
67
68
  ("revision", "keyRevision"),
69
+ ("vehicle_response_required", "vehicleResponseRequired"),
68
70
  ):
69
71
  if (v := _present(m.device, field)) is not None:
70
72
  result[key] = v
@@ -75,7 +75,7 @@ def decode_drive_mode(m: dynamics_pb2.DriveMode) -> dict[str, Any]:
75
75
 
76
76
  Fields:
77
77
  driveMode: str
78
- limitedAccelCold, limitedRegenCold: int — raw flags
78
+ limitedAccelCold, limitedRegenCold: bool
79
79
  """
80
80
  return {
81
81
  "driveMode": _enum(_DRIVE_MODE_MAP, _present(m, "mode"), what="drive mode"),