rivian-python-client 1.0.4__tar.gz → 1.1.1__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.
@@ -1,19 +1,23 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rivian-python-client
3
- Version: 1.0.4
3
+ Version: 1.1.1
4
4
  Summary: Rivian API Client (Unofficial)
5
5
  License: MIT
6
6
  Author: Brian Retterer
7
7
  Author-email: bretterer@gmail.com
8
- Requires-Python: >=3.9,<4.0
8
+ Requires-Python: >=3.9,<3.13
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.9
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Provides-Extra: ble
14
16
  Requires-Dist: aiohttp (>=3.0.0)
15
17
  Requires-Dist: backports-strenum (>=1.2.4,<2.0.0) ; python_version < "3.11"
18
+ Requires-Dist: bleak (>=0.21,<0.22) ; extra == "ble"
16
19
  Requires-Dist: cryptography (>=41.0.1,<42.0.0)
20
+ Requires-Dist: dbus-fast (>=2.11.0,<3.0.0) ; (sys_platform == "linux") and (extra == "ble")
17
21
  Description-Content-Type: text/markdown
18
22
 
19
23
  # Python: Rivian API Client
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "rivian-python-client"
3
- version = "1.0.4"
3
+ version = "1.1.1"
4
4
  description = "Rivian API Client (Unofficial)"
5
5
  readme = "README.md"
6
6
  authors = ["Brian Retterer <bretterer@gmail.com>"]
@@ -10,19 +10,25 @@ packages = [
10
10
  ]
11
11
 
12
12
  [tool.poetry.dependencies]
13
- python = "^3.9"
13
+ python = ">=3.9, <3.13"
14
14
  aiohttp = ">=3.0.0"
15
15
  cryptography = "^41.0.1"
16
16
  backports-strenum = { version = "^1.2.4", python = "<3.11" }
17
+ bleak = { version = "^0.21", optional = true }
18
+ dbus-fast = {version = "^2.11.0", optional = true, platform = "linux"}
17
19
 
18
20
  [tool.poetry.group.dev.dependencies]
19
21
  pytest = "^7.1.2"
20
- pytest-asyncio = "^0.18.3"
22
+ pytest-asyncio = ">=0.18.3,<0.22.0"
21
23
  python-dotenv = "^0.20.0"
22
24
  aresponses = "^2.1.5"
25
+ mypy = "^1.7.0"
26
+
27
+ [tool.poetry.extras]
28
+ ble = ["bleak", "dbus-fast"]
23
29
 
24
30
  [tool.poetry-dynamic-versioning]
25
- enable = true
31
+ enable = false
26
32
  vcs = "git"
27
33
  style = "semver"
28
34
  pattern = "default-unprefixed"
@@ -0,0 +1,166 @@
1
+ """Rivian BLE handler."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import logging
6
+ import platform
7
+ import secrets
8
+
9
+ from .utils import generate_ble_command_hmac
10
+
11
+ _LOGGER = logging.getLogger(__name__)
12
+
13
+ try:
14
+ from bleak import BleakClient, BleakScanner, BLEDevice # type: ignore
15
+ except ImportError:
16
+ _LOGGER.error("Please install 'rivian-python-client[ble]' to use BLE features.")
17
+ raise
18
+
19
+
20
+ DEVICE_LOCAL_NAME = "Rivian Phone Key"
21
+
22
+ ACTIVE_ENTRY_CHARACTERISTIC_UUID = "5249565F-4D4F-424B-4559-5F5752495445"
23
+ PHONE_ID_VEHICLE_ID_UUID = "AA49565A-4D4F-424B-4559-5F5752495445"
24
+ PHONE_NONCE_VEHICLE_NONCE_UUID = "E020A15D-E730-4B2C-908B-51DAF9D41E19"
25
+
26
+ CONNECT_TIMEOUT = 10.0
27
+ NOTIFICATION_TIMEOUT = 3.0
28
+
29
+
30
+ class BleNotificationResponse:
31
+ """BLE notification response helper."""
32
+
33
+ def __init__(self) -> None:
34
+ """Initialize the BLE notification response helper."""
35
+ self.data: bytes | None = None
36
+ self.event = asyncio.Event()
37
+
38
+ def notification_handler(self, _, notification_data: bytearray) -> None:
39
+ """Notification handler."""
40
+ self.data = notification_data
41
+ self.event.set()
42
+
43
+ async def wait(self, timeout: float | None = NOTIFICATION_TIMEOUT) -> bool:
44
+ """Wait for the notification response."""
45
+ return await asyncio.wait_for(self.event.wait(), timeout)
46
+
47
+
48
+ async def create_notification_handler(
49
+ client: BleakClient, char_specifier: str
50
+ ) -> BleNotificationResponse:
51
+ """Create a notification handler."""
52
+ response = BleNotificationResponse()
53
+ await client.start_notify(char_specifier, response.notification_handler)
54
+ return response
55
+
56
+
57
+ async def pair_phone(
58
+ device: BLEDevice,
59
+ phone_id: str,
60
+ vas_vehicle_id: str,
61
+ vehicle_key: str,
62
+ private_key: str,
63
+ ) -> bool:
64
+ """Pair a phone locally via BLE.
65
+
66
+ The phone must first be enrolled via `rivian.enroll_phone`.
67
+ This finishes the process to enable cloud and local vehicle control.
68
+ """
69
+ _LOGGER.debug("Connecting to %s", device)
70
+ try:
71
+ async with BleakClient(device, timeout=CONNECT_TIMEOUT) as client:
72
+ _LOGGER.debug("Connected to %s", device)
73
+ vehicle_id_handler = await create_notification_handler(
74
+ client, PHONE_ID_VEHICLE_ID_UUID
75
+ )
76
+ nonce_handler = await create_notification_handler(
77
+ client, PHONE_NONCE_VEHICLE_NONCE_UUID
78
+ )
79
+
80
+ _LOGGER.debug("Validating id")
81
+ await client.write_gatt_char(
82
+ PHONE_ID_VEHICLE_ID_UUID, bytes.fromhex(phone_id.replace("-", ""))
83
+ )
84
+ await vehicle_id_handler.wait()
85
+
86
+ assert vehicle_id_handler.data
87
+ vehicle_id_response = vehicle_id_handler.data.hex()
88
+ if vehicle_id_response != vas_vehicle_id.replace("-", ""):
89
+ _LOGGER.debug(
90
+ "Incorrect vehicle id: received %s, expected %s",
91
+ vehicle_id_response,
92
+ vas_vehicle_id,
93
+ )
94
+ return False
95
+
96
+ _LOGGER.debug("Exchanging nonce")
97
+ phone_nonce = secrets.token_bytes(16)
98
+ hmac = generate_ble_command_hmac(phone_nonce, vehicle_key, private_key)
99
+ await client.write_gatt_char(
100
+ PHONE_NONCE_VEHICLE_NONCE_UUID, phone_nonce + hmac
101
+ )
102
+ await nonce_handler.wait()
103
+
104
+ # Vehicle is authenticated, trigger bonding
105
+ _LOGGER.debug("Attempting to pair")
106
+ if platform.system() == "Darwin":
107
+ # Mac BLE API doesn't have an explicit way to trigger bonding
108
+ # Instead, enable notification on protected characteristic to trigger bonding manually
109
+ await client.start_notify(
110
+ ACTIVE_ENTRY_CHARACTERISTIC_UUID, lambda _, __: None
111
+ )
112
+ else:
113
+ await client.pair()
114
+
115
+ _LOGGER.debug("Successfully paired with %s", device)
116
+ return True
117
+ except Exception as ex: # pylint: disable=broad-except
118
+ _LOGGER.debug(
119
+ "Couldn't connect to %s. "
120
+ 'Make sure you are in the correct vehicle and have selected "Set Up" for the appropriate key and try again'
121
+ "%s",
122
+ device,
123
+ ("" if isinstance(ex, asyncio.TimeoutError) else f": {ex}"),
124
+ )
125
+ return False
126
+
127
+
128
+ async def find_phone_key() -> BLEDevice | None:
129
+ """Find phone key."""
130
+ async with BleakScanner() as scanner:
131
+ return await scanner.find_device_by_name(DEVICE_LOCAL_NAME)
132
+
133
+
134
+ async def set_bluez_pairable(device: BLEDevice) -> bool:
135
+ """Set bluez to pairable on Linux systems."""
136
+ if (_os := platform.system()) != "Linux":
137
+ raise OSError(f"BlueZ is not available on {_os}-based systems")
138
+
139
+ # pylint: disable=import-error, import-outside-toplevel
140
+ from dbus_fast import BusType # type: ignore
141
+ from dbus_fast.aio import MessageBus # type: ignore
142
+
143
+ try:
144
+ path = device.details["props"]["Adapter"]
145
+ except Exception: # pylint: disable=broad-except
146
+ path = "/org/bluez/hci0"
147
+ _LOGGER.warning(
148
+ "Couldn't determine BT controller path, defaulting to %s: %s",
149
+ path,
150
+ device.details,
151
+ exc_info=True,
152
+ )
153
+
154
+ try:
155
+ bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
156
+ introspection = await bus.introspect("org.bluez", path)
157
+ pobject = bus.get_proxy_object("org.bluez", path, introspection)
158
+ iface = pobject.get_interface("org.bluez.Adapter1")
159
+ if not await iface.get_pairable():
160
+ await iface.set_pairable(True)
161
+ bus.disconnect()
162
+ except Exception as ex: # pylint: disable=broad-except
163
+ _LOGGER.error(ex)
164
+ return False
165
+
166
+ return True
@@ -36,9 +36,9 @@ VEHICLE_STATE_PROPERTIES: Final[set[str]] = {
36
36
  "gnssLocation",
37
37
  # TimeStamped(String|Float|Int)
38
38
  "alarmSoundStatus",
39
+ "batteryCapacity",
39
40
  "batteryHvThermalEvent",
40
41
  "batteryHvThermalEventPropagation",
41
- "batteryCapacity",
42
42
  "batteryLevel",
43
43
  "batteryLimit",
44
44
  "brakeFluidLow",
@@ -46,6 +46,7 @@ VEHICLE_STATE_PROPERTIES: Final[set[str]] = {
46
46
  "cabinClimateInteriorTemperature",
47
47
  "cabinPreconditioningStatus",
48
48
  "cabinPreconditioningType",
49
+ "carWashMode",
49
50
  "chargerDerateStatus",
50
51
  "chargerState",
51
52
  "chargerStatus",
@@ -57,12 +58,16 @@ VEHICLE_STATE_PROPERTIES: Final[set[str]] = {
57
58
  "closureLiftgateNextAction",
58
59
  "closureSideBinLeftClosed",
59
60
  "closureSideBinLeftLocked",
61
+ "closureSideBinLeftNextAction",
60
62
  "closureSideBinRightClosed",
61
63
  "closureSideBinRightLocked",
64
+ "closureSideBinRightNextAction",
62
65
  "closureTailgateClosed",
63
66
  "closureTailgateLocked",
67
+ "closureTailgateNextAction",
64
68
  "closureTonneauClosed",
65
69
  "closureTonneauLocked",
70
+ "closureTonneauNextAction",
66
71
  "defrostDefogStatus",
67
72
  "distanceToEmpty",
68
73
  "doorFrontLeftClosed",
@@ -79,6 +84,7 @@ VEHICLE_STATE_PROPERTIES: Final[set[str]] = {
79
84
  "gearGuardVideoStatus",
80
85
  "gearGuardVideoTermsAccepted",
81
86
  "gearStatus",
87
+ "gnssAltitude",
82
88
  "gnssBearing",
83
89
  "gnssSpeed",
84
90
  "otaAvailableVersion",
@@ -123,6 +129,8 @@ VEHICLE_STATE_PROPERTIES: Final[set[str]] = {
123
129
  "tirePressureStatusValidFrontRight",
124
130
  "tirePressureStatusValidRearLeft",
125
131
  "tirePressureStatusValidRearRight",
132
+ "trailerStatus",
133
+ "twelveVoltBatteryHealth",
126
134
  "vehicleMileage",
127
135
  "windowFrontLeftCalibrated",
128
136
  "windowFrontLeftClosed",
@@ -132,6 +140,7 @@ VEHICLE_STATE_PROPERTIES: Final[set[str]] = {
132
140
  "windowRearLeftClosed",
133
141
  "windowRearRightCalibrated",
134
142
  "windowRearRightClosed",
143
+ "windowsNextAction",
135
144
  "wiperFluidState",
136
145
  }
137
146
 
@@ -248,10 +248,12 @@ class Rivian:
248
248
  device_name: str,
249
249
  public_key: str,
250
250
  ) -> bool:
251
- """Enable control of a vehicle by enrolling a phone.
251
+ """Enroll a phone.
252
252
 
253
253
  To generate a public/private key for enrollment, use the `utils.generate_key_pair` function.
254
254
  The private key will need to be retained to sign commands sent via the `send_vehicle_command` method.
255
+ To enable vehicle control, the phone will then also need to be paired locally via BLE,
256
+ which can be done via `ble.pair_phone`.
255
257
  """
256
258
  url = GRAPHQL_GATEWAY
257
259
  headers = BASE_HEADERS | {
@@ -0,0 +1,179 @@
1
+ type Query {
2
+ chargepoint: ChargepointQuery
3
+ getCompletedSessionSummaries($vehicleId: String): [ChargingSessionSummary]
4
+ getLiveSessionData(vehicleId: ID!): LiveSessionData
5
+ getLiveSessionHistory(vehicleId: ID!): LiveSessionHistory
6
+ getNonRivianUserSession: NonRivianUserSessionData
7
+ getRegisteredWallboxes: [WallboxRecord]
8
+ getSessionHistory(
9
+ userId: ID!
10
+ startDate: String!
11
+ endDate: String!
12
+ ): [SessionHistorySummary]
13
+ getWallboxStatus(wallboxId: String!): WallboxRecord
14
+ getWallChargerHistory(
15
+ serialNumber: String!
16
+ startDate: String
17
+ endDate: String
18
+ ): [SessionSummary]
19
+ payment: PaymentQuery
20
+ }
21
+
22
+ type Mutation {
23
+ updateWallbox(
24
+ wallboxId: String!
25
+ wifiId: String
26
+ name: String
27
+ latitude: String
28
+ longitude: String
29
+ ): WallboxRecord
30
+ }
31
+
32
+ type Card {
33
+ brand: String!
34
+ lastFour: String!
35
+ }
36
+
37
+ type ChargepointQuery {
38
+ checkByRivianId: Boolean!
39
+ getLinkedEmailForRivianId: GetLinkedEmailForRivianIdResponse!
40
+ }
41
+
42
+ type ChargingSessionSummary {
43
+ chargerType: String
44
+ currencyCode: String
45
+ paidTotal: Float
46
+ startInstant: String!
47
+ endInstant: String!
48
+ totalEnergyKwh: Float!
49
+ rangeAddedKm: Float
50
+ city: String
51
+ transactionId: String!
52
+ vehicleId: String
53
+ vehicleName: String
54
+ vendor: String
55
+ isRoamingNetwork: Boolean
56
+ isPublic: Boolean
57
+ isHomeCharger: Boolean
58
+ meta: ChargingSessionSummaryMeta!
59
+ }
60
+
61
+ type ChargingSessionSummaryMeta {
62
+ transactionIdGroupingKey: String!
63
+ dataSources: [String!]!
64
+ }
65
+
66
+ type FloatValueRecord {
67
+ value: Float!
68
+ updatedAt: String!
69
+ }
70
+
71
+ type GetLinkedEmailForRivianIdResponse {
72
+ email: String!
73
+ }
74
+
75
+ type LiveSessionData {
76
+ chargerId: String
77
+ current: FloatValueRecord
78
+ currentCurrency: String
79
+ currentMiles: FloatValueRecord
80
+ currentPrice: Float
81
+ isFreeSession: Boolean
82
+ isRivianCharger: Boolean
83
+ kilometersChargedPerHour: FloatValueRecord
84
+ locationId: String
85
+ power: FloatValueRecord
86
+ rangeAddedThisSession: FloatValueRecord
87
+ soc: FloatValueRecord
88
+ startTime: String
89
+ timeElapsed: Float
90
+ timeRemaining: StringValueRecord
91
+ totalChargedEnergy: FloatValueRecord
92
+ vehicleChargerState: StringValueRecord
93
+ }
94
+
95
+ type LiveSessionHistory {
96
+ chartData: [FloatValueRecord]
97
+ startTime: Float
98
+ vehicleId: String
99
+ chargerId: String
100
+ transactionId: String
101
+ current: FloatValueRecord
102
+ }
103
+
104
+ type NonRivianUserSessionData {
105
+ chargerId: String
106
+ transactionId: String
107
+ isRivianCharger: Boolean!
108
+ vehicleChargerState: StringValueRecord
109
+ }
110
+
111
+ type PaymentMethod {
112
+ default: Boolean!
113
+ card: Card
114
+ }
115
+
116
+ type PaymentQuery {
117
+ getPaymentMethods: [PaymentMethod!]!
118
+ }
119
+
120
+ type SessionHistorySummary {
121
+ transactionId: String!
122
+ locationId: String!
123
+ vehicleId: String
124
+ startDateTime: String!
125
+ endDateTime: String!
126
+ currency: String!
127
+ cost: Float!
128
+ paidTotal: Float!
129
+ energyOffered: Float!
130
+ energyUnits: String
131
+ rangeAdded: Float
132
+ rangeUnits: String
133
+ vendor: String!
134
+ chargerType: String!
135
+ chargerName: String!
136
+ address: String!
137
+ city: String!
138
+ country: String!
139
+ postalCode: string!
140
+ }
141
+
142
+ type SessionSummary {
143
+ transactionId: String
144
+ vehicleInfo: VehicleInfo
145
+ startDateTime: String!
146
+ endDateTime: String!
147
+ totalEnergyKwh: Float!
148
+ rangeAddedKm: Float
149
+ }
150
+
151
+ type StringValueRecord {
152
+ value: String!
153
+ updatedAt: String!
154
+ }
155
+
156
+ type VehicleInfo {
157
+ name: String
158
+ vin: String
159
+ }
160
+
161
+ type WallboxRecord {
162
+ wallboxId: String!
163
+ userId: String!
164
+ wifiId: String!
165
+ name: String!
166
+ linked: Boolean!
167
+ latitude: String!
168
+ longitude: String!
169
+ chargingStatus: String!
170
+ power: String
171
+ currentVoltage: String
172
+ currentAmps: String
173
+ softwareVersion: String!
174
+ model: String!
175
+ serialNumber: String!
176
+ maxPower: String!
177
+ maxVoltage: String!
178
+ maxAmps: String
179
+ }
@@ -0,0 +1,559 @@
1
+ type Query {
2
+ currentUser: User
3
+ delivery(orderId: ID!): Delivery
4
+ getVehicle(id: String!): Vehicle
5
+ getVehicleCommand(id: String!): GetVehicleCommandState
6
+ getVehicleMobileImages(
7
+ resolution: String
8
+ extension: String
9
+ version: String
10
+ ): VehicleMobileImage
11
+ getVehicleOrderMobileImages(
12
+ resolution: String
13
+ extension: String
14
+ version: String
15
+ ): VehicleMobileImage
16
+ planTrip(
17
+ origin: CoordinatesInput!
18
+ destination: CoordinatesInput!
19
+ bearing: Float!
20
+ vehicleId: String!
21
+ startingSoc: Float!
22
+ startingRangeMeters: Float!
23
+ targetArrivalSocPercent: Float
24
+ networkPreferences: [NetworkPreference!]
25
+ ): PlanTripResponse
26
+ vehicleState(id: String!): VehicleState
27
+ }
28
+
29
+ type Mutation {
30
+ createCsrfToken: CreateCsrfTokenResponse
31
+ disenrollPhone(attrs: DisenrollPhoneAttributes!): DisenrollPhoneResponse
32
+ enrollPhone(attrs: EnrollPhoneAttributes!): EnrollPhoneResponse
33
+ login(email: String!, password: String!): LoginResponse
34
+ loginWithOTP(
35
+ email: String!
36
+ otpToken: String!
37
+ otpCode: String!
38
+ ): MobileLoginResponse
39
+ sendVehicleCommand(attrs: VehicleCommandAttributes!): SendVehicleCommandState
40
+ setChargingSchedules(
41
+ vehicleId: String!
42
+ chargingSchedules: [InputChargingSchedule!]!
43
+ ): OK
44
+ setVehicleName(vehicleId: String!, name: String!): OK
45
+ }
46
+
47
+ type Subscription {
48
+ vehicleState(id: String!): VehicleState
49
+ }
50
+
51
+ input CoordinatesInput {
52
+ latitude: Float!
53
+ longitude: Float!
54
+ }
55
+
56
+ input DisenrollPhoneAttributes {
57
+ enrollmentId: String!
58
+ }
59
+
60
+ input EnrollPhoneAttributes {
61
+ userId: String!
62
+ vehicleId: String!
63
+ publicKey: String!
64
+ type: String!
65
+ name: String
66
+ }
67
+
68
+ input InputChargingSchedule {
69
+ weekDays: [String!]!
70
+ startTime: Int!
71
+ duration: Int!
72
+ location: CoordinatesInput!
73
+ amperage: Int!
74
+ enabled: Boolean!
75
+ }
76
+
77
+ input NetworkPreference {
78
+ networkId: String!
79
+ preference: Float
80
+ }
81
+
82
+ input VehicleCommandAttributes {
83
+ command: String!
84
+ hmac: String!
85
+ timestamp: String!
86
+ vasPhoneId: String!
87
+ deviceId: String!
88
+ vehicleId: String!
89
+ params: VehicleCommandParameters
90
+ }
91
+
92
+ input VehicleCommandParameters {
93
+ level: Int
94
+ HVAC_set_temp: Float
95
+ SOC_limit: Int
96
+ }
97
+
98
+ interface TimeStamped {
99
+ timeStamp: String!
100
+ }
101
+
102
+ type CampSpeaker {
103
+ id: String!
104
+ serialNumber: String!
105
+ }
106
+
107
+ type ChargingSchedule {
108
+ startTime: Int!
109
+ duration: Int!
110
+ location: GeoCoordinates!
111
+ amperage: Int!
112
+ enabled: Boolean!
113
+ weekDays: [String!]!
114
+ }
115
+
116
+ type CreateCsrfTokenResponse {
117
+ csrfToken: String!
118
+ appSessionToken: String!
119
+ }
120
+
121
+ type Delivery {
122
+ status: String!
123
+ carrier: String!
124
+ deliveryAddress: DeliveryAddressDetails!
125
+ appointmentDetails: DeliveryAppointment!
126
+ vehicleVIN: String!
127
+ }
128
+
129
+ type DeliveryAddressDetails {
130
+ addressLine1: String!
131
+ addressLine2: String
132
+ city: String!
133
+ state: String!
134
+ country: String!
135
+ zipcode: String!
136
+ }
137
+
138
+ type DeliveryAppointment {
139
+ appointmentId: String!
140
+ startDateTime: String!
141
+ endDateTime: String!
142
+ timeZone: String!
143
+ }
144
+
145
+ type DisenrollPhoneResponse {
146
+ success: Boolean!
147
+ }
148
+
149
+ type EnrollPhoneResponse {
150
+ success: Boolean!
151
+ }
152
+
153
+ type GeoCoordinates {
154
+ latitude: Float!
155
+ longitude: Float!
156
+ }
157
+
158
+ type GetVehicleCommandState {
159
+ id: String!
160
+ command: String!
161
+ createdAt: String!
162
+ state: Int!
163
+ responseCode: Int
164
+ statusCode: Int
165
+ }
166
+
167
+ type MobileLoginResponse {
168
+ accessToken: String!
169
+ refreshToken: String!
170
+ userSessionToken: String!
171
+ }
172
+
173
+ type MobileMFALoginResponse {
174
+ otpToken: String!
175
+ }
176
+
177
+ type NameSetting {
178
+ value: String!
179
+ }
180
+
181
+ type OK {
182
+ success: Boolean!
183
+ value: String
184
+ }
185
+
186
+ type OTAUpdateDetails {
187
+ url: String!
188
+ version: String!
189
+ locale: String!
190
+ }
191
+
192
+ type PlanTripResponse {
193
+ routes: [Route!]!
194
+ tripPlanStatus: String!
195
+ chargeStationsAvailable: Boolean!
196
+ socBelowLimit: Boolean!
197
+ }
198
+
199
+ type ProvisionedUser {
200
+ firstName: String!
201
+ lastName: String!
202
+ email: String!
203
+ userId: String!
204
+ roles: [String!]!
205
+ devices: [ProvisionedUserDevice!]!
206
+ }
207
+
208
+ type ProvisionedUserDevice {
209
+ type: String!
210
+ mappedIdentityId: String!
211
+ id: String!
212
+ hrid: String
213
+ deviceName: String!
214
+ isPaired: Boolean!
215
+ isEnabled: Boolean!
216
+ }
217
+
218
+ type RegistrationChannel {
219
+ type: String!
220
+ }
221
+
222
+ type Route {
223
+ routeResponse: String!
224
+ destinationReached: Boolean!
225
+ totalChargingDuration: Int!
226
+ arrivalSOC: Int!
227
+ arrivalReachableDistance: Float!
228
+ waypoints: [WayPoint!]!
229
+ energyConsumptionOnLeg: Float
230
+ batteryEmptyToDestinationDistance: Float
231
+ batteryEmptyLocationLatitude: Float
232
+ batteryEmptyLocationLongitude: Float
233
+ }
234
+
235
+ type SendVehicleCommandState {
236
+ id: String!
237
+ command: String!
238
+ state: Int!
239
+ }
240
+
241
+ type SupportedFeature {
242
+ name: String!
243
+ status: String!
244
+ }
245
+
246
+ type TimeStampedFloat implements TimeStamped {
247
+ timeStamp: String!
248
+ value: Float!
249
+ }
250
+
251
+ type TimeStampedNullableFloat implements TimeStamped {
252
+ timeStamp: String!
253
+ value: Float
254
+ }
255
+
256
+ type TimeStampedInt implements TimeStamped {
257
+ timeStamp: String!
258
+ value: Int!
259
+ }
260
+
261
+ type TimeStampedString implements TimeStamped {
262
+ timeStamp: String!
263
+ value: String!
264
+ }
265
+
266
+ type UnprovisionedUser {
267
+ email: String!
268
+ inviteId: String!
269
+ status: String!
270
+ }
271
+
272
+ type User {
273
+ id: String!
274
+ firstName: String!
275
+ lastName: String!
276
+ email: String!
277
+ address: UserAddress!
278
+ enrolledPhones: [UserEnrolledPhone]!
279
+ pendingInvites: [UserInvitation!]!
280
+ phones: [UserPhone!]!
281
+ registrationChannels: [RegistrationChannel!]!
282
+ vehicles: [UserVehicle!]!
283
+ }
284
+
285
+ type UserAddress {
286
+ country: String!
287
+ }
288
+
289
+ type UserEnrolledPhone {
290
+ vas: UserEnrolledPhoneAccess!
291
+ enrolled: [UserEnrolledPhoneEntry!]!
292
+ }
293
+
294
+ type UserEnrolledPhoneAccess {
295
+ vasPhoneId: String!
296
+ publicKey: String!
297
+ }
298
+
299
+ type UserEnrolledPhoneEntry {
300
+ deviceType: String!
301
+ deviceName: String!
302
+ vehicleId: String!
303
+ identityId: String!
304
+ shortName: String!
305
+ }
306
+
307
+ type UserInvitation {
308
+ id: String!
309
+ invitedByFirstName: String
310
+ role: String
311
+ status: String
312
+ vehicleId: String
313
+ vehicleModel: String
314
+ email: String
315
+ }
316
+
317
+ type UserPhone {
318
+ countryCode: String!
319
+ formatted: String!
320
+ phone: String!
321
+ national: String!
322
+ }
323
+
324
+ type UserVehicle {
325
+ id: String!
326
+ vin: String!
327
+ name: String!
328
+ owner: String
329
+ state: String
330
+ createdAt: String!
331
+ updatedAt: String
332
+ vas: UserVehicleAccess!
333
+ vehicle: Vehicle!
334
+ roles: [String!]!
335
+ connectedProducts: [ConnectedProduct!]!
336
+ }
337
+
338
+ type UserVehicleAccess {
339
+ vasVehicleId: String!
340
+ vehiclePublicKey: String!
341
+ }
342
+
343
+ type UserVehicleSettingsMap {
344
+ name: NameSetting!
345
+ }
346
+
347
+ type Vehicle {
348
+ id: String!
349
+ vin: String!
350
+ make: String!
351
+ model: String!
352
+ modelYear: Int!
353
+ expectedBuildDate: String
354
+ plannedBuildDate: String
355
+ expectedGeneralAssemblyStartDate: String
356
+ actualGeneralAssemblyDate: String
357
+ otaEarlyAccessStatus: Boolean!
358
+ currentOTAUpdateDetails: OTAUpdateDetails!
359
+ availableOTAUpdateDetails: OTAUpdateDetails
360
+ mobileConfiguration: VehicleMobileConfiguration!
361
+ settings: UserVehicleSettingsMap!
362
+ vehicleState: VehicleState!
363
+ invitedUsers: [InvitedUser!]!
364
+ chargingSchedules: [ChargingSchedule!]!
365
+ estimatedRange(startSoc: Float!): Float!
366
+ }
367
+
368
+ type VehicleCloudConnection {
369
+ lastSync: String!
370
+ }
371
+
372
+ type VehicleLocation implements TimeStamped {
373
+ timeStamp: String!
374
+ latitude: Float!
375
+ longitude: Float!
376
+ }
377
+
378
+ type VehicleMobileConfiguration {
379
+ trimOption: VehicleMobileConfigurationOption!
380
+ driveSystemOption: VehicleMobileConfigurationOption!
381
+ exteriorColorOption: VehicleMobileConfigurationOption!
382
+ interiorColorOption: VehicleMobileConfigurationOption!
383
+ }
384
+
385
+ type VehicleMobileConfigurationOption {
386
+ optionId: String!
387
+ optionName: String!
388
+ }
389
+
390
+ type VehicleMobileImage {
391
+ orderId: String
392
+ vehicleId: String
393
+ design: String!
394
+ extension: String!
395
+ placement: String!
396
+ resolution: String!
397
+ size: String!
398
+ url: String!
399
+ }
400
+
401
+ type VehicleState {
402
+ alarmSoundStatus: TimeStampedString
403
+
404
+ batteryCapacity: TimeStampedFloat
405
+ batteryHvThermalEvent: TimeStampedString
406
+ batteryHvThermalEventPropagation: TimeStampedString
407
+ batteryLevel: TimeStampedFloat
408
+ batteryLimit: TimeStampedInt
409
+
410
+ brakeFluidLow: TimeStampedString
411
+
412
+ cabinClimateDriverTemperature: TimeStampedFloat
413
+ cabinClimateInteriorTemperature: TimeStampedFloat
414
+ cabinPreconditioningStatus: TimeStampedString
415
+ cabinPreconditioningType: TimeStampedString
416
+
417
+ carWashMode: TimeStampedString
418
+
419
+ chargerDerateStatus: TimeStampedString
420
+ chargerState: TimeStampedString
421
+ chargerStatus: TimeStampedString
422
+
423
+ closureFrunkClosed: TimeStampedString
424
+ closureFrunkLocked: TimeStampedString
425
+ closureFrunkNextAction: TimeStampedString
426
+ closureLiftgateClosed: TimeStampedString
427
+ closureLiftgateLocked: TimeStampedString
428
+ closureLiftgateNextAction: TimeStampedString
429
+ closureSideBinLeftClosed: TimeStampedString
430
+ closureSideBinLeftLocked: TimeStampedString
431
+ closureSideBinLeftNextAction: TimeStampedString
432
+ closureSideBinRightClosed: TimeStampedString
433
+ closureSideBinRightLocked: TimeStampedString
434
+ closureSideBinRightNextAction: TimeStampedString
435
+ closureTailgateClosed: TimeStampedString
436
+ closureTailgateLocked: TimeStampedString
437
+ closureTailgateNextAction: TimeStampedString
438
+ closureTonneauClosed: TimeStampedString
439
+ closureTonneauLocked: TimeStampedString
440
+ closureTonneauNextAction: TimeStampedString
441
+
442
+ cloudConnection: VehicleCloudConnection
443
+
444
+ defrostDefogStatus: TimeStampedString
445
+
446
+ distanceToEmpty: TimeStampedInt
447
+
448
+ doorFrontLeftClosed: TimeStampedString
449
+ doorFrontLeftLocked: TimeStampedString
450
+ doorFrontRightClosed: TimeStampedString
451
+ doorFrontRightLocked: TimeStampedString
452
+ doorRearLeftClosed: TimeStampedString
453
+ doorRearLeftLocked: TimeStampedString
454
+ doorRearRightClosed: TimeStampedString
455
+ doorRearRightLocked: TimeStampedString
456
+
457
+ driveMode: TimeStampedString
458
+
459
+ gearGuardLocked: TimeStampedString
460
+ gearGuardVideoMode: TimeStampedString
461
+ gearGuardVideoStatus: TimeStampedString
462
+ gearGuardVideoTermsAccepted: TimeStampedString
463
+ gearStatus: TimeStampedString
464
+
465
+ gnssAltitude: TimeStampedNullableFloat
466
+ gnssBearing: TimeStampedFloat
467
+ gnssLocation: VehicleLocation
468
+ gnssSpeed: TimeStampedFloat
469
+
470
+ otaAvailableVersion: TimeStampedString
471
+ otaAvailableVersionGitHash: TimeStampedString
472
+ otaAvailableVersionNumber: TimeStampedInt
473
+ otaAvailableVersionWeek: TimeStampedInt
474
+ otaAvailableVersionYear: TimeStampedInt
475
+ otaCurrentStatus: TimeStampedString
476
+ otaCurrentVersion: TimeStampedString
477
+ otaCurrentVersionGitHash: TimeStampedString
478
+ otaCurrentVersionNumber: TimeStampedInt
479
+ otaCurrentVersionWeek: TimeStampedInt
480
+ otaCurrentVersionYear: TimeStampedInt
481
+ otaDownloadProgress: TimeStampedInt
482
+ otaInstallDuration: TimeStampedInt
483
+ otaInstallProgress: TimeStampedInt
484
+ otaInstallReady: TimeStampedString
485
+ otaInstallTime: TimeStampedInt
486
+ otaInstallType: TimeStampedString
487
+ otaStatus: TimeStampedString
488
+
489
+ petModeStatus: TimeStampedString
490
+ petModeTemperatureStatus: TimeStampedString
491
+
492
+ powerState: TimeStampedString
493
+
494
+ rangeThreshold: TimeStampedString
495
+
496
+ remoteChargingAvailable: TimeStampedInt
497
+
498
+ seatFrontLeftHeat: TimeStampedString
499
+ seatFrontLeftVent: TimeStampedString
500
+ seatFrontRightHeat: TimeStampedString
501
+ seatFrontRightVent: TimeStampedString
502
+ seatRearLeftHeat: TimeStampedString
503
+ seatRearRightHeat: TimeStampedString
504
+ seatThirdRowLeftHeat: TimeStampedString
505
+ seatThirdRowRightHeat: TimeStampedString
506
+
507
+ serviceMode: TimeStampedString
508
+
509
+ steeringWheelHeat: TimeStampedString
510
+
511
+ timeToEndOfCharge: TimeStampedFloat
512
+
513
+ tirePressureStatusFrontLeft: TimeStampedString
514
+ tirePressureStatusFrontRight: TimeStampedString
515
+ tirePressureStatusRearLeft: TimeStampedString
516
+ tirePressureStatusRearRight: TimeStampedString
517
+ tirePressureStatusValidFrontLeft: TimeStampedString
518
+ tirePressureStatusValidFrontRight: TimeStampedString
519
+ tirePressureStatusValidRearLeft: TimeStampedString
520
+ tirePressureStatusValidRearRight: TimeStampedString
521
+
522
+ trailerStatus: TimeStampedString
523
+
524
+ twelveVoltBatteryHealth: TimeStampedString
525
+
526
+ vehicleMileage: TimeStampedInt
527
+
528
+ windowFrontLeftCalibrated: TimeStampedString
529
+ windowFrontLeftClosed: TimeStampedString
530
+ windowFrontRightCalibrated: TimeStampedString
531
+ windowFrontRightClosed: TimeStampedString
532
+ windowRearLeftCalibrated: TimeStampedString
533
+ windowRearLeftClosed: TimeStampedString
534
+ windowRearRightCalibrated: TimeStampedString
535
+ windowRearRightClosed: TimeStampedString
536
+ windowsNextAction: TimeStampedString
537
+
538
+ wiperFluidState: TimeStampedString
539
+
540
+ supportedFeatures: [SupportedFeature!]!
541
+ }
542
+
543
+ type WayPoint {
544
+ waypointType: String!
545
+ entityId: String
546
+ name: String
547
+ latitude: Float!
548
+ longitude: Float!
549
+ maxPower: Int
550
+ chargeDuration: Int
551
+ arrivalSOC: Int!
552
+ arrivalReachableDistance: Float!
553
+ departureSOC: Int!
554
+ departureReachableDistance: Float!
555
+ }
556
+
557
+ union ConnectedProduct = CampSpeaker
558
+ union InvitedUser = ProvisionedUser | UnprovisionedUser
559
+ union LoginResponse = MobileLoginResponse | MobileMFALoginResponse
@@ -0,0 +1,254 @@
1
+ type Query {
2
+ order(id: String!): Order
3
+ user: User
4
+ }
5
+
6
+ enum UserVehicleInviteStateEnum {
7
+ PENDING
8
+ COMPLETED
9
+ }
10
+
11
+ enum OrderSnapshotTypeEnum {
12
+ PRE_ORDER
13
+ RETAIL
14
+ VEHICLE
15
+ }
16
+
17
+ type Address {
18
+ id: String!
19
+ type: [String!]!
20
+ line1: String!
21
+ line2: String
22
+ city: String!
23
+ state: String!
24
+ country: String!
25
+ postalCode: String!
26
+ }
27
+
28
+ type BankDetails {
29
+ bankName: String!
30
+ country: String!
31
+ last4: String!
32
+ }
33
+
34
+ type CardDetails {
35
+ brand: String!
36
+ last4: String!
37
+ expiryDate: String!
38
+ }
39
+
40
+ type ConfiguratorRulesetGroup {
41
+ name: String
42
+ attrs: String
43
+ multiselect: Boolean!
44
+ required: Boolean!
45
+ options: [String!]!
46
+ }
47
+
48
+ type ConfiguratorRulesetOption {
49
+ name: String
50
+ attrs: String
51
+ price: Float!
52
+ visualExterior: Boolean
53
+ visualInterior: Boolean
54
+ hidden: Boolean
55
+ disabled: Boolean
56
+ required: Boolean
57
+ }
58
+
59
+ type Email {
60
+ email: String!
61
+ }
62
+
63
+ type Order {
64
+ id: String!
65
+ vehicleId: String
66
+ vin: String
67
+ state: String!
68
+ billingAddress: OrderAddress
69
+ shippingAddress: OrderAddress
70
+ orderCancelDate: String
71
+ orderEmail: String
72
+ currency: String!
73
+ locale: String!
74
+ storeId: String!
75
+ type: String!
76
+ subtotal: Float!
77
+ discountTotal: Float!
78
+ taxTotal: Float!
79
+ feesTotal: Float!
80
+ paidTotal: Float!
81
+ remainingTotal: Float!
82
+ outstandingBalance: Float!
83
+ costAfterCredits: Float!
84
+ total: Float!
85
+ payments: [OrderPayment!]!
86
+ tradeIns: [TradeIn!]
87
+ vehicle: Vehicle
88
+ items: [OrderItem!]!
89
+ }
90
+
91
+ type OrderAddress {
92
+ firstName: String!
93
+ lastName: String!
94
+ line1: String!
95
+ line2: String
96
+ city: String!
97
+ state: String!
98
+ country: String!
99
+ postalCode: String!
100
+ }
101
+
102
+ type OrderConfiguration {
103
+ version: String!
104
+ basePrice: Float!
105
+ options: [OrderConfigurationOption!]!
106
+ }
107
+
108
+ type OrderConfigurationOption {
109
+ optionId: String!
110
+ optionName: String!
111
+ optionDetails: ConfiguratorRulesetOption!
112
+ groupId: String!
113
+ groupName: String!
114
+ groupDetails: ConfiguratorRulesetGroup!
115
+ price: Float!
116
+ }
117
+
118
+ type OrderItem {
119
+ id: String!
120
+ productId: String!
121
+ type: String!
122
+ sku: String!
123
+ title: String!
124
+ configuration: OrderConfiguration
125
+ discounts: [OrderItemDiscount!]
126
+ subtotal: Float!
127
+ quantity: Int!
128
+ unitPrice: Float!
129
+ fees: [OrderItemFees!]
130
+ taxes: [OrderItemTaxes!]
131
+ shippingAddress: OrderItemFulfillmentAddress!
132
+ }
133
+
134
+ type OrderItemDiscount {
135
+ total: Float!
136
+ items: [OrderItemDiscountItem!]
137
+ }
138
+
139
+ type OrderItemDiscountItem {
140
+ amount: Float!
141
+ title: String!
142
+ code: String!
143
+ }
144
+
145
+ type OrderItemFeeItem {
146
+ description: String!
147
+ amount: Float!
148
+ code: String!
149
+ type: String!
150
+ }
151
+
152
+ type OrderItemFees {
153
+ items: [OrderItemFeeItem!]!
154
+ total: Float!
155
+ }
156
+
157
+ type OrderItemFulfillmentAddress {
158
+ firstName: String!
159
+ lastName: String!
160
+ line1: String!
161
+ line2: String
162
+ city: String!
163
+ state: String!
164
+ country: String!
165
+ postalCode: String!
166
+ }
167
+
168
+ type OrderItemTaxItem {
169
+ description: String!
170
+ amount: Float!
171
+ code: String!
172
+ rate: Float
173
+ type: String
174
+ }
175
+
176
+ type OrderItemTaxes {
177
+ items: [OrderItemTaxItem!]!
178
+ total: Float!
179
+ }
180
+
181
+ type OrderPayment {
182
+ id: String!
183
+ intent: String!
184
+ date: String!
185
+ method: String!
186
+ amount: Float!
187
+ referenceNumber: String
188
+ status: String!
189
+ card: CardDetails
190
+ bank: BankDetails
191
+ transactionNotes: String!
192
+ }
193
+
194
+ type OrderSnapshot {
195
+ id: String!
196
+ total: Float!
197
+ paidTotal: Float!
198
+ subtotal: Float!
199
+ state: String!
200
+ configurationStatus: String!
201
+ currency: String!
202
+ orderDate: String!
203
+ type: String!
204
+ fulfillmentSummaryStatus: String!
205
+ vehicleId: String
206
+ vin: String
207
+ items: [OrderItem!]!
208
+ }
209
+
210
+ type Phone {
211
+ formatted: String!
212
+ }
213
+
214
+ type RivianIdVehicle {
215
+ id: String!
216
+ vin: String!
217
+ highestPriorityRole: String!
218
+ }
219
+
220
+ type TradeIn {
221
+ tradeInReferenceId: String!
222
+ amount: Float!
223
+ }
224
+
225
+ type User {
226
+ email: Email!
227
+ phone: Phone!
228
+ firstName: String!
229
+ lastName: String!
230
+ addresses: [Address!]!
231
+ newsletterSubscription: Boolean!
232
+ smsSubscription: Boolean!
233
+ registrationChannels2FA: String!
234
+ userId: String!
235
+ vehicles: [RivianIdVehicle!]!
236
+ invites(filterStates: [UserVehicleInviteStateEnum!]): [UserVehicleInvite!]!
237
+ orderSnapshots(filterTypes: [OrderSnapshotTypeEnum!]): [OrderSnapshot!]!
238
+ }
239
+
240
+ type UserVehicleInvite {
241
+ id: String!
242
+ inviteState: String!
243
+ vehicleModel: String!
244
+ vehicleId: String!
245
+ creatorFirstName: String!
246
+ }
247
+
248
+ type Vehicle {
249
+ vehicleId: String!
250
+ vin: String!
251
+ modelYear: String!
252
+ model: String!
253
+ make: String!
254
+ }
@@ -67,6 +67,14 @@ def generate_key_pair() -> tuple[str, str]:
67
67
  return (public_key_str, private_key_str)
68
68
 
69
69
 
70
+ def generate_ble_command_hmac(
71
+ hmac_data: bytes, vehicle_key: str, private_key: str
72
+ ) -> bytes:
73
+ """Generate ble command hmac."""
74
+ secret_key = get_secret_key(private_key, vehicle_key)
75
+ return bytes.fromhex(get_message_signature(secret_key, hmac_data))
76
+
77
+
70
78
  def generate_vehicle_command_hmac(
71
79
  command: str, timestamp: str, vehicle_key: str, private_key: str
72
80
  ):