iobroker.ems-esp
Version:
EMS-ESP and KM200 Interface
1,036 lines (881 loc) • 32.7 kB
JavaScript
/* eslint-disable prefer-const */
/* eslint-disable no-unused-vars */
/* eslint-disable no-empty */
/* eslint-disable no-mixed-spaces-and-tabs */
const Rijndael = require("rijndael-js");
const crypto = require("crypto");
const fs = require("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;
// eslint-disable-next-line no-unused-vars
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(e) {}
let dbversion = "";try {dbversion = obj.native.dbversion;} catch(e) {}
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(e) {}
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 = 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 = 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);
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.setObjectAsync(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") {
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: 10000,
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(e) {}
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(e) {}
}
}
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(e) {}
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(e) {}
if (status.success == false) try {status = await adapter.sendToAsync(db,"storeState", d[i]);} catch(e) {}
}
}
}
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(e) {}
}
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(e) {}
}
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(e) {}
}
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(e) {}
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.setObjectAsync(r[i].km200+"._Hours",{type: "state",common: {name: "db hourly recordings",type: "number", role: "value", read: true, write: true, unit: uom}, native: {}});
await adapter.setObjectAsync(r[i].km200+"._Days",{type: "state",common: {name: "db daily recordings",type: "number", role: "value", read: true, write: true, unit: uom}, native: {}});
await adapter.setObjectAsync(r[i].km200+"._Months",{type: "state",common: {name: "db monthly recordings",type: "number", role: "value", read: true, write: true, unit: uom}, native: {}});
}
await adapter.setObjectAsync(r[i].km200+".Hours",{type: "state",common: {name: "recordings hours",type: "json", role: "value", read: true, write: true, unit: uom}, native: {}});
await adapter.setObjectAsync(r[i].km200+".Days",{type: "state",common: {name: "recordings days",type: "json", role: "value", read: true, write: true, unit: uom}, native: {}});
await adapter.setObjectAsync(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(e) {}
}
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};