Last active
January 29, 2016 22:35
-
-
Save ErikDeBruijn/3315e5f54281c6eaf7f8 to your computer and use it in GitHub Desktop.
A little script to finish charging my car just before I want to leave
This file contains 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
#!/usr/bin/node | |
/* | |
This script does four things for Tesla Model S owners: | |
- it helps increase battery longevity | |
- saves money if your utility charges night-time power differently | |
- gives you a more pleasant start of your drive | |
- preheats for you in the morning, no need to open up the App. | |
How? | |
You tell it when it should complete its charge and what percentage. | |
It will calculate how long it would take and start that much | |
earlier. | |
Why the trouble? | |
Lithium Ion battery chemistry requires you to not keep a battery | |
at a high charge (e.g. 100%) for a long duration. Also, its better | |
for your range and your battery to start driving when it just barely | |
completed its charge, because it will still be warmer. This means | |
more regen during those first kilometers, but also: a cold battery | |
would still suffer from high-current charging, which is what regen | |
does. Actually, a charging schedule should be a standard option on | |
provided by Tesla. But they're busy. And I couldn't wait. | |
by not leaving it highly charged for long and save money | |
on electricity by using more of the night tariff. | |
WARNING: THIS CODE COMES WITH NO WARRANTY OR FITNESS FOR ANY | |
PURPOSE. AND IT IS NOT ENDORSED BY TESLA. | |
NEVER OVER-USE THE TESLA API BY RUNNING LOTS OF REQUESTS ON THEIR | |
SERVERS. | |
Installation and usage: | |
Requires a 24/7 running linux box, e.g. a Raspberry Pi or something. | |
1. Install nodejs (google if you don't know how) | |
In my home-build charger (3 phase 11kW, 60kWh battery) I get about | |
one percent of charge every two minutes. I added 10% margin and set | |
the variable minutesPerPercent to 2.2. | |
2. Change this to your liking. | |
Also, change the coordinates for your home, so it will know not to | |
mess with charging if your not at home. | |
3. Before use, install these: | |
sudo npm install -g json5 shelljs teslams | |
4. Add a ~/.teslams/config.json file with the following: | |
{ | |
"username": "yourMyTeslaLogin@email.com", | |
"password": "yourPassword", | |
} | |
5. Run a "teslacmd --honk" to test and see if you can control the car | |
through the API with hjespers teslams node library. | |
6. Try running this script manually to get some confidence before | |
your think of relying on it. You may not be able to go on your | |
planned trip in time if something goes wrong. | |
7. Add to cron: | |
The recommendation is to put this in cron, one line per | |
day of the week if you have different times when you leave. | |
If you leave at the same time daily (monday-friday), this will work: | |
0,15,30,45 3-8 * * 1-5 /home/erik/tesla-finish-charge-at.js 7:30 85 | |
The "7:30 85" means that at 7:30 it should be charged to 85%. | |
CONTRIBUTING | |
Contact me on github (erikdebruijn) or send me pull requests if you find a | |
problem or fix something. | |
*/ | |
require('shelljs/global'); | |
var JSON5 = require('json5'); | |
if(process.argv.length < 4) { | |
return echo(usage()); | |
} | |
// Change these with your settings | |
var minutesPerPercent = 2.2; | |
var homeCoordinates = "51.586269,5.004745"; | |
// Don't change the following unless you know what you're doing | |
var chargeTargetPerc = process.argv[3]; | |
if(chargeTargetPerc < 50 || chargeTargetPerc > 100) { | |
return echo("Charge percentage should always be between 50 and 100%.\n"+usage()); | |
} | |
if(process.argv[2].split(':').length != 2) { | |
return echo("Tome to complete should be in hh:mm format.\n"+usage()); | |
} | |
var chargeCompleteAtH = process.argv[2].split(':')[0]; | |
var chargeCompleteAtM = process.argv[2].split(':')[1]; | |
var alsoHeat = (process.argv[4] == '--heat')? true : false; | |
if(alsoHeat) | |
echo("Will heat/cool car after charging finished."); | |
var finishChargingAt = new Date(); | |
finishChargingAt.setHours(parseInt(chargeCompleteAtH),parseInt(chargeCompleteAtM),0); | |
echo("Will attempt to finish charging at: "+finishChargingAt.toString()); | |
if(!isAtHome()) | |
return echo("Aborting."); | |
var chargeObj = getChargeInfo(); | |
if(typeof chargeObj != "object") { | |
echo("No charge info found. Trying to ensure charge to 85%"); | |
setChargeRangePerc(85); | |
return false; | |
} | |
var minutesToCharge = (chargeTargetPerc-chargeObj.battery_level) * minutesPerPercent; | |
if(minutesToCharge < 0) | |
return echo("Current SoC (at "+chargeObj.battery_level+"%) is already above your target of "+chargeTargetPerc+"%."); | |
echo(chargeObj.battery_level + "% charged.","It will take "+minutesToCharge+" minutes to charge to "+chargeTargetPerc+"%."); | |
var startAtUnixMillis = finishChargingAt.getTime() - 60000 * minutesToCharge; | |
var startChargingAt = new Date(startAtUnixMillis); | |
var minutesToStartCharging = (startChargingAt - +new Date)/1000/60; | |
console.log("It is "+minutesToStartCharging+" minutesToStartCharging."); | |
if(minutesToStartCharging < 59) | |
{ | |
echo("Waiting for "+minutesToStartCharging+" minutes to charge."); | |
setTimeout(function(){ | |
echo("Waiting finished."); | |
setChargeRangePerc(chargeTargetPerc); | |
},60*1000*minutesToStartCharging); | |
if(alsoHeat) { | |
echo("Will heat/cool car after charging finished."); | |
setTimeout(function(){ | |
echo("Waiting for heating finished."); | |
teslaCmdToJSON("teslacmd --climate on --temp 19"); | |
},60*1000*(minutesToStartCharging+minutesToCharge-1)); | |
} | |
} | |
function usage(){ | |
return "Usage: node tesla-charge.js <time_charge_should_complete> <charge_to_percent>"; | |
} | |
function getCarLocation() { | |
return teslaCmdToJSON("teslamap --json"); | |
} | |
function setChargeRangePerc(perc) { | |
echo("Starting to charge to "+parseInt(perc)+"%..."); | |
var output = exec("teslacmd --charge start --range "+parseInt(perc),{silent:false}).output; | |
return output; | |
} | |
function getChargeInfo() { | |
var jsonOutput; | |
json = teslaCmdToJSON("teslacmd -c"); | |
var repeatCount = 2; | |
while(typeof json != 'object') { | |
repeatCount--; | |
if(repeatCount < 1) break; | |
json = teslaCmdToJSON("sleep 5 ; teslacmd -c"); | |
} | |
return json; | |
} | |
function strToLatLongObj(str) { | |
return {latitude: parseFloat(str.split(',')[0]),longitude: parseFloat(str.split(',')[1])}; | |
} | |
function distance(latlongVector1,latlongVector2,unit) { | |
var radlat1 = Math.PI * latlongVector1.latitude/180 | |
var radlat2 = Math.PI * latlongVector2.latitude/180 | |
var radlon1 = Math.PI * latlongVector1.longitude/180 | |
var radlon2 = Math.PI * latlongVector2.longitude/180 | |
var theta = latlongVector1.longitude-latlongVector2.longitude | |
var radtheta = Math.PI * theta/180 | |
var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); | |
return Math.acos(dist)*180/Math.PI * 60 * 1.1515 * 1.609344; | |
} | |
function teslaCmdToJSON(cmd) | |
{ | |
var jsonOutput = exec(cmd,{silent:true}).output; | |
console.log(jsonOutput); | |
jsonOutput = jsonOutput.split("\n"); | |
jsonOutput.shift(); | |
jsonOutput = jsonOutput.join("\n"); | |
if(jsonOutput.length < 1) { | |
return false; | |
} | |
try { | |
return JSON5.parse(jsonOutput); | |
}catch(e){ return false; } | |
} | |
function isAtHome() { | |
latLong = getCarLocation(); | |
latLongHome = strToLatLongObj(homeCoordinates); | |
dist = distance(latLong,latLongHome,'K'); | |
if(dist >= 1) { | |
echo("Tesla is not at home charger ("+(parseInt(dist*10)/10)+" km from home)."); | |
return false; | |
} | |
echo("Tesla is at home. Continuing."); | |
return true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment