-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
169 lines (137 loc) · 4.71 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request))
})
/**
* Main API Endpoint. Waits for car to wake up, and then sets the temprature and starts HVAC.
* @param {Request} request
*/
async function handleRequest(request) {
const accessToken = request.headers.get('X-Tesla-access_token')
const vin = request.headers.get('X-Tesla-vin')
const temperature = request.headers.get('X-Tesla-temp')
const seats = request.headers.get('X-Tesla-seats')
let successMessage = 'Car is preconditioning'
try {
console.log('Getting vehicle id and state')
const { vehicleID, vehicleState } = await getVehicleIDAndStatusFromVin(accessToken, vin)
if (vehicleState !== 'online') {
console.log('Car is asleep, waking it up')
await wakeVehicle(accessToken, vehicleID)
} else {
console.log('Car is already awake, skipping wake_up')
}
console.log('Starting HVAC')
await startHVAC(accessToken, vehicleID)
if (temperature) {
console.log('Setting temprature')
await setTemperature(accessToken, vehicleID, temperature)
successMessage += ` to ${temperature}C`
}
// Enable seat heaters
if (seats) {
console.log('Setting seat levels')
await Promise.all(
seats.split(',').map(async (seatLevel, seatNumber) => {
await setSeatHeater(accessToken, vehicleID, seatNumber, seatLevel)
}),
)
successMessage += ', and the seats have been set.'
}
return jsonResponse(successMessage)
} catch (errorMessage) {
return jsonResponse('Error: ' + errorMessage)
}
}
async function jsonResponse(message) {
return new Response(JSON.stringify({ response: message }), {
headers: { 'content-type': 'application/json' },
})
}
async function getVehicleIDAndStatusFromVin(accessToken, vin) {
if (vin === null) {
throw 'No X-Tesla-vin header provided'
}
const vehiclesResponse = await teslaRequest(accessToken, null, 'GET', '/vehicles')
const vehiclesJSON = await vehiclesResponse.json()
const vehicle = vehiclesJSON.response.find((vehicle) => vehicle.vin === vin)
if (vehicle) {
return {
vehicleID: vehicle.id_s,
vehicleState: vehicle.state,
}
} else {
throw 'Cannot find vehicle with that VIN'
}
}
async function startHVAC(accessToken, vehicleID) {
return await teslaRequest(accessToken, vehicleID, 'POST', '/command/auto_conditioning_start')
}
async function setTemperature(accessToken, vehicleID, temperature) {
return await teslaRequest(accessToken, vehicleID, 'POST', '/command/set_temps', {
driver_temp: temperature,
passenger_temp: temperature,
})
}
async function setSeatHeater(accessToken, vehicleID, seatNumber, seatLevel) {
return await teslaRequest(accessToken, vehicleID, 'POST', '/command/remote_seat_heater_request', {
heater: seatNumber,
level: seatLevel,
})
}
async function wakeVehicle(accessToken, vehicleID) {
const wakeResponse = await teslaRequest(accessToken, vehicleID, 'POST', '/wake_up')
return await wakeResponse.json()
}
async function teslaRequest(accessToken, vehicleID, method, url, body = null) {
if (accessToken === null) {
throw 'No access token provided'
}
const headers = new Headers()
headers.set('Authorization', 'Bearer ' + accessToken)
if (body !== null) {
headers.set('Content-Type', 'application/json')
body = JSON.stringify(body)
}
let fullUrl = null
if (vehicleID === null && url === '/vehicles') {
fullUrl = 'https://owner-api.teslamotors.com/api/1/vehicles/'
} else {
fullUrl = 'https://owner-api.teslamotors.com/api/1/vehicles/' + vehicleID + url
}
const request = new Request(fullUrl, {
method: method,
headers: headers,
body: body,
})
let response = await performRequest(request, body)
let retryCount = 1
let retryMs = 1000
let retryLimit = 60
while (response.status === 408) {
if (retryCount > retryLimit) {
throw `Timed out waiting for car to wake up (${retryMs}ms).`
}
console.warn(`[Attempt: ${retryCount}/${retryLimit}] Car is sleeping, trying again in ${retryMs}ms`)
await new Promise((r) => setTimeout(r, retryMs))
response = await performRequest(request, body)
retryCount += 1
}
if (response.status === 401) {
throw 'Access Token invalid'
}
if (response.status === 200) {
return response
} else {
throw `Invalid response from ${fullUrl}: [${response.status}] ` + (await response.text())
}
}
async function performRequest(request, body) {
const response = await fetch(request)
const response_log = `[${request.method}] ${request.url}: ${body} => ${response.status}`
if (response.status === 200) {
console.log(response_log)
} else {
console.error(response_log)
}
return response
}