iobroker.broadlink2
Version:
ioBroker Broadlink2 Adapter
1,408 lines (1,307 loc) • 42.4 kB
JavaScript
/* eslint-disable no-await-in-loop */
/* eslint-disable valid-jsdoc */
/* eslint-disable no-prototype-builtins */
/**
* iobroker MyAdapter II class
* (c) 2020- <frankjoke@hotmail.com>
* MIT License
*
* V 1.0.0 June 2020
*/
"use strict";
//@ts-disable TS80006
//@js-disable TS80006
class CacheP {
constructor(fun, delay) {
// neue Einträge werden mit dieser Funktion kreiert
if (typeof fun != "function")
throw "CacheP needs an async function returning a Promise as first argument!";
this._cache = {};
this._fun = fun;
this._delay = delay || 0;
return this;
}
async cacheItem(item, prefereCache = true, fun) {
fun = fun || this._fun;
if (this._delay)
await new Promise((res) => setTimeout(() => res(), this._delay));
if (prefereCache && this._cache[item] !== undefined)
return this._cache[item];
if (!fun) fun = this._fun;
// assert(A.T(fun) === 'function', `checkItem needs a function to fill cache!`);
const res = await fun(item);
this._cache[item] = res;
return res;
}
isCached(x) {
return this._cache[x] !== undefined;
}
clearCache() {
this._cache = {};
}
get cache() {
return this._cache;
}
cacheSync(item, prefereCache = true, fun) {
const cached = this.isCached(item);
if (cached && prefereCache) return this._cache[item];
fun = fun || this._fun;
if (typeof fun == "function")
try {
const res = fun(item);
if (res) {
this._cache[item] = res;
return res;
}
} finally {
// empty
}
return null;
}
}
class HrTime {
constructor(time) {
this._stime = time ? time : process.hrtime();
}
get diff() {
return process.hrtime(this._stime);
}
get text() {
const t = this.diff;
const ns = t[1].toString(10);
return (
t[0].toString(10) + "." + ("0".repeat(9 - ns.length) + ns).slice(0, 6)
);
}
get time() {
return Number(this.text);
}
set time(t) {
this._stime = t || process.hrtime();
}
}
class Sequence {
constructor(p, delay) {
this._p = p ? p : Promise.resolve();
this._delay = delay || 0;
return this;
}
get p() {
return this._p;
}
set p(val) {
this.add(val);
return val;
}
add(val, ...args) {
const fun = typeof val === "function" ? val : () => val;
if (this._delay) {
const n = () =>
new Promise((res) => setTimeout(() => res(), this._delay));
this._p = this._p.then(
() => n(),
() => n()
);
}
this._p = this._p.then(
() => fun(...args),
() => fun(...args)
);
return this;
}
then(res, rej) {
return (this._p = this._p.then(res, rej));
}
catch(rej) {
return (this._p = this._p.then((x) => x, rej));
}
}
// you have to call the adapter function and pass a options object
// name has to be set and has to be equal to adapters folder name and main file name excluding extension
// adapter will be restarted automatically every time as the configuration changed, e.g system.adapter.template.0
const util = require("util"),
exec = require("child_process").exec,
os = require("os"),
fs = require("fs"),
assert = require("assert"),
axios = require("axios");
const objects = {},
stq = new Sequence(),
states = {},
createdStates = {},
sstate = {},
mstate = {};
let adapter,
aoptions,
aname,
amain,
timer,
_onUnload = Promise.resolve(),
_objChange,
_stateChange,
_onStop,
stopping = false,
inDebug = false,
curDebug = 1,
// allStates = null,
// stateChange = null,
systemconf = null;
let _messages = (mes) =>
Promise.resolve(
MyAdapter.W(`Message ${this.O(mes)} received and no handler defined!`)
);
function startAdapter(options) {
if (!options) options = aoptions;
options = options || {};
if (typeof options === "string")
options = {
name: options,
};
else options.name = aname;
options = Object.assign(options, {
/**
* Is called when databases are connected and adapter received configuration.
*/
async ready() {
// Initialize your adapter here
MyAdapter.extendObject = adapter.extendObjectAsync.bind(adapter);
await MyAdapter.initAdapter();
if (amain) amain(adapter);
},
/* Is called when adapter shuts down - callback has to be called under any circumstances!
* @param {() => void} callback
*/
async unload(callback) {
try {
await Promise.resolve(_onUnload ? _onUnload(null) : null).catch((e) =>
MyAdapter.Wf(e)
);
MyAdapter.stop(null, callback);
callback();
} catch (e) {
callback();
}
},
/**
* Is called if a subscribed object changes
* @param {string} id
* @param {ioBroker.Object | null | undefined} obj
*/
objectChange(id, obj) {
// MyAdapter.Df("Object %s was changed top %O", id, obj);
if (typeof _objChange == "function")
setImmediate(() => _objChange(id, obj));
if (obj) {
// The object was changed
objects[id] = obj;
// this.log.info(`object ${id} changed: ${JSON.stringify(obj)}`);
} else if (id) {
// The object was deleted
if (states[id]) delete states[id];
if (sstate[id]) delete sstate[id];
if (objects[id]) delete objects[id];
// this.log.info(`object ${id} deleted`);
}
},
/**
* Is called if a subscribed state changes
* @param {string} id
* @param {ioBroker.State | null | undefined} state
*/
stateChange(id, state) {
// MyAdapter.Df("State %s was changed top %O", id, state);
if (
_stateChange &&
(!state || state.from !== "system.adapter." + MyAdapter.ains)
)
setImmediate(() =>
_stateChange(id, state).catch((err) =>
MyAdapter.W(`Error in StateChange for ${id} = ${MyAdapter.O(err)}`)
)
);
// if (allStates)
// allStates(id, state).catch(e => this.W(`Error in AllStates for ${id} = ${this.O(e)}`)));
if (state) {
states[id] = state;
// The state was changed
// this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
} else if (id) {
// The state was deleted
delete states[id];
delete sstate[id];
// this.log.info(`state ${id} deleted`);
}
},
// /**
// * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
// * Using this method requires "common.message" property to be set to true in io-package.json
// * @param {ioBroker.Message} obj
// */
message(obj) {
if (typeof obj === "object" && obj.command)
MyAdapter.processMessage(
MyAdapter.D(`received Message ${MyAdapter.O(obj)}`, obj)
);
// if (obj.command === "send") {
// // e.g. send email or pushover or whatever
// this.log.info("send command");
// // Send response in callback if required
// if (obj.callback) this.sendTo(obj.from, obj.command, "Message received", obj.callback);
// }
// }
},
});
try {
const utils = require("@iobroker/adapter-core");
adapter = new utils.Adapter(options);
MyAdapter.If("got following adapter: %O", options);
} catch (e) {
// eslint-disable-next-line no-console
console.error("cannot find ioBroker...");
}
if (adapter) MyAdapter.init2(adapter);
return adapter;
}
function slog(log, text) {
if (inDebug === undefined) return text;
return adapter && adapter.log && typeof adapter.log[log] === "function"
? // eslint-disable-next-line no-console
adapter.log[log](text)
: console.log(log + ": " + text);
}
function addSState(n, id) {
if (!mstate[n]) {
if (sstate[n] && sstate[n] !== id) {
sstate[id] = id;
mstate[n] = [id];
delete sstate[n];
} else sstate[n] = id;
} else {
mstate[n].push(id);
sstate[id] = id;
}
}
class MyAdapter {
static get config() {
return adapter.config;
}
static set stateChange(val) {
_stateChange = val;
}
static get stateChange() {
return _stateChange;
}
static set objChange(val) {
_objChange = val;
}
static get objChange() {
return _objChange;
}
static set onStop(val) {
_onStop = val;
}
static set messages(val) {
_messages = val;
}
static get messages() {
return _messages;
}
static set onUnload(val) {
_onUnload = val;
}
static async processMessage(obj) {
let res = null;
if (obj.command === "debug")
res = inDebug = isNaN(parseInt(obj.message))
? this.parseLogic(obj.message)
: parseInt(obj.message);
else
res = _messages
? await _messages(obj).catch((e) =>
this.Wf("Message execution error: %O", e)
)
: null;
// console.log(res, obj);
// this.D(`Message from '${obj.from}', command '${obj.command}', message '${this.S(obj.message)}' executed with result:"${this.S(res)}"`);
// err => this.W(`invalid Message ${this.O(obj)} caused error ${this.O(err)}`, err))
// return (obj.command === "debug" ? this.resolve(`debug set to '${inDebug = isNaN(parseInt(obj.message)) ? this.parseLogic(obj.message) : parseInt(obj.message)}'`) : messages(obj))
if (obj.callback)
await adapter.sendTo(obj.from, obj.command, res, obj.callback);
return res;
}
static getObjects(name) {
name = !name ? "" : name;
const opt = {
include_docs: true,
};
if (name) {
name = name === "*" ? "" : name;
opt.startkey = (name.startsWith("system.") ? "" : this.ain) + name;
opt.endkey =
(name.startsWith("system.") ? "" : this.ain) + name + "\u9999";
}
return adapter.getObjectListAsync(opt).then(
(res) => (res && res.rows ? res.rows : []),
() => []
);
}
// eslint-disable-next-line complexity
static async initAdapter() {
try {
this.Df("Adapter %s starting.", this.ains);
this.getObjectList = adapter.getObjectListAsync
? adapter.getObjectListAsync.bind(adapter)
: this.c2p(adapter.objects.getObjectList).bind(adapter.objects);
this.getForeignState = adapter.getForeignStateAsync.bind(adapter);
this.setForeignState = adapter.setForeignStateAsync.bind(adapter);
this.getState = adapter.getStateAsync.bind(adapter);
this.setState = adapter.setStateAsync.bind(adapter);
this.getStates = adapter.getStatesAsync.bind(adapter);
this.removeState = async (id, opt) => {
await adapter.delStateAsync(id, opt).catch(this.nop);
await adapter
.delObjectAsync((delete states[id], id), opt)
.catch(this.nop);
};
const ms = await adapter.getStatesAsync("*").catch((err) => this.W(err));
for (const s of Object.keys(ms)) states[s] = ms[s];
// console.log(states);
let res = await this.getObjects("*");
const len = res.length;
for (const i of res) {
const o = i.doc;
objects[o._id] = o;
if (o.type === "state" && o.common && o.common.name) {
if (adapter.config.forceinit && o._id.startsWith(this.ain))
await this.removeState(o.common.name);
// if (!o._id.startsWith('system.adapter.'))
addSState(o.common.name, o._id);
}
}
res = await adapter
.getForeignObjectAsync("system.config")
.catch(() => null);
if (res) {
systemconf = res.common;
// this.If('systemconf: %O', systemconf);
if (systemconf && systemconf.language)
adapter.config.lang = systemconf.language;
if (systemconf && systemconf.latitude) {
adapter.config.latitude = parseFloat(systemconf.latitude);
adapter.config.longitude = parseFloat(systemconf.longitude);
}
// if (adapter.config.forceinit)
// this.seriesOf(res, (i) => this.removeState(i.doc.common.name), 2)
// this.If('loaded adapter config: %O', adapter.config);
}
res = await adapter
.getForeignObjectAsync("system.adapter." + this.ains)
.catch(() => null);
if (res) {
adapter.config.adapterConf = res.common;
// this.If('adapterconf = %s: %O', 'system.adapter.' + this.ains, adapterconf);
// this.If('adapter: %O', adapter);
if (adapter.config.adapterConf && adapter.config.adapterConf.loglevel)
adapter.config.loglevel = adapter.config.adapterConf.loglevel;
if (
adapter.config.loglevel === "debug" ||
adapter.config.loglevel === "silly"
)
this.debug = true;
// this.If('loglevel: %s, debug: %s', adapter.config.loglevel, MyAdapter.debug);
// if (adapter.config.forceinit)
// this.seriesOf(res, (i) => this.removeState(i.doc.common.name), 2)
// this.If('loaded adapter config: %O', adapter.config);
}
MyAdapter.D(
`${adapter.name} received ${len} objects and ${
this.ownKeys(states).length
} states, with config ${this.ownKeys(adapter.config)}`
);
adapter.subscribeStates("*");
if (adapter._objChange) adapter.subscribeObjects("*");
// .then(() => objChange ? MyAdapter.c2p(adapter.subscribeObjects)('*').then(a => MyAdapter.I('eso '+a),a => MyAdapter.I('eso '+a)) : MyAdapter.resolve())
this.I(aname + " initialization started...");
// process.on('rejectionHandled', (reason, promise) => this.Wr(true, 'Promise problem rejectionHandled of Promise %s with reason %s', promise, reason));
// process.on('unhandledRejection', (reason, promise) => this.Wr(true, 'Promise problem unhandledRejection of Promise %O with reason %O', promise, reason));
} catch (e) {
this.stop(this.E(aname + " Initialization Error:" + this.F(e)));
}
}
static init(amodule, options, ori_main) {
// assert(!adapter, `myAdapter:(${ori_adapter.name}) defined already!`);
amain = ori_main;
if (typeof options === "string")
options = {
name: options,
};
aoptions = Object.assign({}, options);
aname = aoptions.name;
if (!amain) return;
if (amodule && amodule.parent) {
amodule.exports = (options) => (adapter = startAdapter(options));
} else {
adapter = startAdapter(aoptions);
}
}
static get AI() {
return adapter;
}
static init2() {
// if (adapter) this.If('adpter: %O',adapter);
assert(adapter && adapter.name, "myAdapter:(adapter) no adapter here!");
aname = adapter.name;
inDebug = timer = stopping = false;
curDebug = 1;
systemconf = null;
this.writeFile = this.c2p(fs.writeFile);
this.readFile = this.c2p(fs.readFile);
this.getForeignObject = adapter.getForeignObjectAsync.bind(adapter);
this.setForeignObject = adapter.setForeignObjectAsync.bind(adapter);
this.getForeignObjects = adapter.getForeignObjectsAsync.bind(adapter);
this.getObject = adapter.getObjectAsync.bind(adapter);
this.deleteState = (id) =>
adapter
.deleteStateAsync(id)
.catch((res) =>
res === "Not exists" ? this.resolve() : this.reject(res)
);
this.delObject = (id, opt) =>
adapter
.delObjectAsync(id, opt)
.catch((res) =>
res === "Not exists" ? this.resolve() : this.reject(res)
);
this.delState = (id, opt) =>
adapter
.delStateAsync(id, opt)
.catch((res) =>
res === "Not exists" ? this.resolve() : this.reject(res)
);
this.removeState = (id, opt) =>
adapter
.delStateAsync(id, opt)
.then(() => this.delObject((delete states[id], id), opt));
this.setObject = adapter.setObjectAsync.bind(adapter);
this.createState = adapter.createStateAsync.bind(adapter);
this.extendObject = adapter.extendObjectAsync.bind(adapter);
this.extendForeignObject = adapter.extendForeignObjectAsync.bind(adapter);
// adapter.removeAllListeners();
process.on("rejectionHandled", (reason, promise) =>
this.Wr(
true,
"Promise problem rejectionHandled of Promise %s with reason %s",
promise,
reason
)
);
process.on("unhandledRejection", (reason, promise) =>
this.Wr(
true,
"Promise problem unhandledRejection of Promise %O with reason %O",
promise,
reason
)
);
/*
adapter.on('message', obj => obj && this.processMessage(this.D(`received Message ${this.O(obj)}`, obj)))
.on('unload', callback => this.stop(false, callback))
.on('ready', () => this.resolve().then(() => this.initAdapter()).then(() => setImmediate(amain), e => this.Ef('Adapter Error, stop: %O', e)))
.on('objectChange', (id, obj) => obj && obj._id && _objChange && setTimeout((id, obj) => objChange(id, obj), 0, id, obj))
.on('stateChange', (id, state) => setTimeout((id, state) => {
(state && _stateChange && state.from !== 'system.adapter.' + this.ains ?
stateChange(id, state).catch(err => this.W(`Error in StateChange for ${id} = ${this.O(err)}`)) :
Promise.resolve())
.then(() => allStates && allStates(id, state).catch(e => this.W(`Error in AllStates for ${id} = ${this.O(e)}`)))
.then(() => states[id] = state);
}, 0, id, state));
*/
return adapter;
}
static idName(id) {
if (objects[id] && objects[id].common) return objects[id].common.name; // + '(' + id + ')';
if (sstate[id] && sstate[id] !== id) return id; // + '(' + sstate[id] + ')';
return id; // +'(?)';
}
static J(/** string */ str, /** function */ reviewer) {
let res;
if (!str) return str;
if (typeof str !== "string") str = str.toString();
try {
res = JSON.parse(str, reviewer);
} catch (e) {
res = {
error: e,
error_description: `${e} on string ${str}`,
};
}
return res;
}
static pE(x, y) {
y = y ? y : MyAdapter.pE;
function get() {
const oldLimit = Error.stackTraceLimit;
Error.stackTraceLimit = Infinity;
const orig = Error.prepareStackTrace;
Error.prepareStackTrace = function (_, stack) {
return stack;
};
const err = new Error("Test");
Error.captureStackTrace(err, y);
const stack = err.stack;
Error.prepareStackTrace = orig;
Error.stackTraceLimit = oldLimit;
return stack.map((site) =>
site.getFileName()
? (site.getFunctionName() || "anonymous") +
" in " +
site.getFileName() +
" @" +
site.getLineNumber() +
":" +
site.getColumnNumber()
: ""
);
}
MyAdapter.Wf("Promise failed @ %O error: %o", get().join("; "), x);
return x;
}
static nop(obj) {
return obj;
}
static split(x, s) {
return this.trim((typeof x === "string" ? x : `${x}`).split(s));
}
static trim(x) {
return Array.isArray(x)
? x.map(this.trim)
: typeof x === "string"
? x.trim()
: `${x}`.trim();
}
/* static A(arg) {
if (!arg)
this.E(this.f.apply(null, Array.prototype.slice.call(arguments, 1)));
assert.apply(null, arguments);
}
*/
static D(str, val) {
if (!inDebug || curDebug > Number(inDebug))
return val !== undefined ? val : str;
return (
inDebug ? slog("info", `debug: ${str}`) : slog("debug", str),
val !== undefined ? val : str
);
}
static Dr(str) {
if (!inDebug && curDebug > Number(inDebug)) return str;
else {
const s = this.f.apply(null, Array.prototype.slice.call(arguments, 1));
if (inDebug) slog("info", `debug: ` + s);
else slog("debug", s);
}
return str;
}
static Df(...str) {
if (!(!inDebug && curDebug > Number(inDebug))) {
str = this.f(...str);
return inDebug ? slog("info", "debug: " + str) : slog("debug", str);
}
}
static F() {
return util.format.apply(null, arguments);
}
static f() {
return util.format.apply(null, arguments).replace(/\n\s+/g, " ");
}
static I(l, v) {
return slog("info", l), v === undefined ? l : v;
}
static Ir(ret) {
slog("info", this.f.apply(null, Array.prototype.slice.call(arguments, 1)));
return ret;
}
static If() {
return slog("info", this.f.apply(null, arguments));
}
static Wf() {
return slog("warn", this.f.apply(null, arguments));
}
static Wr(ret) {
slog("warn", this.f.apply(null, Array.prototype.slice.call(arguments, 1)));
return ret;
}
static Er(ret) {
slog("error", this.f.apply(null, Array.prototype.slice.call(arguments, 1)));
return ret;
}
static W(l, v) {
return slog("warn", l), v === undefined ? l : v;
}
static E(l, v) {
return slog("error", l), v === undefined ? l : v;
}
static Ef() {
return slog("error", this.f.apply(null, arguments));
}
static toNumber(v) {
return isNaN(Number(v)) ? 0 : Number(v);
}
static toInteger(v) {
return parseInt(this.toNumber(v));
}
static set addq(promise) {
stq.p = promise;
return stq;
}
static get debug() {
return inDebug;
}
static set debug(y) {
inDebug = y;
}
static get timer() {
return timer;
}
static set timer(y) {
timer = y;
}
// static get stateChange() {
// return stateChange;
// }
// static set stateChange(y) {
// stateChange = (assert(typeof y === "function", "Error: StateChange handler not a function!"), y);
// }
// static get allStates() {
// return allStates;
// }
// static set allStates(y) {
// allStates = (assert(typeof y === "function", "Error: StateChange handler not a function!"), y);
// }
static get name() {
return aname;
}
static get states() {
return states;
}
static get adapter() {
return adapter;
}
static get aObjects() {
return adapter.objects;
}
static get objects() {
return objects;
}
static get debugLevel() {
return curDebug;
}
static set debugLevel(y) {
curDebug = y;
}
static get ains() {
return adapter.namespace;
}
static get ain() {
return this.ains + ".";
}
static get C() {
return adapter.config;
}
static fullName(id) {
return this.ain + id;
}
static parseLogic(obj) {
return this.includes(
["0", "off", "aus", "false", "inactive", ""],
obj.toString().trim().toLowerCase()
)
? false
: this.includes(
["1", "-1", "on", "ein", "true", "active"],
obj.toString().trim().toLowerCase()
);
}
static clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
/* static wait(time, arg) {
if (isNaN(Number(time)) || Number(time) < 0)
time = 0;
if (typeof arg === 'function') {
let args = Array.prototype.slice.call(arguments, 2);
return new Promise(res => setTimeout(r => res(arg.apply(null, args)), time));
}
return new Promise(res => setTimeout(res, time, arg));
}
*/
static P(pv, res, rej) {
if (pv instanceof Promise) return pv;
if (pv && typeof pv.then === "function")
return new Promise((rs, rj) => pv.then(rs, rj));
if (pv) return this.resolve(res || pv);
return this.reject(rej || pv);
}
static nothing() {
return null;
}
static nextTick(x) {
return new Promise((res) => process.nextTick(() => res(x)));
}
static resolve(x) {
return this.nextTick(x);
}
static async reject(x) {
await this.nextTick();
return Promise.reject(x);
}
static wait(time, ...arg) {
time = parseInt(this.toNumber(time));
if (time < 0) return this.nextTick(...arg);
return new Promise((resolve) => setTimeout(() => resolve(...arg), time));
}
static async retry(nretry, fn, wait, ...args) {
//change args!!!
// assert(typeof fn === 'function', 'retry (,fn,) error: fn is not a function!');
nretry = this.toInteger(nretry);
nretry = nretry || 2;
while (nretry > 0)
try {
const res = await fn(...args);
return res;
} catch (err) {
nretry--;
if (!nretry) return Promise.reject(err);
await this.wait(wait || 0);
}
return null;
}
static async pSequence(arr, promise, wait) {
wait = wait || 0;
if (!Array.isArray(arr) && typeof arr === "object")
arr = Object.entries(arr).filter((o) => arr.hasOwnProperty(o[0]));
const res = [];
for (const i of arr) {
if (res.length) await this.wait(wait);
try {
const r = await promise(i);
res.push(r);
} catch (e) {
res.push(e);
}
}
return res;
}
static pTimeout(pr, time, callback) {
const t = this.toNumber(time);
let st = null;
return new Promise((resolve, reject) => {
const rs = (res) => {
if (st) clearTimeout(st);
st = null;
return resolve(res);
},
rj = (err) => {
if (st) clearTimeout(st);
st = null;
return reject(err);
};
st = setTimeout(() => {
st = null;
reject(`timer ${t} run out`);
}, t);
if (callback) callback(rs, rj);
this.P(pr).then(rs, rj);
});
}
static async Ptime(promise, arg) {
const start = Date.now();
if (typeof promise === "function") promise = promise(arg);
await Promise.resolve(promise).catch(() => null);
const end = Date.now();
return end - start;
}
static O(obj, level) {
return util
.inspect(obj, {
depth: level || 2,
colors: false,
})
.replace(/\n\s*/g, "");
}
static removeEmpty(obj) {
if (this.T(obj) !== "object") return obj;
const a = this.clone(obj);
for (const n of Object.getOwnPropertyNames(a))
if (!a[n] && typeof a[n] !== "boolean") delete a[n];
return a;
}
static S(obj, level) {
return typeof obj === "string" ? obj : this.O(obj, level);
}
static N(fun, ...args) {
return setImmediate(fun, ...args);
} // move fun to next schedule keeping arguments
static T(i, j) {
let t = typeof i;
if (t === "object") {
if (Array.isArray(i)) t = "array";
else if (i instanceof RegExp) t = "regexp";
else if (i === null) t = "null";
} else if (t === "number" && isNaN(i)) t = "NaN";
return j === undefined ? t : this.T(j) === t;
}
static locDate(date) {
return date instanceof Date
? new Date(date.getTime() - date.getTimezoneOffset() * 60000)
: typeof date === "string"
? new Date(Date.parse(date) - new Date().getTimezoneOffset() * 60000)
: !isNaN(+date)
? new Date(+date - new Date().getTimezoneOffset() * 60000)
: new Date(Date.now() - new Date().getTimezoneOffset() * 60000);
}
static dateTime(date) {
return this.locDate(date).toISOString().slice(0, -5).replace("T", "@");
}
static obToArray(obj) {
return Object.keys(obj)
.filter((x) => obj.hasOwnProperty(x))
.map((i) => obj[i]);
}
static includes(obj, value) {
return this.T(obj) === "object"
? obj[value] !== undefined
: Array.isArray(obj)
? obj.find((x) => x === value) !== undefined
: obj === value;
}
static ownKeys(obj) {
return this.T(obj) === "object" ? Object.getOwnPropertyNames(obj) : [];
// return this.T(obj) === 'object' ? Object.keys(obj).filter(k => obj.hasOwnProperty(k)) : [];
}
static ownKeysSorted(obj) {
return this.ownKeys(obj).sort(function (a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a > b) return 1;
if (a < b) return -1;
return 0;
});
}
static async stop(dostop, callback) {
// dostop
if (stopping) return;
try {
if (_onStop) await _onStop(dostop);
} finally {
stopping = true;
if (timer) {
if (Array.isArray(timer)) timer.forEach((t) => clearInterval(t));
else clearInterval(timer);
timer = null;
}
}
this.I(
`Adapter disconnected and stopped with dostop(${dostop}) and callback(${!!callback})`
);
if (!callback) {
const x = dostop < 0 ? 0 : dostop || 0;
MyAdapter.Df(
"Adapter will exit now with code %s and method %s!",
x,
adapter && adapter.terminate ? "adapter.terminate" : "process.exit"
);
if (adapter && adapter.terminate) adapter.terminate(x);
else process.exit(x);
} else
try {
if (callback) callback();
} finally {
if (callback) callback();
}
}
static seriesOf(obj, promfn, delay) {
// fun gets(item) and returns a promise
assert(
typeof promfn === "function",
"series(obj,promfn,delay) error: promfn is not a function!"
);
delay = parseInt(delay) || 0;
let p = Promise.resolve();
const nv = [],
f =
delay > 0
? (k) =>
(p = p.then(() =>
promfn(k).then((res) => this.wait(delay, nv.push(res)))
))
: (k) => (p = p.then(() => promfn(k)));
for (const item of obj) f(item);
return p.then(() => nv);
}
static seriesInOI(obj, promfn, delay) {
// fun gets(item) and returns a promise
assert(
typeof promfn === "function",
"series(obj,promfn,delay) error: promfn is not a function!"
);
delay = parseInt(delay) || 0;
let p = Promise.resolve();
const nv = [],
f =
delay > 0
? (k) =>
(p = p.then(() =>
promfn(k).then((res) => this.wait(delay, nv.push(res)))
))
: (k) => (p = p.then(() => promfn(k)));
for (const item in obj) f(obj[item]);
return p.then(() => nv);
}
static seriesIn(obj, promfn, delay) {
// fun gets(item,object) and returns a promise
assert(
typeof promfn === "function",
"series(obj,promfn,delay) error: promfn is not a function!"
);
delay = parseInt(delay) || 0;
let p = Promise.resolve();
const nv = [],
f =
delay > 0
? (k) =>
(p = p.then(() =>
promfn(k).then((res) => this.wait(delay, nv.push(res)))
))
: (k) => (p = p.then(() => promfn(k)));
for (const item in obj) f(item, obj);
return p.then(() => nv);
}
static c2p(f, b) {
const p = util.promisify(f);
return b ? p.bind(b) : p;
}
/*
static c1p(f) {
assert(typeof f === 'function', 'c1p (f) error: f is not a function!');
return function () {
const args = Array.prototype.slice.call(arguments);
return new Promise(res => (args.push((result) => res(result)), f.apply(this, args)));
};
}
static c1pe(f) { // one parameter != null = error
assert(typeof f === 'function', 'c1pe (f) error: f is not a function!');
return function () {
const args = Array.prototype.slice.call(arguments);
return new Promise((res, rej) => (args.push((result) => !result ? res(result) : rej(result)), f.apply(this, args)));
};
}
static retry(nretry, fn, arg, wait) {
assert(typeof fn === "function", "retry (,fn,) error: fn is not a function!");
nretry = parseInt(nretry);
nretry = isNaN(nretry) ? 2 : nretry;
return Promise.resolve(fn(arg)).catch(err => nretry <= 0 ? this.reject(err) : this.wait(wait > 0 ? wait : 0).then(() => this.retry(nretry - 1, fn, arg, wait)));
}
static
while ( fw, fn, time) {
assert(typeof fw === "function" && typeof fn === "function", "retry (fw,fn,) error: fw or fn is not a function!");
time = parseInt(time) || 0;
return !fw() ? this.resolve(true) :
fn().then(() => true, () => true)
.then(() => this.wait(time))
.then(() => this.while(fw, fn, time));
}
static repeat( nretry, fn, arg, len) {
assert(typeof fn === "function", "repeat (,fn,) error: fn is not a function!");
nretry = parseInt(nretry) || 0;
return fn(arg)
.then(res => this.reject(res))
.catch(res => nretry <= 0 ? this.resolve(res) : this.wait(len > 0 ? len : 0).then(() => this.repeat(nretry - 1, fn, arg)));
}
*/
static async repeat(nretry, fn, wait, ...args) {
nretry = nretry <= 0 ? 2 : nretry || 2;
while (nretry--) {
await fn(...args);
await this.wait(parseInt(wait) || 10);
}
return true;
}
static exec(command) {
// assert(typeof command === "string", "exec (fn) error: fn is not a string!");
const istest = command.startsWith("!");
return new Promise((resolve, reject) => {
try {
exec(istest ? command.slice(1) : command, (error, stdout, stderr) => {
if (istest && error) {
error[stderr] = stderr;
return reject(error);
}
resolve(stdout);
});
} catch (e) {
reject(e);
}
});
}
/*
static request(opt, value, transform) {
if (typeof opt === "string")
opt = this.url(opt.trim());
if (!(opt instanceof url.Url)) {
if (this.T(opt) !== "object" || !opt.hasOwnProperty("url"))
return Promise.reject(this.W(`Invalid opt or Url for request: ${this.O(opt)}`));
opt = this.url(opt.url, opt);
}
if (opt.json)
if (opt.headers) opt.headers.Accept = "application/json";
else opt.headers = {
Accept: "application/json"
};
if (!opt.protocol)
opt.protocol = "http:";
const fun = opt.protocol.startsWith("https") ? https.request : http.request;
// this.D(`opt: ${this.O(opt)}`);
return new Promise((resolve, reject) => {
let data = new Buffer(""),
res;
const req = fun(opt, function (result) {
res = result;
// MyAdapter.D(`status: ${MyAdapter.O(res.statusCode)}/${http.STATUS_CODES[res.statusCode]}`);
res.setEncoding(opt.encoding ? opt.encoding : "utf8");
if (MyAdapter.T(opt.status) === "array" && opt.status.indexOf(res.statusCode) < 0)
return reject(MyAdapter.D(`request for ${url.format(opt)} had status ${res.statusCode}/${http.STATUS_CODES[res.statusCode]} other than supported ${opt.status}`));
res.on("data", chunk => data += chunk)
.on("end", () => {
// res.removeAllListeners();
// req.removeAllListeners();
if (MyAdapter.T(transform) === "function")
data = transform(data);
if (opt.json) {
try {
return resolve(JSON.parse(data));
} catch (e) {
return err(`request JSON error ${MyAdapter.O(e)}`);
}
}
return resolve(data);
})
.on("error", e => err(e))
.on("close", () => err(`Connection closed before data was received!`));
}).on("error", e => err(e));
function err(e, msg) {
if (!msg)
msg = e;
// if (res) res.removeAllListeners();
// req && req.removeAllListeners();
// if (req && !req.aborted) req.abort();
// res && res.destroy();
// MyAdapter.Df('err in response: %s = %O', msg);
return reject(msg);
}
if (opt.timeout)
req.setTimeout(opt.timeout, () => err("request timeout Error: " + opt.timeout + "ms"));
req.on("error", (e) => err("request Error: " + MyAdapter.O(e)))
.on("aborted", (e) => err("request aborted: " + MyAdapter.O(e)));
// write data to request body
return req.end(value, opt.encoding ? opt.encoding : "utf8");
});
}
*/
static async get(url, retry) {
// get a web page either with http or https and return a promise for the data, could be done also with request but request is now an external package and http/https are part of nodejs.
const options = {};
if (typeof retry === "object") {
Object.assign(options, retry);
retry = options.retry || 1;
}
retry = this.toInteger(retry);
let res;
while (retry >= 0)
try {
res = await axios(url, options);
return res && res.data;
} catch (e) {
if (retry <= 0) return Promise.reject(e);
} finally {
--retry;
}
return null;
}
static equal(a, b) {
if (a == b) return true;
const ta = this.T(a),
tb = this.T(b);
if (ta === tb) {
if (ta === "array" || ta === "function" || ta === "object")
return JSON.stringify(a) === JSON.stringify(b);
} else if (
ta === "string" &&
(tb === "array" || tb === "function" || tb === "object") &&
a === this.O(b)
)
return true;
return false;
}
static get getMyStates() {
return states;
}
static async changeState(id, value, options) {
// this.If('ChangeState got called on %s with ack:%s = %O', id,ack,value)
options = options || {};
const always = options.always;
const ts = options.ts;
const ack = options.ack;
if (value === undefined) {
this.Wf(
"You tried to set state '%s' to 'undefined' with %j!",
id,
options
);
return null;
}
const stn = {
val: value,
ack: !!ack,
};
if (ts) stn.ts = ts;
let st = states[id]
? states[id]
: (states[id] = await adapter.getStateAsync(id).catch(() => undefined));
if (st && !always && this.equal(st.val, value) && st.ack === ack) return st;
await adapter
.setStateAsync(id, stn)
.catch(
(e) => (this.Wf("Error %j is setState for %s with %j", e, id, stn), stn)
);
if (states[id]) {
st = states[id];
st.val = value;
st.ack = ack;
if (ts) st.ts = ts;
} else states[id] = st = await adapter.getStateAsync(id);
this.Df("ChangeState ack:%s of %s = %s", !!ack, id, value);
return st;
}
static getClass(obj) {
if (typeof obj === "undefined") return "undefined";
if (obj === null) return "null";
const ret = Object.prototype.toString
.call(obj)
.match(/^\[object\s(.*)\]$/)[1];
// this.I(this.F('get class of ',obj, ' = ', ret));
return ret;
}
static async myGetState(id) {
if (states[id]) return states[id];
if (!id.startsWith(this.ain) && states[this.ain + id])
return states[this.ain + id];
let nid = sstate[id];
if (nid && states[nid]) return states[nid];
if (!nid) nid = id;
const s = await adapter.getForeignStateAsync(nid);
if (s) states[nid] = s;
return s;
}
static async makeState(ido, value, ack, always, define) {
// ack = ack === undefined || !!ack;
// this.Df(`Make State %s and set value to:%O ack:%s`,typeof ido === 'string' ? ido : ido.id,value,ack); ///TC
const options =
typeof ack == "object"
? ack
: {
ack,
};
if (always) options.always = always;
if (define) options.define = define;
let id = ido;
if (typeof id === "string")
ido = id.endsWith("Percent")
? {
unit: "%",
}
: {};
else if (typeof id.id === "string") {
id = id.id;
} else throw new Error(this.W(`Invalid makeState id: ${this.O(id)}`));
const idl = id.startsWith(this.ain) ? id : this.ain + id;
if ((!options.define || typeof ido !== "object") && createdStates[idl])
return this.changeState(id, value, options);
// this.Df(`Make State ack:%s %s = %s`, ack, id, value); ///TC
const st = {
common: {
name: id, // You can add here some description
read: true,
write: false,
state: "state",
role: "value",
type: this.T(value),
},
type: "state",
_id: idl,
};
if (options.common) Object.assign(st.common, options.common);
if (st.common.type === "object") st.common.type = "mixed";
for (const i in ido) {
if (i === "native") {
st.native = st.native || {};
for (const j in ido[i]) st.native[j] = ido[i][j];
} else if (i !== "id" && i !== "val") st.common[i] = ido[i];
}
if (st.common.write)
st.common.role = st.common.role.replace(/^value/, "level");
// this.I(`will create state:${id} with ${this.O(st)}`);
addSState(id, idl);
createdStates[idl] = id;
await adapter
.extendObjectAsync(idl, st, null)
.catch((e) => (this.Wf("error %j extend object %s", e, idl), null));
this.Df("created State %s", idl); // REM
if (st.common.state === "state" && !objects[this.ain + id]) {
objects[idl] = st;
}
return this.changeState(idl, value, options);
}
static async cleanup(name) {
// .then(() => A.I(A.F(A.sstate)))
// .then(() => A.I(A.F(A.ownKeysSorted(A.states))))
const res = await this.getObjects(name);
for (const item of res) {
// clean all states which are not part of the list
// this.I(`Check ${this.O(item)}`);
const id = item.id;
if (!id || !id.startsWith(this.ain) || createdStates[id])
// eslint-disable-next-line no-continue
continue;
// this.I(`check state ${item.id} and ${id}: ${states[item.id]} , ${states[id]}`);
if (states[id]) {
this.Df("Cleanup delete state %s", id);
await adapter.deleteStateAsync(id).catch(MyAdapter.nop);
}
// .catch(err => this.D(`Del State err: ${this.O(err)}`));
let found = false;
for (const cs of Object.keys(createdStates))
if (cs.startsWith(id + ".")) {
found = true;
break;
}
if (!found) {
this.Df("Cleanup delete object %s", id);
await adapter.delObjectAsync(id).catch(MyAdapter.nop);
}
// .catch(err => this.D(`Del Object err: ${this.O(err)}`)); ///TC
await this.wait(10);
}
}
static isLinuxApp(name) {
if (os.platform() !== "linux") return false;
return this.exec("!which " + name)
.then((x) => x.length >= name.length)
.catch(() => false);
}
}
MyAdapter.Sequence = Sequence;
//MyAdapter.Setter = Setter;
MyAdapter.CacheP = CacheP;
MyAdapter.HrTime = HrTime;
module.exports = MyAdapter;