Tesla + SolarEdge
How to Use
This example obtains the solar production from SolarEdge and change the charging current in Tesla. The goal is to charge the Tesla from solar production, not from the grid. It will check in 10 minutes interval.
To run it:
Source Code
/*
This is an example, it checks the solar production from SolarEdge application and adjust Tesla charging current,
it will stop when the production (minus reserve) reaches below "minAmp" or non-zero retriesBeforeStop is reached.
The idea is to charge Tesla with solar energy, not from the grid.
Version: 0.1
*/
var controls = {};
var settings = {
// How many voltage per Amp
voltagePerAmp: 245,
// Lower than this Amp, will exit if retriesBeforeStop is reached
minAmp: 6,
// Maximum Amp Tesla can accept
maxAmp: 32,
// Wattage to reserve for other purposes
reserve: 500,
// Check every x minutes
waitInterval: 10,
// Number of failed retries before stop, zero will not stop
retriesBeforeStop: 0,
// Number of zero before exit (e.g. sunset)
retriesOnZero: 3,
// Number of restart on error
retriesRestart: 3,
// Display interaction
display: true
}
function padZero(str, length) {
length = length || 2;
return str.toString().padStart(length, '0');
}
function getLocalDateTimeWithAmPm() {
const now = new Date();
const hours = now.getHours();
const amPm = hours >= 12 ? 'PM' : 'AM';
const hours12 = hours % 12 || 12;
return now.getFullYear() + '-' + padZero(now.getMonth() + 1) + '-' + padZero(now.getDate()) + ' ' +
padZero(hours12) + ':' + padZero(now.getMinutes()) + ":" +
padZero(now.getSeconds()) + ' ' + amPm;
}
function xprint(msg) {
print(getLocalDateTimeWithAmPm(new Date()) + " : " + msg);
}
const fileExists = require('fs').exists;
const removeFile = require('fs').remove;
function xsleep(sleepTime) {
const sleepInterval = 1000;
var curTime = 0;
while (curTime < sleepTime) {
sleep(sleepInterval);
curTime += sleepInterval;
if (fileExists('/tmp/StopNow')) {
removeFile('/tmp/StopNow')
exitNow(0,"Stopped by user");
}
if (fileExists('/tmp/CheckNow')) {
removeFile('/tmp/CheckNow');
return;
}
}
}
function sendAai(obj, ignoreError) {
ignoreError = ignoreError || false;
var retval = getDevice().sendAai(obj);
if (retval == null) {
errorStr = "Error on: " + JSON.stringify(obj) + ":" + lastError();
if (ignoreError) {
xprint(">> " + errorStr + ". Ignored");
return null;
}
throw new Error(errorStr);
}
if (settings.display) {
xprint(">>" + JSON.stringify(obj) + "\n" + JSON.stringify(retval));
}
return retval;
}
function setCharging(newState) {
openTesla();
const pat = "(Start|Stop) Charging";
const retval = sendAai({action:`newQuery('T:/${pat}/', 2000);getIds;getText`}, true);
if (retval == null) {
throw new Error("Cannot obtain the charging status pattern");
}
const id = retval.list[1].ids[0];
const chargingString = retval.list[2].retval;
const regExp = new RegExp(pat);
const groups = chargingString.match(regExp);
if (groups == null) {
throw new Error("Invalid charging string");
}
// Action is reverse, "Start Charging" current is not charging.
const curState = groups[1] == "Start" ? false : true;
if (curState == newState) {
return;
}
sendAai({elements:[id], action:"click"});
const text = (newState ? "Start" : "Stop") + " Charging";
return sendAai({action:`waitQuery(T:${text}, 3000)`}, true) != null;
}
var lastErrorMsg = "";
function setError(msg) {
lastErrorMsg = msg;
}
function getError() {
return lastErrorMsg;
}
function getSolarProduction() {
const retval = sendAai({actions:[
"openApp(mySolarEdge)",
"waitQuery(T:Lifetime,25000)",
"newQuery(R:.pv_power_now)",
"getText",
"sendKey(Back)",
"sendKey(Back)"]});
// getText
const prodStr = retval.list[3].retval;
const groups = prodStr.match(/^(\d+(\.?\d+)?) (W|kW)/);
if (groups == null) {
setError("Cannot find pattern in mySolarEdge");
return -1;
}
const unit = groups.splice(-1);
var number = groups[1];
if (unit == "kW") {
number = number * 1000;
} else if (unit != "W") {
setError("Unknown unit: " + unit);
return -1;
}
return Math.round(number);
}
function startCharging() {
return setCharging(true);
}
// Some time stop Charging will take effect on 2nd or 3rd click
function stopCharging() {
return setCharging(false);
}
function adjAmp(num) {
openTesla();
var ctrl;
var count;
// Sometime the "<" or ">" is not fully recognized, run several times to make
// sure the number match
for (var i = 0; i < 5; ++i) {
var amp = sendAai({elements:[controls.amp], action:"getText"}).retval.split(" ")[0];
if (amp == num) {
return true;
} else if (amp > num) {
ctrl = controls.less;
count = amp - num;
} else {
ctrl = controls.more;
num = Math.min(settings.maxAmp, num);
count = num - amp;
}
sendAai({elements:[ctrl], action:`repeat(${count},clickWait)`});
}
}
function init() {
// May take 20-30 seconds to bring up the charging information
var success = sendAai({action:"restartApp(Tesla, 'T:/(Start|Stop) Charging/', 30000)"}, true);
if (success == null) {
exitNow(2, "Cannot find charging information, please make sure app in charging mode");
}
var retval = sendAai({query:"T:/[0-9]+ A/&&TX"});
if (retval.count != 3) {
exitNow(3, "Cannot locate controls");
}
controls.less = retval.ids[0];
controls.more = retval.ids[2];
controls.amp = retval.ids[1];
var solarProd = getSolarProduction();
if (solarProd == -1) {
exitNow(4, "Cannot obtain solar production: " + getError());
}
// Create a function "clickWait"
sendAai({action:"function(clickWait)", clickWait:["sleep(200)", "nsClick"]});
return true;
}
function start() {
var retriesCounter = 0;
var retriesOnZero = 0;
while (true) {
var solarProd = getSolarProduction();
if (solarProd == -1) {
throw new Error("Cannot get solar production");
}
if (solarProd == 0 && settings.retriesOnZero != 0) {
retriesOnZero++;
if (retriesOnZero >= settings.retriesOnZero) {
exitNow(1, "Zero production");
}
}
// .7 or above will carry to next integer
var amp = Math.floor((solarProd - settings.reserve)/settings.voltagePerAmp + 0.3);
amp = Math.min(amp, settings.maxAmp);
amp = amp < 0 ? 0 : amp;
xprint("Receive solar production: " + solarProd + " Watts, adjust to " + amp + " A");
if (amp < settings.minAmp) {
retriesCounter++;
if (retriesCounter < settings.retriesBeforeStop || settings.retriesBeforeStop == 0) {
stopCharging();
xprint("Current is too low, " + amp + " A. Retrying next interval. Waiting...");
}
} else {
startCharging();
retriesCounter = 0;
adjAmp(amp);
}
xsleep(settings.waitInterval*60*1000);
}
}
function exitNow(code, msg) {
xprint(msg);
xprint("Exit code: " + code);
// last attempt to stop charging
try {stopCharging()} catch (e) {};
exit(code);
}
if (device == null) {
throw new Error("No device is connected");
}
const getDevice = () => device;
const openTesla = () => sendAai({action:"openApp(Tesla);sleep(300)"});
var retriesRestart = 0;
while(true) {
try {
init();
start();
} catch (e) {
bypassExit(e);
retriesRestart++;
if (retriesRestart >= settings.retriesRestart) break;
xprint("Error found: " + e);
xprint("Restart the script");
}
}