tesla-fleet-api 0.4.0 → 0.5.0

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 (219) hide show
  1. package/README.md +284 -1
  2. package/dist/_virtual/_rolldown/runtime.cjs +10 -0
  3. package/dist/bluetooth/adapters/shared.cjs +38 -0
  4. package/dist/bluetooth/adapters/shared.mjs +36 -0
  5. package/dist/bluetooth/async.cjs +121 -0
  6. package/dist/bluetooth/async.d.cts +10 -0
  7. package/dist/bluetooth/async.d.mts +10 -0
  8. package/dist/bluetooth/async.mjs +116 -0
  9. package/dist/bluetooth/broadcast.cjs +153 -0
  10. package/dist/bluetooth/broadcast.d.cts +59 -0
  11. package/dist/bluetooth/broadcast.d.mts +59 -0
  12. package/dist/bluetooth/broadcast.mjs +151 -0
  13. package/dist/bluetooth/constants.cjs +99 -0
  14. package/dist/bluetooth/constants.d.cts +70 -0
  15. package/dist/bluetooth/constants.d.mts +70 -0
  16. package/dist/bluetooth/constants.mjs +72 -0
  17. package/dist/bluetooth/esphome.cjs +292 -0
  18. package/dist/bluetooth/esphome.d.cts +83 -0
  19. package/dist/bluetooth/esphome.d.mts +83 -0
  20. package/dist/bluetooth/esphome.mjs +291 -0
  21. package/dist/bluetooth/framing.cjs +125 -0
  22. package/dist/bluetooth/framing.d.cts +45 -0
  23. package/dist/bluetooth/framing.d.mts +45 -0
  24. package/dist/bluetooth/framing.mjs +122 -0
  25. package/dist/bluetooth/homey.cjs +155 -0
  26. package/dist/bluetooth/homey.d.cts +75 -0
  27. package/dist/bluetooth/homey.d.mts +75 -0
  28. package/dist/bluetooth/homey.mjs +150 -0
  29. package/dist/bluetooth/names.cjs +24 -0
  30. package/dist/bluetooth/names.d.cts +10 -0
  31. package/dist/bluetooth/names.d.mts +10 -0
  32. package/dist/bluetooth/names.mjs +20 -0
  33. package/dist/bluetooth/noble.cjs +178 -0
  34. package/dist/bluetooth/noble.d.cts +58 -0
  35. package/dist/bluetooth/noble.d.mts +58 -0
  36. package/dist/bluetooth/noble.mjs +177 -0
  37. package/dist/bluetooth/streamglue.cjs +157 -0
  38. package/dist/bluetooth/streamglue.d.cts +37 -0
  39. package/dist/bluetooth/streamglue.d.mts +37 -0
  40. package/dist/bluetooth/streamglue.mjs +154 -0
  41. package/dist/bluetooth/teslabluetooth.cjs +67 -0
  42. package/dist/bluetooth/teslabluetooth.d.cts +41 -0
  43. package/dist/bluetooth/teslabluetooth.d.mts +41 -0
  44. package/dist/bluetooth/teslabluetooth.mjs +66 -0
  45. package/dist/bluetooth/transport.cjs +10 -0
  46. package/dist/bluetooth/transport.d.cts +95 -0
  47. package/dist/bluetooth/transport.d.mts +95 -0
  48. package/dist/bluetooth/transport.mjs +10 -0
  49. package/dist/bluetooth/vehiclebluetooth.cjs +1272 -0
  50. package/dist/bluetooth/vehiclebluetooth.d.cts +495 -0
  51. package/dist/bluetooth/vehiclebluetooth.d.mts +495 -0
  52. package/dist/bluetooth/vehiclebluetooth.mjs +1270 -0
  53. package/dist/bluetooth/verify.cjs +93 -0
  54. package/dist/bluetooth/verify.d.cts +35 -0
  55. package/dist/bluetooth/verify.d.mts +35 -0
  56. package/dist/bluetooth/verify.mjs +91 -0
  57. package/dist/bluetooth.cjs +53 -0
  58. package/dist/bluetooth.d.cts +10 -0
  59. package/dist/bluetooth.d.mts +10 -0
  60. package/dist/bluetooth.mjs +9 -0
  61. package/dist/commands.cjs +1080 -85
  62. package/dist/commands.d.cts +585 -54
  63. package/dist/commands.d.mts +583 -51
  64. package/dist/commands.mjs +1084 -93
  65. package/dist/const.cjs +22 -0
  66. package/dist/const.d.cts +21 -0
  67. package/dist/const.d.mts +21 -0
  68. package/dist/const.mjs +20 -1
  69. package/dist/energy.cjs +24 -19
  70. package/dist/energy.d.cts +21 -15
  71. package/dist/energy.d.mts +21 -15
  72. package/dist/energy.mjs +24 -19
  73. package/dist/energyisland.cjs +38 -0
  74. package/dist/energyisland.mjs +37 -0
  75. package/dist/energyparsers.cjs +118 -0
  76. package/dist/energyparsers.d.cts +39 -0
  77. package/dist/energyparsers.d.mts +39 -0
  78. package/dist/energyparsers.mjs +116 -0
  79. package/dist/energyspecific.cjs +375 -17
  80. package/dist/energyspecific.d.cts +199 -19
  81. package/dist/energyspecific.d.mts +197 -16
  82. package/dist/energyspecific.mjs +370 -18
  83. package/dist/exceptions.cjs +3 -2
  84. package/dist/exceptions.d.cts +3 -2
  85. package/dist/exceptions.d.mts +3 -2
  86. package/dist/exceptions.mjs +3 -2
  87. package/dist/funnel.cjs +297 -0
  88. package/dist/funnel.d.cts +118 -0
  89. package/dist/funnel.d.mts +118 -0
  90. package/dist/funnel.mjs +292 -0
  91. package/dist/index.cjs +61 -0
  92. package/dist/index.d.cts +22 -8
  93. package/dist/index.d.mts +22 -8
  94. package/dist/index.mjs +19 -6
  95. package/dist/log.cjs +75 -0
  96. package/dist/log.d.cts +4 -0
  97. package/dist/log.d.mts +4 -0
  98. package/dist/log.mjs +71 -0
  99. package/dist/oauth.cjs +143 -0
  100. package/dist/oauth.d.cts +69 -0
  101. package/dist/oauth.d.mts +69 -0
  102. package/dist/oauth.mjs +143 -0
  103. package/dist/powerwall/client.cjs +818 -0
  104. package/dist/powerwall/client.d.cts +297 -0
  105. package/dist/powerwall/client.d.mts +297 -0
  106. package/dist/powerwall/client.mjs +807 -0
  107. package/dist/powerwall/components.cjs +188 -0
  108. package/dist/powerwall/components.d.cts +45 -0
  109. package/dist/powerwall/components.d.mts +45 -0
  110. package/dist/powerwall/components.mjs +182 -0
  111. package/dist/powerwall/energysite.cjs +216 -0
  112. package/dist/powerwall/energysite.d.cts +104 -0
  113. package/dist/powerwall/energysite.d.mts +104 -0
  114. package/dist/powerwall/energysite.mjs +214 -0
  115. package/dist/powerwall/exceptions.cjs +59 -0
  116. package/dist/powerwall/exceptions.d.cts +38 -0
  117. package/dist/powerwall/exceptions.d.mts +38 -0
  118. package/dist/powerwall/exceptions.mjs +51 -0
  119. package/dist/powerwall/gateway.cjs +102 -0
  120. package/dist/powerwall/gateway.d.cts +57 -0
  121. package/dist/powerwall/gateway.d.mts +57 -0
  122. package/dist/powerwall/gateway.mjs +101 -0
  123. package/dist/powerwall/merge.cjs +163 -0
  124. package/dist/powerwall/merge.d.cts +63 -0
  125. package/dist/powerwall/merge.d.mts +63 -0
  126. package/dist/powerwall/merge.mjs +159 -0
  127. package/dist/powerwall/models.d.cts +95 -0
  128. package/dist/powerwall/models.d.mts +95 -0
  129. package/dist/powerwall/queries.cjs +17 -0
  130. package/dist/powerwall/queries.mjs +10 -0
  131. package/dist/powerwall/transport.cjs +232 -0
  132. package/dist/powerwall/transport.mjs +227 -0
  133. package/dist/powerwall.cjs +47 -0
  134. package/dist/powerwall.d.cts +9 -0
  135. package/dist/powerwall.d.mts +9 -0
  136. package/dist/powerwall.mjs +8 -0
  137. package/dist/router/base.cjs +203 -0
  138. package/dist/router/base.d.cts +112 -0
  139. package/dist/router/base.d.mts +112 -0
  140. package/dist/router/base.mjs +200 -0
  141. package/dist/router/energysite.cjs +65 -0
  142. package/dist/router/energysite.d.cts +47 -0
  143. package/dist/router/energysite.d.mts +47 -0
  144. package/dist/router/energysite.mjs +60 -0
  145. package/dist/router.cjs +13 -0
  146. package/dist/router.d.cts +3 -1
  147. package/dist/router.d.mts +3 -1
  148. package/dist/router.mjs +3 -1
  149. package/dist/signing/crypto.cjs +28 -0
  150. package/dist/signing/crypto.mjs +26 -1
  151. package/dist/signing/energy.cjs +123 -0
  152. package/dist/signing/energy.d.cts +45 -0
  153. package/dist/signing/energy.d.mts +45 -0
  154. package/dist/signing/energy.mjs +120 -0
  155. package/dist/signing/jws.cjs +229 -0
  156. package/dist/signing/jws.d.cts +45 -0
  157. package/dist/signing/jws.d.mts +45 -0
  158. package/dist/signing/jws.mjs +211 -0
  159. package/dist/signing/keys.cjs +191 -0
  160. package/dist/signing/keys.d.cts +21 -0
  161. package/dist/signing/keys.d.mts +21 -0
  162. package/dist/signing/keys.mjs +182 -0
  163. package/dist/signing/session.cjs +104 -30
  164. package/dist/signing/session.d.cts +60 -23
  165. package/dist/signing/session.d.mts +60 -23
  166. package/dist/signing/session.mjs +105 -31
  167. package/dist/tariff.cjs +27 -0
  168. package/dist/tariff.d.cts +11 -0
  169. package/dist/tariff.d.mts +11 -0
  170. package/dist/tariff.mjs +27 -1
  171. package/dist/teslafleetapi.cjs +256 -55
  172. package/dist/teslafleetapi.d.cts +110 -14
  173. package/dist/teslafleetapi.d.mts +110 -14
  174. package/dist/teslafleetapi.mjs +258 -57
  175. package/dist/teslemetry.cjs +152 -5
  176. package/dist/teslemetry.d.cts +95 -5
  177. package/dist/teslemetry.d.mts +93 -3
  178. package/dist/teslemetry.mjs +151 -6
  179. package/dist/teslemetryenergy.cjs +287 -0
  180. package/dist/teslemetryenergy.d.cts +154 -0
  181. package/dist/teslemetryenergy.d.mts +154 -0
  182. package/dist/teslemetryenergy.mjs +282 -0
  183. package/dist/teslemetryvehicle.cjs +407 -0
  184. package/dist/teslemetryvehicle.d.cts +238 -0
  185. package/dist/teslemetryvehicle.d.mts +238 -0
  186. package/dist/teslemetryvehicle.mjs +405 -0
  187. package/dist/tessie.cjs +42 -5
  188. package/dist/tessie.d.cts +51 -5
  189. package/dist/tessie.d.mts +49 -3
  190. package/dist/tessie.mjs +42 -5
  191. package/dist/tessievehicle.cjs +514 -0
  192. package/dist/tessievehicle.d.cts +333 -0
  193. package/dist/tessievehicle.d.mts +333 -0
  194. package/dist/tessievehicle.mjs +513 -0
  195. package/dist/types/live_status.d.cts +16 -16
  196. package/dist/types/live_status.d.mts +16 -16
  197. package/dist/types/responses.d.cts +5 -1
  198. package/dist/types/responses.d.mts +5 -1
  199. package/dist/types/vehicle.d.cts +7 -1
  200. package/dist/types/vehicle.d.mts +7 -1
  201. package/dist/types.d.cts +3 -3
  202. package/dist/types.d.mts +3 -3
  203. package/dist/util.cjs +41 -0
  204. package/dist/util.d.cts +18 -0
  205. package/dist/util.d.mts +18 -0
  206. package/dist/util.mjs +40 -0
  207. package/dist/vehicle.cjs +79 -36
  208. package/dist/vehicle.d.cts +42 -26
  209. package/dist/vehicle.d.mts +42 -26
  210. package/dist/vehicle.mjs +80 -37
  211. package/dist/vehiclesigned.cjs +20 -5
  212. package/dist/vehiclesigned.d.cts +14 -5
  213. package/dist/vehiclesigned.d.mts +14 -5
  214. package/dist/vehiclesigned.mjs +20 -5
  215. package/dist/vehiclespecific.cjs +126 -15
  216. package/dist/vehiclespecific.d.cts +82 -18
  217. package/dist/vehiclespecific.d.mts +82 -18
  218. package/dist/vehiclespecific.mjs +126 -15
  219. package/package.json +70 -3
package/dist/commands.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
2
2
  const require_const = require("./const.cjs");
3
3
  const require_vehiclespecific = require("./vehiclespecific.cjs");
4
+ const require_jws = require("./signing/jws.cjs");
4
5
  const require_session = require("./signing/session.cjs");
6
+ const require_log = require("./log.cjs");
5
7
  const require_crypto = require("./signing/crypto.cjs");
6
8
  const require_exceptions = require("./exceptions.cjs");
7
9
  let crypto = require("crypto");
@@ -39,10 +41,13 @@ const SeatHeaterLevels = [
39
41
  "seatHeaterMed",
40
42
  "seatHeaterHigh"
41
43
  ];
42
- /** Seat cooling and auto-climate only cover the front seats on the wire protocol. */
44
+ /**
45
+ * Keyed by the REST `seat_position`, which Tesla's vehicle-command proxy reads
46
+ * as the proto enum value (1 front left, 2 front right), not the 0-based `Seats`.
47
+ */
43
48
  const SeatCoolerPositions = {
44
- [require_const.Seats.FRONT_LEFT]: _teslemetry_tesla_protocol_command_car_server.HvacSeatCoolerActions_HvacSeatCoolerPositionE.HvacSeatCoolerPosition_FrontLeft,
45
- [require_const.Seats.FRONT_RIGHT]: _teslemetry_tesla_protocol_command_car_server.HvacSeatCoolerActions_HvacSeatCoolerPositionE.HvacSeatCoolerPosition_FrontRight
49
+ [require_const.AutoSeat.FRONT_LEFT]: _teslemetry_tesla_protocol_command_car_server.HvacSeatCoolerActions_HvacSeatCoolerPositionE.HvacSeatCoolerPosition_FrontLeft,
50
+ [require_const.AutoSeat.FRONT_RIGHT]: _teslemetry_tesla_protocol_command_car_server.HvacSeatCoolerActions_HvacSeatCoolerPositionE.HvacSeatCoolerPosition_FrontRight
46
51
  };
47
52
  const SeatCoolerLevels = [
48
53
  _teslemetry_tesla_protocol_command_car_server.HvacSeatCoolerActions_HvacSeatCoolerLevelE.HvacSeatCoolerLevel_Off,
@@ -55,11 +60,12 @@ const AutoSeatPositions = {
55
60
  [require_const.AutoSeat.FRONT_LEFT]: _teslemetry_tesla_protocol_command_car_server.AutoSeatClimateAction_AutoSeatPositionE.AutoSeatPosition_FrontLeft,
56
61
  [require_const.AutoSeat.FRONT_RIGHT]: _teslemetry_tesla_protocol_command_car_server.AutoSeatClimateAction_AutoSeatPositionE.AutoSeatPosition_FrontRight
57
62
  };
58
- const StwHeatLevels = [
59
- _teslemetry_tesla_protocol_command_common.StwHeatLevel.StwHeatLevel_Off,
60
- _teslemetry_tesla_protocol_command_common.StwHeatLevel.StwHeatLevel_Low,
61
- _teslemetry_tesla_protocol_command_common.StwHeatLevel.StwHeatLevel_High
62
- ];
63
+ /** Keyed by the REST `Level`, which takes 0, 1 and 3 here: the protocol has no medium steering wheel heat. */
64
+ const StwHeatLevels = {
65
+ 0: _teslemetry_tesla_protocol_command_common.StwHeatLevel.StwHeatLevel_Off,
66
+ 1: _teslemetry_tesla_protocol_command_common.StwHeatLevel.StwHeatLevel_Low,
67
+ 3: _teslemetry_tesla_protocol_command_common.StwHeatLevel.StwHeatLevel_High
68
+ };
63
69
  const ClimateKeeperActions = {
64
70
  [require_const.ClimateModes.Off]: _teslemetry_tesla_protocol_command_car_server.HvacClimateKeeperAction_ClimateKeeperActionE.ClimateKeeperAction_Off,
65
71
  [require_const.ClimateModes.Keep]: _teslemetry_tesla_protocol_command_car_server.HvacClimateKeeperAction_ClimateKeeperActionE.ClimateKeeperAction_On,
@@ -81,6 +87,23 @@ const CopActivationTemps = {
81
87
  function sleep(ms) {
82
88
  return new Promise((resolve) => setTimeout(resolve, ms));
83
89
  }
90
+ /**
91
+ * The name of a message's populated oneof field, as `WhichOneof` gives it in
92
+ * Python. ts-proto leaves unset oneof members `undefined`; `plainFields` names
93
+ * any fields outside the oneof, which always carry a value.
94
+ */
95
+ function populatedField(message, plainFields = []) {
96
+ return Object.entries(message).find(([key, value]) => value !== void 0 && !plainFields.includes(key))?.[0];
97
+ }
98
+ /** A short debug-log name for a VCSEC command: the RKE action name, or the populated field. */
99
+ function vcsec_command_name(command) {
100
+ if (command.RKEAction !== void 0) return (0, _teslemetry_tesla_protocol_command_vcsec.rKEActionEToJSON)(command.RKEAction);
101
+ return populatedField(command) ?? "unknown";
102
+ }
103
+ /** A short debug-log name for an infotainment command: the populated `VehicleAction` field. */
104
+ function infotainment_command_name(command) {
105
+ return (command.vehicleAction && populatedField(command.vehicleAction, ["dropNominalSuccessResponses"])) ?? "unknown";
106
+ }
84
107
  function ok(reason = "") {
85
108
  return { response: {
86
109
  result: true,
@@ -94,37 +117,105 @@ function fail(reason) {
94
117
  } };
95
118
  }
96
119
  /**
120
+ * Day names Tesla's vehicle-command proxy accepts for `days_of_week`, which the
121
+ * REST route takes as a comma-joined string rather than the proto bitmask.
122
+ */
123
+ const DAY_OF_WEEK_BITS = {
124
+ SUN: 1,
125
+ SUNDAY: 1,
126
+ MON: 2,
127
+ MONDAY: 2,
128
+ TUES: 4,
129
+ TUESDAY: 4,
130
+ WED: 8,
131
+ WEDNESDAY: 8,
132
+ THURS: 16,
133
+ THURSDAY: 16,
134
+ FRI: 32,
135
+ FRIDAY: 32,
136
+ SAT: 64,
137
+ SATURDAY: 64,
138
+ ALL: 127,
139
+ WEEKDAYS: 62
140
+ };
141
+ /**
142
+ * Convert a bitmask or comma-joined day names (e.g. `"Mon,Wed"`, `"All"`) to the proto bitmask.
143
+ * @throws Error for an unknown day name.
144
+ */
145
+ function days_of_week_mask(days_of_week) {
146
+ if (typeof days_of_week === "number") return Math.trunc(days_of_week);
147
+ if (/^\d+$/.test(days_of_week.trim())) return parseInt(days_of_week.trim(), 10);
148
+ let mask = 0;
149
+ for (const day of days_of_week.split(",")) {
150
+ const bit = DAY_OF_WEEK_BITS[day.trim().toUpperCase()];
151
+ if (bit === void 0) throw new Error(`Invalid day of week: ${day}`);
152
+ mask |= bit;
153
+ }
154
+ return mask;
155
+ }
156
+ /**
97
157
  * Transport-agnostic signed-command builder for the Tesla Vehicle Command
98
158
  * Protocol: mirrors `python-tesla-fleet-api`'s `Commands` ABC. Every method
99
- * here builds a `RoutableMessage`, signs it (HMAC-personalized), and
100
- * dispatches it through the single abstract seam `_send`. A concrete
101
- * subclass fills that seam for one transport - the cloud `/signed_command`
102
- * HTTP endpoint (`VehicleSigned`) today, a BLE GATT link in a future story.
159
+ * here builds a `RoutableMessage`, signs it by `authMethod`, and dispatches
160
+ * it through the single abstract seam `_send`. A concrete subclass fills that
161
+ * seam for one transport: the cloud `/signed_command` HTTP endpoint
162
+ * (`VehicleSigned`) today, a BLE GATT link next. The protected members are
163
+ * what such a transport builds on.
103
164
  *
104
- * AES-GCM personalized signing (the BLE-only auth mode) is intentionally out
105
- * of scope here; see AGENTS.md.
165
+ * `"hmac"` signs a plaintext command. `"aes"` encrypts it with AES-GCM and asks
166
+ * the vehicle to encrypt its reply, which firmware before 2024.38 ignores.
106
167
  */
107
168
  var Commands = class extends require_vehiclespecific.default {
108
169
  #privateKey;
109
170
  #publicKey;
110
171
  /** Delay between WAIT-retries; overridable so tests don't have to wait for real timers. */
111
172
  retryDelayMs = 2e3;
173
+ /** Session-info requests sent before a handshake gives up on replies that fail authentication. */
174
+ handshakeAttempts = 3;
175
+ /** Delay between those requests. */
176
+ handshakeRetryDelayMs = 1e3;
177
+ /** How `buildMessage` signs a command. */
178
+ authMethod = "hmac";
179
+ /** One random routing address per instance: replies addressed to it are ours. */
112
180
  fromDestination;
113
181
  sessions;
182
+ /**
183
+ * @param parent
184
+ * @param vin
185
+ * @param privateKey Raw 32-byte P-256 scalar. `undefined` falls back to the parent's key, and throws if there is none. `false` disables signing, for a passive listener that never sends a command: any handshake then raises `SigningDisabled`.
186
+ * @param publicKey Uncompressed P-256 point. Derived from `privateKey` if omitted.
187
+ */
114
188
  constructor(parent, vin, privateKey, publicKey) {
115
189
  super(parent, vin);
116
190
  if (vin.length !== 17) throw new Error(`Signed commands require a 17-character VIN, got ${vin.length} characters.`);
117
- const resolvedPrivateKey = privateKey ?? parent.private_key;
118
- if (!resolvedPrivateKey) throw new Error("No private key provided for signed commands.");
119
- this.#privateKey = resolvedPrivateKey;
120
- this.#publicKey = publicKey ?? require_crypto.publicKeyFor(this.#privateKey);
191
+ if (privateKey === false) this.#privateKey = null;
192
+ else {
193
+ const resolvedPrivateKey = privateKey ?? parent.private_key ?? parent.parent.private_key;
194
+ if (!resolvedPrivateKey) throw new Error("No private key provided for signed commands.");
195
+ this.#privateKey = resolvedPrivateKey;
196
+ }
197
+ this.#publicKey = publicKey ?? (this.#privateKey ? require_crypto.publicKeyFor(this.#privateKey) : Buffer.alloc(0));
121
198
  this.fromDestination = crypto.default.randomBytes(16);
122
- const deriveKey = (vehiclePublicKey) => require_crypto.deriveSharedKey(this.#privateKey, vehiclePublicKey);
199
+ const deriveKey = (vehiclePublicKey) => this.shared_key(vehiclePublicKey);
123
200
  this.sessions = {
124
201
  [_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY]: new require_session.Session(_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY, deriveKey),
125
202
  [_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_INFOTAINMENT]: new require_session.Session(_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_INFOTAINMENT, deriveKey)
126
203
  };
127
204
  }
205
+ /** The uncompressed public key this instance signs with. Empty when signing is disabled. */
206
+ get publicKey() {
207
+ return this.#publicKey;
208
+ }
209
+ /** Derive the 16-byte shared key used for signed-command session encryption. */
210
+ shared_key(vehicleKey) {
211
+ if (this.#privateKey === null) throw new require_exceptions.SigningDisabled();
212
+ return require_crypto.deriveSharedKey(this.#privateKey, vehicleKey);
213
+ }
214
+ /** A Tesla.SS256 JWS over a Fleet Telemetry config, signed here so the key never leaves this class. */
215
+ signFleetTelemetryConfig(config) {
216
+ if (this.#privateKey === null) throw new require_exceptions.SigningDisabled();
217
+ return require_jws.sign_fleet_telemetry_config(this.#privateKey, config);
218
+ }
128
219
  sessionFor(domain) {
129
220
  const session = this.sessions[domain];
130
221
  if (!session) throw new Error(`No signed-command session configured for domain ${domain}.`);
@@ -133,26 +224,49 @@ var Commands = class extends require_vehiclespecific.default {
133
224
  /**
134
225
  * Absorb session info piggy-backed on any signed-command reply and raise
135
226
  * on a whitelist rejection or a terminal message fault. Every concrete
136
- * `_send` must call this on the reply - passing the `uuid` of the request
137
- * it just sent - before returning it.
227
+ * `_send` must call this on the reply - passing the `uuid` and destination
228
+ * domain of the request it just sent - before returning it. The session comes
229
+ * from that request: a reply's own `fromDestination` is unauthenticated, so it
230
+ * can only be checked against the request, never select a session.
138
231
  *
139
232
  * A `SessionInfo` is untrusted wire data until its `session_info_tag` is
140
- * verified: the tag is an HMAC over the exact bytes received, keyed by a
141
- * key derived from the *candidate* vehicle public key carried inside that
142
- * same SessionInfo, and bound to this request's `uuid` as a challenge (so
143
- * a captured older reply can't be replayed against a newer request). Only
144
- * once that tag checks out do we act on anything the message claims -
233
+ * verified: the tag is an HMAC over the exact bytes received, bound to this
234
+ * request's `uuid` as a challenge (so a captured older reply can't be
235
+ * replayed against a newer request). It is keyed by the session's stored
236
+ * key. Only at first contact, when there is none, is that key derived from
237
+ * the public key in the reply, which is trust on first use; see `Session`.
238
+ * Only once that tag checks out do we act on anything the message claims -
145
239
  * including its own whitelist-status field - and even then `Session.commit`
146
240
  * still refuses a clock time that regresses within the same epoch.
241
+ *
242
+ * The one exception is an empty public key: no shared key can be derived
243
+ * from it to verify a tag. An unpaired key has no session, so
244
+ * key-not-on-whitelist is the only real reply that omits the key, and that
245
+ * one status is accepted unauthenticated. Any other status with no key is
246
+ * malformed.
147
247
  */
148
- validateAndUpdateSession(resp, requestUuid) {
248
+ validate_msg(resp, requestUuid, requestDomain) {
149
249
  if (resp.sessionInfo && resp.sessionInfo.length > 0) {
150
- const domain = resp.fromDestination?.domain;
151
- const session = domain !== void 0 ? this.sessions[domain] : void 0;
152
- if (!session) throw new require_exceptions.SessionInfoAuthenticationFault("Session info reply is missing a recognized source domain.");
250
+ const session = requestDomain !== void 0 ? this.sessions[requestDomain] : void 0;
251
+ if (!session || resp.fromDestination?.domain !== requestDomain) throw new require_exceptions.SessionInfoAuthenticationFault("Session info reply is not from the domain the request went to; discarded.");
153
252
  if (resp.requestUuid && resp.requestUuid.length > 0 && !requestUuid.equals(Buffer.from(resp.requestUuid))) throw new require_exceptions.SessionInfoAuthenticationFault("Session info reply does not match an outstanding request; discarded.");
154
- const info = _teslemetry_tesla_protocol_command_signatures.SessionInfo.decode(resp.sessionInfo);
155
- const keys = session.keysFor(Buffer.from(info.publicKey));
253
+ let info;
254
+ try {
255
+ info = _teslemetry_tesla_protocol_command_signatures.SessionInfo.decode(resp.sessionInfo);
256
+ } catch (cause) {
257
+ throw new require_exceptions.SessionInfoAuthenticationFault("Session info reply could not be decoded.", null, { cause });
258
+ }
259
+ if (info.publicKey.length === 0) {
260
+ if (info.status === _teslemetry_tesla_protocol_command_signatures.SessionInfoStatus.SESSION_INFO_STATUS_KEY_NOT_ON_WHITELIST) throw new require_exceptions.NotOnWhitelistFault();
261
+ throw new require_exceptions.SessionInfoAuthenticationFault("Session info reply has no public key.");
262
+ }
263
+ let keys;
264
+ try {
265
+ keys = session.trustedKeys(Buffer.from(info.publicKey));
266
+ } catch (cause) {
267
+ if (cause instanceof require_exceptions.SessionInfoAuthenticationFault || cause instanceof require_exceptions.SigningDisabled) throw cause;
268
+ throw new require_exceptions.SessionInfoAuthenticationFault("Session info reply has an invalid public key.", null, { cause });
269
+ }
156
270
  const tag = resp.signatureData?.sessionInfoTag?.tag;
157
271
  if (!tag || tag.length === 0) throw new require_exceptions.SessionInfoAuthenticationFault("Session info reply is missing its authentication tag; discarded.");
158
272
  const metadata = Buffer.concat([
@@ -176,14 +290,61 @@ var Commands = class extends require_vehiclespecific.default {
176
290
  const fault = resp.signedMessageStatus?.signedMessageFault;
177
291
  if (fault !== void 0 && fault !== _teslemetry_tesla_protocol_command_universal_message.MessageFaultE.MESSAGEFAULT_ERROR_NONE) throw new (require_exceptions.MESSAGE_FAULTS[fault] ?? require_exceptions.TeslaFleetMessageFault)({ fault });
178
292
  }
179
- async handshake(domain) {
180
- const msg = _teslemetry_tesla_protocol_command_universal_message.RoutableMessage.create({
293
+ /** The unsigned session-info request that opens a domain session. */
294
+ sessionInfoRequest(domain) {
295
+ return _teslemetry_tesla_protocol_command_universal_message.RoutableMessage.create({
181
296
  toDestination: { domain },
182
297
  fromDestination: { routingAddress: this.fromDestination },
183
298
  sessionInfoRequest: { publicKey: this.#publicKey },
184
299
  uuid: crypto.default.randomBytes(16)
185
300
  });
186
- await this._send(msg, "session_info");
301
+ }
302
+ /**
303
+ * Open or refresh a domain session. A reply that fails authentication is
304
+ * discarded and the unsigned, idempotent request sent again, rather than
305
+ * failing the handshake on one bad reply. Never trusted either way.
306
+ *
307
+ * For VCSEC the caller must hold the session lock; `ensureHandshake` and
308
+ * `handshakeVehicleSecurity` do.
309
+ * @returns Whether the session can now sign.
310
+ * @throws SigningDisabled before anything is sent, when this instance has no key.
311
+ */
312
+ async handshake(domain, options) {
313
+ if (this.#privateKey === null) throw new require_exceptions.SigningDisabled();
314
+ for (let attempt = 1;; attempt++) try {
315
+ await this._send(this.sessionInfoRequest(domain), "session_info", options);
316
+ return this.sessionFor(domain).ready;
317
+ } catch (error) {
318
+ if (!(error instanceof require_exceptions.SessionInfoAuthenticationFault) || attempt >= this.handshakeAttempts) throw error;
319
+ await sleep(this.handshakeRetryDelayMs);
320
+ }
321
+ }
322
+ /**
323
+ * Open a domain session unless one is ready, for a subclass that must know
324
+ * it can sign before it dispatches. Do not call this from inside the VCSEC
325
+ * session lock, which it takes.
326
+ */
327
+ async ensureHandshake(domain) {
328
+ const session = this.sessionFor(domain);
329
+ if (domain !== _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY) {
330
+ if (!session.ready) await this.handshake(domain);
331
+ return;
332
+ }
333
+ await session.withLock(async () => {
334
+ if (!session.ready) await this.handshake(domain);
335
+ });
336
+ }
337
+ /** Perform a handshake with the vehicle security domain, even if a session is already established. */
338
+ async handshakeVehicleSecurity() {
339
+ await this.sessionFor(_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY).withLock(() => this.handshake(_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY));
340
+ }
341
+ /** Perform a handshake with the infotainment domain, even if a session is already established. */
342
+ async handshakeInfotainment() {
343
+ await this.handshake(_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_INFOTAINMENT);
344
+ }
345
+ /** Sign `command` for `session` by `authMethod`. Assigns a counter, so run it under the session lock. */
346
+ buildMessage(session, command) {
347
+ return this.authMethod === "aes" ? this.buildAesMessage(session, command) : this.buildHmacMessage(session, command);
187
348
  }
188
349
  buildHmacMessage(session, command) {
189
350
  const personalized = session.hmacPersonalized();
@@ -224,6 +385,103 @@ var Commands = class extends require_vehiclespecific.default {
224
385
  }
225
386
  });
226
387
  }
388
+ buildAesMessage(session, command) {
389
+ const personalized = session.aesGcmPersonalized();
390
+ if (!session.sharedKey) throw new Error("Session shared key is missing.");
391
+ const flags = 1 << _teslemetry_tesla_protocol_command_universal_message.Flags.FLAG_ENCRYPT_RESPONSE;
392
+ const metadata = Buffer.concat([
393
+ Buffer.from([
394
+ _teslemetry_tesla_protocol_command_signatures.Tag.TAG_SIGNATURE_TYPE,
395
+ 1,
396
+ _teslemetry_tesla_protocol_command_signatures.SignatureType.SIGNATURE_TYPE_AES_GCM_PERSONALIZED
397
+ ]),
398
+ Buffer.from([
399
+ _teslemetry_tesla_protocol_command_signatures.Tag.TAG_DOMAIN,
400
+ 1,
401
+ session.domain
402
+ ]),
403
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_PERSONALIZATION, 17]),
404
+ Buffer.from(this.vin, "ascii"),
405
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_EPOCH, personalized.epoch.length]),
406
+ Buffer.from(personalized.epoch),
407
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_EXPIRES_AT, 4]),
408
+ require_crypto.uint32BE(personalized.expiresAt),
409
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_COUNTER, 4]),
410
+ require_crypto.uint32BE(personalized.counter),
411
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_FLAGS, 4]),
412
+ require_crypto.uint32BE(flags),
413
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_END])
414
+ ]);
415
+ const { ciphertext, tag } = require_crypto.aesGcmEncrypt(session.sharedKey, personalized.nonce, command, require_crypto.sha256(metadata));
416
+ return _teslemetry_tesla_protocol_command_universal_message.RoutableMessage.create({
417
+ toDestination: { domain: session.domain },
418
+ fromDestination: { routingAddress: this.fromDestination },
419
+ protobufMessageAsBytes: ciphertext,
420
+ uuid: crypto.default.randomBytes(16),
421
+ flags,
422
+ signatureData: {
423
+ signerIdentity: _teslemetry_tesla_protocol_command_signatures.KeyIdentity.create({ publicKey: this.#publicKey }),
424
+ AESGCMPersonalizedData: {
425
+ ...personalized,
426
+ tag
427
+ }
428
+ }
429
+ });
430
+ }
431
+ /** The authentication method of `request` followed by its tag, which a response's metadata binds to. */
432
+ requestHash(session, request) {
433
+ const aes = request.signatureData?.AESGCMPersonalizedData;
434
+ if (aes) return Buffer.concat([Buffer.from([_teslemetry_tesla_protocol_command_signatures.SignatureType.SIGNATURE_TYPE_AES_GCM_PERSONALIZED]), aes.tag]);
435
+ const hmac = request.signatureData?.HMACPersonalizedData;
436
+ if (!hmac) throw new Error("Invalid request signature data.");
437
+ const hash = Buffer.concat([Buffer.from([_teslemetry_tesla_protocol_command_signatures.SignatureType.SIGNATURE_TYPE_HMAC_PERSONALIZED]), hmac.tag]);
438
+ return session.domain === _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY ? hash.subarray(0, 17) : hash;
439
+ }
440
+ /**
441
+ * Open an encrypted reply to `request`. A reply with no `AESGCMResponseData`
442
+ * is plaintext and comes back as it is.
443
+ *
444
+ * A reply that fails to open cannot be trusted, and says nothing about
445
+ * whether the command ran.
446
+ * @returns The reply with its payload in the clear.
447
+ * @throws Error when the reply fails to open.
448
+ * @throws SignedCommandResponseReplayed when this request already saw this response counter.
449
+ */
450
+ decryptResponse(session, request, resp) {
451
+ const data = resp.signatureData?.AESGCMResponseData;
452
+ if (!data || resp.protobufMessageAsBytes === void 0) return resp;
453
+ if (!session.sharedKey) throw new Error("Session shared key is missing.");
454
+ const requestHash = this.requestHash(session, request);
455
+ const metadata = Buffer.concat([
456
+ Buffer.from([
457
+ _teslemetry_tesla_protocol_command_signatures.Tag.TAG_SIGNATURE_TYPE,
458
+ 1,
459
+ _teslemetry_tesla_protocol_command_signatures.SignatureType.SIGNATURE_TYPE_AES_GCM_RESPONSE
460
+ ]),
461
+ Buffer.from([
462
+ _teslemetry_tesla_protocol_command_signatures.Tag.TAG_DOMAIN,
463
+ 1,
464
+ resp.fromDestination?.domain ?? _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_BROADCAST
465
+ ]),
466
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_PERSONALIZATION, 17]),
467
+ Buffer.from(this.vin, "ascii"),
468
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_COUNTER, 4]),
469
+ require_crypto.uint32BE(data.counter),
470
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_FLAGS, 4]),
471
+ require_crypto.uint32BE(resp.flags),
472
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_REQUEST_HASH, requestHash.length]),
473
+ requestHash,
474
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_FAULT, 4]),
475
+ require_crypto.uint32BE(resp.signedMessageStatus?.signedMessageFault ?? 0),
476
+ Buffer.from([_teslemetry_tesla_protocol_command_signatures.Tag.TAG_END])
477
+ ]);
478
+ const plaintext = require_crypto.aesGcmDecrypt(session.sharedKey, data.nonce, resp.protobufMessageAsBytes, data.tag, require_crypto.sha256(metadata));
479
+ if (!session.record_response_counter(requestHash, data.counter)) throw new require_exceptions.SignedCommandResponseReplayed();
480
+ return {
481
+ ...resp,
482
+ protobufMessageAsBytes: plaintext
483
+ };
484
+ }
227
485
  /**
228
486
  * Build, sign, dispatch (with WAIT/epoch-fault retry), and interpret one
229
487
  * domain command.
@@ -236,16 +494,16 @@ var Commands = class extends require_vehiclespecific.default {
236
494
  * most one VCSEC message is ever in flight. Infotainment keeps the
237
495
  * narrower build-only lock, serializing only counter assignment.
238
496
  */
239
- async dispatch(domain, command, expectsData) {
497
+ async dispatch(domain, command, options = {}) {
240
498
  const session = this.sessionFor(domain);
241
499
  const serializeWholeDispatch = domain === _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY;
242
500
  const run = async () => {
243
501
  if (!session.ready) await this.handshake(domain);
244
502
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
245
- const msg = serializeWholeDispatch ? this.buildHmacMessage(session, command) : await session.withLock(() => this.buildHmacMessage(session, command));
503
+ const msg = serializeWholeDispatch ? this.buildMessage(session, command) : await session.withLock(() => this.buildMessage(session, command));
246
504
  let resp;
247
505
  try {
248
- resp = await this._send(msg, "protobuf_message_as_bytes", expectsData);
506
+ resp = await this._send(msg, "protobuf_message_as_bytes", options);
249
507
  } catch (error) {
250
508
  if ((error instanceof require_exceptions.TeslaFleetMessageFaultIncorrectEpoch || error instanceof require_exceptions.TeslaFleetMessageFaultInvalidTokenOrCounter) && attempt < 3) continue;
251
509
  throw error;
@@ -255,7 +513,7 @@ var Commands = class extends require_vehiclespecific.default {
255
513
  await sleep(this.retryDelayMs);
256
514
  continue;
257
515
  }
258
- const result = this.interpretResponse(resp);
516
+ const result = this.interpretResponse(this.decryptResponse(session, msg, resp), options.expectsData ?? true);
259
517
  if (result === "wait") {
260
518
  if (attempt >= 3) return fail("Too many retries");
261
519
  await sleep(this.retryDelayMs);
@@ -267,53 +525,240 @@ var Commands = class extends require_vehiclespecific.default {
267
525
  };
268
526
  return serializeWholeDispatch ? session.withLock(run) : run();
269
527
  }
270
- interpretResponse(resp) {
528
+ interpretResponse(resp, expectsData) {
271
529
  if (!resp.protobufMessageAsBytes || resp.protobufMessageAsBytes.length === 0) return ok();
272
530
  const domain = resp.fromDestination?.domain;
273
- if (domain === _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY) return this.interpretVcsecResponse(resp.protobufMessageAsBytes);
531
+ if (domain === _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY) return this.interpretVcsecResponse(resp.protobufMessageAsBytes, expectsData);
274
532
  if (domain === _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_INFOTAINMENT) return this.interpretInfotainmentResponse(resp.protobufMessageAsBytes);
275
533
  return ok();
276
534
  }
277
- interpretVcsecResponse(bytes) {
535
+ interpretVcsecResponse(bytes, expectsData) {
278
536
  const vcsec = _teslemetry_tesla_protocol_command_vcsec.FromVCSECMessage.decode(bytes);
279
537
  if (vcsec.nominalError) return fail((0, _teslemetry_tesla_protocol_command_errors.genericErrorEToJSON)(vcsec.nominalError.genericError));
280
- if (vcsec.vehicleStatus) return ok();
538
+ if (vcsec.vehicleStatus) return expectsData ? { response: vcsec.vehicleStatus } : ok();
281
539
  if (vcsec.commandStatus) {
282
- if (vcsec.commandStatus.operationStatus === _teslemetry_tesla_protocol_command_vcsec.OperationStatusE.OPERATIONSTATUS_OK) return ok();
283
- if (vcsec.commandStatus.operationStatus === _teslemetry_tesla_protocol_command_vcsec.OperationStatusE.OPERATIONSTATUS_WAIT) return "wait";
284
- if (vcsec.commandStatus.operationStatus === _teslemetry_tesla_protocol_command_vcsec.OperationStatusE.OPERATIONSTATUS_ERROR) {
285
- const information = vcsec.commandStatus.signedMessageStatus?.signedMessageInformation;
286
- if (information) throw new (require_exceptions.SIGNED_MESSAGE_INFORMATION_FAULTS[information] ?? require_exceptions.SignedMessageInformationFault)({ information: (0, _teslemetry_tesla_protocol_command_vcsec.signedMessageInformationEToJSON)(information) });
540
+ const status = vcsec.commandStatus;
541
+ if (status.operationStatus === _teslemetry_tesla_protocol_command_vcsec.OperationStatusE.OPERATIONSTATUS_OK) {
542
+ this.raiseWhitelistFault(status.whitelistOperationStatus?.whitelistOperationInformation);
543
+ return ok();
544
+ }
545
+ if (status.operationStatus === _teslemetry_tesla_protocol_command_vcsec.OperationStatusE.OPERATIONSTATUS_WAIT) return "wait";
546
+ if (status.operationStatus === _teslemetry_tesla_protocol_command_vcsec.OperationStatusE.OPERATIONSTATUS_ERROR) {
547
+ if (status.signedMessageStatus) {
548
+ const information = status.signedMessageStatus.signedMessageInformation;
549
+ const cls = require_exceptions.SIGNED_MESSAGE_INFORMATION_FAULTS[information];
550
+ if (cls) throw new cls({ information: (0, _teslemetry_tesla_protocol_command_vcsec.signedMessageInformationEToJSON)(information) });
551
+ } else this.raiseWhitelistFault(status.whitelistOperationStatus?.whitelistOperationInformation);
552
+ return fail((0, _teslemetry_tesla_protocol_command_vcsec.operationStatusEToJSON)(status.operationStatus));
287
553
  }
288
554
  }
289
555
  return ok();
290
556
  }
557
+ raiseWhitelistFault(information) {
558
+ if (!information) return;
559
+ if (information >= require_exceptions.WHITELIST_OPERATION_STATUS.length) throw new require_exceptions.WhitelistOperationStatus(`Unknown whitelist operation failure: ${information}`);
560
+ const cls = require_exceptions.WHITELIST_OPERATION_STATUS[information];
561
+ if (cls) throw new cls({ information: (0, _teslemetry_tesla_protocol_command_vcsec.whitelistOperationInformationEToJSON)(information) });
562
+ }
291
563
  interpretInfotainmentResponse(bytes) {
292
564
  const response = _teslemetry_tesla_protocol_command_car_server.Response.decode(bytes);
293
- if (response.ping) return ok(String(response.ping.localTimestamp ?? ""));
565
+ if (response.ping) return ok(response.ping.localTimestamp?.toISOString() ?? "");
566
+ if (response.vehicleData) return { response: response.vehicleData };
567
+ const field = populatedField(response, ["actionStatus"]);
568
+ const data = field ? { [field]: response[field] } : {};
294
569
  if (response.actionStatus) return { response: {
295
570
  result: response.actionStatus.result === _teslemetry_tesla_protocol_command_car_server.OperationStatusE.OPERATIONSTATUS_OK,
296
- reason: response.actionStatus.resultReason?.plainText ?? ""
571
+ reason: response.actionStatus.resultReason?.plainText ?? "",
572
+ ...data
297
573
  } };
298
- return ok();
574
+ return { response: {
575
+ result: true,
576
+ reason: "",
577
+ ...data
578
+ } };
579
+ }
580
+ /** Dispatch one signed command, logging its name, transport and outcome. */
581
+ async dispatchLogged(name, domain, command, options) {
582
+ const log = (message) => this.parent.parent._log(message);
583
+ let result;
584
+ try {
585
+ result = await this.dispatch(domain, command, options);
586
+ } catch (error) {
587
+ require_log.logRequestError(log, name, this._transport_name, error);
588
+ throw error;
589
+ }
590
+ require_log.logRequestResult(log, name, this._transport_name, result);
591
+ return result;
592
+ }
593
+ /** The data a read asked for. A reply that only reports an outcome carries none. */
594
+ readData(reply) {
595
+ if ("result" in reply.response) throw new require_exceptions.InvalidResponse({
596
+ result: reply.response.result,
597
+ reason: reply.response.reason
598
+ });
599
+ return reply.response;
600
+ }
601
+ /** One `getVehicleImageState` read, with the single image its reply must carry. */
602
+ async readVehicleImage(request) {
603
+ const data = await this._getInfotainment(_teslemetry_tesla_protocol_command_car_server.Action.create({ vehicleAction: _teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ getVehicleData: _teslemetry_tesla_protocol_command_car_server.GetVehicleData.create({ getVehicleImageState: _teslemetry_tesla_protocol_command_car_server.GetVehicleImageState.create({ imageRequests: [_teslemetry_tesla_protocol_command_car_server.VehicleImageRequest.create(request)] }) }) }) }));
604
+ const image = data.vehicleImageState?.vehicleImages[0];
605
+ if (!image) throw new require_exceptions.InvalidResponse({ vehicleImageState: data.vehicleImageState ?? null });
606
+ return image;
607
+ }
608
+ /**
609
+ * Fetches a rendered vehicle image and returns its assembled bytes. Unlike
610
+ * any other read it is paged: an `ID` request reports the total size, then
611
+ * `DATA` requests advance by `chunk_size` until the whole range has arrived.
612
+ * @param image_type `VehicleImageType`
613
+ * @param chunk_size Bytes per request. A transport with a small frame needs a smaller value than the default.
614
+ * @throws Error when `chunk_size` is invalid, before anything is sent, or when the chunks do not add up to the reported size.
615
+ * @throws InvalidResponse when a reply carries no image.
616
+ */
617
+ async vehicle_image_state(image_type, chunk_size = 16384) {
618
+ if (!Number.isInteger(chunk_size) || chunk_size <= 0) throw new Error("chunk_size must be a positive integer");
619
+ const { totalImageSize: totalSize } = await this.readVehicleImage({
620
+ dataType: _teslemetry_tesla_protocol_command_car_server.VehicleImageRequest_Type.ID,
621
+ imageType: image_type
622
+ });
623
+ const chunks = [];
624
+ let offset = 0;
625
+ while (offset < totalSize) {
626
+ const { assetData } = await this.readVehicleImage({
627
+ dataType: _teslemetry_tesla_protocol_command_car_server.VehicleImageRequest_Type.DATA,
628
+ imageType: image_type,
629
+ chunkRequest: _teslemetry_tesla_protocol_command_car_server.VehicleImageDataChunkRequest.create({
630
+ chunkOffset: offset,
631
+ chunkSize: Math.min(chunk_size, totalSize - offset)
632
+ })
633
+ });
634
+ const startOffset = assetData?.startOffset ?? 0;
635
+ if (startOffset !== offset) throw new Error(`Unexpected vehicle image chunk offset ${startOffset}; expected ${offset}`);
636
+ const chunk = assetData?.data;
637
+ if (!chunk || chunk.length === 0) throw new Error(`Vehicle image ended after ${offset} of ${totalSize} bytes`);
638
+ chunks.push(chunk);
639
+ offset += chunk.length;
640
+ if (offset > totalSize) throw new Error(`Vehicle image exceeded declared size of ${totalSize} bytes`);
641
+ }
642
+ return Buffer.concat(chunks);
643
+ }
644
+ /** Sign and send an actuation: its reply is a bare terminal ack, so `expectsData` is false. */
645
+ async sendVehicleSecurity(command, options = {}) {
646
+ return await this.dispatchLogged(vcsec_command_name(command), _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY, Buffer.from(_teslemetry_tesla_protocol_command_vcsec.UnsignedMessage.encode(command).finish()), {
647
+ ...options,
648
+ expectsData: false
649
+ });
650
+ }
651
+ /**
652
+ * Sign and send a read to the Vehicle Security computer.
653
+ * @throws InvalidResponse when the reply carries no `VehicleStatus`.
654
+ */
655
+ async _getVehicleSecurity(command) {
656
+ const reply = await this.dispatchLogged(vcsec_command_name(command), _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY, Buffer.from(_teslemetry_tesla_protocol_command_vcsec.UnsignedMessage.encode(command).finish()));
657
+ return this.readData(reply);
299
658
  }
300
- async sendVehicleSecurity(command) {
301
- return this.dispatch(_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY, Buffer.from(_teslemetry_tesla_protocol_command_vcsec.UnsignedMessage.encode(command).finish()), false);
659
+ /**
660
+ * Sign and send a command to the Infotainment computer. Reads go through
661
+ * `_getInfotainment`. `mutating` is false for a command that cannot change
662
+ * the vehicle, so a transport can give up on it rather than ask whether it
663
+ * ran; `ping` is the only one. The cloud transport has no use for it.
664
+ */
665
+ async sendInfotainment(action, { onSubmitted } = {}) {
666
+ return await this.dispatchLogged(infotainment_command_name(action), _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_INFOTAINMENT, Buffer.from(_teslemetry_tesla_protocol_command_car_server.Action.encode(action).finish()), { onSubmitted });
302
667
  }
303
- async sendInfotainment(action) {
304
- return this.dispatch(_teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_INFOTAINMENT, Buffer.from(_teslemetry_tesla_protocol_command_car_server.Action.encode(action).finish()), true);
668
+ /**
669
+ * Sign and send a read to the Infotainment computer.
670
+ * @throws InvalidResponse when the reply carries no `VehicleData`.
671
+ */
672
+ async _getInfotainment(action) {
673
+ const reply = await this.dispatchLogged(infotainment_command_name(action), _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_INFOTAINMENT, Buffer.from(_teslemetry_tesla_protocol_command_car_server.Action.encode(action).finish()));
674
+ return this.readData(reply);
305
675
  }
306
- vehicleAction(action) {
307
- return this.sendInfotainment(_teslemetry_tesla_protocol_command_car_server.Action.create({ vehicleAction: action }));
676
+ vehicleAction(action, options) {
677
+ return this.sendInfotainment(_teslemetry_tesla_protocol_command_car_server.Action.create({ vehicleAction: action }), options);
678
+ }
679
+ moveTonneau(tonneau) {
680
+ return this.sendVehicleSecurity(_teslemetry_tesla_protocol_command_vcsec.UnsignedMessage.create({ closureMoveRequest: _teslemetry_tesla_protocol_command_vcsec.ClosureMoveRequest.create({ tonneau }) }));
308
681
  }
309
682
  resolveSeat(seat_position) {
310
683
  return typeof seat_position === "string" ? require_const.Seats[seat_position] : seat_position;
311
684
  }
685
+ /** Pings the vehicle. On success, `reason` is the vehicle's timestamp in ISO 8601. */
686
+ async ping() {
687
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ ping: _teslemetry_tesla_protocol_command_car_server.Ping.create({ pingId: 0 }) }), { mutating: false });
688
+ }
689
+ /**
690
+ * Add or modify a charging schedule. At least one of `start_time` or `end_time` is required.
691
+ * @param days_of_week Comma-joined day names (e.g. `"Mon,Wed"`, `"All"`, `"Weekdays"`) or a day bitmask
692
+ * @param enabled `true` or `false`
693
+ * @param lat Latitude of the schedule's location
694
+ * @param lon Longitude of the schedule's location
695
+ * @param start_time Minutes after midnight to start charging
696
+ * @param end_time Minutes after midnight to stop charging
697
+ * @param one_time Run the schedule only once
698
+ * @param id Schedule ID to modify an existing schedule
699
+ * @param name Schedule name
700
+ */
701
+ async add_charge_schedule(days_of_week, enabled, lat, lon, start_time, end_time, one_time, id, name) {
702
+ if (start_time == null && end_time == null) throw new Error("Either start_time or end_time or both must be provided");
703
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ addChargeScheduleAction: _teslemetry_tesla_protocol_command_common.ChargeSchedule.create({
704
+ daysOfWeek: days_of_week_mask(days_of_week),
705
+ enabled,
706
+ startEnabled: start_time != null,
707
+ endEnabled: end_time != null,
708
+ latitude: lat,
709
+ longitude: lon,
710
+ startTime: start_time,
711
+ endTime: end_time,
712
+ oneTime: one_time,
713
+ id,
714
+ name
715
+ }) }));
716
+ }
717
+ /**
718
+ * Registers a managed charging site, for a utility managed-charging program, on this vehicle.
719
+ * @param public_key The raw EC point string the vehicle expects, not the DER a gateway's `get_signed_commands_public_key` returns
720
+ * @param din The gateway's DIN, which the vehicle matches to the site controller
721
+ * @param lat Latitude
722
+ * @param lon Longitude
723
+ */
724
+ async add_managed_charging_site(public_key, din, lat, lon) {
725
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ addManagedChargingSiteRequest: _teslemetry_tesla_protocol_command_car_server.AddManagedChargingSiteRequest.create({ site: _teslemetry_tesla_protocol_command_car_server.ManagedChargingSite.create({
726
+ publicKey: public_key,
727
+ managerType: _teslemetry_tesla_protocol_command_car_server.ManagerType.create({ siteController: _teslemetry_tesla_protocol_command_car_server.SiteController.create({ din }) }),
728
+ latLon: _teslemetry_tesla_protocol_command_common.LatLong.create({
729
+ latitude: lat,
730
+ longitude: lon
731
+ })
732
+ }) }) }));
733
+ }
734
+ /**
735
+ * Add or modify a preconditioning schedule.
736
+ * @param days_of_week Comma-joined day names (e.g. `"Mon,Wed"`, `"All"`, `"Weekdays"`) or a day bitmask
737
+ * @param enabled `true` or `false`
738
+ * @param lat Latitude of the schedule's location
739
+ * @param lon Longitude of the schedule's location
740
+ * @param precondition_time Minutes after midnight to be preconditioned by
741
+ * @param id Schedule ID to modify an existing schedule
742
+ * @param one_time Run the schedule only once
743
+ * @param name Schedule name
744
+ */
745
+ async add_precondition_schedule(days_of_week, enabled, lat, lon, precondition_time, id, one_time, name) {
746
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ addPreconditionScheduleAction: _teslemetry_tesla_protocol_command_common.PreconditionSchedule.create({
747
+ daysOfWeek: days_of_week_mask(days_of_week),
748
+ enabled,
749
+ preconditionTime: precondition_time,
750
+ latitude: lat,
751
+ longitude: lon,
752
+ oneTime: one_time,
753
+ id,
754
+ name
755
+ }) }));
756
+ }
312
757
  /**
313
758
  * Controls the front (which_trunk: "front") or rear (which_trunk: "rear") trunk.
314
759
  * @param which_trunk "front" or "rear"
315
760
  */
316
- async actuate_truck(which_trunk) {
761
+ async actuate_trunk(which_trunk) {
317
762
  const field = TrunkFields[which_trunk];
318
763
  if (!field) throw new Error(`Invalid trunk: ${which_trunk}`);
319
764
  const closureMoveRequest = {};
@@ -325,6 +770,7 @@ var Commands = class extends require_vehiclespecific.default {
325
770
  * @param volume A floating point number from 0.0 to 11.0.
326
771
  */
327
772
  async adjust_volume(volume) {
773
+ if (volume < 0 || volume > 11) throw new Error("Volume must a number from 0.0 to 11.0");
328
774
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ mediaUpdateVolume: _teslemetry_tesla_protocol_command_car_server.MediaUpdateVolume.create({ volumeAbsoluteFloat: volume }) }));
329
775
  }
330
776
  /** Starts climate preconditioning. */
@@ -335,10 +781,44 @@ var Commands = class extends require_vehiclespecific.default {
335
781
  async auto_conditioning_stop() {
336
782
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ hvacAutoAction: _teslemetry_tesla_protocol_command_car_server.HvacAutoAction.create({ powerOn: false }) }));
337
783
  }
784
+ /** Auto secures the vehicle: locks it and closes what it can. */
785
+ async auto_secure_vehicle() {
786
+ return this.sendVehicleSecurity(_teslemetry_tesla_protocol_command_vcsec.UnsignedMessage.create({ RKEAction: _teslemetry_tesla_protocol_command_vcsec.RKEActionE.RKE_ACTION_AUTO_SECURE_VEHICLE }));
787
+ }
788
+ /**
789
+ * Removes charging schedules by location type.
790
+ * @param home Remove schedules at home
791
+ * @param work Remove schedules at work
792
+ * @param other Remove schedules at other locations
793
+ */
794
+ async batch_remove_charge_schedules(home, work, other) {
795
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ batchRemoveChargeSchedulesAction: _teslemetry_tesla_protocol_command_car_server.BatchRemoveChargeSchedulesAction.create({
796
+ home,
797
+ work,
798
+ other
799
+ }) }));
800
+ }
801
+ /**
802
+ * Removes preconditioning schedules by location type.
803
+ * @param home Remove schedules at home
804
+ * @param work Remove schedules at work
805
+ * @param other Remove schedules at other locations
806
+ */
807
+ async batch_remove_precondition_schedules(home, work, other) {
808
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ batchRemovePreconditionSchedulesAction: _teslemetry_tesla_protocol_command_car_server.BatchRemovePreconditionSchedulesAction.create({
809
+ home,
810
+ work,
811
+ other
812
+ }) }));
813
+ }
338
814
  /** Cancels the countdown to install the vehicle software update. */
339
815
  async cancel_software_update() {
340
816
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlCancelSoftwareUpdateAction: _teslemetry_tesla_protocol_command_car_server.VehicleControlCancelSoftwareUpdateAction.create({}) }));
341
817
  }
818
+ /** Cancels a State of Health test. */
819
+ async cancel_soh_test() {
820
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ cancelSohTestAction: _teslemetry_tesla_protocol_command_car_server.CancelSohTestAction.create({}) }));
821
+ }
342
822
  /** Charges in max range mode -- we recommend limiting the use of this mode to long trips. */
343
823
  async charge_max_range() {
344
824
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ chargingStartStopAction: _teslemetry_tesla_protocol_command_car_server.ChargingStartStopAction.create({ startMaxRange: VOID }) }));
@@ -365,9 +845,18 @@ var Commands = class extends require_vehiclespecific.default {
365
845
  }
366
846
  /**
367
847
  * Deactivates PIN to Drive and resets the associated PIN for vehicles running firmware versions 2023.44+. This command is only accessible to fleet managers or owners.
848
+ * @param _pin Accepted for parity with the REST command; the signed action carries no PIN.
368
849
  */
369
- async clear_pin_to_drive_admin() {
370
- return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlResetPinToDriveAdminAction: _teslemetry_tesla_protocol_command_car_server.VehicleControlResetPinToDriveAdminAction.create({}) }));
850
+ async clear_pin_to_drive_admin(_pin) {
851
+ return this.reset_pin_to_drive_admin();
852
+ }
853
+ /** Closes the Cybertruck tonneau cover. */
854
+ async close_tonneau() {
855
+ return this.moveTonneau(_teslemetry_tesla_protocol_command_vcsec.ClosureMoveTypeE.CLOSURE_MOVE_TYPE_CLOSE);
856
+ }
857
+ /** Saves a dashcam clip. */
858
+ async dashcam_save_clip() {
859
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ dashcamSaveClipAction: _teslemetry_tesla_protocol_command_car_server.DashcamSaveClipAction.create({}) }));
371
860
  }
372
861
  /** Locks the vehicle. */
373
862
  async door_lock() {
@@ -385,6 +874,22 @@ var Commands = class extends require_vehiclespecific.default {
385
874
  async flash_lights() {
386
875
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlFlashLightsAction: _teslemetry_tesla_protocol_command_car_server.VehicleControlFlashLightsAction.create({}) }));
387
876
  }
877
+ /** Gets the charge on solar settings, under `response.getChargeOnSolarFeatureResponse`. */
878
+ async get_charge_on_solar() {
879
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ getChargeOnSolarFeatureRequest: _teslemetry_tesla_protocol_command_car_server.GetChargeOnSolarFeatureRequest.create({}) }), { mutating: false });
880
+ }
881
+ /** Gets the registered managed charging sites, under `response.getManagedChargingSitesResponse`. */
882
+ async get_managed_charging_sites() {
883
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ getManagedChargingSitesRequest: _teslemetry_tesla_protocol_command_car_server.GetManagedChargingSitesRequest.create({}) }), { mutating: false });
884
+ }
885
+ /** Gets vehicle in-car messages. The reply carries the vehicle's response under `getMessagesResponse`, if it sends one. */
886
+ async get_messages() {
887
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ getMessagesAction: _teslemetry_tesla_protocol_command_car_server.GetMessagesAction.create({}) }), { mutating: false });
888
+ }
889
+ /** Gets the current time-of-use rate tariff schedule, under `response.getRateTariffResponse`. */
890
+ async get_rate_tariff() {
891
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ getRateTariffRequest: _teslemetry_tesla_protocol_command_car_server.GetRateTariffRequest.create({}) }), { mutating: false });
892
+ }
388
893
  /**
389
894
  * Restricts certain vehicle UI functionality from guest users
390
895
  * @param enable `true` or `false`
@@ -420,6 +925,25 @@ var Commands = class extends require_vehiclespecific.default {
420
925
  async media_volume_down() {
421
926
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ mediaUpdateVolume: _teslemetry_tesla_protocol_command_car_server.MediaUpdateVolume.create({ volumeDelta: -1 }) }));
422
927
  }
928
+ /** Turns the volume up by one. */
929
+ async media_volume_up() {
930
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ mediaUpdateVolume: _teslemetry_tesla_protocol_command_car_server.MediaUpdateVolume.create({ volumeDelta: 1 }) }));
931
+ }
932
+ /**
933
+ * Start navigation to coordinates with a named destination.
934
+ * @param lat Latitude
935
+ * @param lon Longitude
936
+ * @param destination Destination name
937
+ * @param order Trip order: `0` replaces the current trip (default), `1` prepends it as the next stop, `2` appends it as the last stop
938
+ */
939
+ async navigation_gps_destination_request(lat, lon, destination, order = 0) {
940
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ navigationGpsDestinationRequest: _teslemetry_tesla_protocol_command_car_server.NavigationGpsDestinationRequest.create({
941
+ lat,
942
+ lon,
943
+ destination,
944
+ order
945
+ }) }));
946
+ }
423
947
  /**
424
948
  * Start navigation to given coordinates.
425
949
  * @param lat Latitude
@@ -436,13 +960,13 @@ var Commands = class extends require_vehiclespecific.default {
436
960
  /**
437
961
  * Sends a location to the in-vehicle navigation system.
438
962
  *
439
- * `locale` and `type` are REST-only fields with no equivalent in the
440
- * signed-command protocol (which only carries a destination string and a
441
- * multi-stop order), so they are accepted for cross-transport signature
442
- * parity but not sent.
963
+ * `type`, `locale` and `timestamp_ms` are REST-only fields with no equivalent
964
+ * in the signed-command protocol, which only carries a destination string
965
+ * and a multi-stop order. They keep REST's positions, so the router's types
966
+ * agree, and are not sent.
443
967
  * @param value Destination text
444
968
  */
445
- async navigation_request(value, _locale, _type) {
969
+ async navigation_request(value, _type, _locale, _timestamp_ms) {
446
970
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ navigationRequest: _teslemetry_tesla_protocol_command_car_server.NavigationRequest.create({ destination: value }) }));
447
971
  }
448
972
  /**
@@ -456,6 +980,35 @@ var Commands = class extends require_vehiclespecific.default {
456
980
  remoteNavTripOrder: order ?? 0
457
981
  }) }));
458
982
  }
983
+ /** Triggers vehicle route navigation. */
984
+ async navigation_route() {
985
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ navigationRouteAction: _teslemetry_tesla_protocol_command_car_server.NavigationRouteAction.create({}) }));
986
+ }
987
+ /**
988
+ * Sends a list of waypoints to the vehicle's navigation system.
989
+ * @param waypoints Comma-separated Google Maps place IDs, e.g. `"refId:ChIJ...,refId:ChIJ..."`
990
+ */
991
+ async navigation_waypoints_request(waypoints) {
992
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ navigationWaypointsRequest: _teslemetry_tesla_protocol_command_car_server.NavigationWaypointsRequest.create({ waypoints }) }));
993
+ }
994
+ /** Opens the Cybertruck tonneau cover. */
995
+ async open_tonneau() {
996
+ return this.moveTonneau(_teslemetry_tesla_protocol_command_vcsec.ClosureMoveTypeE.CLOSURE_MOVE_TYPE_OPEN);
997
+ }
998
+ /**
999
+ * Returns the charging sites near the current location of the vehicle.
1000
+ * The sites are under `response.getNearbyChargingSites`.
1001
+ * @param count Number of sites to return
1002
+ * @param radius Search radius
1003
+ * @param detail Whether to include site metadata
1004
+ */
1005
+ async nearby_charging_sites(count, radius, detail) {
1006
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ getNearbyChargingSites: _teslemetry_tesla_protocol_command_car_server.GetNearbyChargingSites.create({
1007
+ count,
1008
+ radius,
1009
+ includeMetaData: detail
1010
+ }) }), { mutating: false });
1011
+ }
459
1012
  /**
460
1013
  * Sets automatic seat heating and cooling. Only the front seats support this action.
461
1014
  * @param auto_seat_position `AutoSeat` position (1-indexed: front left is 1, front right is 2)
@@ -477,6 +1030,27 @@ var Commands = class extends require_vehiclespecific.default {
477
1030
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ autoStwHeatAction: _teslemetry_tesla_protocol_command_car_server.AutoStwHeatAction.create({ on }) }));
478
1031
  }
479
1032
  /**
1033
+ * Removes a charging schedule.
1034
+ * @param id Schedule ID
1035
+ */
1036
+ async remove_charge_schedule(id) {
1037
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ removeChargeScheduleAction: _teslemetry_tesla_protocol_command_car_server.RemoveChargeScheduleAction.create({ id }) }));
1038
+ }
1039
+ /**
1040
+ * Removes a registered managed charging site.
1041
+ * @param public_key The public key it was registered with
1042
+ */
1043
+ async remove_managed_charging_site(public_key) {
1044
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ removeManagedChargingSiteRequest: _teslemetry_tesla_protocol_command_car_server.RemoveManagedChargingSiteRequest.create({ publicKey: public_key }) }));
1045
+ }
1046
+ /**
1047
+ * Removes a preconditioning schedule.
1048
+ * @param id Schedule ID
1049
+ */
1050
+ async remove_precondition_schedule(id) {
1051
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ removePreconditionScheduleAction: _teslemetry_tesla_protocol_command_car_server.RemovePreconditionScheduleAction.create({ id }) }));
1052
+ }
1053
+ /**
480
1054
  * Plays a sound through the vehicle external speaker.
481
1055
  * @param sound Fart is `0`, Locate ping is `2000`
482
1056
  */
@@ -485,11 +1059,11 @@ var Commands = class extends require_vehiclespecific.default {
485
1059
  }
486
1060
  /**
487
1061
  * Sets seat cooling. Only the front seats support this action.
488
- * @param seat_position Seat position
1062
+ * @param seat_position `AutoSeat` position (1-indexed: front left is 1, front right is 2), unlike seat heating
489
1063
  * @param seat_cooler_level Cooling level
490
1064
  */
491
1065
  async remote_seat_cooler_request(seat_position, seat_cooler_level) {
492
- const position = SeatCoolerPositions[this.resolveSeat(seat_position)];
1066
+ const position = SeatCoolerPositions[typeof seat_position === "string" ? require_const.AutoSeat[seat_position] : seat_position];
493
1067
  if (position === void 0) throw new Error(`Invalid seat position for seat cooling: ${seat_position}`);
494
1068
  const level = SeatCoolerLevels[seat_cooler_level];
495
1069
  if (level === void 0) throw new Error(`Invalid seat cooler level: ${seat_cooler_level}`);
@@ -519,11 +1093,11 @@ var Commands = class extends require_vehiclespecific.default {
519
1093
  }
520
1094
  /**
521
1095
  * Sets steering wheel heat level.
522
- * @param level Heating level (0=Off, 1=Low, 2=High)
1096
+ * @param level `Level` OFF, LOW or HIGH (0, 1, 3). There is no medium level.
523
1097
  */
524
1098
  async remote_steering_wheel_heat_level_request(level) {
525
1099
  const stwLevel = StwHeatLevels[level];
526
- if (stwLevel === void 0) throw new Error(`Invalid steering wheel heat level: ${level}`);
1100
+ if (stwLevel === void 0) throw new Error(`Invalid steering wheel heat level ${level}; expected 0, 1 or 3`);
527
1101
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ stwHeatLevelAction: _teslemetry_tesla_protocol_command_car_server.StwHeatLevelAction.create({ stwHeatLevel: stwLevel }) }));
528
1102
  }
529
1103
  /**
@@ -533,6 +1107,14 @@ var Commands = class extends require_vehiclespecific.default {
533
1107
  async remote_steering_wheel_heater_request(on) {
534
1108
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ hvacSteeringWheelHeaterAction: _teslemetry_tesla_protocol_command_car_server.HvacSteeringWheelHeaterAction.create({ powerOn: on }) }));
535
1109
  }
1110
+ /**
1111
+ * Resets PIN to Drive for fleet managers and owners. Requires firmware 2023.44+.
1112
+ * The vehicle only accepts this with account credentials, which the cloud
1113
+ * transport has and a raw Bluetooth link does not.
1114
+ */
1115
+ async reset_pin_to_drive_admin() {
1116
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlResetPinToDriveAdminAction: _teslemetry_tesla_protocol_command_car_server.VehicleControlResetPinToDriveAdminAction.create({}) }));
1117
+ }
536
1118
  /** Removes PIN to Drive. Requires the car to be in Pin to Drive mode and not in Valet mode. */
537
1119
  async reset_pin_to_drive_pin() {
538
1120
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlResetPinToDriveAction: _teslemetry_tesla_protocol_command_car_server.VehicleControlResetPinToDriveAction.create({}) }));
@@ -608,6 +1190,27 @@ var Commands = class extends require_vehiclespecific.default {
608
1190
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setCopTempAction: _teslemetry_tesla_protocol_command_car_server.SetCopTempAction.create({ copActivationTemp }) }));
609
1191
  }
610
1192
  /**
1193
+ * Sets the vehicle's general discharge limit. This is not `set_powershare_discharge_limit`, which is a separate message.
1194
+ * @param discharge_limit Discharge limit
1195
+ */
1196
+ async set_discharge_limit(discharge_limit) {
1197
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setDischargeLimitAction: _teslemetry_tesla_protocol_command_car_server.SetDischargeLimitAction.create({ dischargeLimit: discharge_limit }) }));
1198
+ }
1199
+ /**
1200
+ * Turns Keep Accessory Power mode on and off, keeping 12V accessory power available while the vehicle is parked.
1201
+ * @param on `true` or `false`
1202
+ */
1203
+ async set_keep_accessory_power_mode(on) {
1204
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setKeepAccessoryPowerModeAction: _teslemetry_tesla_protocol_command_car_server.SetKeepAccessoryPowerModeAction.create({ keepAccessoryPowerMode: on }) }));
1205
+ }
1206
+ /**
1207
+ * Turns Low Power mode on and off, reducing standby power consumption while the vehicle is parked.
1208
+ * @param on `true` or `false`
1209
+ */
1210
+ async set_low_power_mode(on) {
1211
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setLowPowerModeAction: _teslemetry_tesla_protocol_command_car_server.SetLowPowerModeAction.create({ lowPowerMode: on }) }));
1212
+ }
1213
+ /**
611
1214
  * Sets a four-digit passcode for PIN to Drive.
612
1215
  * @param on `true` or `false`
613
1216
  * @param password Four-digit passcode
@@ -630,6 +1233,26 @@ var Commands = class extends require_vehiclespecific.default {
630
1233
  }) }));
631
1234
  }
632
1235
  /**
1236
+ * Sets a time-of-use rate tariff schedule. The schedule nests up to five
1237
+ * named seasons of four period types each, so build `seasons` and `tariff`
1238
+ * with the `tesla-protocol` message types.
1239
+ * @param seasons The tariff's seasons
1240
+ * @param tariff The tariff
1241
+ */
1242
+ async set_rate_tariff(seasons, tariff) {
1243
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setRateTariffRequest: _teslemetry_tesla_protocol_command_car_server.SetRateTariffRequest.create({
1244
+ seasons,
1245
+ tariff
1246
+ }) }));
1247
+ }
1248
+ /**
1249
+ * Sets HVAC recirculation mode on and off.
1250
+ * @param on `true` or `false`
1251
+ */
1252
+ async set_recirculation(on) {
1253
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ hvacRecirculationAction: _teslemetry_tesla_protocol_command_car_server.HvacRecirculationAction.create({ on }) }));
1254
+ }
1255
+ /**
633
1256
  * Sets a time at which charging should be completed.
634
1257
  * @param enable `true` or `false`
635
1258
  * @param time Minutes after midnight
@@ -643,20 +1266,25 @@ var Commands = class extends require_vehiclespecific.default {
643
1266
  /**
644
1267
  * Sets a time at which departure should be completed.
645
1268
  *
646
- * `preconditioning_enabled` and `off_peak_charging_enabled` are accepted
647
- * for cross-transport signature parity but have no field in the signed
648
- * command protobuf, so they are not sent.
1269
+ * The protocol has no enable flags: a present recurrence block switches its
1270
+ * feature on. So preconditioning and off-peak charging are sent only when
1271
+ * their `*_enabled` or `*_weekdays_only` argument is true, and an absent
1272
+ * block is what turns the feature off.
649
1273
  * @param enable `true` or `false`
650
- * @param preconditioning_enabled not representable in a signed command
1274
+ * @param preconditioning_enabled `true` or `false`
651
1275
  * @param preconditioning_weekdays_only `true` or `false`
652
1276
  * @param departure_time Minutes after midnight
653
- * @param off_peak_charging_enabled not representable in a signed command
1277
+ * @param off_peak_charging_enabled `true` or `false`
654
1278
  * @param off_peak_charging_weekdays_only `true` or `false`
655
1279
  * @param end_off_peak_time Minutes after midnight
656
1280
  */
657
- async set_scheduled_departure(enable = true, _preconditioning_enabled = false, preconditioning_weekdays_only = false, departure_time = 0, _off_peak_charging_enabled = false, off_peak_charging_weekdays_only = false, end_off_peak_time = 0) {
658
- const preconditioningTimes = preconditioning_weekdays_only ? _teslemetry_tesla_protocol_command_common.PreconditioningTimes.create({ weekdays: VOID }) : _teslemetry_tesla_protocol_command_common.PreconditioningTimes.create({ allWeek: VOID });
659
- const offPeakChargingTimes = off_peak_charging_weekdays_only ? _teslemetry_tesla_protocol_command_common.OffPeakChargingTimes.create({ weekdays: VOID }) : _teslemetry_tesla_protocol_command_common.OffPeakChargingTimes.create({ allWeek: VOID });
1281
+ async set_scheduled_departure(enable = true, preconditioning_enabled = false, preconditioning_weekdays_only = false, departure_time = 0, off_peak_charging_enabled = false, off_peak_charging_weekdays_only = false, end_off_peak_time = 0) {
1282
+ let preconditioningTimes;
1283
+ if (preconditioning_weekdays_only) preconditioningTimes = _teslemetry_tesla_protocol_command_common.PreconditioningTimes.create({ weekdays: VOID });
1284
+ else if (preconditioning_enabled) preconditioningTimes = _teslemetry_tesla_protocol_command_common.PreconditioningTimes.create({ allWeek: VOID });
1285
+ let offPeakChargingTimes;
1286
+ if (off_peak_charging_weekdays_only) offPeakChargingTimes = _teslemetry_tesla_protocol_command_common.OffPeakChargingTimes.create({ weekdays: VOID });
1287
+ else if (off_peak_charging_enabled) offPeakChargingTimes = _teslemetry_tesla_protocol_command_common.OffPeakChargingTimes.create({ allWeek: VOID });
660
1288
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ scheduledDepartureAction: _teslemetry_tesla_protocol_command_car_server.ScheduledDepartureAction.create({
661
1289
  enabled: enable,
662
1290
  departureTime: departure_time,
@@ -673,6 +1301,13 @@ var Commands = class extends require_vehiclespecific.default {
673
1301
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlSetSentryModeAction: _teslemetry_tesla_protocol_command_car_server.VehicleControlSetSentryModeAction.create({ on }) }));
674
1302
  }
675
1303
  /**
1304
+ * Sets the vehicle suspension level.
1305
+ * @param level `1` entry, `2` low, `3` medium, `4` high, `5` very high, `6` extract
1306
+ */
1307
+ async set_suspension_level(level) {
1308
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setSuspensionLevelAction: _teslemetry_tesla_protocol_command_car_server.SetSuspensionLevelAction.create({ suspensionLevel: level }) }));
1309
+ }
1310
+ /**
676
1311
  * Sets the driver and/or passenger-side cabin temperature (and other zones if sync is enabled).
677
1312
  * @param driver_temp Driver temperature
678
1313
  * @param passenger_temp Passenger temperature
@@ -684,6 +1319,13 @@ var Commands = class extends require_vehiclespecific.default {
684
1319
  }) }));
685
1320
  }
686
1321
  /**
1322
+ * Enables or disables tent mode.
1323
+ * @param on `true` or `false`
1324
+ */
1325
+ async set_tent_mode(on) {
1326
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setTentModeRequestAction: _teslemetry_tesla_protocol_command_car_server.SetTentModeRequestAction.create({ on }) }));
1327
+ }
1328
+ /**
687
1329
  * Turns on Valet Mode and sets a four-digit passcode.
688
1330
  * @param on `true` or `false`
689
1331
  * @param password Four-digit passcode
@@ -691,7 +1333,7 @@ var Commands = class extends require_vehiclespecific.default {
691
1333
  async set_valet_mode(on, password) {
692
1334
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlSetValetModeAction: _teslemetry_tesla_protocol_command_car_server.VehicleControlSetValetModeAction.create({
693
1335
  on,
694
- password: String(password)
1336
+ password: password == null ? void 0 : String(password)
695
1337
  }) }));
696
1338
  }
697
1339
  /**
@@ -740,15 +1382,39 @@ var Commands = class extends require_vehiclespecific.default {
740
1382
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ drivingSetSpeedLimitAction: _teslemetry_tesla_protocol_command_car_server.DrivingSetSpeedLimitAction.create({ limitMph: limit_mph }) }));
741
1383
  }
742
1384
  /**
1385
+ * Starts a light show.
1386
+ * @param show_index Index of the show
1387
+ * @param start_time Start time
1388
+ * @param volume Volume
1389
+ * @param dance_moves Whether the vehicle moves during the show
1390
+ */
1391
+ async start_light_show(show_index = 0, start_time = 0, volume = 0, dance_moves = false) {
1392
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ startLightShowAction: _teslemetry_tesla_protocol_command_car_server.StartLightShowAction.create({
1393
+ showIndex: show_index,
1394
+ startTime: start_time,
1395
+ volume,
1396
+ danceMoves: dance_moves
1397
+ }) }));
1398
+ }
1399
+ /** Stops the current light show. */
1400
+ async stop_light_show() {
1401
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ stopLightShowAction: _teslemetry_tesla_protocol_command_car_server.StopLightShowAction.create({}) }));
1402
+ }
1403
+ /** Stops the Cybertruck tonneau cover. */
1404
+ async stop_tonneau() {
1405
+ return this.moveTonneau(_teslemetry_tesla_protocol_command_vcsec.ClosureMoveTypeE.CLOSURE_MOVE_TYPE_STOP);
1406
+ }
1407
+ /**
743
1408
  * Controls the panoramic sunroof on the Model S.
744
1409
  *
745
1410
  * "stop" has no corresponding field in the signed-command protobuf (only
746
1411
  * vent/close/open), so it is rejected rather than guessed at.
747
- * @param state "close" or "vent"
1412
+ * @param state "open", "close" or "vent"
748
1413
  */
749
1414
  async sun_roof_control(state) {
750
1415
  let action;
751
1416
  if (state === "vent") action = _teslemetry_tesla_protocol_command_car_server.VehicleControlSunroofOpenCloseAction.create({ vent: VOID });
1417
+ else if (state === "open") action = _teslemetry_tesla_protocol_command_car_server.VehicleControlSunroofOpenCloseAction.create({ open: VOID });
752
1418
  else if (state === "close") action = _teslemetry_tesla_protocol_command_car_server.VehicleControlSunroofOpenCloseAction.create({ close: VOID });
753
1419
  else throw new Error("sun_roof_control: \"stop\" is not representable in a signed command.");
754
1420
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlSunroofOpenCloseAction: action }));
@@ -765,20 +1431,33 @@ var Commands = class extends require_vehiclespecific.default {
765
1431
  }
766
1432
  /**
767
1433
  * Turns on HomeLink (used to open and close garage doors).
768
- * @param lat Latitude
769
- * @param lon Longitude
770
1434
  * @param token
1435
+ * @param lat Latitude. `0` is a coordinate; the location is sent when both are given.
1436
+ * @param lon Longitude
771
1437
  */
772
- async trigger_homelink(lat, lon, token = "") {
1438
+ async trigger_homelink(token, lat, lon) {
773
1439
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlTriggerHomelinkAction: _teslemetry_tesla_protocol_command_car_server.VehicleControlTriggerHomelinkAction.create({
774
- location: _teslemetry_tesla_protocol_command_common.LatLong.create({
1440
+ location: lat != null && lon != null ? _teslemetry_tesla_protocol_command_common.LatLong.create({
775
1441
  latitude: lat,
776
1442
  longitude: lon
777
- }),
1443
+ }) : void 0,
778
1444
  token
779
1445
  }) }));
780
1446
  }
781
1447
  /**
1448
+ * Updates the charge on solar settings.
1449
+ * @param enabled `true` or `false`
1450
+ * @param lower_charge_limit Lower charge limit
1451
+ * @param upper_charge_limit Upper charge limit
1452
+ */
1453
+ async update_charge_on_solar(enabled, lower_charge_limit, upper_charge_limit) {
1454
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ updateChargeOnSolarFeatureRequest: _teslemetry_tesla_protocol_command_car_server.UpdateChargeOnSolarFeatureRequest.create({ chargeOnSolar: _teslemetry_tesla_protocol_command_car_server.ChargeOnSolarFeature.create({
1455
+ enabled,
1456
+ lowerChargeLimit: lower_charge_limit,
1457
+ upperChargeLimit: upper_charge_limit
1458
+ }) }) }));
1459
+ }
1460
+ /**
782
1461
  * Sends upcoming calendar entries to the vehicle. Overrides the inherited
783
1462
  * plaintext command so it is signed and dispatched over the vehicle-command
784
1463
  * protocol rather than silently falling through to the unsigned REST
@@ -789,14 +1468,330 @@ var Commands = class extends require_vehiclespecific.default {
789
1468
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ uiSetUpcomingCalendarEntries: _teslemetry_tesla_protocol_command_car_server.UiSetUpcomingCalendarEntries.create({ calendarData: calendar_data }) }));
790
1469
  }
791
1470
  /**
1471
+ * Asks the vehicle to open a video stream.
1472
+ * @param url Video URL
1473
+ */
1474
+ async video_request(url) {
1475
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ videoRequestAction: _teslemetry_tesla_protocol_command_car_server.VideoRequestAction.create({ url }) }));
1476
+ }
1477
+ /**
792
1478
  * Control the windows of a parked vehicle. Supported commands: vent and close.
793
1479
  * @param command "vent" or "close"
794
1480
  * @param lat Latitude
795
1481
  * @param lon Longitude
796
1482
  */
797
1483
  async window_control(command, _lat, _lon) {
798
- const action = command === "vent" ? _teslemetry_tesla_protocol_command_car_server.VehicleControlWindowAction.create({ vent: VOID }) : _teslemetry_tesla_protocol_command_car_server.VehicleControlWindowAction.create({ close: VOID });
1484
+ let action;
1485
+ if (command === "vent") action = _teslemetry_tesla_protocol_command_car_server.VehicleControlWindowAction.create({ vent: VOID });
1486
+ else if (command === "close") action = _teslemetry_tesla_protocol_command_car_server.VehicleControlWindowAction.create({ close: VOID });
1487
+ else throw new Error(`Invalid window command: ${command}`);
799
1488
  return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ vehicleControlWindowAction: action }));
800
1489
  }
1490
+ /**
1491
+ * Requests a PII key, for privacy and telemetry pseudonymization.
1492
+ * @param subscriber_public_key Subscriber public key
1493
+ * @param pii_key_expiration Expiry as a Unix timestamp in seconds
1494
+ */
1495
+ async pii_key_request(subscriber_public_key, pii_key_expiration) {
1496
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ piiKeyRequest: _teslemetry_tesla_protocol_command_car_server.PiiKeyRequest.create({
1497
+ subscriberPublicKey: subscriber_public_key,
1498
+ piiKeyExpiration: /* @__PURE__ */ new Date(pii_key_expiration * 1e3)
1499
+ }) }));
1500
+ }
1501
+ /**
1502
+ * Syncs the vehicle's privacy and telemetry pseudonym with a previously known hashed value.
1503
+ * @param last_known_pseudonym_hashed The hashed pseudonym
1504
+ */
1505
+ async pseudonym_sync_request(last_known_pseudonym_hashed) {
1506
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ pseudonymSyncRequest: _teslemetry_tesla_protocol_command_car_server.PseudonymSyncRequest.create({ lastKnownPseudonymHashed: last_known_pseudonym_hashed }) }));
1507
+ }
1508
+ /**
1509
+ * Passes a completed "Sign in with Tesla" OAuth response to the vehicle
1510
+ * screen. The caller must already hold that response from its own auth flow.
1511
+ * @param client_id OAuth client ID
1512
+ * @param scope OAuth scope
1513
+ * @param access_token Access token
1514
+ * @param refresh_token Refresh token
1515
+ * @param expiry_timestamp Token expiry
1516
+ * @param error OAuth error, if any
1517
+ * @param scoped_token Scoped token
1518
+ */
1519
+ async tesla_auth_response(client_id, scope, access_token, refresh_token, expiry_timestamp, error = "", scoped_token = "") {
1520
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ teslaAuthResponseAction: _teslemetry_tesla_protocol_command_car_server.TeslaAuthResponseAction.create({
1521
+ clientId: client_id,
1522
+ scope,
1523
+ accessToken: access_token,
1524
+ refreshToken: refresh_token,
1525
+ expiryTimestamp: expiry_timestamp,
1526
+ error,
1527
+ scopedToken: scoped_token
1528
+ }) }));
1529
+ }
1530
+ /**
1531
+ * Links a local Tesla profile to a cloud vault profile.
1532
+ * @param cloud_vault_uuid Cloud vault UUID
1533
+ * @param local_profile_uuid Local profile UUID
1534
+ * @param delete_local_profile_after_setup Whether to delete the local profile afterwards
1535
+ */
1536
+ async setup_cloud_profile_with_local_profile_uuid(cloud_vault_uuid, local_profile_uuid, delete_local_profile_after_setup = false) {
1537
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setupCloudProfileWithLocalProfileUuidAction: _teslemetry_tesla_protocol_command_car_server.SetupCloudProfileWithLocalProfileUuidAction.create({
1538
+ cloudVaultUuid: cloud_vault_uuid,
1539
+ localProfileUuid: local_profile_uuid,
1540
+ deleteLocalProfileAfterSetup: delete_local_profile_after_setup
1541
+ }) }));
1542
+ }
1543
+ /**
1544
+ * Gets the local Tesla profiles of a cloud vault, under `response.getLocalProfilesResponse`.
1545
+ * @param vault_uuid Cloud vault UUID
1546
+ */
1547
+ async get_local_profiles_for_vault_uuid(vault_uuid) {
1548
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ getLocalProfilesForVaultUuidAction: _teslemetry_tesla_protocol_command_car_server.GetLocalProfilesForVaultUuidAction.create({ vaultUuid: vault_uuid }) }), { mutating: false });
1549
+ }
1550
+ /**
1551
+ * Requests Bluetooth Classic, not BLE, pairing with a phone for calls and audio.
1552
+ * @param name Device name
1553
+ * @param mac_address Device MAC address
1554
+ */
1555
+ async bluetooth_classic_pairing_request(name, mac_address) {
1556
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ bluetoothClassicPairingRequest: _teslemetry_tesla_protocol_command_car_server.BluetoothClassicPairingRequest.create({
1557
+ utf8Name: name,
1558
+ macAddress: mac_address
1559
+ }) }));
1560
+ }
1561
+ /**
1562
+ * Runs a diagnostic bandwidth test, with its result under `response.bandwidthTestResponse`.
1563
+ * @param requested_size Size in bytes
1564
+ */
1565
+ async bandwidth_test(requested_size) {
1566
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ bandwidthTest: _teslemetry_tesla_protocol_command_car_server.BandwidthTest.create({ requestedSize: requested_size }) }));
1567
+ }
1568
+ /** Gets information about keys paired with the vehicle, under `response.keysInfoResponse`. */
1569
+ async fetch_keys_info() {
1570
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ fetchKeysInfoAction: _teslemetry_tesla_protocol_command_car_server.FetchKeysInfoAction.create({}) }), { mutating: false });
1571
+ }
1572
+ /** Formats the USB drive connected to the vehicle, erasing all files on it. Irreversible. */
1573
+ async format_usb() {
1574
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ formatUsbAction: _teslemetry_tesla_protocol_command_car_server.FormatUSBAction.create({ formatUsb: true }) }));
1575
+ }
1576
+ /** Deletes all dashcam clips stored on the vehicle. Irreversible. */
1577
+ async delete_dashcam_clips() {
1578
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ deleteDashcamClipsAction: _teslemetry_tesla_protocol_command_car_server.DeleteDashcamClipsAction.create({ deleteClips: true }) }));
1579
+ }
1580
+ /**
1581
+ * Activates or deactivates parental controls.
1582
+ * @param activate `true` or `false`
1583
+ * @param pin Four-digit PIN
1584
+ */
1585
+ async parental_controls(activate, pin) {
1586
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ parentalControlsAction: _teslemetry_tesla_protocol_command_car_server.ParentalControlsAction.create({
1587
+ activate,
1588
+ pin: String(pin)
1589
+ }) }));
1590
+ }
1591
+ /**
1592
+ * Clears the parental controls PIN.
1593
+ * @param pin Four-digit PIN
1594
+ */
1595
+ async parental_controls_clear_pin(pin) {
1596
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ parentalControlsClearPinAction: _teslemetry_tesla_protocol_command_car_server.ParentalControlsClearPinAction.create({ pin: String(pin) }) }));
1597
+ }
1598
+ /** Clears the parental controls PIN, for fleet managers and owners. */
1599
+ async parental_controls_clear_pin_admin() {
1600
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ parentalControlsClearPinAdminAction: _teslemetry_tesla_protocol_command_car_server.ParentalControlsClearPinAdminAction.create({}) }));
1601
+ }
1602
+ /**
1603
+ * Enables or disables a parental controls setting.
1604
+ * @param setting `0` speed limit, `1` acceleration, `2` safety features, `3` curfew, `4` browser blocked, `5` theater blocked, `6` arcade blocked
1605
+ * @param enable `true` or `false`
1606
+ */
1607
+ async parental_controls_enable_setting(setting, enable) {
1608
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ parentalControlsEnableSettingsAction: _teslemetry_tesla_protocol_command_car_server.ParentalControlsEnableSettingsAction.create({
1609
+ setting,
1610
+ enable
1611
+ }) }));
1612
+ }
1613
+ /**
1614
+ * Sets the parental controls speed limit.
1615
+ * @param limit_mph Maximum speed in MPH
1616
+ */
1617
+ async parental_controls_set_speed_limit(limit_mph) {
1618
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ parentalControlsSetSpeedLimitAction: _teslemetry_tesla_protocol_command_car_server.ParentalControlsSetSpeedLimitAction.create({ limitMph: limit_mph }) }));
1619
+ }
1620
+ /**
1621
+ * Sets the displayed temperature unit.
1622
+ * @param unit `TemperatureUnit`
1623
+ */
1624
+ async set_temperature_unit(unit) {
1625
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setTemperatureUnitAction: _teslemetry_tesla_protocol_command_car_server.SetTemperatureUnitAction.create({ unit }) }));
1626
+ }
1627
+ /**
1628
+ * Sets the displayed distance unit.
1629
+ * @param unit `DistanceUnit`
1630
+ */
1631
+ async set_distance_unit(unit) {
1632
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setDistanceUnitAction: _teslemetry_tesla_protocol_command_car_server.SetDistanceUnitAction.create({ unit }) }));
1633
+ }
1634
+ /**
1635
+ * Sets the displayed clock format.
1636
+ * @param format `TimeDisplayFormat`
1637
+ */
1638
+ async set_time_display_format(format) {
1639
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setTimeDisplayFormatAction: _teslemetry_tesla_protocol_command_car_server.SetTimeDisplayFormatAction.create({ format }) }));
1640
+ }
1641
+ /**
1642
+ * Sets the displayed tire pressure unit.
1643
+ * @param unit `TirePressureUnit`
1644
+ */
1645
+ async set_tire_pressure_unit(unit) {
1646
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setTirePressureUnitAction: _teslemetry_tesla_protocol_command_car_server.SetTirePressureUnitAction.create({ unit }) }));
1647
+ }
1648
+ /**
1649
+ * Sets the displayed energy or range unit.
1650
+ * @param format `EnergyDisplayFormat`
1651
+ */
1652
+ async set_energy_display_format(format) {
1653
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setEnergyDisplayFormatAction: _teslemetry_tesla_protocol_command_car_server.SetEnergyDisplayFormatAction.create({ format }) }));
1654
+ }
1655
+ /**
1656
+ * Sets paired-phone display preferences.
1657
+ * @param font_size `PhoneFontSize`
1658
+ * @param options.distance_unit `DistanceUnit`
1659
+ * @param options.temperature_unit `TemperatureUnit`
1660
+ */
1661
+ async set_phone_setting_preferences(font_size = require_const.PhoneFontSize.STANDARD, { distance_unit, temperature_unit } = {}) {
1662
+ const unitPreferences = distance_unit != null || temperature_unit != null ? _teslemetry_tesla_protocol_command_car_server.PhoneUnitPreferences.create({
1663
+ distanceUnit: distance_unit,
1664
+ temperatureUnit: temperature_unit
1665
+ }) : void 0;
1666
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setPhoneSettingPreferencesAction: _teslemetry_tesla_protocol_command_car_server.SetPhoneSettingPreferencesAction.create({
1667
+ fontSize: font_size,
1668
+ unitPreferences
1669
+ }) }));
1670
+ }
1671
+ /**
1672
+ * Enables or disables the Powershare feature.
1673
+ * @param on `true` or `false`
1674
+ */
1675
+ async set_powershare_feature(on) {
1676
+ const powershareFeatureRequest = on ? _teslemetry_tesla_protocol_command_car_server.SetPowershareFeatureAction_PowershareFeatureRequest.POWERSHARE_FEATURE_REQUEST_ON : _teslemetry_tesla_protocol_command_car_server.SetPowershareFeatureAction_PowershareFeatureRequest.POWERSHARE_FEATURE_REQUEST_OFF;
1677
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setPowershareFeatureAction: _teslemetry_tesla_protocol_command_car_server.SetPowershareFeatureAction.create({ powershareFeatureRequest }) }));
1678
+ }
1679
+ /**
1680
+ * Enables or disables powershare.
1681
+ * @param on `true` or `false`
1682
+ */
1683
+ async set_powershare_request(on) {
1684
+ const powershareRequest = on ? _teslemetry_tesla_protocol_command_car_server.SetPowershareRequestAction_PowershareRequest.POWERSHARE_REQUEST_ON : _teslemetry_tesla_protocol_command_car_server.SetPowershareRequestAction_PowershareRequest.POWERSHARE_REQUEST_OFF;
1685
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setPowershareRequestAction: _teslemetry_tesla_protocol_command_car_server.SetPowershareRequestAction.create({ powershareRequest }) }));
1686
+ }
1687
+ /**
1688
+ * Sets the Powershare discharge limit. This is not `set_discharge_limit`, which is a separate message.
1689
+ * @param percent Discharge limit percentage
1690
+ */
1691
+ async set_powershare_discharge_limit(percent) {
1692
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setPowershareDischargeLimitAction: _teslemetry_tesla_protocol_command_car_server.SetPowershareDischargeLimitAction.create({ powershareDischargeLimit: percent }) }));
1693
+ }
1694
+ /**
1695
+ * Sets the outlets on or off.
1696
+ * @param request `0` off, `1` cabin and bed, `2` cabin
1697
+ */
1698
+ async set_outlets(request) {
1699
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setOutletsOnOffAction: _teslemetry_tesla_protocol_command_car_server.SetOutletsOnOffAction.create({ outletRequest: request }) }));
1700
+ }
1701
+ /**
1702
+ * Sets the outlet timer.
1703
+ * @param num_minutes Minutes
1704
+ */
1705
+ async set_outlet_timer(num_minutes) {
1706
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setOutletTimerAction: _teslemetry_tesla_protocol_command_car_server.SetOutletTimerAction.create({ numMinutes: num_minutes }) }));
1707
+ }
1708
+ /**
1709
+ * Sets the outlet state of charge limit.
1710
+ * @param percent State of charge percentage
1711
+ */
1712
+ async set_outlet_soc_limit(percent) {
1713
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setOutletSocLimitAction: _teslemetry_tesla_protocol_command_car_server.SetOutletSocLimitAction.create({ percent }) }));
1714
+ }
1715
+ /**
1716
+ * Sets the power feed on or off.
1717
+ * @param request `0` off, `1` feed 1, `2` feed 2, `3` both
1718
+ */
1719
+ async set_power_feed(request) {
1720
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setPowerFeedOnOffAction: _teslemetry_tesla_protocol_command_car_server.SetPowerFeedOnOffAction.create({ powerFeedRequest: request }) }));
1721
+ }
1722
+ /**
1723
+ * Sets the power feed timer.
1724
+ * @param num_minutes Minutes
1725
+ */
1726
+ async set_power_feed_timer(num_minutes) {
1727
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setPowerFeedTimerAction: _teslemetry_tesla_protocol_command_car_server.SetPowerFeedTimerAction.create({ numMinutes: num_minutes }) }));
1728
+ }
1729
+ /**
1730
+ * Sets the power feed state of charge limit.
1731
+ * @param percent State of charge percentage
1732
+ */
1733
+ async set_power_feed_soc_limit(percent) {
1734
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setPowerFeedSocLimitAction: _teslemetry_tesla_protocol_command_car_server.SetPowerFeedSocLimitAction.create({ percent }) }));
1735
+ }
1736
+ /**
1737
+ * Sets the lightbar brightness.
1738
+ * @param brightness Brightness
1739
+ */
1740
+ async set_lightbar_brightness(brightness) {
1741
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setLightbarBrightnessAction: _teslemetry_tesla_protocol_command_car_server.SetLightbarBrightnessAction.create({ brightnessRequest: brightness }) }));
1742
+ }
1743
+ /**
1744
+ * Turns the lightbar middle light on or off.
1745
+ * @param on `true` or `false`
1746
+ */
1747
+ async set_lightbar_middle(on) {
1748
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setLightbarMiddleAction: _teslemetry_tesla_protocol_command_car_server.SetLightbarMiddleAction.create({ middleLightRequest: on }) }));
1749
+ }
1750
+ /**
1751
+ * Turns the ditch lights on or off.
1752
+ * @param on `true` or `false`
1753
+ */
1754
+ async set_lightbar_ditch(on) {
1755
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setLightbarDitchAction: _teslemetry_tesla_protocol_command_car_server.SetLightbarDitchAction.create({ ditchLightsRequest: on }) }));
1756
+ }
1757
+ /**
1758
+ * Sets the front zone light level.
1759
+ * @param level `0` off, `1` low, `2` medium, `3` high
1760
+ */
1761
+ async set_front_zone_lights(level) {
1762
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setFrontZoneLightRequestAction: _teslemetry_tesla_protocol_command_car_server.SetZoneLightRequestAction.create({ zoneLightRequest: level }) }));
1763
+ }
1764
+ /**
1765
+ * Sets the rear zone light level.
1766
+ * @param level `0` off, `1` low, `2` medium, `3` high
1767
+ */
1768
+ async set_rear_zone_lights(level) {
1769
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setRearZoneLightRequestAction: _teslemetry_tesla_protocol_command_car_server.SetZoneLightRequestAction.create({ zoneLightRequest: level }) }));
1770
+ }
1771
+ /**
1772
+ * Starts or stops the trailer light test.
1773
+ * @param on `true` or `false`
1774
+ */
1775
+ async set_trailer_light_test(on) {
1776
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setTrailerLightTestStartStopAction: _teslemetry_tesla_protocol_command_car_server.SetTrailerLightTestStartStopAction.create({ startStop: on }) }));
1777
+ }
1778
+ /**
1779
+ * Sets the truck bed light's auto state.
1780
+ * @param on `true` or `false`
1781
+ */
1782
+ async set_truck_bed_light_auto(on) {
1783
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setTruckBedLightAutoStateAction: _teslemetry_tesla_protocol_command_car_server.SetTruckBedLightAutoStateAction.create({ powerState: on }) }));
1784
+ }
1785
+ /**
1786
+ * Sets the truck bed light brightness.
1787
+ * @param brightness Brightness
1788
+ */
1789
+ async set_truck_bed_light_brightness(brightness) {
1790
+ return this.vehicleAction(_teslemetry_tesla_protocol_command_car_server.VehicleAction.create({ setTruckBedLightBrightnessAction: _teslemetry_tesla_protocol_command_car_server.SetTruckBedLightBrightnessAction.create({ brightness }) }));
1791
+ }
801
1792
  };
1793
+ exports.DAY_OF_WEEK_BITS = DAY_OF_WEEK_BITS;
1794
+ exports.days_of_week_mask = days_of_week_mask;
802
1795
  exports.default = Commands;
1796
+ exports.infotainment_command_name = infotainment_command_name;
1797
+ exports.vcsec_command_name = vcsec_command_name;