Last active
June 5, 2023 09:18
-
-
Save MattJeanes/ee6d396ac1323f8683809202f33fb3dd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fetch = require("node-fetch"); | |
const oauthClientUrl = "https://pastebin.com/raw/pS7Z6yyP" | |
const baseUrl = "https://owner-api.teslamotors.com"; | |
function wait(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
async function getHeaders() { | |
if (!process.env.TESLA_ACCESS_TOKEN) { | |
console.log("Getting access token - getting oauth client details"); | |
const email = process.env.TESLA_USERNAME; | |
const password = process.env.TESLA_PASSWORD; | |
const client = await (await fetch(oauthClientUrl)).text(); | |
const regex = new RegExp("^.+=(.+)$", "gm"); | |
const clientId = regex.exec(client)[1]; | |
const clientSecret = regex.exec(client)[1]; | |
console.log(`Got client details - id: ${clientId}, secret: ${clientSecret} - getting access token`); | |
const creds = await (await fetch(`${baseUrl}/oauth/token?grant_type=password&client_id=${clientId}&client_secret=${clientSecret}&email=${email}&password=${password}`, { method: "POST" })).json(); | |
process.env.TESLA_ACCESS_TOKEN = creds.access_token; | |
console.log("Access token retrieved"); | |
} | |
return { "Authorization": `Bearer ${process.env.TESLA_ACCESS_TOKEN}`, "Content-Type": "application/json" } | |
} | |
async function runCommand(url, method = "GET", body = undefined) { | |
body = body && JSON.stringify(body) || undefined; | |
let carId = process.env.TESLA_CAR_ID || ""; | |
if (!carId && url) { | |
console.log("Finding car"); | |
const vehicles = await runCommand(""); | |
process.env.TESLA_CAR_ID = carId = vehicles[0].id_s; | |
console.log(`Found car id ${carId}`); | |
} | |
console.log(`Running command '${url}' with method ${method} and body ${body}`); | |
const resp = await fetch(`${baseUrl}/api/1/vehicles/${carId}${url}`, { headers: await getHeaders(), method, body }); | |
if (resp.status === 408) { | |
if (url === "/wake_up") { | |
console.log("Car still waking up"); | |
return (await resp.json()).response; | |
} | |
console.log("Waking up car"); | |
while (true) { | |
const wakeResp = await runCommand("/wake_up", "POST"); | |
if (wakeResp.state === "online") { | |
break; | |
} | |
await wait(5000); | |
} | |
console.log("Car is awake, running original command"); | |
return await runCommand(url, method, body); | |
} else { | |
return (await resp.json()).response; | |
} | |
} | |
async function unlockChargePort() { | |
console.log("Requesting charge state"); | |
const chargeState = await runCommand("/data_request/charge_state"); | |
if (!chargeState.charge_port_door_open) { | |
console.log("Charge port is not open"); | |
} else if (chargeState.charge_port_latch !== "Engaged") { | |
console.log("Charge port latch is already disengaged") | |
} else if (chargeState.charging_state === "Charging") { | |
console.log("Car is currently charging"); | |
} else { | |
console.log("Charge port is opened and engaged, unlocking"); | |
const openCommand = await runCommand("/command/charge_port_door_open", "POST"); | |
if (!openCommand.result) { | |
console.log(`Charge port failed to unlock: ${openCommand.reason}`); | |
throw new Error(openCommand.reason); | |
} else { | |
console.log("Charge port unlocked"); | |
} | |
} | |
} | |
async function manualChargePort() { | |
const chargeState = await runCommand("/data_request/charge_state"); | |
let open = true; | |
if (chargeState.charge_port_door_open && chargeState.charge_port_latch !== "Engaged") { | |
console.log("Charge port door open but unlatched, closing"); | |
open = false; | |
} else if (chargeState.charging_state === "Charging") { | |
console.log("Car currently charging, stopping"); | |
const chargeStopCommand = await runCommand("/command/charge_stop", "POST"); | |
if (!chargeStopCommand.result) { | |
console.log(`Charge stop failed: ${chargeStopCommand.reason}`); | |
throw new Error(chargeStopCommand.reason); | |
} | |
} | |
const chargePortDoorCommand = await runCommand(`/command/charge_port_door_${open ? "open" : "close"}`, "POST"); | |
if (!chargePortDoorCommand.result) { | |
console.log(`Charge port action failed: ${chargePortDoorCommand.reason}`); | |
throw new Error(chargePortDoorCommand.reason); | |
} | |
} | |
/** | |
* Responds to any HTTP request. | |
* | |
* @param {!express:Request} req HTTP request context. | |
* @param {!express:Response} res HTTP response context. | |
*/ | |
exports.run = async (req, res) => { | |
try { | |
if (req.body.key !== process.env.PASSWORD) { | |
res.status(401); | |
res.send("Unauthorized"); | |
return; | |
} | |
switch (req.body.action) { | |
case "force": | |
await manualChargePort(); | |
break; | |
default: | |
await unlockChargePort(); | |
break; | |
} | |
res.status(200); | |
res.send("OK"); | |
} catch (e) { | |
console.log(`Fatal error: ${e.toString()}`); | |
res.status(500); | |
res.send(e.toString()); | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "charge_port_door_open", | |
"version": "1.0.0", | |
"dependencies": { | |
"node-fetch": "^2.6.0" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment