iobroker.heizoel24-mex
Version:
Read in data from MEX
561 lines (506 loc) • 26.4 kB
JavaScript
"use strict";
/*
* Created with @iobroker/create-adapter v2.6.2
*/
const utils = require("@iobroker/adapter-core");
const axios = require("axios");
const mqtt = require("mqtt");
const fs = require("fs");
axios.defaults.timeout = 2000;
class Heizoel24Mex extends utils.Adapter {
constructor(options) {
super({
...options,
name: "heizoel24-mex",
});
this.on("ready", this.onReady.bind(this));
this.on("unload", this.onUnload.bind(this));
this.Items = [
{id : "DataReceived", role : "indicator", unit : "", type : "boolean"},
{id : "SensorId", role : "value", unit : "", type : "number" },
{id : "IsMain", role : "indicator", unit : "", type : "boolean"},
{id : "CurrentVolumePercentage", role : "level", unit : "%", type : "number" },
{id : "CurrentVolume", role : "level", unit : "L", type : "number" },
{id : "NotifyAtLowLevel", role : "level.color.red", unit : "%", type : "number" },
{id : "NotifyAtAlmostEmptyLevel", role : "level.color.red", unit : "%", type : "number" },
{id : "NotificationsEnabled", role : "indicator", unit : "", type : "boolean"},
{id : "Usage", role : "value", unit : "L/Day", type : "number" },
{id : "RemainsUntil", role : "date", unit : "", type : "string" },
{id : "MaxVolume", role : "level.max", unit : "L", type : "number" },
{id : "ZipCode", role : "value", unit : "", type : "string" },
{id : "MexName", role : "value", unit : "", type : "string" },
{id : "LastMeasurementTimeStamp", role : "date", unit : "", type : "string" },
{id : "LastMeasurementWithDifferentValue", role : "date", unit : "", type : "string" },
{id : "BatteryPercentage", role : "value.battery", unit : "%", type : "number" },
{id : "Battery", role : "value.battery", unit : "V", type : "number" },
{id : "LitresPerCentimeter", role : "value", unit : "L/cm", type : "number" },
{id : "LastMeasurementWasSuccessfully", role : "indicator", unit : "", type : "boolean"},
{id : "SensorTypeId", role : "value", unit : "", type : "number" },
{id : "HasMeasurements", role : "indicator", unit : "", type : "boolean"},
{id : "MeasuredDaysCount", role : "value", unit : "Days", type : "number" },
{id : "LastMeasurementWasTooHigh", role : "indicator", unit : "", type : "boolean"},
{id : "YearlyOilUsage", role : "value", unit : "L", type : "number" },
{id : "RemainingDays", role : "value", unit : "Days", type : "number" },
{id : "LastOrderPrice", role : "value", unit : "€|CHF", type : "number" },
{id : "ResultCode", role : "value", unit : "", type : "boolean"},
{id : "ResultMessage", role : "value", unit : "", type : "boolean"},
];
this.PricingForecast = [
{id : "LastOrderPrice", role : "value", unit : "€|CHF/100L", type : "number" },
{id : "PriceComparedToYesterdayPercentage", role : "value", unit : "%", type : "number" },
{id : "PriceForecastPercentage", role : "value", unit : "%", type : "number" },
{id : "HasMultipleMexDevices", role : "indicator", unit : "", type : "boolean"},
{id : "DashboardViewMode", role : "value", unit : "", type : "number" },
{id : "ShowComparedToYesterday", role : "indicator", unit : "", type : "boolean"},
{id : "ShowForecast", role : "indicator", unit : "", type : "boolean"},
{id : "ResultCode", role : "value", unit : "", type : "boolean"},
{id : "ResultMessage", role : "value", unit : "", type : "boolean"},
];
this.RemainsUntilCombined = [
{id : "MonthAndYear", role : "value", unit : "", type : "string" },
{id : "RemainsValue", role : "value", unit : "", type : "string" },
{id : "RemainsUnit", role : "value", unit : "", type : "string" },
];
this.contentItems = [];
this.contentPricingForecast = [];
this.contentRemainsUntilCombined = [];
}
async onReady() {
const username = this.config.username;
const passwort = this.config.passwort;
const broker_address = this.config.broker_address;
const mqtt_active = this.config.mqtt_active;
const mqtt_user = this.config.mqtt_user;
const mqtt_pass = this.config.mqtt_pass;
const mqtt_port = this.config.mqtt_port;
const sensor_in = this.config.sensor_id;
let sensor_id = 1;
const storeJson = this.config.storeJson;
const storeDir = this.config.storeDir;
if (Number(sensor_in)) {
sensor_id = parseInt(sensor_in);
if (sensor_id < 1 || sensor_id > 20) {
this.log.error("Sensor ID has no value between 1 and 20");
this.terminate ? this.terminate("Sensor ID has no value between 1 and 20", 0) : process.exit(0);
}
} else {
this.log.error("Sensor ID has no valid value");
this.terminate ? this.terminate("Sensor ID has no valid value", 0) : process.exit(0);
}
this.log.debug("Sensor ID is " + sensor_id);
if (username.trim().length === 0 || passwort.trim().length === 0) {
this.log.error("User email and/or user password empty - please check instance configuration");
this.terminate ? this.terminate("User email and/or user password empty - please check instance configuration", 0) : process.exit(0);
}
let client = null;
if (mqtt_active) {
if (broker_address.trim().length === 0 || broker_address == "0.0.0.0") {
this.log.error("MQTT IP address is empty - please check instance configuration");
this.terminate ? this.terminate("MQTT IP address is empty - please check instance configuration", 0) : process.exit(0);
}
client = mqtt.connect(`mqtt://${broker_address}:${mqtt_port}`, {
connectTimeout: 4000,
username: mqtt_user,
password: mqtt_pass
});
}
try {
const instObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
if (instObj && instObj.common && instObj.common.schedule && instObj.common.schedule === "0 */3 * * *") {
instObj.common.schedule = `${Math.floor(Math.random() * 60)} */3 * * *`;
this.log.info(`Default schedule found and adjusted to spread calls better over the full hour!`);
await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, instObj);
this.terminate ? this.terminate() : process.exit(0);
return;
}
} catch (err) {
this.log.error(`Could not check or adjust the schedule: ${err.message}`);
}
this.log.debug("MQTT active: " + mqtt_active);
this.log.debug("MQTT port: " + mqtt_port);
const dataReceived = await this.main(client, username, passwort, mqtt_active, sensor_id, storeJson, storeDir);
if (dataReceived === true) {
await this.setObjectNotExistsAsync(sensor_id.toString(), {
type: "device",
common: {
name: ""
},
native: {},
});
// Items
await this.setObjectNotExistsAsync(sensor_id.toString() + ".Items", {
type: "channel",
common: {
name: "Items"
},
native: {},
});
for (let n = 0; n < this.Items.length; n++) {
// @ts-ignore
await this.setObjectNotExistsAsync(sensor_id.toString() + ".Items." + this.Items[n].id, {
type: "state",
common: {
name: this.Items[n].id,
type: this.Items[n].type,
role: this.Items[n].role,
unit: this.Items[n].unit,
read: true,
write: false
},
native: {},
});
await this.setStateAsync(sensor_id.toString() + ".Items." + this.Items[n].id, { val: this.contentItems[n], ack: true });
}
// PricingForecast
await this.setObjectNotExistsAsync(sensor_id.toString() + ".PricingForecast", {
type: "channel",
common: {
name: "PricingForecast"
},
native: {},
});
for (let n = 0; n < this.PricingForecast.length; n++) {
// @ts-ignore
await this.setObjectNotExistsAsync(sensor_id.toString() + ".PricingForecast." + this.PricingForecast[n].id, {
type: "state",
common: {
name: this.PricingForecast[n].name,
type: this.PricingForecast[n].type,
role: this.PricingForecast[n].role,
unit: this.PricingForecast[n].unit,
read: true,
write: false
},
native: {},
});
if (this.contentPricingForecast[n] == false &&
(this.PricingForecast[n].id == "PriceComparedToYesterdayPercentage" ||
this.PricingForecast[n].id == "PriceForecastPercentage")) {
this.log.debug(this.PricingForecast[n].id + " omitted, because it's false");
} else {
await this.setStateAsync(sensor_id.toString() + ".PricingForecast." + this.PricingForecast[n].id, { val: this.contentPricingForecast[n], ack: true });
}
}
// RemainsUntilCombined
await this.setObjectNotExistsAsync(sensor_id.toString() + ".RemainsUntilCombined", {
type: "channel",
common: {
name: "RemainsUntilCombined"
},
native: {},
});
for (let n = 0; n < this.RemainsUntilCombined.length; n++) {
// @ts-ignore
await this.setObjectNotExistsAsync(sensor_id.toString() + ".RemainsUntilCombined." + this.RemainsUntilCombined[n].id, {
type: "state",
common: {
name: this.RemainsUntilCombined[n].id,
type: this.RemainsUntilCombined[n].type,
role: this.RemainsUntilCombined[n].role,
unit: this.RemainsUntilCombined[n].unit,
read: true,
write: false
},
native: {},
});
await this.setStateAsync(sensor_id.toString() + ".RemainsUntilCombined." + this.RemainsUntilCombined[n].id, { val: this.contentRemainsUntilCombined[n], ack: true });
}
} else {
await this.setObjectNotExistsAsync(sensor_id.toString() + ".Items." + this.Items[0].id, {
type: "state",
common: {
name: this.Items[0].id,
type: "boolean",
role: "indicator",
read: true,
write: false
},
native: {},
});
await this.setStateAsync(sensor_id.toString() + ".Items." + this.Items[0].id, { val: false, ack: true });
this.log.error("No data received");
this.terminate ? this.terminate("No data received", 1) : process.exit(1);
}
// Finished - stopping instance
this.terminate ? this.terminate("Everything done. Going to terminate till next schedule", 0) : process.exit(0);
}
async sendMqtt(sensor_id, mqtt_active, client, topic, wert) {
if (mqtt_active) {
client.publish("MEX/"+ sensor_id.toString() + "/" + topic, wert);
}
}
async login(username, passwort) {
this.log.debug("Login in...");
this.username = username;
this.passwort = passwort;
this.url = "https://api.heizoel24.de/app/api/app/Login";
this.newHeaders = { "Content-type": "application/json" };
try {
const reply = await axios.post(this.url, { "Password": this.passwort, "Username": this.username }, { headers: this.newHeaders });
if (reply.status === 200) {
this.log.debug("Login OK");
const reply_json = reply.data;
if (reply_json["ResultCode"] === 0) {
const session_id = reply_json["SessionId"];
this.log.debug("Session ID: " + session_id);
this.log.debug("Logged in");
return [true, session_id];
} else {
this.log.error("ResultCode not 0. No session ID received!");
}
}
} catch (error) {
this.log.error("Login failed! Error: " + error.response.status);
this.terminate ? this.terminate("Login failed!", 1) : process.exit(1);
}
return [false, ""];
}
async measurement(sensor_id, session_id) {
this.log.debug("Get future residual oil levels...");
const url = `https://api.heizoel24.de/app/api/app/measurement/CalculateRemaining/${session_id}/${sensor_id}/False`;
try {
const reply = await axios.get(url);
if (reply.status === 200) {
this.log.debug("Future residual oil levels received");
return reply.data;
} else {
this.log.debug("Heizoel24 residual oil levels > Status Code: " + reply.status);
return "error";
}
} catch (error) {
this.log.error("Error fetching data: " + error.response.status);
return "error";
}
}
async mex(username, passwort, sensor_id) {
const [login_status, session_id] = await this.login(username, passwort);
if (!login_status) {
return [false, false, false];
}
this.log.debug("Refresh sensor data cache...");
const url1 = `https://api.heizoel24.de/app/api/app/GetDashboardData/${session_id}/2/${sensor_id}/False`;
const url2 = `https://api.heizoel24.de/app/api/app/GetOilUsage/${session_id}/False`;
try {
const reply1 = await axios.get(url1);
if (reply1.status !== 200) {
this.log.error("Dashboard data unsuccessful.");
return [false, false, false];
}
let reply2 = false;
try {
const r2 = await axios.get(url2);
if (r2.status === 200) {
reply2 = r2.data;
} else {
this.log.warn("Oil consumption could not be loaded (status ≠ 200).");
}
} catch (e) {
this.log.warn("Error retrieving oil consumption: " + (e?.response?.status || e.message));
}
this.log.debug("Dashboard data successfully received.");
return [reply1.data, reply2, session_id];
} catch (error) {
const status = error?.response?.status || "unknown";
this.log.error("Error retrieving dashboard data. Status: " + status);
this.terminate ? this.terminate("Error retrieving dashboard data!", 1) : process.exit(1);
}
return [false, false, false];
}
async main(client, username, passwort, mqtt_active, sensor_id, storeJson, storeDir) {
const [daten, oil_usage, session_id] = await this.mex(username, passwort, sensor_id);
if (daten === false) {
this.log.error("No data received");
if (mqtt_active) {
await this.sendMqtt(sensor_id, mqtt_active, client, "Items/DataReceived", "false");
client.end();
}
return false;
}
const datenJson = daten;
for (let n = 0; n < this.PricingForecast.length; n++) {
const result = datenJson[this.PricingForecast[n].id] || false;
this.contentPricingForecast[n] = result;
if (mqtt_active) {
if (this.contentPricingForecast[n] == false &&
(this.PricingForecast[n].id == "PriceComparedToYesterdayPercentage" ||
this.PricingForecast[n].id == "PriceForecastPercentage")) {
this.log.debug(this.PricingForecast[n].id + " omitted, because it's false");
} else {
await this.sendMqtt(sensor_id, mqtt_active, client, "PricingForecast/" + this.PricingForecast[n].id, result.toString());
}
}
this.log.debug("PricingForecast: " + this.PricingForecast[n].id + ": " + result.toString() + ", unit: " + this.PricingForecast[n].unit + ", Typ: " + (typeof datenJson[this.PricingForecast[n].id]));
}
const items = datenJson["Items"][0];
for (let n = 1; n < this.Items.length; n++) {
const result = items[this.Items[n].id] || false;
this.contentItems[n] = result;
if (mqtt_active) {
await this.sendMqtt(sensor_id, mqtt_active, client, "Items/" + this.Items[n].id, result.toString());
}
this.log.debug("Items: " + this.Items[n].id + ": " + result.toString() + ", unit: " + this.Items[n].unit + ", Typ: " + (typeof result));
}
if (mqtt_active) {
await this.sendMqtt(sensor_id, mqtt_active, client, "Items/DataReceived", "true");
}
this.contentItems[0] = true;
const daten3 = items["RemainsUntilCombined"];
for (let n = 0; n < this.RemainsUntilCombined.length; n++) {
let result = "empty";
try {
result = daten3[this.RemainsUntilCombined[n].id] || false;
} catch (error) {
this.log.debug("RemainsUntilCombined no data found");
}
this.contentRemainsUntilCombined[n] = result;
try {
if (mqtt_active) {
await this.sendMqtt(sensor_id, mqtt_active, client, "RemainsUntilCombined/" + this.RemainsUntilCombined[n].id, result.toString());
}
this.log.debug("RemainsUntilCombined: " + this.RemainsUntilCombined[n].id + ": " + result.toString() + ", unit: " + this.RemainsUntilCombined[n].unit + ", Typ: " + (typeof daten3[this.RemainsUntilCombined[n].id]));
} catch (error) {
this.log.debug("RemainsUntilCombined no data found");
}
}
const sensorId = this.contentItems[1]; // get SensorId
let zukunftsDaten = await this.measurement(sensorId, session_id);
if (zukunftsDaten === "error") {
this.log.debug("Error. No data received.");
return false;
}
if (storeJson) {
try {
const json = JSON.stringify(zukunftsDaten, null, 4);
fs.writeFileSync(storeDir + "/CalculatedRemaining.json", json, "utf8");
} catch (error) {
this.log.warn("Json file not saved. Does ioBroker have write permissions in the specified folder?");
}
if (oil_usage) {
try {
const oilJson = JSON.stringify(oil_usage, null, 4);
fs.writeFileSync(storeDir + "/OilUsage.json", oilJson, "utf8");
} catch (error) {
this.log.warn("OilUsage file not saved. Does ioBroker have write permissions?");
}
} else {
this.log.warn("OilUsage data not available – file was not saved.");
}
}
await this.setObjectNotExistsAsync(sensor_id.toString() + ".CalculatedRemaining", {
type: "channel",
common: {
name: "CalculatedRemaining"
},
native: {},
});
zukunftsDaten = zukunftsDaten["ConsumptionCurveResult"];
let jsonData = "[\n";
let unixTimestamp = 0;
let key = "";
let datum = "";
let n = 0;
for (key in zukunftsDaten) {
datum = key.split("T")[0];
if (n % 14 == 0) { // Only every 14 days
if (mqtt_active) {
await this.sendMqtt(sensor_id, mqtt_active, client, "CalculatedRemaining/Today+" + String(n).padStart(4, "0") + " Days.Date", datum);
await this.sendMqtt(sensor_id, mqtt_active, client, "CalculatedRemaining/Today+" + String(n).padStart(4, "0") + " Days.Liter", zukunftsDaten[key].toString());
}
unixTimestamp = new Date(datum).getTime() / 1000;
jsonData = jsonData + ' {"ts": ' + unixTimestamp + ', "val": ' + zukunftsDaten[key].toString() + "},\n";
}
n++;
}
if (mqtt_active) {
await this.sendMqtt(sensor_id, mqtt_active, client, "CalculatedRemaining/Today+" + String(n).padStart(4, "0") + " Days.Date", datum);
try {
await this.sendMqtt(sensor_id, mqtt_active, client, "CalculatedRemaining/Today+" + String(n).padStart(4, "0") + " Days.Liter", zukunftsDaten[key].toString());
} catch (error) {
this.log.debug("CalculatedRemaining is empty");
}
}
this.log.debug(n.toString() + " future days saved");
unixTimestamp = new Date(datum).getTime() / 1000;
try {
jsonData = jsonData + ' {"ts": ' + unixTimestamp + ', "val": ' + zukunftsDaten[key].toString() + "}\n]";
} catch (error) {
this.log.debug("CalculatedRemaining is empty");
}
await this.setObjectNotExistsAsync(sensor_id.toString() + ".CalculatedRemaining.JsonForEcharts", {
type: "state",
common: {
name: "OilLevelsInTheFuture",
type: "string",
role: "value",
unit: "",
read: true,
write: false
},
native: {},
});
await this.setStateAsync(sensor_id.toString() + ".CalculatedRemaining.JsonForEcharts", { val: jsonData, ack: true });
// OilUsage verarbeiten und senden
if (oil_usage && oil_usage["Values"]) {
const oilUsage = oil_usage["Values"];
let oilJsonData = "[\n";
let count = 0;
for (const key in oilUsage) {
const datum = key.split("T")[0];
const liter = oilUsage[key];
const unixTimestamp = new Date(datum).getTime() / 1000;
if (mqtt_active) {
await this.sendMqtt(sensor_id, mqtt_active, client, "OilUsage/" + datum, liter + " Ltr.");
}
oilJsonData += ` {"ts": ${unixTimestamp}, "val": ${liter}},\n`;
count++;
}
this.log.debug(count.toString() + " OilUsage-Einträge verarbeitet");
// Letztes Komma ersetzen
if (count > 0) {
oilJsonData = oilJsonData.trimEnd().replace(/,$/, "") + "\n]";
} else {
oilJsonData = "[]";
}
// ioBroker-Datenpunkt erstellen
await this.setObjectNotExistsAsync(sensor_id.toString() + ".OilUsage.JsonForEcharts", {
type: "state",
common: {
name: "OilUsage over time",
type: "string",
role: "value",
unit: "",
read: true,
write: false
},
native: {},
});
// Wert setzen
await this.setStateAsync(sensor_id.toString() + ".OilUsage.JsonForEcharts", { val: oilJsonData, ack: true });
// Datei speichern, falls gewünscht
if (storeJson) {
try {
const json = JSON.stringify(oil_usage, null, 4);
fs.writeFileSync(storeDir + "/OilUsage.json", json, "utf8");
} catch (error) {
this.log.warn("OilUsage.json konnte nicht gespeichert werden. Schreibrechte prüfen?");
}
}
}
if (mqtt_active) {
client.end();
}
return true;
}
onUnload(callback) {
try {
callback();
} catch (e) {
callback();
}
}
}
if (require.main !== module) {
module.exports = (options) => new Heizoel24Mex(options);
} else {
new Heizoel24Mex();
}