Skip to content

Instantly share code, notes, and snippets.

@MattJeanes
Last active June 5, 2023 09:18
Show Gist options
  • Save MattJeanes/ee6d396ac1323f8683809202f33fb3dd to your computer and use it in GitHub Desktop.
Save MattJeanes/ee6d396ac1323f8683809202f33fb3dd to your computer and use it in GitHub Desktop.
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());
}
};
{
"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