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