node-red-contrib-tado-client
Version:
Tado web API client node for Node Red
329 lines (281 loc) • 11 kB
JavaScript
/**
* @param { import("node-red").NodeAPI } RED
*/
module.exports = function(RED) {
"use strict";
const { Tado } = require('node-tado-client');
/**
* Tado Token Node
*/
function TadoTokenNode(config) {
RED.nodes.createNode(this, config);
const node = this;
const tadoConfig = RED.nodes.getNode(config.configName);
tadoConfig.tado.setTokenCallback((token) => {
const msg = {
topic: "refresh_token",
payload: token.refresh_token,
};
node.send(msg);
});
node.on('close', function() {
tadoConfig.tado.setTokenCallback(undefined);
});
}
RED.nodes.registerType("tado-token", TadoTokenNode);
/**
* Config node
*/
function TadoConfigNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.tado = new Tado();
node.call = async function(method) {
const args = [...arguments].slice(1);
return node.tado[method](...args);
}
}
RED.nodes.registerType("tado-config", TadoConfigNode);
/**
* Tado node
*/
function TadoNode(config) {
RED.nodes.createNode(this, config);
const node = this;
[
"acMode",
"apiCall",
"childlock",
"configName",
"defaultTerminationTimeout",
"defaultTerminationType",
"deviceId",
"endDate",
"fanSpeed",
"geoTracking",
"homeId",
"horizontalSwing",
"month",
"name",
"openWindowMode",
"power",
"presence",
"reading",
"readingDate",
"readingId",
"reportDate",
"startDate",
"tariffId",
"tariffInCents",
"temperature",
"temperatureOffset",
"terminationTimeout",
"terminationType",
"timetableId",
"unit",
"verticalSwing",
"windowDetection",
"windowDetectionTimeout",
"year",
"zoneId",
].forEach((k) => {
node[k] = config[k];
});
node.tadoConfig = RED.nodes.getNode(node.configName);
if (!node.tadoConfig) {
node.status({ fill: "grey", shape: "ring", text: "unconfigured" });
node.error(RED._("tado.errors.missingconfig"));
return;
}
node.on("input", (msg, send, done) => {
const arg = (name) => {
return msg.hasOwnProperty(name) ? msg[name] : node[name];
}
const bool = (x) => {
return x === true || x === "true" || x === "True";
}
const apiCall = arg("apiCall");
const call = function() {
node.tadoConfig.call(apiCall, ...arguments).then(resp => {
node.status({ fill: "green", shape: "dot", text: apiCall });
msg.payload = resp;
if (send) {
send(msg);
} else {
node.send(msg);
}
if (done) {
done();
}
}).catch(err => {
node.status({ fill: "red", shape: "ring", text: "errored" });
if (done) {
if (err.response) {
done(err.response.data);
} else if (err.request) {
done('No response received from the server');
} else {
done(err.message);
}
} else {
node.error(err, msg); // Node-RED 0.x compatible
}
});
}
const authenticate = function() {
const refreshToken = msg["refreshToken"];
node.tadoConfig.tado.authenticate(refreshToken).then(([verify, token]) => {
if (verify) {
const msg = {
topic: "authenticating",
payload: {
uri: verify.verification_uri_complete,
interval: verify.interval,
expires_in: verify.expires_in,
},
};
if (send) {
send(msg);
} else {
node.send(msg);
}
}
token.then(() => {
node.status({ fill: "green", shape: "dot", text: "Authenticated" });
if (done) {
done();
}
}).catch((err) => {
node.status({ fill: "red", shape: "ring", text: "Errored" });
if (done) {
done(err);
} else {
node.error(err, msg);
}
});
}).catch((err) => {
node.status({ fill: "red", shape: "ring", text: "Errored" });
if (done) {
done(err);
} else {
node.error(err, msg);
}
});
}
msg.topic = apiCall;
switch (apiCall) {
case "authenticate":
authenticate();
break;
case "getMe":
case "getRatelimit":
call();
break;
case "getAirComfort":
case "getAirComfortDetailed":
case "getDevices":
case "getEnergyIQMeterReadings":
case "getEnergyIQTariff":
case "getHeatingCircuits":
case "getHome":
case "getInstallations":
case "getMobileDevices":
case "getState":
case "getUsers":
case "getWeather":
case "getZones":
case "getZoneStates":
case "isAnyoneAtHome":
case "updatePresence":
call(arg("homeId"));
break;
case "clearZoneOverlay":
case "getAwayConfiguration":
case "getTimeTables":
case "getZoneCapabilities":
case "getZoneControl":
case "getZoneDefaultOverlay":
case "getZoneOverlay":
case "getZoneState":
call(arg("homeId"), arg("zoneId"));
break;
case "getDeviceTemperatureOffset":
case "identifyDevice":
call(arg("deviceId"));
break;
case "getMobileDevice":
case "getMobileDeviceSettings":
call(arg("homeId"), arg("deviceId"));
break;
case "setGeoTracking":
call(arg("homeId"), arg("deviceId"), bool(arg("geoTracking")));
break;
case "getZoneDayReport":
call(arg("homeId"), arg("zoneId"), arg("reportDate"));
break;
case "getTimeTable":
call(arg("homeId"), arg("zoneId"), arg("timetableId"));
break;
case "setZoneDefaultOverlay": {
const type = arg("defaultTerminationType");
const termination = {
terminationCondition: {
type,
durationInSeconds: type == "TIMER" ? arg("defaultTerminationTimeout") : undefined,
},
};
call(arg("homeId"), arg("zoneId"), termination);
break;
}
case "setZoneOverlay": {
const type = arg("terminationType");
const termination = type === "timer" ? arg("terminationTimeout") : type;
call(arg("homeId"), arg("zoneId"), arg("power"), arg("temperature"), termination, arg("fanSpeed"), arg("acMode"), arg("verticalSwing"), arg("horizontalSwing"));
break;
}
case "setDeviceTemperatureOffset":
call(arg("deviceId"), arg("temperatureOffset"));
break;
case "setChildlock":
call(arg("deviceId"), arg("childlock"));
break;
case "setPresence":
call(arg("homeId"), arg("presence"));
break;
case "setWindowDetection":
call(arg("homeId"), arg("zoneId"), bool(arg("windowDetection")), arg("windowDetectionTimeout"));
break;
case "setOpenWindowMode":
call(arg("homeId"), arg("zoneId"), bool(arg("openWindowMode")));
break;
case "addEnergyIQTariff":
call(arg("homeId"), arg("unit"), arg("startDate"), arg("endDate"), parseFloat(arg("tariffInCents").toFixed(2)));
break;
case "getEnergyIQOverview":
call(arg("homeId"), arg("month"), arg("year"));
break;
case "updateEnergyIQTariff":
call(arg("homeId"), arg("tariffId"), arg("unit"), arg("startDate"), arg("endDate"), parseFloat(arg("tariffInCents").toFixed(2)));
break;
case "addEnergyIQMeterReading":
call(arg("homeId"), arg("readingDate"), parseInt(arg("reading"), 10));
break;
case "deleteEnergyIQMeterReading":
call(arg("homeId"), arg("readingId"));
break;
default:
if (typeof node.tadoConfig.tado[apiCall] === "function") {
if (Array.isArray(msg.payload)) {
call(...msg.payload);
} else {
call(msg.payload);
}
} else {
node.error(`invalid apiCall "${apiCall}"`);
}
break;
}
});
}
RED.nodes.registerType("tado", TadoNode);
}