UNPKG

iobroker.ems-esp

Version:
1,306 lines (1,189 loc) 44.3 kB
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-empty */ const Rijndael = require('rijndael-js'); const crypto = require('node:crypto'); const fs = require('node:fs'); const { default: axios } = require('axios'); const F = require('./functions.js'); const km200_crypt_md5_salt = new Uint8Array([ 0x86, 0x78, 0x45, 0xe9, 0x7c, 0x4e, 0x29, 0xdc, 0xe5, 0x22, 0xb9, 0xa7, 0xd3, 0xa3, 0xe0, 0x7b, 0x15, 0x2b, 0xff, 0xad, 0xdd, 0xbe, 0xd7, 0xf5, 0xff, 0xd8, 0x42, 0xe9, 0x89, 0x5a, 0xd1, 0xe4, ]); //let datafields = []; let datafields; let km200_server, km200_gatewaypassword, km200_privatepassword, km200_key, km200_aeskey, cipher, km200_polling = 300; // -------- energy recordings parameters ------------------------------------ let db = 'sql.0', database = 'iobroker', recordings = false; let sum_month = 0; let r_multi = 1, r_month = true; // ------------------------------------------------------------------------------------------------------------------------------------------------------------------- // Startup / Initialisation: let unloaded = false; let utils; let adapter; const init = async function (a, u, i) { adapter = a; utils = u; //aliveState = "system.adapter."+adapter.namespace + ".alive"; adapter.setState('info.connection_km200', false, true); km200_server = adapter.config.km200_ip; if (km200_server.substr(0, 7) != 'http://') { km200_server = `http://${km200_server}`; } km200_polling = adapter.config.km200_polling; if (km200_polling < 90) { km200_polling = 90; } km200_gatewaypassword = adapter.config.gateway_pw.trim(); km200_privatepassword = adapter.config.private_pw.trim(); recordings = adapter.config.recordings; r_multi = adapter.config.r_multi; r_month = adapter.config.r_month; if (adapter.config.db.trim() == '') { db = ''; } else { db = adapter.config.db; } if (db == '') { adapter.log.info('KM200 no database instance selected for recordings'); } if (adapter.config.db.substring(0, 8) == 'influxdb') { const obj = await adapter.getForeignObjectAsync(`system.adapter.${db}`); let adapterversion = ''; try { adapterversion = obj.common.version; } catch {} let dbversion = ''; try { dbversion = obj.native.dbversion; } catch {} if (dbversion == '2.x' && adapterversion < '4.0.2' && adapter.config.recordings) { db = ''; } } if (db != '') { const state = await adapter.getForeignStateAsync(`system.adapter.${db}.connected`); if (state == undefined) { adapter.log.warn(`KM200 database instance ${db}for recordings not existing`); db = ''; } else if (state.val == false) { adapter.log.warn(`KM200 database instance ${db} for recordings not active`); db = ''; } try { const obj = await adapter.getForeignObjectAsync(`system.adapter.${db}`); database = obj.native.dbname; } catch (e) { adapter.log.error("KM200 can't read database name"); database = 'iobroker'; } } km200_key = km200_getAccesskey(km200_gatewaypassword, km200_privatepassword); km200_aeskey = Buffer.from(km200_key, 'hex'); cipher = new Rijndael(km200_aeskey, 'ecb'); const active = await km200_test(); if (active) { adapter.setState('info.connection_km200', true, true); // ------------------------------------------------------------------------------------------------------------------------------------------------------------------- // Read csv-file: const dataDir = utils.getAbsoluteDefaultDataDir(); // /opt/iobroker/iobroker-data await fs.promises.mkdir(`${dataDir}files/ems-esp.${adapter.instance}`, { recursive: true }); // old directory to be deleted if existant try { await fs.promises.rmdir(`${dataDir}ems-esp`, { recursive: true }); } catch {} const fn = `${dataDir}files/ems-esp.${adapter.instance}/${adapter.config.control_file}`; let data = ''; if (adapter.config.control_file !== '' && adapter.config.control_file !== '*') { try { data = fs.readFileSync(fn, 'utf8'); } catch (err) { adapter.log.info(err); } } if (adapter.config.control_file !== '*') { datafields = read_file(data); if (adapter.config.states_reorg) { await init_states_km200(datafields); } else { km200_read(datafields); } } else { datafields = await read_km200structure(); const fnw = `${dataDir}files/ems-esp.${adapter.instance}/km200.csv`; write_file(fnw, datafields); await init_states_km200(datafields); } if (!unloaded) { adapter.log.info(`KM200: polling every ${km200_polling} secs`); i.km200 = adapter.setInterval(function () { km200_read(datafields); }, km200_polling * 1000); // 90 sec } if (adapter.config.recordings && !unloaded) { await initrecs(datafields); await km200_recordings(datafields); adapter.log.info('KM200 recordings: polling every hour'); i.recordings = adapter.setInterval(function () { km200_recordings(datafields); }, 3600000); // 1 hour = 3600 secs } } return i; }; async function km200_test() { try { const data = await km200_get('gateway'); if (data == undefined || data == '' || data == ' ') { adapter.log.error( 'error reading KM200 gateway information (wrong passwords please re-enter) - stop km200 read', ); //adapter.log.error( // 'encryption for private password has changed with version >= 5.0.0. The private password needs to entered again', //); return false; } } catch (error) { adapter.log.error( `error reading KM200 gateway (wrong ip address) - stop KM200 read: ${km200_server} ${error} `, ); return false; } return true; } async function init_states_km200(datafields) { if (unloaded) { return; } adapter.log.info('start initializing KM200 states'); for (let i = 1; i < datafields.length; i++) { if (unloaded) { break; } const r = datafields[i]; //adapter.log.info(JSON.stringify(r)); if (r.km200 !== '') { let o; try { o = await km200_get(r.km200); } catch (error) { adapter.log.warn(`http KM200 read error (gateway not responding):${r.km200}`); } if (o != undefined) { try { const obj1 = km200_obj(r.km200, o); obj1._id = r.km200; obj1.common.name = `km200:${r.km200}`; //obj1.native.source = "km200"; obj1.native.ems_km200 = r.km200; //if (o.type != "yRecording") await adapter.setObjectNotExistsAsync(obj1._id, obj1); if (o.type != 'yRecording') { await adapter.setObjectNotExistsAsync(obj1._id, obj1); } else { if (adapter.config.recordings) { await adapter.setObjectNotExistsAsync(obj1._id, obj1); } } F.enums(adapter, obj1._id); let val = o.value; if (o.type == 'stringValue' && o.allowedValues != undefined) { val = o.allowedValues.indexOf(o.value); } if (o.type == 'switchProgram' && o.switchPoints != undefined) { val = JSON.stringify(o.switchPoints); } if (o.type == 'arrayData' && o.values != undefined) { val = JSON.stringify(o.values); } if (o.type == 'errorList' && o.values != undefined) { val = JSON.stringify(o.values); } if (o.type == 'systeminfo' && o.values != undefined) { val = JSON.stringify(o.values); } if (o.type == 'yRecording') { val = ''; } //await adapter.setStateChangedAsync(r.km200, {ack: true, val: val}); if (o.type != 'yRecording') { await adapter.setStateAsync(r.km200, { ack: true, val: val }); } else if (adapter.config.recordings) { await adapter.setStateAsync(r.km200, { ack: true, val: val }); } } catch (error) { adapter.log.info('initializing KM200 states interrupted'); unloaded = true; break; } } } } adapter.log.info('end of initializing KM200 states'); } async function km200_read(result) { if (unloaded) { return; } const t1 = new Date().getTime(); for (let i = 1; i < result.length; i++) { if (unloaded) { break; } if (result[i].km200 != '' && result[i].type != 'yRecording') { let body; try { body = await km200_get(result[i].km200); adapter.setState('info.connection_km200', true, true); } catch (error) { adapter.log.debug(`KM200 get error state:${result[i].km200}`); adapter.setState('info.connection_km200', false, true); } if (body != undefined) { try { let val = body.value; if (body.type == 'stringValue' && body.allowedValues != undefined) { val = body.allowedValues.indexOf(body.value); } if (body.type == 'switchProgram' && body.switchPoints != undefined) { val = JSON.stringify(body.switchPoints); } if (body.type == 'arrayData' && body.values != undefined) { val = JSON.stringify(body.values); } if (body.type == 'errorList' && body.values != undefined) { val = JSON.stringify(body.values); } if (body.type == 'systeminfo' && body.values != undefined) { val = JSON.stringify(body.values); } const obj = await adapter.getObjectAsync(result[i].km200); if (body.type == 'floatValue' || body.type == 'stringValue') { if (obj.common.min != undefined && obj.common.max != undefined) { let val1 = val; if (val < obj.common.min) { val = 'invalid'; } if (val > obj.common.max) { val = 'invalid'; } if (val == 'invalid') { } if (val == 'invalid') { adapter.log.debug( `value ${val1} not within min/max range (${obj.common.min}/${obj.common.max}) for ${ result[i].km200 }`, ); } } } if (!unloaded && val != 'invalid') { await adapter.setStateAsync(result[i].km200, { ack: true, val: val }); } } catch (error) { adapter.log.warn(`KM200 read interrupted ${result[i].km200} ${error}`); adapter.setState('info.connection_km200', false, true); unloaded = true; break; } } } } const t2 = new Date().getTime(); const t3 = (t2 - t1) / 1000; if (adapter.config.statistics) { adapter.setObjectNotExists('statistics.km200-read', { type: 'state', common: { type: 'number', name: 'km200 read time for polling', unit: 'seconds', role: 'value', read: true, write: true, }, native: {}, }); await adapter.setStateAsync('statistics.km200-read', { ack: true, val: t3 }); } } function read_file(data) { const results = []; let km200_count = 0; // Eingelesenen Text in ein Array splitten (\r\n, \n und\r sind die Trennzeichen für verschiedene Betriebssysteme wie Windows, Linux, OS X) const textArray = data.split(/(\n|\r)/gm); for (let i = 1; i < textArray.length; i++) { if (textArray[i].length > 1) { const element = {}; const separator = ';'; const elementArray = textArray[i].split(separator); elementArray.splice(elementArray.length - 1, 1); element.km200 = elementArray[0].trim(); element.id = elementArray[1].trim(); element.type = elementArray[2]; if (element.km200 != '') { km200_count += 1; } results.push(element); } // End if } // End for adapter.log.info(`End reading KM200 csv-file: ${km200_count} km200-fields found`); return results; } function write_file(fnw, datafields) { adapter.log.info('write file km200.csv'); let data = 'km200 field;id;type;\n'; for (let i = 0; i < datafields.length; i++) { data += `${datafields[i].km200};${datafields[i].id};${datafields[i].type}; \n`; } try { fs.writeFileSync(fnw, data, 'utf8'); } catch (err) { adapter.log.info(err); } } async function read_km200structure() { if (unloaded) { return; } adapter.log.info('start reading KM200 data-structure'); const results = []; results.push({ km200: '', id: '', type: '' }); await tree('heatSources'); await tree('dhwCircuits'); await tree('heatingCircuits'); await tree('system'); await tree('notifications'); await tree('gateway'); await tree('solarCircuits'); await tree('ventilation'); await tree('recordings'); await tree(adapter.config.km200_entry); const c = results.length - 1; adapter.log.info(`End reading KM200 data-structure: ${c} fields found`); return results; async function tree(reference) { try { const data = await km200_get(reference); adapter.log.debug(JSON.stringify(data)); if (data.type != 'refEnum' && data != '') { try { const element = data.id.substring(1).split('/').join('.'); results.push({ km200: element, id: data.id.substring(1), type: data.type }); } catch (e) { adapter.log.error(`${e} ${data.id}`); } } else { if (data != '') { await refEnum(data); } //if(data == "") adapter.log.debug("not a valid KM200 entry point: "+reference); } } catch (error) { //adapter.log.warn("http error reading KM200 tree entry "+ reference + " : " + error); } } async function refEnum(data) { let data1, field1, element; for (let i = 0; i < data.references.length; i++) { data1 = ''; try { field1 = data.references[i].id.substring(1).split('/').join('.'); data1 = await km200_get(field1); } catch (e) { //adapter.log.error(e+ " "+data.references[i].id); data1 = ''; } if (data1 != '' && data1 != undefined) { if (data1.type != 'refEnum') { element = data1.id.substring(1).split('/').join('.'); results.push({ km200: element, id: data1.id.substring(1), type: data1.type }); } else { await refEnum(data1); } } } } } //------- km200 functions ------------------------------------------------------------------------------------------ async function km200_get(url) { let data, b; const urls = `${km200_server}/${url.split('.').join('/')}`; const options = { url: urls, method: 'GET', status: [200], timeout: 30000, encoding: 'utf8', port: 80, headers: { Accept: 'application/json', agent: 'TeleHeater/2.2.3', 'User-Agent': 'TeleHeater/2.2.3' }, }; try { b = await axios(options); } catch (e) { await adapter.delay(500); b = await axios(options); } if (b.status == 403 || b.status == 404) { return ''; } if (b.status == 200) { try { const body = b.data; data = km200_decrypt(body); return data; } catch (decrypt) { data = ''; } } } async function km200_put(url, value, type) { if (unloaded) { return; } let data; switch (type) { case 'switchProgram': data = km200_encrypt(Buffer.from(value)); break; case 'arrayData': data = `{"values":${value}}`; data = km200_encrypt(Buffer.from(data)); break; default: data = km200_encrypt(Buffer.from(JSON.stringify({ value: value }))); } const urls = `${km200_server}/${url.split('.').join('/')}`; try { let res = await axios({ method: 'put', url: urls, data: data, headers: { timeout: 10000, encoding: 'utf8', port: 80, Accept: 'application/json', 'User-Agent': 'TeleHeater/2.2.3', }, }); let r = res.status; return r; } catch (e) { adapter.log.error(`axios put: ${url} ${e}`); } } function km200_decrypt(input) { // Decrypt let output; try { let s = Buffer.from(cipher.decrypt(Buffer.from(input, 'base64'), 16)).toString('utf8'); while (s.charCodeAt(s.length - 1) === 0) { s = s.slice(0, s.length - 1); } output = JSON.parse(s); } catch (d) { output = ''; } return output; } function km200_encrypt(input) { // Encrypt let output; try { output = Buffer.from(cipher.encrypt(input, 16)).toString('base64'); } catch {} return output; } // -----km200-recordings------------------------------------------------------------------------------------------------------------------------------------------------ async function km200_recordings(result) { const adapt = `${adapter.namespace}.`; const temp = false; for (let i = 1; i < result.length; i++) { if (unloaded) { break; } if (result[i].type == 'yRecording') { sum_month = 0; await hours(result[i]); await days(result[i]); await months(result[i]); } } } async function recsw(field, d, t) { if (d.length == 0) { return; } if (db.substring(0, 3) == 'sql') { const id = await getid(field, db); const src = await getsource(db); if (id == 0) { adapter.log.info(`KM200 recordings first init: ${field}`); for (let i = 0; i < d.length; i++) { try { await adapter.sendToAsync(db, 'storeState', d[i]); } catch {} } } else { for (let i = 0; i < d.length; i++) { let values = '', command = ''; if (t == 'hh' || t == 'dd' || d[i].state.val > 0) { values = `(${id},${d[i].state.val},${d[i].state.ts},1,${src},0)`; command = `INSERT INTO ${database}.ts_number (id, val, ts, ack, _from, q) VALUES ${values}`; command += 'ON DUPLICATE KEY UPDATE val=values(val)' + ';'; await adapter.sendToAsync(db, 'query', command); } } } } if (db.substring(0, 8) == 'influxdb') { let id; try { id = d[0].id.substring(10); } catch (e) { adapter.log.error(`recsw data:${JSON.stringify(d)}`); return; } const objx = await adapter.getForeignObjectAsync(`system.adapter.${db}`); let retention = 1; try { retention = objx.native.retention; } catch {} if (retention == -1) { retention = objx.native.customRetentionDuration * 24 * 60 * 60; } let ts = Date.now(); if (retention == 0) { retention = ts; } else { retention = retention * 1000; } let tsmin = ts - retention; await adapter.sendToAsync(db, 'deleteAll', [{ id: id }]); for (let i = 0; i < d.length; i++) { if (!unloaded) { if (d[i].state.ts > tsmin) { //adapter.log.info(ts+"---"+retention +"--->"+JSON.stringify(d[i])); await adapter.sendToAsync(db, 'storeState', d[i]); } } } } if (db.substring(0, 7) == 'history') { //await adapter.sendToAsync(db,"deleteAll",[{id:field}]); for (let i = 0; i < d.length; i++) { if (!unloaded) { let status; try { status = await adapter.sendToAsync(db, 'update', d[i]); } catch {} if (status.success == false) { try { status = await adapter.sendToAsync(db, 'storeState', d[i]); } catch {} } } } } let end = Date.now() + +24 * 15 * 3600000; const v = []; for (let i = 0; i < d.length; i++) { if (d[i].state.ts <= end) { v.push({ ts: d[i].state.ts, val: d[i].state.val }); } } function SortArray(x, y) { if (x.ts < y.ts) { return 1; } if (x.ts > y.ts) { return -1; } return 0; } const s = v.sort(SortArray); const ss = [], sss = []; for (let i = 0; i < s.length; i++) { const date = new Date(s[i].ts); const m = date.getMonth() + 1; let mm = m.toString(); if (m < 10) { mm = `0${mm}`; } const d = date.getDate(); let dd = d.toString(); if (d < 10) { dd = `0${dd}`; } let ddd = ''; if (t == 'hh') { ddd = `${date.getFullYear()}-${mm}-${dd} ${date.getHours()} hrs`; } if (t == 'dd') { ddd = `${date.getFullYear()}-${mm}-${dd}`; } if (t == 'mm') { ddd = `${date.getFullYear()}-${mm}`; } ss.push({ date: ddd, val: s[i].val }); sss.push(s[i].val); } let field1 = field.replace(/_Days/g, 'Days'); field1 = field1.replace(/_Hours/g, 'Hours'); field1 = field1.replace(/_Months/g, 'Months'); if (adapter.config.recordings_format == 0) { await adapter.setStateAsync(field1, { ack: true, val: JSON.stringify(sss) }); } if (adapter.config.recordings_format == 1) { await adapter.setStateAsync(field1, { ack: true, val: JSON.stringify(s) }); } if (adapter.config.recordings_format == 2) { await adapter.setStateAsync(field1, { ack: true, val: JSON.stringify(ss) }); } } async function write_state_rec(statename, value) { const obj = { _id: statename, type: 'state', common: {}, native: {} }; obj.common.id = statename; obj.common.name = `recordings: ${statename}`; obj.common.type = 'json'; obj.common.unit = ''; obj.common.read = true; obj.common.write = false; obj.common.role = 'value'; await adapter.setObjectNotExistsAsync(statename, obj); adapter.setStateAsync(statename, { ack: true, val: value }); } async function hours(r) { const adapt = `${adapter.namespace}.`; let statename = ''; const datum = new Date(); let daten = [], data; const field = `${adapt + r.km200}._Hours`; const feld = `${r.km200}?interval=`; for (let i = 0; i < 3; i++) { const url1 = `${feld + datum.getFullYear()}-${datum.getMonth() + 1}-${datum.getDate()}`; try { data = await km200_get(url1); } catch (error) { //adapter.log.error("KM200 error reading recordings on hour " + error); data = ' '; } if (data != ' ') { if (i == 0) { statename = `${adapt + r.km200}.km200.Hours.today`; } if (i == 1) { statename = `${adapt + r.km200}.km200.Hours.yesterday`; } if (i == 2) { statename = `${adapt + r.km200}.km200.Hours.2days_before`; } //const ut1 = new Date(data.interval).getTime(); let ut1 = new Date(data.interval).getTime(); let offset = new Date(data.interval).getTimezoneOffset(); ut1 = ut1 + offset * 60000; await write_state_rec(statename, JSON.stringify(data)); try { for (let ii = 0; ii < data.recording.length; ii++) { if (data.recording[ii].c != 0) { let multi = 1; let wert = 0; if (data.recording[ii].c > 0 && adapter.config.r_c) { multi = 60 / data.recording[ii].c; } if (r.uom == 'C' || r.uom == '°C') { wert = data.recording[ii].y / data.recording[ii].c; wert = Math.round(wert * 10) / 10; } else { wert = data.recording[ii].y * multi * r_multi; wert = Math.round(wert / 6) / 10; } const ts = ut1 + (ii + 1) * 3600000; daten.push({ id: field, state: { ts: ts, val: wert, ack: true } }); } } } catch {} } datum.setDate(datum.getDate() - 1); } await recsw(field, daten, 'hh'); } async function days(r) { const adapt = `${adapter.namespace}.`; let statename = ''; const datum = new Date(); let daten = [], data; const field = `${adapt + r.km200}._Days`; const feld = `${r.km200}?interval=`; let jahr = datum.getFullYear(); let monat = datum.getMonth() + 1; for (let i = 0; i < 3; i++) { const url1 = `${feld + jahr}-${monat}`; try { data = await km200_get(url1); } catch (error) { //adapter.log.error("KM200 error reading recordings on days "+ error); data = ' '; } if (data != ' ') { if (i == 0) { statename = `${adapt + r.km200}.km200.Days.actual_month`; } if (i == 1) { statename = `${adapt + r.km200}.km200.Days.last_month`; } if (i == 2) { statename = `${adapt + r.km200}.km200.Days.2months_ago`; } await write_state_rec(statename, JSON.stringify(data)); const ut1 = new Date(data.interval).getTime(); try { for (let ii = 0; ii < data.recording.length; ii++) { if (data.recording[ii].c != 0) { let multi = 1; let wert = 0; if (adapter.config.r_c) { if (data.recording[ii].c > 0) { multi = (60 * 24) / data.recording[ii].c; } if (i == 0 && ii < data.recording.length - 2) { if (data.recording[ii + 1].c == 0) { multi = 1; } } if (i == 0 && ii == data.recording.length - 1) { multi = 1; } } if (r.uom == 'C' || r.uom == '°C') { wert = data.recording[ii].y / data.recording[ii].c; wert = Math.round(wert * 10) / 10; } else { wert = data.recording[ii].y * multi * r_multi; wert = Math.round(wert / 6) / 10; if (i == 0) { sum_month += wert; } } const ts = ut1 + 60000 + ii * 3600000 * 24; daten.push({ id: field, state: { ts: ts, val: wert, ack: true } }); } } } catch {} } if (monat == 1) { jahr = jahr - 1; monat = 12; } else if (monat > 1) { monat = monat - 1; } } await recsw(field, daten, 'dd'); } async function months(r) { const adapt = `${adapter.namespace}.`; let statename = ''; const datum = new Date(); let daten = [], data; const field = `${adapt + r.km200}._Months`; const feld = `${r.km200}?interval=`; let jahr = datum.getFullYear(); const ja = jahr; const ma = datum.getMonth() + 1; const da = datum.getDate(); let sum = 0; for (let i = 0; i < 3; i++) { const url1 = feld + jahr; try { data = await km200_get(url1); } catch (error) { //adapter.log.error("KM200 error reading recordings on months "+ error); data = ' '; } if (data != ' ') { if (i == 0) { statename = `${adapt + r.km200}.km200.Months.actual_year`; } if (i == 1) { statename = `${adapt + r.km200}.km200.Months.last_year`; } if (i == 2) { statename = `${adapt + r.km200}.km200.Months.2years_ago`; } await write_state_rec(statename, JSON.stringify(data)); try { for (let ii = 0; ii < data.recording.length; ii++) { const m = ii + 1; const t = `${jahr}-${m.toString()}-15`; const ts = new Date(t).getTime(); const tsa = new Date(); const days = new Date(jahr, m, 0).getDate(); let multi = 1; let wert = 0; if (adapter.config.r_c) { if (data.recording[ii].c > 0) { multi = (60 * 24 * days) / data.recording[ii].c; } } if ((r.uom == 'C' || r.uom == '°C') && data.recording[ii].c > 0) { wert = data.recording[ii].y / data.recording[ii].c; wert = Math.round(wert * 10) / 10; } else { wert = data.recording[ii].y * multi * r_multi; wert = Math.round(wert / 6) / 10; if (jahr == ja && m < ma) { sum += wert; } if (jahr == ja - 1 && m >= ma) { sum += wert; } } if (i == 0 && ma == m && r.uom != 'C' && r.uom != '°C') { multi = 1; wert = Math.round(sum_month * 10) / 10; data.recording[ii].c = 1; } daten.push({ id: field, state: { ts: ts, val: wert, ack: true } }); //if (data.recording[ii].c != 0 || ts < tsa){ // daten.push({id: field,state: {ts: ts ,val: wert,ack: true}}); //} } } catch {} } jahr = jahr - 1; } await recsw(field, daten, 'mm'); if (r.uom == 'kWh') { sum = Math.round(sum); statename = `${adapt + r.km200}.last12m`; write_state_sum(statename, r.km200, sum); } } async function write_state_sum(statename, field, value) { const obj = { _id: statename, type: 'state', common: {}, native: {} }; obj.common.id = statename; obj.common.name = 'kWh total for last 12 months'; obj.common.type = 'number'; obj.common.unit = 'kWh'; obj.common.read = true; obj.common.write = false; obj.common.role = 'value'; await adapter.setObjectNotExistsAsync(statename, obj); F.enums(adapter, statename); adapter.setStateAsync(statename, { ack: true, val: value }); } // ------------------------------------------------------------------------------------------------------------------------------------------------------------------- function km200_getAccesskey(gatewaypassword, privatepassword) { function md5(text) { return crypto.createHash('md5').update(text).digest('hex'); } function str2ab(str) { const buf = new ArrayBuffer(str.length * 1); // 2 bytes for each char const bufView = new Uint8Array(buf); for (let i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return bufView; } function concatUint8Array(array1, array2) { const array3 = new Uint8Array(array1.length + array2.length); for (let i = 0; i < array1.length; i++) { array3[i] = array1[i]; } for (let i = 0; i < array2.length; i++) { array3[array1.length + i] = array2[i]; } return array3; } gatewaypassword = gatewaypassword.replace(/-/g, ''); const km200_gateway_password = str2ab(gatewaypassword); const km200_private_password = str2ab(privatepassword); const key_1 = md5(concatUint8Array(km200_gateway_password, km200_crypt_md5_salt)); const key_2_private = md5(concatUint8Array(km200_crypt_md5_salt, km200_private_password)); const km200_crypt_key_private = key_1 + key_2_private; return km200_crypt_key_private.trim().toLowerCase(); } function km200_obj(n, o) { let t = o.type; let u = o.unitOfMeasure; let v = o.value; o.valIs = 'value'; let w = !!o.writeable; let r = w ? 'level' : 'value'; let states = false, s = {}; if (u === 'C') { u = '°C'; r += '.temperature'; } else if (typeof u === 'undefined') { u = ''; } switch (t) { case 'stringValue': if (Array.isArray(o.allowedValues)) { o.valIs = 'states'; t = 'number'; v = o.allowedValues.indexOf(o.value); states = true; s = {}; for (let ii = 0; ii < o.allowedValues.length; ++ii) { s[ii] = o.allowedValues[ii]; } } else { t = 'string'; } break; case 'floatValue': t = 'number'; break; case 'systeminfo': case 'errorList': case 'arrayData': v = o.values; //***** o.valIs = 'values'; t = 'string'; //w = false; break; case 'switchProgram': v = o.switchPoints; //***** o.valIs = 'switchPoints'; t = 'string'; // w = false; break; case 'yRecording': v = o.values; o.valIs = 'values'; t = 'string'; w = false; break; default: // put others in pure objects' v = o; //***** o.valIs = 'values'; t = 'string'; w = false; } const c = { type: 'state', id: n, common: { id: n, name: n, type: t, unit: u, read: true, write: w, role: r, }, native: {}, }; if (states) { c.common.states = s; c.common.min = 0; c.common.max = o.allowedValues.length - 1; } if (typeof o.minValue !== 'undefined') { c.common.min = o.minValue; } if (typeof o.maxValue !== 'undefined') { c.common.max = o.maxValue; } try { if (o.state != undefined) { for (const [key, value] of Object.entries(o.state)) { for (let ii in value) { v = parseFloat(value[ii]); if (v < c.common.min) { c.common.min = v; } if (v > c.common.max) { c.common.max = v; } } } } } catch {} c.native.km200 = o; return c; } async function initrecs(r) { for (let i = 1; i < r.length; i++) { if (r[i].type == 'yRecording') { let obj, f; let uom = ''; r[i].uom = ''; try { obj = await adapter.getObjectAsync(r[i].km200); f = obj.native.km200.recordedResource.id.substring(1).split('/').join('.'); obj = await adapter.getObjectAsync(f); } catch (e) { adapter.log.debug(`KM200 can't read recordings reference object: ${f}`); } try { uom = obj.native.km200.unitOfMeasure; } catch (e) { adapter.log.debug(`KM200 can't read uom of reference object: ${f}`); } if (uom == 'C' || uom == '°C') { uom = '°C'; } else { uom = 'kWh'; } r[i].uom = uom; if (db.trim() != '') { await adapter.setObjectNotExistsAsync(`${r[i].km200}._Hours`, { type: 'state', common: { name: 'db hourly recordings', type: 'number', role: 'value', read: true, write: true, unit: uom, }, native: {}, }); await adapter.setObjectNotExistsAsync(`${r[i].km200}._Days`, { type: 'state', common: { name: 'db daily recordings', type: 'number', role: 'value', read: true, write: true, unit: uom, }, native: {}, }); await adapter.setObjectNotExistsAsync(`${r[i].km200}._Months`, { type: 'state', common: { name: 'db monthly recordings', type: 'number', role: 'value', read: true, write: true, unit: uom, }, native: {}, }); } await adapter.setObjectNotExistsAsync(`${r[i].km200}.Hours`, { type: 'state', common: { name: 'recordings hours', type: 'json', role: 'value', read: true, write: true, unit: uom }, native: {}, }); await adapter.setObjectNotExistsAsync(`${r[i].km200}.Days`, { type: 'state', common: { name: 'recordings days', type: 'json', role: 'value', read: true, write: true, unit: uom }, native: {}, }); await adapter.setObjectNotExistsAsync(`${r[i].km200}.Months`, { type: 'state', common: { name: 'recordings months', type: 'json', role: 'value', read: true, write: true, unit: uom }, native: {}, }); } } for (let i = 1; i < r.length; i++) { if (r[i].type == 'yRecording') { enable_state(`${r[i].km200}._Hours`, 0, 0); enable_state(`${r[i].km200}._Days`, 0, 0); enable_state(`${r[i].km200}._Months`, 0, 0); } } } async function enable_state(stateid, retention, interval) { if (unloaded) { return; } const id = `${adapter.namespace}.${stateid}`; const obj = await adapter.getObjectAsync(stateid); try { //if (obj.common.custom == undefined) { adapter.sendTo( db, 'enableHistory', { id: id, options: { changesOnly: false, debounce: 0, retention: retention, changesRelogInterval: interval, maxLength: 3, changesMinDelta: 0, aliasId: '', }, }, function (result) { if (result.error) { adapter.log.error(`KM200 enable state error: ${stateid} ${result.error}`); } if (result.success) { } }, ); //} } catch {} } const state_change = async function (id, state, obj) { if (unloaded) { return; } let value = state.val; adapter.log.debug(`KM200 write change: ${id}: ${value}`); try { if (typeof obj.native.km200.allowedValues != 'undefined' && obj.native.km200.type == 'stringValue') { value = obj.native.km200.allowedValues[value]; } const resp = await km200_put(obj.native.ems_km200, value, obj.native.km200.type); if (resp != 200 && resp != 204) { adapter.log.warn(`KM200 http write error ${resp}:${obj.native.ems_km200}`); } } catch (error) { adapter.log.warn(`KM200 http write error ${error}:${obj.native.ems_km200}`); } }; async function getsource(db) { return new Promise(function (resolve) { const query = `select id from ${database}.sources where name = "system.adapter.ems-esp.${adapter.instance}";`; adapter.sendTo(db, 'query', query, function (result) { if (result.error || result.result[0] == null) { resolve(0); } else { resolve(result.result[0].id); } }); }); } async function getid(field, db) { return new Promise(function (resolve) { const query = `select id from ${database}.datapoints where name = "${field}";`; adapter.sendTo(db, 'query', query, function (result, reject) { if (result.error || result.result[0] == null) { resolve(0); } else { resolve(result.result[0].id); } }); }); } const unload = function (u) { unloaded = u; }; module.exports = { init, state_change, unload };