rivian-python-client 2.3.0__py3-none-any.whl → 2.3.1__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/parallax/departure.py +16 -6
- rivian/parallax/navigation.py +7 -3
- rivian/parallax/proto/departure.proto +12 -0
- rivian/parallax/proto/departure_pb2.py +23 -19
- rivian/parallax/proto/departure_pb2.pyi +16 -2
- rivian/rivian.py +104 -60
- rivian/ws_monitor.py +166 -33
- {rivian_python_client-2.3.0.dist-info → rivian_python_client-2.3.1.dist-info}/METADATA +1 -1
- {rivian_python_client-2.3.0.dist-info → rivian_python_client-2.3.1.dist-info}/RECORD +11 -11
- {rivian_python_client-2.3.0.dist-info → rivian_python_client-2.3.1.dist-info}/WHEEL +0 -0
rivian/__version__.py
CHANGED
rivian/parallax/departure.py
CHANGED
|
@@ -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
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
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",
|
rivian/parallax/navigation.py
CHANGED
|
@@ -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
|
|
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)
|
|
@@ -15,9 +15,21 @@ message DepartureSchedules {
|
|
|
15
15
|
optional string name = 2;
|
|
16
16
|
bool is_enabled = 3;
|
|
17
17
|
Occurrence occurrence = 4;
|
|
18
|
+
// Set instead of occurrence on the one-off "s-" schedule a departNow
|
|
19
|
+
// adds; the gateway leaves that schedule out.
|
|
20
|
+
SingleOccurrence single_occurrence = 5;
|
|
18
21
|
DepartureSettings departure_settings = 6;
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
message SingleOccurrence {
|
|
25
|
+
StartsAt starts_at = 1;
|
|
26
|
+
|
|
27
|
+
message StartsAt {
|
|
28
|
+
int64 seconds = 1; // epoch seconds
|
|
29
|
+
int32 nanos = 2;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
message Occurrence {
|
|
22
34
|
Days days = 1;
|
|
23
35
|
optional int32 starts_at = 2; // minutes after local midnight
|
|
@@ -24,31 +24,35 @@ _sym_db = _symbol_database.Default()
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%rivian/parallax/proto/departure.proto\x12\x19rivian.parallax.departure\"\
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%rivian/parallax/proto/departure.proto\x12\x19rivian.parallax.departure\"\xca\r\n\x12\x44\x65partureSchedules\x12H\n\x08schedule\x18\x01 \x03(\x0b\x32\x36.rivian.parallax.departure.DepartureSchedules.Schedule\x12K\n\nupdated_at\x18\x02 \x01(\x0b\x32\x37.rivian.parallax.departure.DepartureSchedules.UpdatedAt\x1a\xd8\x02\n\x08Schedule\x12\x0f\n\x02id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x11\n\x04name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x12\n\nis_enabled\x18\x03 \x01(\x08\x12L\n\noccurrence\x18\x04 \x01(\x0b\x32\x38.rivian.parallax.departure.DepartureSchedules.Occurrence\x12Y\n\x11single_occurrence\x18\x05 \x01(\x0b\x32>.rivian.parallax.departure.DepartureSchedules.SingleOccurrence\x12[\n\x12\x64\x65parture_settings\x18\x06 \x01(\x0b\x32?.rivian.parallax.departure.DepartureSchedules.DepartureSettingsB\x05\n\x03_idB\x07\n\x05_name\x1a\x9a\x01\n\x10SingleOccurrence\x12Z\n\tstarts_at\x18\x01 \x01(\x0b\x32G.rivian.parallax.departure.DepartureSchedules.SingleOccurrence.StartsAt\x1a*\n\x08StartsAt\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\x1at\n\nOccurrence\x12@\n\x04\x64\x61ys\x18\x01 \x01(\x0b\x32\x32.rivian.parallax.departure.DepartureSchedules.Days\x12\x16\n\tstarts_at\x18\x02 \x01(\x05H\x00\x88\x01\x01\x42\x0c\n\n_starts_at\x1a~\n\x04\x44\x61ys\x12\x0e\n\x06monday\x18\x01 \x01(\x08\x12\x0f\n\x07tuesday\x18\x02 \x01(\x08\x12\x11\n\twednesday\x18\x03 \x01(\x08\x12\x10\n\x08thursday\x18\x04 \x01(\x08\x12\x0e\n\x06\x66riday\x18\x05 \x01(\x08\x12\x10\n\x08saturday\x18\x06 \x01(\x08\x12\x0e\n\x06sunday\x18\x07 \x01(\x08\x1a\x95\x01\n\x11\x44\x65partureSettings\x12W\n\x10\x63omfort_settings\x18\x01 \x01(\x0b\x32=.rivian.parallax.departure.DepartureSchedules.ComfortSettings\x12\'\n\x1fshould_override_charge_schedule\x18\x02 \x01(\x08\x1a\xde\x01\n\x0f\x43omfortSettings\x12\x19\n\x11\x63\x61\x62in_temperature\x18\x01 \x01(\x02\x12I\n\x13\x66ront_defog_defrost\x18\x02 \x01(\x0e\x32,.rivian.parallax.departure.DefogDefrostLevel\x12\x65\n\x18surface_heat_vent_levels\x18\x03 \x01(\x0b\x32\x43.rivian.parallax.departure.DepartureSchedules.SurfaceHeatVentLevels\x1a\x88\x03\n\x15SurfaceHeatVentLevels\x12H\n\x0f\x66ront_left_seat\x18\x01 \x01(\x0e\x32/.rivian.parallax.departure.SurfaceHeatVentLevel\x12I\n\x10\x66ront_right_seat\x18\x02 \x01(\x0e\x32/.rivian.parallax.departure.SurfaceHeatVentLevel\x12G\n\x0erear_left_seat\x18\x03 \x01(\x0e\x32/.rivian.parallax.departure.SurfaceHeatVentLevel\x12H\n\x0frear_right_seat\x18\x04 \x01(\x0e\x32/.rivian.parallax.departure.SurfaceHeatVentLevel\x12G\n\x0esteering_wheel\x18\x05 \x01(\x0e\x32/.rivian.parallax.departure.SurfaceHeatVentLevel\x1a+\n\tUpdatedAt\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05*^\n\x11\x44\x65\x66ogDefrostLevel\x12\x15\n\x11\x44\x45\x46OG_DEFROST_OFF\x10\x00\x12\x17\n\x13\x44\x45\x46OG_DEFROST_DEFOG\x10\x01\x12\x19\n\x15\x44\x45\x46OG_DEFROST_DEFROST\x10\x02*\x9f\x01\n\x14SurfaceHeatVentLevel\x12\x0f\n\x0bSURFACE_OFF\x10\x00\x12\x12\n\x0eSURFACE_HEAT_1\x10\x01\x12\x12\n\x0eSURFACE_HEAT_2\x10\x02\x12\x12\n\x0eSURFACE_HEAT_3\x10\x03\x12\x12\n\x0eSURFACE_VENT_1\x10\x04\x12\x12\n\x0eSURFACE_VENT_2\x10\x05\x12\x12\n\x0eSURFACE_VENT_3\x10\x06\x62\x06proto3')
|
|
28
28
|
|
|
29
29
|
_globals = globals()
|
|
30
30
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
31
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'rivian.parallax.proto.departure_pb2', _globals)
|
|
32
32
|
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
33
|
DESCRIPTOR._loaded_options = None
|
|
34
|
-
_globals['_DEFOGDEFROSTLEVEL']._serialized_start=
|
|
35
|
-
_globals['_DEFOGDEFROSTLEVEL']._serialized_end=
|
|
36
|
-
_globals['_SURFACEHEATVENTLEVEL']._serialized_start=
|
|
37
|
-
_globals['_SURFACEHEATVENTLEVEL']._serialized_end=
|
|
34
|
+
_globals['_DEFOGDEFROSTLEVEL']._serialized_start=1809
|
|
35
|
+
_globals['_DEFOGDEFROSTLEVEL']._serialized_end=1903
|
|
36
|
+
_globals['_SURFACEHEATVENTLEVEL']._serialized_start=1906
|
|
37
|
+
_globals['_SURFACEHEATVENTLEVEL']._serialized_end=2065
|
|
38
38
|
_globals['_DEPARTURESCHEDULES']._serialized_start=69
|
|
39
|
-
_globals['_DEPARTURESCHEDULES']._serialized_end=
|
|
39
|
+
_globals['_DEPARTURESCHEDULES']._serialized_end=1807
|
|
40
40
|
_globals['_DEPARTURESCHEDULES_SCHEDULE']._serialized_start=243
|
|
41
|
-
_globals['_DEPARTURESCHEDULES_SCHEDULE']._serialized_end=
|
|
42
|
-
_globals['
|
|
43
|
-
_globals['
|
|
44
|
-
_globals['
|
|
45
|
-
_globals['
|
|
46
|
-
_globals['
|
|
47
|
-
_globals['
|
|
48
|
-
_globals['
|
|
49
|
-
_globals['
|
|
50
|
-
_globals['
|
|
51
|
-
_globals['
|
|
52
|
-
_globals['
|
|
53
|
-
_globals['
|
|
41
|
+
_globals['_DEPARTURESCHEDULES_SCHEDULE']._serialized_end=587
|
|
42
|
+
_globals['_DEPARTURESCHEDULES_SINGLEOCCURRENCE']._serialized_start=590
|
|
43
|
+
_globals['_DEPARTURESCHEDULES_SINGLEOCCURRENCE']._serialized_end=744
|
|
44
|
+
_globals['_DEPARTURESCHEDULES_SINGLEOCCURRENCE_STARTSAT']._serialized_start=702
|
|
45
|
+
_globals['_DEPARTURESCHEDULES_SINGLEOCCURRENCE_STARTSAT']._serialized_end=744
|
|
46
|
+
_globals['_DEPARTURESCHEDULES_OCCURRENCE']._serialized_start=746
|
|
47
|
+
_globals['_DEPARTURESCHEDULES_OCCURRENCE']._serialized_end=862
|
|
48
|
+
_globals['_DEPARTURESCHEDULES_DAYS']._serialized_start=864
|
|
49
|
+
_globals['_DEPARTURESCHEDULES_DAYS']._serialized_end=990
|
|
50
|
+
_globals['_DEPARTURESCHEDULES_DEPARTURESETTINGS']._serialized_start=993
|
|
51
|
+
_globals['_DEPARTURESCHEDULES_DEPARTURESETTINGS']._serialized_end=1142
|
|
52
|
+
_globals['_DEPARTURESCHEDULES_COMFORTSETTINGS']._serialized_start=1145
|
|
53
|
+
_globals['_DEPARTURESCHEDULES_COMFORTSETTINGS']._serialized_end=1367
|
|
54
|
+
_globals['_DEPARTURESCHEDULES_SURFACEHEATVENTLEVELS']._serialized_start=1370
|
|
55
|
+
_globals['_DEPARTURESCHEDULES_SURFACEHEATVENTLEVELS']._serialized_end=1762
|
|
56
|
+
_globals['_DEPARTURESCHEDULES_UPDATEDAT']._serialized_start=1764
|
|
57
|
+
_globals['_DEPARTURESCHEDULES_UPDATEDAT']._serialized_end=1807
|
|
54
58
|
# @@protoc_insertion_point(module_scope)
|
|
@@ -36,18 +36,32 @@ SURFACE_VENT_3: SurfaceHeatVentLevel
|
|
|
36
36
|
class DepartureSchedules(_message.Message):
|
|
37
37
|
__slots__ = ("schedule", "updated_at")
|
|
38
38
|
class Schedule(_message.Message):
|
|
39
|
-
__slots__ = ("id", "name", "is_enabled", "occurrence", "departure_settings")
|
|
39
|
+
__slots__ = ("id", "name", "is_enabled", "occurrence", "single_occurrence", "departure_settings")
|
|
40
40
|
ID_FIELD_NUMBER: _ClassVar[int]
|
|
41
41
|
NAME_FIELD_NUMBER: _ClassVar[int]
|
|
42
42
|
IS_ENABLED_FIELD_NUMBER: _ClassVar[int]
|
|
43
43
|
OCCURRENCE_FIELD_NUMBER: _ClassVar[int]
|
|
44
|
+
SINGLE_OCCURRENCE_FIELD_NUMBER: _ClassVar[int]
|
|
44
45
|
DEPARTURE_SETTINGS_FIELD_NUMBER: _ClassVar[int]
|
|
45
46
|
id: str
|
|
46
47
|
name: str
|
|
47
48
|
is_enabled: bool
|
|
48
49
|
occurrence: DepartureSchedules.Occurrence
|
|
50
|
+
single_occurrence: DepartureSchedules.SingleOccurrence
|
|
49
51
|
departure_settings: DepartureSchedules.DepartureSettings
|
|
50
|
-
def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., is_enabled: bool = ..., occurrence: _Optional[_Union[DepartureSchedules.Occurrence, _Mapping]] = ..., departure_settings: _Optional[_Union[DepartureSchedules.DepartureSettings, _Mapping]] = ...) -> None: ...
|
|
52
|
+
def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., is_enabled: bool = ..., occurrence: _Optional[_Union[DepartureSchedules.Occurrence, _Mapping]] = ..., single_occurrence: _Optional[_Union[DepartureSchedules.SingleOccurrence, _Mapping]] = ..., departure_settings: _Optional[_Union[DepartureSchedules.DepartureSettings, _Mapping]] = ...) -> None: ...
|
|
53
|
+
class SingleOccurrence(_message.Message):
|
|
54
|
+
__slots__ = ("starts_at",)
|
|
55
|
+
class StartsAt(_message.Message):
|
|
56
|
+
__slots__ = ("seconds", "nanos")
|
|
57
|
+
SECONDS_FIELD_NUMBER: _ClassVar[int]
|
|
58
|
+
NANOS_FIELD_NUMBER: _ClassVar[int]
|
|
59
|
+
seconds: int
|
|
60
|
+
nanos: int
|
|
61
|
+
def __init__(self, seconds: _Optional[int] = ..., nanos: _Optional[int] = ...) -> None: ...
|
|
62
|
+
STARTS_AT_FIELD_NUMBER: _ClassVar[int]
|
|
63
|
+
starts_at: DepartureSchedules.SingleOccurrence.StartsAt
|
|
64
|
+
def __init__(self, starts_at: _Optional[_Union[DepartureSchedules.SingleOccurrence.StartsAt, _Mapping]] = ...) -> None: ...
|
|
51
65
|
class Occurrence(_message.Message):
|
|
52
66
|
__slots__ = ("days", "starts_at")
|
|
53
67
|
DAYS_FIELD_NUMBER: _ClassVar[int]
|
rivian/rivian.py
CHANGED
|
@@ -133,6 +133,32 @@ class Rivian:
|
|
|
133
133
|
self._ws_monitor: WebSocketMonitor | None = None
|
|
134
134
|
self._subscriptions: dict[str, str] = {}
|
|
135
135
|
|
|
136
|
+
@property
|
|
137
|
+
def ws_backoff_remaining(self) -> float:
|
|
138
|
+
"""Seconds until the web socket may reconnect after a failed connection.
|
|
139
|
+
|
|
140
|
+
0 when it isn't backing off. While it is, subscribe_for_* fail without
|
|
141
|
+
connecting.
|
|
142
|
+
"""
|
|
143
|
+
return self._ws_monitor.backoff_remaining if self._ws_monitor else 0
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def ws_rate_limited(self) -> bool:
|
|
147
|
+
"""Whether the web socket is backing off after a rate limit.
|
|
148
|
+
|
|
149
|
+
True for the rest of the backoff, including on a new client for the same
|
|
150
|
+
session; False when backing off for a missing acknowledgement instead.
|
|
151
|
+
"""
|
|
152
|
+
return self._ws_monitor.rate_limited if self._ws_monitor else False
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def ws_unauthenticated(self) -> bool:
|
|
156
|
+
"""Whether the server closed the latest web socket as unauthenticated.
|
|
157
|
+
|
|
158
|
+
The session needs a new login; the web socket doesn't reconnect.
|
|
159
|
+
"""
|
|
160
|
+
return self._ws_monitor.unauthenticated if self._ws_monitor else False
|
|
161
|
+
|
|
136
162
|
async def create_csrf_token(self) -> None:
|
|
137
163
|
"""Create cross-site-request-forgery (csrf) token."""
|
|
138
164
|
url = GRAPHQL_GATEWAY
|
|
@@ -812,22 +838,14 @@ class Rivian:
|
|
|
812
838
|
if not properties:
|
|
813
839
|
properties = VEHICLE_STATES_SUBSCRIPTION_PROPERTIES
|
|
814
840
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
"variables": {"vehicleID": vehicle_id},
|
|
824
|
-
}
|
|
825
|
-
unsubscribe = await self._ws_monitor.start_subscription(payload, callback)
|
|
826
|
-
_LOGGER.debug("%s subscribed to updates", vehicle_id)
|
|
827
|
-
return unsubscribe
|
|
828
|
-
except Exception as ex: # pylint: disable=broad-except # noqa: BLE001
|
|
829
|
-
_LOGGER.error(ex)
|
|
830
|
-
return None
|
|
841
|
+
payload = {
|
|
842
|
+
"operationName": "VehicleState",
|
|
843
|
+
"query": f"subscription VehicleState($vehicleID: String!) {{ vehicleState(id: $vehicleID) {self._build_vehicle_state_fragment(properties)} }}",
|
|
844
|
+
"variables": {"vehicleID": vehicle_id},
|
|
845
|
+
}
|
|
846
|
+
return await self._start_subscription(
|
|
847
|
+
payload, callback, f"{vehicle_id} vehicle updates"
|
|
848
|
+
)
|
|
831
849
|
|
|
832
850
|
async def subscribe_for_departure_schedules(
|
|
833
851
|
self, vehicle_id: str, callback: Callable[[dict[str, Any]], None]
|
|
@@ -836,22 +854,14 @@ class Rivian:
|
|
|
836
854
|
|
|
837
855
|
The current schedules are sent right after subscribing.
|
|
838
856
|
"""
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
"variables": {"vehicleId": vehicle_id},
|
|
848
|
-
}
|
|
849
|
-
unsubscribe = await self._ws_monitor.start_subscription(payload, callback)
|
|
850
|
-
_LOGGER.debug("%s subscribed to departure schedules", vehicle_id)
|
|
851
|
-
return unsubscribe
|
|
852
|
-
except Exception as ex: # pylint: disable=broad-except # noqa: BLE001
|
|
853
|
-
_LOGGER.error(ex)
|
|
854
|
-
return None
|
|
857
|
+
payload = {
|
|
858
|
+
"operationName": "VehicleDepartureSchedules",
|
|
859
|
+
"query": "subscription VehicleDepartureSchedules($vehicleId: String!) { vehicleDepartureSchedules(vehicleId: $vehicleId) { id name isEnabled occurrence { __typename ... on RepeatsWeekly { days startsAtMin skippedOn } } departureSettings { shouldOverrideChargeSchedule comfortSettings { cabinTempCelsius frontDefogDefrost surfaceHeatVentLevels { frontLeftSeat frontRightSeat rearLeftSeat rearRightSeat steeringWheel } } } } }",
|
|
860
|
+
"variables": {"vehicleId": vehicle_id},
|
|
861
|
+
}
|
|
862
|
+
return await self._start_subscription(
|
|
863
|
+
payload, callback, f"{vehicle_id} departure schedules"
|
|
864
|
+
)
|
|
855
865
|
|
|
856
866
|
async def subscribe_for_parallax_messages(
|
|
857
867
|
self,
|
|
@@ -866,32 +876,60 @@ class Rivian:
|
|
|
866
876
|
no decoder yet (`decode_parallax_message` returns None for those).
|
|
867
877
|
Pass a list, e.g. `PARALLAX_RVMS`, to only receive those topics.
|
|
868
878
|
"""
|
|
879
|
+
variables: dict[str, Any] = {"vehicleId": vehicle_id}
|
|
880
|
+
if rvms is not None:
|
|
881
|
+
variables["rvms"] = rvms
|
|
882
|
+
payload = {
|
|
883
|
+
"operationName": "ParallaxMessages",
|
|
884
|
+
"query": "subscription ParallaxMessages($vehicleId: String!, $rvms: [String!]) { parallaxMessages(vehicleId: $vehicleId, rvms: $rvms) { payload timestamp rvm } }",
|
|
885
|
+
"variables": variables,
|
|
886
|
+
}
|
|
887
|
+
topics = "all Parallax RVMs" if rvms is None else f"{len(rvms)} Parallax RVMs"
|
|
888
|
+
return await self._start_subscription(
|
|
889
|
+
payload, callback, f"{vehicle_id} {topics}"
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
async def _start_subscription(
|
|
893
|
+
self,
|
|
894
|
+
payload: dict[str, Any],
|
|
895
|
+
callback: Callable[[dict[str, Any]], None],
|
|
896
|
+
description: str,
|
|
897
|
+
) -> Callable[[], Awaitable[None]] | None:
|
|
898
|
+
"""Connect the web socket if needed and start a subscription.
|
|
899
|
+
|
|
900
|
+
Returns the unsubscribe function, or None (after logging why) on failure.
|
|
901
|
+
"""
|
|
902
|
+
reason: str | Exception
|
|
869
903
|
try:
|
|
870
|
-
await self._ws_connect()
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
await
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
"
|
|
879
|
-
|
|
880
|
-
"
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
_LOGGER.debug(
|
|
884
|
-
"%s subscribed to %s",
|
|
885
|
-
vehicle_id,
|
|
886
|
-
"all Parallax RVMs" if rvms is None else f"{len(rvms)} Parallax RVMs",
|
|
887
|
-
)
|
|
888
|
-
return unsubscribe
|
|
904
|
+
ws_monitor = await self._ws_connect()
|
|
905
|
+
# Ends early if the connection closes, e.g. when rate limited
|
|
906
|
+
if await ws_monitor.wait_for_ack() and (
|
|
907
|
+
unsubscribe := await ws_monitor.start_subscription(payload, callback)
|
|
908
|
+
):
|
|
909
|
+
_LOGGER.debug("Subscribed to %s", description)
|
|
910
|
+
return unsubscribe
|
|
911
|
+
if backoff_reason := ws_monitor.backoff_reason:
|
|
912
|
+
reason = f"the web socket connection {backoff_reason}"
|
|
913
|
+
elif close_message := ws_monitor.close_message:
|
|
914
|
+
reason = f"the web socket connection closed ({close_message})"
|
|
915
|
+
else:
|
|
916
|
+
reason = "the web socket connection closed"
|
|
889
917
|
except Exception as ex: # pylint: disable=broad-except # noqa: BLE001
|
|
890
|
-
|
|
891
|
-
|
|
918
|
+
reason = ex
|
|
919
|
+
# Expected while backing off, so less severe than other failures
|
|
920
|
+
_LOGGER.log(
|
|
921
|
+
logging.WARNING if self.ws_backoff_remaining else logging.ERROR,
|
|
922
|
+
"Unable to subscribe to %s: %s",
|
|
923
|
+
description,
|
|
924
|
+
reason,
|
|
925
|
+
)
|
|
926
|
+
if self._ws_monitor and not self._ws_monitor.has_subscriptions:
|
|
927
|
+
# Don't leave a connection open, or reconnecting, with nothing on it
|
|
928
|
+
await self._ws_monitor.close()
|
|
929
|
+
return None
|
|
892
930
|
|
|
893
|
-
async def _ws_connect(self) ->
|
|
894
|
-
"""
|
|
931
|
+
async def _ws_connect(self) -> WebSocketMonitor:
|
|
932
|
+
"""Connect the web socket if needed and return its monitor."""
|
|
895
933
|
|
|
896
934
|
async def connection_init(websocket: ClientWebSocketResponse[bool]) -> None:
|
|
897
935
|
await websocket.send_json(
|
|
@@ -908,17 +946,23 @@ class Rivian:
|
|
|
908
946
|
|
|
909
947
|
if not self._ws_monitor:
|
|
910
948
|
self._ws_monitor = WebSocketMonitor(
|
|
911
|
-
self,
|
|
949
|
+
self,
|
|
950
|
+
GRAPHQL_WEBSOCKET,
|
|
951
|
+
connection_init,
|
|
952
|
+
backoff_key=self._user_session_token,
|
|
912
953
|
)
|
|
913
954
|
ws_monitor = self._ws_monitor
|
|
914
|
-
if ws_monitor.backing_off:
|
|
915
|
-
raise RivianApiException("Web socket is waiting to reconnect")
|
|
916
955
|
if ws_monitor.websocket is None or ws_monitor.websocket.closed:
|
|
956
|
+
# A backoff only holds off new connections, not an open one
|
|
957
|
+
if remaining := ws_monitor.backoff_remaining:
|
|
958
|
+
raise RivianApiException(
|
|
959
|
+
"the web socket is waiting to reconnect "
|
|
960
|
+
f"({remaining:.0f} seconds left)"
|
|
961
|
+
)
|
|
917
962
|
await ws_monitor.new_connection(True)
|
|
918
|
-
assert ws_monitor.websocket
|
|
919
963
|
if ws_monitor.monitor is None or ws_monitor.monitor.done():
|
|
920
964
|
await ws_monitor.start_monitor()
|
|
921
|
-
return ws_monitor
|
|
965
|
+
return ws_monitor
|
|
922
966
|
|
|
923
967
|
async def __graphql_query(
|
|
924
968
|
self, headers: dict[str, str], url: str, body: dict[str, Any]
|
rivian/ws_monitor.py
CHANGED
|
@@ -5,11 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import inspect
|
|
7
7
|
import logging
|
|
8
|
-
|
|
8
|
+
import time
|
|
9
|
+
from collections.abc import Awaitable, Callable, Hashable
|
|
10
|
+
from dataclasses import dataclass
|
|
9
11
|
from datetime import UTC, datetime
|
|
10
12
|
from json import loads
|
|
11
13
|
from random import uniform
|
|
12
|
-
from typing import TYPE_CHECKING, Any
|
|
14
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
13
15
|
from uuid import uuid4
|
|
14
16
|
|
|
15
17
|
from aiohttp import ClientWebSocketResponse, WSMessage, WSMsgType
|
|
@@ -21,14 +23,20 @@ if TYPE_CHECKING:
|
|
|
21
23
|
_LOGGER = logging.getLogger(__name__)
|
|
22
24
|
|
|
23
25
|
RATE_LIMITED = "Rate limited"
|
|
26
|
+
UNAUTHENTICATED = "Unauthenticated"
|
|
24
27
|
RATE_LIMIT_BACKOFF_MIN = 30
|
|
25
28
|
RATE_LIMIT_BACKOFF_MAX = 900
|
|
26
29
|
|
|
30
|
+
# Backoff reasons, completing "the web socket connection ..."
|
|
31
|
+
BACKOFF_RATE_LIMITED = "was rate limited"
|
|
32
|
+
BACKOFF_NOT_ACKNOWLEDGED = "was not acknowledged"
|
|
33
|
+
|
|
27
34
|
|
|
28
35
|
async def cancel_task(*tasks: asyncio.Task | None) -> None:
|
|
29
|
-
"""Cancel task(s)."""
|
|
36
|
+
"""Cancel task(s), other than the one running this."""
|
|
37
|
+
current = asyncio.current_task()
|
|
30
38
|
for task in tasks:
|
|
31
|
-
if task is not None and not task.done():
|
|
39
|
+
if task is not None and task is not current and not task.done():
|
|
32
40
|
task.cancel()
|
|
33
41
|
try:
|
|
34
42
|
await task
|
|
@@ -36,24 +44,52 @@ async def cancel_task(*tasks: asyncio.Task | None) -> None:
|
|
|
36
44
|
pass
|
|
37
45
|
|
|
38
46
|
|
|
47
|
+
@dataclass
|
|
48
|
+
class _Backoff:
|
|
49
|
+
"""Backoff state: consecutive failed connections and when to retry."""
|
|
50
|
+
|
|
51
|
+
attempt: int = 0
|
|
52
|
+
retry_at: float = 0 # time.monotonic()
|
|
53
|
+
rate_limited: bool = False # whether the backoff until retry_at is a rate limit
|
|
54
|
+
|
|
55
|
+
|
|
39
56
|
class WebSocketMonitor:
|
|
40
57
|
"""Web socket monitor for a vehicle."""
|
|
41
58
|
|
|
59
|
+
# Backoff state by key, shared so a new monitor for the same session doesn't
|
|
60
|
+
# connect while another is waiting out a rate limit
|
|
61
|
+
_shared_backoffs: ClassVar[dict[Hashable, _Backoff]] = {}
|
|
62
|
+
|
|
42
63
|
def __init__(
|
|
43
64
|
self,
|
|
44
65
|
account: Rivian,
|
|
45
66
|
url: str,
|
|
46
67
|
connection_init: Callable[[ClientWebSocketResponse[bool]], Awaitable[None]],
|
|
68
|
+
backoff_key: Hashable | None = None,
|
|
47
69
|
) -> None:
|
|
48
|
-
"""Initialize a web socket monitor.
|
|
70
|
+
"""Initialize a web socket monitor.
|
|
71
|
+
|
|
72
|
+
Monitors with the same `backoff_key` (e.g. the user session) share their
|
|
73
|
+
backoff state; without one, the state is the monitor's own.
|
|
74
|
+
"""
|
|
49
75
|
self._account = account
|
|
50
76
|
self._url = url
|
|
51
77
|
self._connection_init = connection_init
|
|
52
78
|
|
|
53
79
|
self._connection_ack: asyncio.Event = asyncio.Event()
|
|
80
|
+
# Set while there's no open connection
|
|
81
|
+
self._closed: asyncio.Event = asyncio.Event()
|
|
82
|
+
self._closed.set()
|
|
54
83
|
self._disconnect = False
|
|
84
|
+
# Why the current connection is backing off, at most once per connection
|
|
55
85
|
self._backoff_reason: str | None = None
|
|
56
|
-
|
|
86
|
+
# The server's reason for closing the current connection, if it gave one
|
|
87
|
+
self._close_message: str | None = None
|
|
88
|
+
self._backoff_state = (
|
|
89
|
+
_Backoff()
|
|
90
|
+
if backoff_key is None
|
|
91
|
+
else self._shared_backoffs.setdefault(backoff_key, _Backoff())
|
|
92
|
+
)
|
|
57
93
|
self._ws: ClientWebSocketResponse[bool] | None = None
|
|
58
94
|
self._monitor_task: asyncio.Task | None = None
|
|
59
95
|
self._receiver_task: asyncio.Task | None = None
|
|
@@ -87,12 +123,47 @@ class WebSocketMonitor:
|
|
|
87
123
|
@property
|
|
88
124
|
def backing_off(self) -> bool:
|
|
89
125
|
"""Return `True` while waiting to reconnect after a failed connection."""
|
|
90
|
-
return self.
|
|
126
|
+
return self.backoff_remaining > 0
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def backoff_reason(self) -> str | None:
|
|
130
|
+
"""Return why the latest connection is backing off, if it is."""
|
|
131
|
+
return self._backoff_reason
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def close_message(self) -> str | None:
|
|
135
|
+
"""Return the server's reason for closing the latest connection, if any."""
|
|
136
|
+
return self._close_message
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def rate_limited(self) -> bool:
|
|
140
|
+
"""Return `True` while backing off after a rate limit.
|
|
141
|
+
|
|
142
|
+
Shared like the backoff, so a new monitor for the same session sees it too.
|
|
143
|
+
"""
|
|
144
|
+
return self._backoff_state.rate_limited and self.backoff_remaining > 0
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def unauthenticated(self) -> bool:
|
|
148
|
+
"""Return `True` if the server closed the latest connection as unauthenticated."""
|
|
149
|
+
return self._close_message == UNAUTHENTICATED
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def backoff_remaining(self) -> float:
|
|
153
|
+
"""Return the seconds left before reconnecting is allowed."""
|
|
154
|
+
return max(self._backoff_state.retry_at - time.monotonic(), 0)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def has_subscriptions(self) -> bool:
|
|
158
|
+
"""Return `True` if there are subscriptions to resume on reconnect."""
|
|
159
|
+
return bool(self._subscriptions)
|
|
91
160
|
|
|
92
161
|
async def new_connection(self, start_monitor: bool = False) -> None:
|
|
93
162
|
"""Create a new connection and, optionally, start the monitor."""
|
|
94
163
|
await cancel_task(self._receiver_task)
|
|
95
164
|
self._disconnect = False
|
|
165
|
+
self._backoff_reason = None
|
|
166
|
+
self._close_message = None
|
|
96
167
|
self._connection_ack.clear()
|
|
97
168
|
# pylint: disable=protected-access
|
|
98
169
|
assert self._account._session
|
|
@@ -100,10 +171,36 @@ class WebSocketMonitor:
|
|
|
100
171
|
url=self._url, headers={"sec-websocket-protocol": "graphql-transport-ws"}
|
|
101
172
|
)
|
|
102
173
|
await self._connection_init(self._ws)
|
|
174
|
+
self._closed.clear()
|
|
103
175
|
self._receiver_task = asyncio.ensure_future(self._receiver())
|
|
104
176
|
if start_monitor:
|
|
105
177
|
await self.start_monitor()
|
|
106
178
|
|
|
179
|
+
async def wait_for_ack(self) -> bool:
|
|
180
|
+
"""Wait for the connection to be acknowledged or to close.
|
|
181
|
+
|
|
182
|
+
Returns whether it was acknowledged. A connection that isn't acknowledged
|
|
183
|
+
within the request timeout is closed and backed off.
|
|
184
|
+
"""
|
|
185
|
+
if not self._connection_ack.is_set():
|
|
186
|
+
waits = [
|
|
187
|
+
asyncio.ensure_future(self._connection_ack.wait()),
|
|
188
|
+
asyncio.ensure_future(self._closed.wait()),
|
|
189
|
+
]
|
|
190
|
+
try:
|
|
191
|
+
done, _ = await asyncio.wait(
|
|
192
|
+
waits,
|
|
193
|
+
timeout=self._account.request_timeout,
|
|
194
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
195
|
+
)
|
|
196
|
+
finally:
|
|
197
|
+
await cancel_task(*waits)
|
|
198
|
+
if not done:
|
|
199
|
+
self._start_backoff(BACKOFF_NOT_ACKNOWLEDGED)
|
|
200
|
+
if self._ws:
|
|
201
|
+
await self._ws.close()
|
|
202
|
+
return self._connection_ack.is_set()
|
|
203
|
+
|
|
107
204
|
async def start_subscription(
|
|
108
205
|
self, payload: dict[str, Any], callback: Callable[[dict[str, Any]], None]
|
|
109
206
|
) -> Callable[[], Awaitable[None]] | None:
|
|
@@ -121,6 +218,10 @@ class WebSocketMonitor:
|
|
|
121
218
|
if self.connected:
|
|
122
219
|
assert self._ws
|
|
123
220
|
await self._ws.send_json({"id": _id, "type": "complete"})
|
|
221
|
+
if not self._subscriptions:
|
|
222
|
+
# Don't keep a connection open with nothing on it; the next
|
|
223
|
+
# subscription reconnects
|
|
224
|
+
await self.close()
|
|
124
225
|
|
|
125
226
|
return unsubscribe
|
|
126
227
|
|
|
@@ -144,6 +245,17 @@ class WebSocketMonitor:
|
|
|
144
245
|
"""Receive a message from a web socket."""
|
|
145
246
|
if not (websocket := self._ws):
|
|
146
247
|
return
|
|
248
|
+
try:
|
|
249
|
+
await self._receive(websocket)
|
|
250
|
+
finally:
|
|
251
|
+
# A receiver restarted on an open connection keeps its ack
|
|
252
|
+
if websocket.closed:
|
|
253
|
+
self._connection_ack.clear()
|
|
254
|
+
self._closed.set()
|
|
255
|
+
self._log_message("web socket stopped")
|
|
256
|
+
|
|
257
|
+
async def _receive(self, websocket: ClientWebSocketResponse[bool]) -> None:
|
|
258
|
+
"""Handle messages until the web socket closes."""
|
|
147
259
|
loop = asyncio.get_running_loop()
|
|
148
260
|
ack_deadline = loop.time() + self._account.request_timeout
|
|
149
261
|
while not websocket.closed:
|
|
@@ -157,10 +269,11 @@ class WebSocketMonitor:
|
|
|
157
269
|
msg = await websocket.receive(timeout=timeout)
|
|
158
270
|
if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED):
|
|
159
271
|
self._log_message(msg)
|
|
160
|
-
|
|
272
|
+
self._close_message = msg.extra or None
|
|
273
|
+
if msg.extra == UNAUTHENTICATED:
|
|
161
274
|
self._disconnect = True
|
|
162
275
|
elif msg.extra == RATE_LIMITED:
|
|
163
|
-
self.
|
|
276
|
+
self._start_backoff(BACKOFF_RATE_LIMITED)
|
|
164
277
|
break
|
|
165
278
|
self._last_received = datetime.now(UTC)
|
|
166
279
|
if msg.type == WSMsgType.TEXT:
|
|
@@ -168,13 +281,17 @@ class WebSocketMonitor:
|
|
|
168
281
|
if (data_type := data.get("type")) == "connection_ack":
|
|
169
282
|
self._connection_ack.set()
|
|
170
283
|
elif data_type == "next":
|
|
171
|
-
self.
|
|
284
|
+
self._reset_backoff()
|
|
172
285
|
if (_id := data.get("id")) in self._subscriptions:
|
|
173
286
|
_fn = self._subscriptions[_id][0]
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
287
|
+
# A failing callback shouldn't take the connection down
|
|
288
|
+
try:
|
|
289
|
+
if inspect.iscoroutinefunction(_fn):
|
|
290
|
+
await _fn(data)
|
|
291
|
+
else:
|
|
292
|
+
_fn(data)
|
|
293
|
+
except Exception: # pylint: disable=broad-except
|
|
294
|
+
_LOGGER.exception("Error in a subscription callback")
|
|
178
295
|
else:
|
|
179
296
|
self._log_message(msg)
|
|
180
297
|
elif msg.type == WSMsgType.ERROR:
|
|
@@ -182,14 +299,12 @@ class WebSocketMonitor:
|
|
|
182
299
|
continue
|
|
183
300
|
except TimeoutError:
|
|
184
301
|
if not acknowledged:
|
|
185
|
-
self.
|
|
302
|
+
self._start_backoff(BACKOFF_NOT_ACKNOWLEDGED)
|
|
186
303
|
await websocket.close()
|
|
187
304
|
break
|
|
188
305
|
# The connection has stayed open, so any rate limit has passed
|
|
189
|
-
self.
|
|
306
|
+
self._reset_backoff()
|
|
190
307
|
await self._resubscribe_all()
|
|
191
|
-
self._connection_ack.clear()
|
|
192
|
-
self._log_message("web socket stopped")
|
|
193
308
|
|
|
194
309
|
async def _monitor(self) -> None:
|
|
195
310
|
"""Monitor a web socket connection."""
|
|
@@ -200,10 +315,17 @@ class WebSocketMonitor:
|
|
|
200
315
|
# Need to restart the receiver
|
|
201
316
|
self._receiver_task = asyncio.ensure_future(self._receiver())
|
|
202
317
|
await asyncio.sleep(1)
|
|
203
|
-
if self.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
318
|
+
if not self._subscriptions:
|
|
319
|
+
# Nothing to resume, so stop rather than keep reconnecting; the
|
|
320
|
+
# next subscription reconnects once any backoff has passed
|
|
321
|
+
self._log_message("web socket monitor stopped, nothing subscribed")
|
|
322
|
+
break
|
|
323
|
+
while delay := self.backoff_remaining:
|
|
324
|
+
# Re-check after waiting, as another monitor for the same session
|
|
325
|
+
# may have pushed the retry back
|
|
326
|
+
await asyncio.sleep(delay)
|
|
327
|
+
if self._disconnect or self.connected:
|
|
328
|
+
continue
|
|
207
329
|
if not self._disconnect:
|
|
208
330
|
try:
|
|
209
331
|
await self.new_connection()
|
|
@@ -217,23 +339,35 @@ class WebSocketMonitor:
|
|
|
217
339
|
self._log_message("web socket connection reopened")
|
|
218
340
|
await self._resubscribe_all()
|
|
219
341
|
|
|
220
|
-
|
|
221
|
-
"""
|
|
342
|
+
def _start_backoff(self, reason: str) -> None:
|
|
343
|
+
"""Hold off reconnecting after a rate limit or a missing connection_ack.
|
|
344
|
+
|
|
345
|
+
The retry time is recorded right away, so closing the monitor can't lose
|
|
346
|
+
it; a connection backs off at most once.
|
|
347
|
+
"""
|
|
348
|
+
if self._backoff_reason:
|
|
349
|
+
return
|
|
350
|
+
self._backoff_reason = reason
|
|
351
|
+
state = self._backoff_state
|
|
222
352
|
delay = min(
|
|
223
|
-
RATE_LIMIT_BACKOFF_MIN * 2**
|
|
353
|
+
RATE_LIMIT_BACKOFF_MIN * 2**state.attempt
|
|
224
354
|
+ uniform(0, RATE_LIMIT_BACKOFF_MIN),
|
|
225
355
|
RATE_LIMIT_BACKOFF_MAX,
|
|
226
356
|
)
|
|
227
|
-
|
|
357
|
+
state.attempt += 1
|
|
358
|
+
# Don't cut short a longer backoff another monitor for the session started
|
|
359
|
+
if (retry_at := time.monotonic() + delay) > state.retry_at:
|
|
360
|
+
state.retry_at = retry_at
|
|
361
|
+
state.rate_limited = reason == BACKOFF_RATE_LIMITED
|
|
228
362
|
_LOGGER.warning(
|
|
229
363
|
"Web socket connection %s, reconnecting in %.0f seconds",
|
|
230
|
-
|
|
231
|
-
|
|
364
|
+
reason,
|
|
365
|
+
self.backoff_remaining,
|
|
232
366
|
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
367
|
+
|
|
368
|
+
def _reset_backoff(self) -> None:
|
|
369
|
+
"""Start the next backoff over, now that the connection is healthy."""
|
|
370
|
+
self._backoff_state.attempt = 0
|
|
237
371
|
|
|
238
372
|
async def start_monitor(self) -> None:
|
|
239
373
|
"""Start or restart the monitor task."""
|
|
@@ -247,7 +381,6 @@ class WebSocketMonitor:
|
|
|
247
381
|
async def close(self) -> None:
|
|
248
382
|
"""Close the web socket."""
|
|
249
383
|
self._disconnect = True
|
|
250
|
-
self._backoff_reason = None
|
|
251
384
|
if self._ws:
|
|
252
385
|
await self._ws.close()
|
|
253
386
|
await cancel_task(self._monitor_task, self._receiver_task)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rivian-python-client
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
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
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
rivian/__init__.py,sha256=m1arAfL3-WG5NQDkraIni2h-tGr-2nmltIabQetCFw8,207
|
|
2
|
-
rivian/__version__.py,sha256=
|
|
2
|
+
rivian/__version__.py,sha256=yVb3AjuZro0OqxMVJdMikqQkASaz8ZvNpOKLn4o1e30,171
|
|
3
3
|
rivian/ble.py,sha256=isTZQHRzINhEE7_fsrP8Z3YRfvC67SN1-EeXphBa8gc,5880
|
|
4
4
|
rivian/const.py,sha256=xMGZJILe9FqBH5b-pxrwJo5llmcKFJJDjTRBnjy7-_o,7122
|
|
5
5
|
rivian/exceptions.py,sha256=JVSOTzbfbIp4pobx9mFQm12m_qgVNV2p-Zp34m590Uw,969
|
|
6
6
|
rivian/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
rivian/rivian.py,sha256=
|
|
7
|
+
rivian/rivian.py,sha256=wAOm4Fm_dcIWNVsApajtpW5WM5O-GCHdeU_IP_S9zBM,44161
|
|
8
8
|
rivian/utils.py,sha256=2GeF73EF2PGEzyqEKnlaMFBcYpN2b0QgeyDsJoiyOHQ,3907
|
|
9
|
-
rivian/ws_monitor.py,sha256=
|
|
9
|
+
rivian/ws_monitor.py,sha256=M4QqvbaDQBvV29YI_w7GF2TF4EuoqQf6KxOFVdbNyrU,15511
|
|
10
10
|
rivian/parallax/__init__.py,sha256=8CozrZe8Z2o4SoFTGydYH0lAbuXEDB7Q0RCukzwP4L0,2535
|
|
11
11
|
rivian/parallax/body.py,sha256=uN2b6eGrel5jn8aJzbfD30lAOy4xEf0tJpdCTRNKs2I,9604
|
|
12
12
|
rivian/parallax/charging.py,sha256=pB_RtqiK9yMF3qGJdCjoqSSX3t8RMizU4mHGV6f2Y5E,17370
|
|
13
13
|
rivian/parallax/comfort.py,sha256=AhRl8Hz43EQWIT6kSgWQxgZ6MEE3Yi8BpZquIPZl4yw,12682
|
|
14
14
|
rivian/parallax/core.py,sha256=9dFAxtfNo93HO8Da1qlGknQ6G60K_r3xMFRrtwXRm1A,5737
|
|
15
|
-
rivian/parallax/departure.py,sha256=
|
|
15
|
+
rivian/parallax/departure.py,sha256=SzgcYPG74TrkE7m4DhrspIlx2pcl7EcKowTJT7y0iDw,4244
|
|
16
16
|
rivian/parallax/device_table.py,sha256=eTn3Ld-tLbil8VurrPfRWx6kwugKZ0YQPrWI6NdWi4Y,5757
|
|
17
17
|
rivian/parallax/dynamics.py,sha256=F554PzA70_j32uG-8DBxsy3Lcc9QcVtTQ3N2eZbKG78,10480
|
|
18
18
|
rivian/parallax/energy.py,sha256=_y4T0YLuJggDwYd7TJdu-bpObY1Dr9SxcVwTovKlfP8,5356
|
|
@@ -20,7 +20,7 @@ rivian/parallax/energy_edge_compute.py,sha256=V0WQTKzmMKmjL51BFgHhIKnXwa787KP-Dn
|
|
|
20
20
|
rivian/parallax/gearguard_streaming.py,sha256=Nv8fDj6DbQVKby5MjCQE802Bv2bqSWE3y4gs6fK5KPA,2420
|
|
21
21
|
rivian/parallax/geofence.py,sha256=OxDJGoTA832ptcLrKYB7Zcy5ce6iIGAbGVZunIg-LPA,1143
|
|
22
22
|
rivian/parallax/holiday_celebration.py,sha256=Y-nYlZ0Bl23DnhysmvDyAfG8yuBLXu2kKXIUYUz10Rs,6918
|
|
23
|
-
rivian/parallax/navigation.py,sha256=
|
|
23
|
+
rivian/parallax/navigation.py,sha256=W1yb63bBfhjKz8u3nlUfHIpcTzZbgmy9tJfQlD6S-hA,14989
|
|
24
24
|
rivian/parallax/operation.py,sha256=VI-dXwvw8menAO6psTPjREsbrPCYOONjJJ4xzRA2WyY,1118
|
|
25
25
|
rivian/parallax/ota.py,sha256=5cfzejj7ch4ecnUe4rwBYoVnMiPnfkhpy5k2Ki4YgF8,10326
|
|
26
26
|
rivian/parallax/parallax.py,sha256=Db3FBI6bcmIR6wj7d6_ltMQh7sK0LdZ8GSyze-tIlvg,1075
|
|
@@ -39,9 +39,9 @@ rivian/parallax/proto/charging_pb2.pyi,sha256=lSDCDP4o1obDnCUXq2KYGCvPY-Oq1Pzo6q
|
|
|
39
39
|
rivian/parallax/proto/comfort.proto,sha256=I6Lxh0FH0tkFFMeo5iV49NZfbiKViaaCb-1eTfSHJ8Y,4789
|
|
40
40
|
rivian/parallax/proto/comfort_pb2.py,sha256=_RW6pCjZdWuhKQCRT4DBj-a_YNVd7d4oex_pbMDR36k,10203
|
|
41
41
|
rivian/parallax/proto/comfort_pb2.pyi,sha256=BXChLWZlv3d6slTIk99pdFMtM-5Xmc8vH1ov6AnwsPg,14342
|
|
42
|
-
rivian/parallax/proto/departure.proto,sha256=
|
|
43
|
-
rivian/parallax/proto/departure_pb2.py,sha256=
|
|
44
|
-
rivian/parallax/proto/departure_pb2.pyi,sha256=
|
|
42
|
+
rivian/parallax/proto/departure.proto,sha256=bjt6kSNIGvW0vlMSyQVchP0jGJhSmez8E4d2orVrWww,2224
|
|
43
|
+
rivian/parallax/proto/departure_pb2.py,sha256=eKBiWihlUWx3gvCTGpiuG8CepwWcSxEMJGpjC541hyw,5952
|
|
44
|
+
rivian/parallax/proto/departure_pb2.pyi,sha256=Jw8vfV8PtnsK-sDbpJmiJBNiK4xgDi2TuKBTAoTeJEE,7502
|
|
45
45
|
rivian/parallax/proto/device_table.proto,sha256=A148tE-b6YJncWKdBmD4865d6rbCbGOmNRWE4ggGoFA,3550
|
|
46
46
|
rivian/parallax/proto/device_table_pb2.py,sha256=QRjS6QBgKH3dwdlZfnIlxcg4SV2S0P0-huDtGy-KJm0,7881
|
|
47
47
|
rivian/parallax/proto/device_table_pb2.pyi,sha256=8aUw8RA4ABAznz2N1jdkQyGHjF6sQmRy-5icp2vuKXE,9475
|
|
@@ -93,6 +93,6 @@ rivian/parallax/proto/vehicle_pb2.pyi,sha256=uM5JxmmlviPjtUq1FFqyZ_i-ih28A46KIYU
|
|
|
93
93
|
rivian/schemas/charging.graphql,sha256=YkaQbGtCDPtxGzmgNxOUHgYZKNDDtW7IipoaAFdGmGo,3061
|
|
94
94
|
rivian/schemas/gateway.graphql,sha256=0ZJX5_bKHqLmk3rtpp9to9GwrdCxyTasMhxAPwEhsqI,17865
|
|
95
95
|
rivian/schemas/orders.graphql,sha256=goM4x7gbkFHvpMf7XUv2cTkTKMaIicrT4JbIi07F_Do,4290
|
|
96
|
-
rivian_python_client-2.3.
|
|
97
|
-
rivian_python_client-2.3.
|
|
98
|
-
rivian_python_client-2.3.
|
|
96
|
+
rivian_python_client-2.3.1.dist-info/METADATA,sha256=dcHrmYLJdC68E5S6NtMYuUfGAS51c80gi88r3kE7QXI,1757
|
|
97
|
+
rivian_python_client-2.3.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
98
|
+
rivian_python_client-2.3.1.dist-info/RECORD,,
|
|
File without changes
|