UNPKG

iobroker.ems-esp

Version:
688 lines (556 loc) 26.8 kB
/* eslint-disable no-unused-vars */ /* eslint-disable no-empty */ //eslint-disable no-empty */ //"eslint-disable no-mixed-spaces-and-tabs" //"use strict"; //"esversion":"6"; /* * ems-esp adapter * */ const utils = require("@iobroker/adapter-core"); const adapterName = require("./package.json").name.split(".").pop(); const { default: axios } = require("axios"); const K = require("./lib/km200.js"); const E = require("./lib/ems.js"); const O = require("./lib/custom.js"); const S = require("./lib/syslog.js"); const F = require("./lib/functions.js"); const datafields = []; const adapterIntervals = {}; let adapter, unloaded = false; let db = "sql.0"; // ------------------------------------------------------------------------------------------------------------------------------------------------------------------- function startAdapter(options) { options = options || {}; Object.assign(options, { name: adapterName, unload: function (callback) { K.unload(true); E.unload(true); O.unload(true); unloaded = true; try { Object.keys(adapterIntervals).forEach(interval => adapter.log.debug("Interval cleared: " + adapterIntervals[interval])); Object.keys(adapterIntervals).forEach(interval => clearInterval(adapterIntervals[interval])); callback(); } catch (e) { callback(); } }, ready: function () { main(); }, stateChange: (id, state) => { if (state && !state.ack) { try { adapter.getObject(id, function (err, obj) { // check if state was writable if (obj != undefined) { if (obj.common.write) { if (obj.native.ems_km200 != null) K.state_change(id, state, obj); if (obj.native.ems_api == "raw") O.state_change(id, state, obj); if (obj.native.ems_api != null && obj.native.ems_api != "raw") E.state_change(id, state, obj); if (id == adapter.namespace + ".controls.active" && (state.val == false || state.val == 0)) control_reset(); } else adapter.log.warn("state is not writable: " + id); } }); } catch (e) { } } } }); adapter = new utils.Adapter(options); return adapter; } // If started as allInOne/compact mode => return function to create instance // @ts-ignore if (module && module.parent) { module.exports = startAdapter; } else { // or start the instance directly startAdapter(); } //--------- main --------------------------------------------------------------------------------------------------------- async function main() { // test for old db config try { if (adapter.config.db.trim() != "" && adapter.config.db_instance.trim() != "" && adapter.config.db.indexOf(".") < 1) { const db = adapter.config.db.trim() + "." + adapter.config.db_instance.trim(); const obj = await adapter.getForeignObjectAsync("system.adapter." + adapter.namespace); obj.native.db = db; await adapter.setForeignObjectAsync("system.adapter." + adapter.namespace, obj); adapter.log.info("old database parameters are updated to new version .... instance will restart"); } } catch (e) {} if (adapter.config.states_reorg) await delete_states_emsesp(); await adapter.setObjectNotExistsAsync("info.connection", { type: "state", common: { type: "boolean", name: "connected to gateways", role: "indicator.connected", read: true, write: false, def: false }, native: {} }); await adapter.setStateAsync("info.connection", false, true); await adapter.setObjectNotExistsAsync("info.connection_km200", { type: "state", common: { type: "boolean", name: "connected to km200 gateway", role: "indicator.connected", read: true, write: false, def: false }, native: {} }); await adapter.setStateAsync("info.connection_km200", null, true); await adapter.setObjectNotExistsAsync("info.connection_ems", { type: "state", common: { type: "boolean", name: "connected to ems-esp gateway", role: "indicator.connected", read: true, write: false, def: false }, native: {} }); await adapter.setStateAsync("info.connection_ems", null, true); db = adapter.config.database_instance; // Read own custom states if (adapter.config.db.trim() == "") { db = ""; if (adapter.config.statistics) adapter.log.info("no database instance selected for statistics - statistics partly disabled"); } else { db = adapter.config.db; // Test for InfluxDB V2 - Set warning if (adapter.config.db.substring(0, 8) == "influxdb") { const obj = await adapter.getForeignObjectAsync("system.adapter." + db); //adapter.log.info(JSON.stringify(obj)); let dbversion = ""; try { dbversion = obj.native.dbversion; } catch (e) { } let retention = 0; try { retention = obj.native.retention; } catch (e) { } let retdays; if (retention == 0) retdays = 999999; if (retention == -1) retdays = obj.native.customRetentionDuration; else retdays = retention / (24 * 60 * 60); let adapterversion = ""; try { adapterversion = obj.common.version; } catch (e) { } adapter.log.info("InfluxDB " + dbversion + " - Retention: " + retdays + " days --- Adapterversion: " + adapterversion); if (dbversion == "2.x" && adapterversion < "4.0.2" && adapter.config.recordings) { adapter.log.warn("************************************************************************************************"); adapter.log.warn("KM200 recordings with InfluxDB require adapter version >= 4.0.2"); adapter.log.warn("Database entries for recordings will be disabled"); adapter.log.warn("************************************************************************************************"); } } } if (!unloaded) adapterIntervals.status = setInterval(function () { info(); }, 10000); // 10 sec if (!unloaded && adapter.config.statistics && (adapter.config.km200_active || adapter.config.emsesp_active)) await init_statistics(); if (adapter.config.emsesp_active && !unloaded) await E.init(adapter, adapterIntervals); if (adapter.config.km200_active && !unloaded) await K.init(adapter, utils, adapterIntervals); if (adapter.config.emsesp_active && adapter.config.ems_custom && !unloaded) await O.init(adapter, adapterIntervals); if (adapter.config.syslog && !unloaded) await S.init(adapter, utils); if (!unloaded) adapter.subscribeStates("*"); if (!unloaded && adapter.config.statistics && (adapter.config.km200_active || adapter.config.emsesp_active)) { if (db != "") { await init_statistics2(); //read_statistics(); adapterIntervals.stat = setInterval(function () { read_statistics(); }, 300000); // 300 sec } } if (adapter.config.eff_active && !unloaded) adapterIntervals.eff = setInterval(function () { read_efficiency(); }, 60000); // 60 sec if (adapter.config.heatdemand == 1 || adapter.config.heatdemand == true) { await init_controls(); await heatdemand(); adapter.log.info("heat demand processing: polling every minute"); adapterIntervals.heatdemand = setInterval(function () { heatdemand(); }, 60000); // 60 sec } } //--------- functions --------------------------------------------------------------------------------------------------------- async function info() { try { const ems = (await adapter.getStateAsync("info.connection_ems")).val; const km200 = (await adapter.getStateAsync("info.connection_km200")).val; if (ems == null && km200 == true) adapter.setState("info.connection", true, true); if (ems == true && km200 == null) adapter.setState("info.connection", true, true); if (ems == true && km200 == true) adapter.setState("info.connection", true, true); if (ems == false || km200 == false) adapter.setState("info.connection", false, true); } catch (e) { } } async function enable_state(stateid, retention, interval) { const id = adapter.namespace + "." + stateid; try { const obj = await adapter.getObjectAsync(id); if (obj.common.custom == undefined) { adapter.sendTo(db, "enableHistory", { id: id, options: { changesOnly: true, debounce: 0, retention: retention, changesRelogInterval: interval, maxLength: 3, changesMinDelta: 0, aliasId: "" } }, function (result) { if (result.error) { adapter.log.error("enable history error " + stateid); } }); await adapter.delay(500); const state = await adapter.getState(stateid); if (state == null || state.val === undefined) await await adapter.setStateAsync(stateid, { ack: false, val: 0 }); else await await adapter.setStateAsync(stateid, { ack: true, val: state.val }); } } catch (e) { adapter.log.info("statistics state not found " + stateid + " - it is recommended to disable statistics"); } } async function init_controls() { try { await adapter.setObjectNotExistsAsync("controls.active", { type: "state", common: { type: "boolean", name: "heat demand control active", role: "value", read: true, write: true }, native: {} }); try { const active = (await adapter.getStateAsync("controls.active")).val; } catch (e) { await adapter.setStateAsync("controls.active", { ack: true, val: true }); } let value = 0; for (let i = 0; i < adapter.config.heatingcircuits.length; i++) { const state = adapter.config.heatingcircuits[i].hc + "."; value = parseFloat(adapter.config.heatingcircuits[i].weighton); control_state(state + "weighton", "number", "hc weight for switching on", value, true); value = parseFloat(adapter.config.heatingcircuits[i].weightoff); control_state(state + "weightoff", "number", "hc weight for switching off", value, true); await control_state(state + "weight", "number", "hc weight actual", 99, false); await control_state(state + "state", "string", "state for heating control", adapter.config.heatingcircuits[i].state, false); await control_state(state + "on", "string", "state value on", adapter.config.heatingcircuits[i].on, false); await control_state(state + "off", "string", "state value off", adapter.config.heatingcircuits[i].off, false); await control_state(state + "status", "boolean", "hc control status", true, false); if (adapter.config.heatingcircuits[i].savesettemp) await control_state(state + "savesettemp", "number", "saved settemp when switching off", -1, false); } for (let i = 0; i < adapter.config.thermostats.length; i++) { const state = adapter.config.thermostats[i].hc + "." + adapter.config.thermostats[i].room + "."; value = 0; try { const state1 = await adapter.getForeignStateAsync(adapter.config.thermostats[i].settemp); value = state1.val; } catch (e) { value = -99; } await control_state(state + "settemp", "number", "set temperature", value, false); try { const state1 = await adapter.getForeignStateAsync(adapter.config.thermostats[i].actualtemp); value = state1.val; } catch (e) { value = -99; } await control_state(state + "actualtemp", "number", "actual temperature", value, false); await control_state(state + "actualweight", "number", "actual weight", 0, false); value = parseFloat(adapter.config.thermostats[i].weight); await control_state(state + "weight", "number", "room weight for switching off", value, true); value = parseFloat(adapter.config.thermostats[i].deltam); await control_state(state + "deltam", "number", "minimum room delta temperature for switching off", value, true); } } catch (e) { } } async function control_state(state, type, name, value, write) { await adapter.setObjectAsync("controls." + state, { type: "state", common: { type: type, name: name, role: "value", read: true, write: write }, native: {} }); await adapter.setStateAsync("controls." + state, { ack: true, val: value }); } async function control_reset() { // heat demand control switched off - reset control states for hc's for (let i = 0; i < adapter.config.heatingcircuits.length; i++) { const hc = adapter.config.heatingcircuits[i].hc; const on = parseInt(adapter.config.heatingcircuits[i].on); adapter.log.debug("heat demand control switched off for " + hc + " --> reset to on control value: " + on); await adapter.setStateAsync(adapter.config.heatingcircuits[i].state, { ack: false, val: on }); await adapter.setStateAsync("controls." + hc + ".status", { ack: true, val: true }); } } async function heatdemand() { let w1 = 0, w2 = 0, w3 = 0, w4 = 0; try { if (adapter.config.thermostats.length == 0 || adapter.config.thermostats.length == undefined) return; } catch (e) { return; } for (let i = 0; i < adapter.config.thermostats.length; i++) { const state = "controls." + adapter.config.thermostats[i].hc + "." + adapter.config.thermostats[i].room + "."; let settemp = 0, acttemp = 0, savetemp = 0; try { const state1 = await adapter.getForeignStateAsync(adapter.config.thermostats[i].settemp); settemp = state1.val; } catch (e) { adapter.log.error(adapter.config.thermostats[i].settemp + ": heat demand thermostat wrongly defined"); return; } await adapter.setStateAsync(state + "settemp", { ack: true, val: settemp }); const state2 = "controls." + adapter.config.thermostats[i].hc + ".savesettemp"; try { const state3 = await adapter.getStateAsync(state2); savetemp = state3.val; if (savetemp > settemp) settemp = savetemp; } catch (e) { } try { const state4 = await adapter.getForeignStateAsync(adapter.config.thermostats[i].actualtemp); acttemp = state4.val; } catch (e) { adapter.log.error(adapter.config.thermostats[i].settemp + ": heat demand thermostat wrongly defined"); return; } await adapter.setStateAsync(state + "actualtemp", { ack: true, val: acttemp }); //const deltam = parseFloat(adapter.config.thermostats[i].deltam); let deltam = 0; try { deltam = parseFloat((await adapter.getStateAsync(state + "deltam")).val); } catch (e) { adapter.log.error(e); } const delta = settemp - acttemp; //const weight = parseInt(adapter.config.thermostats[i].weight); let weight = 0; try { weight = (await adapter.getStateAsync(state + "weight")).val; } catch (e) { adapter.log.error(e); } let actualweight = 0; actualweight = (await adapter.getStateAsync(state + "actualweight")).val; if (delta >= deltam) { await adapter.setStateAsync(state + "actualweight", { ack: true, val: weight }); if (adapter.config.thermostats[i].hc == "hc1") w1 += weight; if (adapter.config.thermostats[i].hc == "hc2") w2 += weight; if (adapter.config.thermostats[i].hc == "hc3") w3 += weight; if (adapter.config.thermostats[i].hc == "hc4") w4 += weight; } if (delta < deltam && delta >= 0 && actualweight > 0) { actualweight = weight; await adapter.setStateAsync(state + "actualweight", { ack: true, val: weight }); if (adapter.config.thermostats[i].hc == "hc1") w1 += weight; if (adapter.config.thermostats[i].hc == "hc2") w2 += weight; if (adapter.config.thermostats[i].hc == "hc3") w3 += weight; if (adapter.config.thermostats[i].hc == "hc4") w4 += weight; } if (delta < 0) { actualweight = 0; await adapter.setStateAsync(state + "actualweight", { ack: true, val: 0 }); } } let hd = false; try { const active = await adapter.getStateAsync("controls.active"); if (active.val == true || active.val == 1) hd = true; } catch (e) { } for (let i = 0; i < adapter.config.heatingcircuits.length; i++) { const hc = adapter.config.heatingcircuits[i].hc; const state = "controls." + hc + "."; let w = 99; if (hc == "hc1") w = w1; if (hc == "hc2") w = w2; if (hc == "hc3") w = w3; if (hc == "hc4") w = w4; await adapter.setStateAsync(state + "weight", { ack: true, val: w }); if (hd == true) { let state5, v, weighton, weightoff, status, von, voff; try { state5 = await adapter.getForeignStateAsync(adapter.config.heatingcircuits[i].state); v = state5.val; weighton = (await adapter.getStateAsync(state + "weighton")).val; weightoff = (await adapter.getStateAsync(state + "weightoff")).val; status = (await adapter.getStateAsync(state + "status")).val; } catch (e) { adapter.log.error(adapter.config.heatingcircuits[i].state + ": heat demand heating circuit wrongly defined"); return; } try { von = parseInt(adapter.config.heatingcircuits[i].on); voff = parseInt(adapter.config.heatingcircuits[i].off); if (w >= weighton && v == voff) { await adapter.setStateAsync(state + "status", { ack: true, val: true }); adapter.log.debug("new heat demand for " + hc + " --> switching on"); await adapter.setStateAsync(adapter.config.heatingcircuits[i].state, { ack: false, val: von }); if (adapter.config.heatingcircuits[i].savesettemp) { for (let ii = 0; ii < adapter.config.thermostats.length; ii++) { if (adapter.config.thermostats[ii].hc == hc) await adapter.setStateAsync(state + "savesettemp", { ack: true, val: 0 }); } } } if (w <= weightoff && v == von) { await adapter.setStateAsync(state + "status", { ack: true, val: false }); adapter.log.debug("no heat demand anymore for " + hc + " --> switching off"); await adapter.setStateAsync(adapter.config.heatingcircuits[i].state, { ack: false, val: voff }); if (adapter.config.heatingcircuits[i].savesettemp) { for (let ii = 0; ii < adapter.config.thermostats.length; ii++) { if (adapter.config.thermostats[ii].hc == hc) { let settemp; try { const state6 = await adapter.getForeignStateAsync(adapter.config.thermostats[ii].settemp); settemp = state6.val; } catch (e) { settemp = -1; } await adapter.setStateAsync(state + "savesettemp", { ack: true, val: settemp }); } } } } } catch (e) { adapter.log.warn("can not process heatdemand state: " + adapter.config.heatingcircuits[i].state); } } } } async function init_statistics() { try { await adapter.setObjectAsync("statistics.created", { type: "state", common: { type: "boolean", name: "Database (mySQL/InfluxDB) enabled for fields needed for statistics", unit: "", role: "value", read: true, write: true }, native: {} }); adapter.setObject("statistics.ems-read", { type: "state", common: { type: "number", name: "ems read time for polling", unit: "seconds", role: "value", read: true, write: true }, native: {} }); adapter.setObject("statistics.km200-read", { type: "state", common: { type: "number", name: "km200 read time for polling", unit: "seconds", role: "value", read: true, write: true }, native: {} }); adapter.setObject("statistics.boiler-on-1h", { type: "state", common: { type: "number", name: "percentage boiler on per hour", unit: "%", role: "value", read: true, write: true }, native: {} }); adapter.setObject("statistics.boiler-starts-1h", { type: "state", common: { type: "number", name: "boiler starts per hour", unit: "", role: "value", read: true, write: true }, native: {} }); adapter.setObject("statistics.boiler-starts-24h", { type: "state", common: { type: "number", name: "boiler starts per 24 hours", unit: "", role: "value", read: true, write: true }, native: {} }); adapter.setObject("statistics.efficiency", { type: "state", common: { type: "number", name: "boiler efficiency", unit: "%", role: "value", read: true, write: true }, native: {} }); await adapter.delay(500); } catch (e) { } } async function init_statistics2() { if (adapter.config.db.trim() == "") db = ""; else db = adapter.config.db; if (db == "") { adapter.log.error("no database instance selected for statistics"); } else { try { adapter.getState("statistics.created", function (err, state) { if (state == null || state.val === false) { adapter.setState("statistics.created", { ack: true, val: true }); } }); if (adapter.config.emsesp_active && adapter.config.km200_structure) enable_state("heatSources.hs1.burnstarts", 86400, 60); if (adapter.config.emsesp_active && adapter.config.km200_structure === false) enable_state("boiler.burnstarts", 86400, 60); if (adapter.config.km200_active) enable_state("heatSources.numberOfStarts", 86400, 60); //if (adapter.config.emsesp_active && adapter.config.km200_structure) enable_state("dhwCircuits.dhw1.wwstarts",86400,60); //if (adapter.config.emsesp_active && adapter.config.km200_structure === false) enable_state("boiler.wwstarts",86400,60); if (adapter.config.emsesp_active && adapter.config.km200_structure) enable_state("heatSources.hs1.burngas", 86400, 15); if (adapter.config.emsesp_active && adapter.config.km200_structure === false) enable_state("boiler.burngas", 86400, 15); if (adapter.config.km200_active) enable_state("heatSources.hs1.flameStatus", 86400, 15); await adapter.delay(500); } catch (e) { } } } async function read_efficiency() { if (!unloaded) { let value = 0, power = 0, temp = 0, tempr = 0, tempavg = 0, state; let m = adapter.config.modulation; let s = adapter.config.supplytemp; let r = adapter.config.returntemp; // re-initialize config parameters for previous km200 states - not to be used for ems-esp ! if (adapter.config.emsesp_active) { if (m == "heatSources.hs1.actualModulation") m = ""; if (s == "heatSources.actualSupplyTemperature") s = ""; if (r == "heatSources.returnTemperature") r = ""; } if (adapter.config.emsesp_active && adapter.config.km200_structure === false) { if (m.trim() == "") m = "boiler.curburnpow"; if (s.trim() == "") s = "boiler.curflowtemp"; if (r.trim() == "") r = "boiler.rettemp"; } if (adapter.config.emsesp_active && adapter.config.km200_structure) { if (m.trim() == "") m = "heatSources.hs1.curburnpow"; if (s.trim() == "") s = "heatSources.hs1.curflowtemp"; if (r.trim() == "") r = "heatSources.hs1.rettemp"; } if (adapter.config.emsesp_active === false && adapter.config.km200_active) { if (m.trim() == "") m = "heatSources.hs1.actualModulation"; if (s.trim() == "") s = "heatSources.actualSupplyTemperature"; if (r.trim() == "") r = "heatSources.returnTemperature"; } try { state = await adapter.getStateAsync(m); power = state.val; } catch (e) { power = 0; } if (power == 0) { try { state = await adapter.getForeignStateAsync(m); power = state.val; } catch (e) { power = 0; } } try { state = await adapter.getStateAsync(s); temp = state.val; } catch (e) { temp = 0; } if (temp == 0) { try { state = await adapter.getForeignStateAsync(s); temp = state.val; } catch (e) { temp = 0; } } try { state = await adapter.getStateAsync(r); tempr = state.val; } catch (e) { tempr = 0; } if (tempr == 0) { try { state = await adapter.getForeignStateAsync(r); tempr = state.val; } catch (e) { tempr = 0; } } if (power > 0) { if (tempr == 0) tempr = temp - 10; // when return flow temp is not available tempavg = (temp + tempr) / 2; if (tempavg > 60) value = adapter.config.eff70; else { if (tempavg > 55) value = adapter.config.eff60; else { if (tempavg > 50) value = adapter.config.eff55; else { if (tempavg > 45) value = adapter.config.eff50; else { if (tempavg > 40) value = adapter.config.eff45; else { if (tempavg > 35) value = adapter.config.eff40; else { if (tempavg > 30) value = adapter.config.eff35; else { if (tempavg > 25) value = adapter.config.eff30; else { if (tempavg > 20) value = adapter.config.eff25; else { if (tempavg <= 20) value = adapter.config.eff20; } } } } } } } } } } await adapter.setObjectNotExists("statistics.efficiency", { type: "state", common: { type: "number", name: "boiler efficiency", unit: "%", role: "value", read: true, write: true }, native: {} }); await adapter.setStateAsync("statistics.efficiency", { ack: true, val: value }); } } async function read_statistics() { if (!unloaded) { let id = ""; const end = Date.now(); if (adapter.config.km200_active) { id = adapter.namespace + ".heatSources.numberOfStarts"; } if (adapter.config.emsesp_active && adapter.config.km200_structure) { id = adapter.namespace + ".heatSources.hs1.burnstarts"; } if (adapter.config.emsesp_active && adapter.config.km200_structure === false) { id = adapter.namespace + ".boiler.burnstarts"; } stat(db, id, 1, "statistics.boiler-starts-1h"); stat(db, id, 24, "statistics.boiler-starts-24h"); /* if (adapter.config.emsesp_active) { id = adapter.namespace + ".boiler.wwstarts"; if (adapter.config.km200_structure) id = adapter.namespace + ".dhwCircuits.dhw1.wwstarts"; stat(db,id,1,"statistics.ww-starts-1h"); stat(db,id,24,"statistics.ww-starts-24h"); } */ if (adapter.config.km200_active) { id = adapter.namespace + ".heatSources.hs1.flameStatus"; } if (adapter.config.emsesp_active && adapter.config.km200_structure) { id = adapter.namespace + ".heatSources.hs1.burngas"; } if (adapter.config.emsesp_active && adapter.config.km200_structure === false) { id = adapter.namespace + ".boiler.burngas"; } try { adapter.sendTo(db, "getHistory", { id: id, options: { start: end - 3600000, end: end, aggregate: "none" } }, function (result) { if (!unloaded) { let count = 0; let on = 0; try { count = result.result.length; for (let i = 0; i < count; i++) { if (Math.round(result.result[i].val) == 1) on += 1; } } catch (e) { } let value = 0; if (count !== 0 && count != undefined) value = on / count * 100; value = Math.round(value * 10) / 10; adapter.setState("statistics.boiler-on-1h", { ack: true, val: value }); } }); } catch (e) { } } } async function stat(db, id, hour, state) { if (!unloaded && id != undefined) { const end = Date.now(); const intervall = hour * 3600000; try { adapter.sendTo(db, "getHistory", { id: id, options: { start: end - intervall, end: end, step: intervall, aggregate: "minmax" } }, function (result) { if (!unloaded) { let value = 0; let c = 0; try { c = result.result.length; } catch (e) { } if (c == 0 || c == 1) value = 0; try { let val1 = result.result[0].val; if (val1 == 0) val1 = result.result[1].val; value = Math.round(result.result[c - 1].val - val1); if (value < 0) value = 0; //adapter.log.info(id + " " +hour + ": " + c +" "+ + Math.round(val1)+" - " + Math.round(result.result[c-1].val) + " = " + value); } catch (e) { } adapter.setStateAsync(state, { ack: true, val: value }); } }); } catch (e) { adapter.log.error("error reading statistics records " + id); } } } async function delete_states_emsesp() { const pattern = adapter.namespace + ".*"; const states = await adapter.getStatesAsync(pattern); for (const id in states) { const obj = await adapter.getObjectAsync(id); if (obj.common.custom == undefined) await adapter.delObjectAsync(id); } }