UNPKG

iobroker.nspanel-lovelace-ui

Version:

NsPanel Lovelace UI is a Firmware for the nextion screen inside of NSPanel in the Design of Lovelace UI Design.

667 lines (666 loc) 23.6 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var states_controller_exports = {}; __export(states_controller_exports, { StatesControler: () => StatesControler }); module.exports = __toCommonJS(states_controller_exports); var import_data_item = require("../classes/data-item"); var import_library = require("../classes/library"); var import_tools = require("../const/tools"); class StatesControler extends import_library.BaseClass { triggerDB = {}; deletePageInterval; stateDB = {}; objectDatabase = {}; intervalObjectDatabase; timespan; constructor(adapter, name = "", timespan = 15e3) { super(adapter, name || "StatesDB"); this.timespan = timespan; this.deletePageInterval = this.adapter.setInterval(this.deletePageLoop, 6e4); this.intervalObjectDatabase = this.adapter.setInterval(() => { if (this.unload) { return; } this.objectDatabase = {}; }, 18e5); } deletePageLoop = () => { var _a, _b; const removeId = []; for (const id in this.triggerDB) { const entry = this.triggerDB[id]; const removeIndex = []; for (let i = 0; i < entry.to.length; i++) { const item = entry.to[i]; if (item.unload) { removeIndex.push(Number(i)); } else if ((_b = (_a = item.parent) == null ? void 0 : _a.panel) == null ? void 0 : _b.unload) { removeIndex.push(Number(i)); } } for (const i of removeIndex) { for (const key in entry) { const k = key; const item = entry[k]; if (Array.isArray(item)) { item.splice(i, 1); } } } if (entry.to.length === 0 && !entry.internal) { removeId.push(id); } } for (const id of removeId) { delete this.triggerDB[id]; } }; async delete() { await super.delete(); if (StatesControler.tempObjectDBTimeout) { this.adapter.clearTimeout(StatesControler.tempObjectDBTimeout); } if (this.intervalObjectDatabase) { this.adapter.clearInterval(this.intervalObjectDatabase); } if (this.deletePageInterval) { this.adapter.clearInterval(this.deletePageInterval); } } /** * Set a subscript to an foreignState and write current state/value to db * * @param id state id * @param from the page that handle the trigger * @param internal if the state is internal * @param trigger if the state should trigger other classes * @param change if the state should trigger other classes */ async setTrigger(id, from, internal = false, trigger = true, change) { if (id.startsWith(this.adapter.namespace)) { this.log.warn(`Id: ${id} refers to the adapter's own namespace, this is not allowed!`); return; } if (this.triggerDB[id] !== void 0) { const index = this.triggerDB[id].to.findIndex((a) => a == from); if (index === -1) { this.triggerDB[id].to.push(from); this.triggerDB[id].subscribed.push(false); this.triggerDB[id].triggerAllowed.push(trigger); this.triggerDB[id].change.push(change ? change : "ne"); this.log.debug(`Add a trigger for ${from.name} to ${id}`); } else { } } else if (internal) { this.log.error("setInternal Trigger too early"); } else { this.triggerDB[id] = { state: { val: null, ack: false, ts: Date.now(), from: "", lc: Date.now() }, to: [from], ts: Date.now(), subscribed: [false], common: { name: id, type: "number", role: "state", write: false, read: true }, triggerAllowed: [trigger], change: [change ? change : "ne"] }; const state = await this.adapter.getForeignStateAsync(id); if (state) { const obj = await this.getObjectAsync(id); if (!obj || !obj.common || obj.type !== "state") { throw new Error(`Got invalid object for ${id}`); } this.triggerDB[id].state = state; this.triggerDB[id].common = obj.common; await this.adapter.subscribeForeignStatesAsync(id); if (this.stateDB[id] !== void 0) { delete this.stateDB[id]; } this.log.debug(`Set a new trigger for ${from.panel.name}.${from.name} to ${id}`); } else { delete this.triggerDB[id]; } } } /** * Activate the triggers of a page. First subscribes to the state. * * @param to Page */ async activateTrigger(to) { if (!to) { return; } for (const id in this.triggerDB) { const entry = this.triggerDB[id]; const index = entry.to.indexOf(to); if (index === -1) { continue; } if (entry.subscribed[index]) { continue; } if (!entry.triggerAllowed[index]) { continue; } if (!entry.subscribed.some((a) => a)) { entry.subscribed[index] = true; await this.adapter.subscribeForeignStatesAsync(id); const state = await this.adapter.getForeignStateAsync(id); if (state) { entry.state = state; } } entry.subscribed[index] = true; } } /** * Deactivate the triggers of a page. Last unsubscribes to the state. * * @param to Page */ async deactivateTrigger(to) { for (const id in this.triggerDB) { if (to.neverDeactivateTrigger) { continue; } const entry = this.triggerDB[id]; if (entry.internal) { continue; } const index = entry.to.indexOf(to); if (index === -1) { continue; } if (!entry.subscribed[index]) { continue; } entry.subscribed[index] = false; this.log.debug(`Deactivate trigger from ${to.name} to ${id}`); if (!entry.subscribed.some((a) => a)) { await this.adapter.unsubscribeForeignStatesAsync(id); } } } async getStateVal(id) { var _a; try { const state = await this.getState(id); if (state) { return (_a = state.val) != null ? _a : null; } } catch (e) { this.log.error(`Error 1004: ${typeof e === "string" ? e.replaceAll("Error: ", "") : e}`); } return null; } /** * Read a state from DB or js-controller * * @param id state id with namespace * @param internal if the state is internal * @returns nsPanelState or null */ async getState(id, internal = false) { let timespan = this.timespan; timespan = 10; if (this.triggerDB[id] !== void 0 && (this.triggerDB[id].internal || this.triggerDB[id].subscribed.some((a) => a))) { let state = null; const f = this.triggerDB[id].f; if (f) { state = { ...this.triggerDB[id].state, val: await f(id, void 0) }; } else { state = this.triggerDB[id].state; } return state; } else if (this.stateDB[id] && timespan) { if (Date.now() - timespan - this.stateDB[id].ts < 0) { return this.stateDB[id].state; } } if (id.includes("/")) { internal = true; } if (!internal) { const state = await this.adapter.getForeignStateAsync(id); if (state) { if (!this.stateDB[id]) { const obj = await this.getObjectAsync(id); if (!obj || !obj.common || obj.type !== "state") { throw new Error(`Got invalid object for ${id}`); } this.stateDB[id] = { state, ts: Date.now(), common: obj.common }; } else { this.stateDB[id].state = state; this.stateDB[id].ts = Date.now(); } return state; } } throw new Error(`State id invalid ${id} no data!`); } getType(id) { if (!id) { return void 0; } if (this.triggerDB[id] !== void 0 && this.triggerDB[id].common) { return this.triggerDB[id].common.type; } if (this.stateDB[id] !== void 0) { return this.stateDB[id].common.type; } return void 0; } async getCommonStates(id, force = false) { if (!id) { return void 0; } let j = void 0; if (force) { const obj = await this.adapter.getObjectAsync(id); if (obj && obj.common && obj.common.states) { j = obj.common.state; } } else if (this.triggerDB[id] !== void 0 && this.triggerDB[id].common) { j = this.triggerDB[id].common.states; } else if (this.stateDB[id] !== void 0 && this.stateDB[id].common) { j = this.stateDB[id].common.states; } if (!j || typeof j === "string") { return void 0; } if (Array.isArray(j)) { const a = {}; j.forEach((e, i) => a[String(i)] = e); j = a; } return j; } /** * Check if the trigger should trigger other classes. dont check if object has a active subscription. this is done in next step with visible & neverDeactiveTrigger * * @param dp internal/external * @param state iobroker state */ async onStateChange(dp, state) { if (dp && state) { if (this.triggerDB[dp] && this.triggerDB[dp].state) { this.log.debug(`Trigger from ${dp} with state ${JSON.stringify(state)}`); this.triggerDB[dp].ts = Date.now(); const oldState = { val: this.triggerDB[dp].state.val, ack: this.triggerDB[dp].state.ack }; this.triggerDB[dp].state = state; if (state.ack || this.triggerDB[dp].internal || dp.startsWith("0_userdata.0") || dp.startsWith("alias.0")) { for (let i = 0; i < this.triggerDB[dp].to.length; i++) { const c = this.triggerDB[dp].to[i]; if (oldState.val !== state.val || oldState.ack !== state.ack || this.triggerDB[dp].change[i] === "ts") { if (!c.neverDeactivateTrigger && !this.triggerDB[dp].subscribed[i] || !this.triggerDB[dp].triggerAllowed[i]) { if (i === this.triggerDB[dp].to.length - 1) { this.log.debug(`Ignore trigger from state ${dp} not subscribed or not allowed!`); this.log.debug( `c: ${c.name} !c.neverDeactivateTrigger: ${!c.neverDeactivateTrigger} && !this.triggerDB[dp].subscribed[i]: ${!this.triggerDB[dp].subscribed[i]} || !this.triggerDB[dp].triggerAllowed[i]: ${!this.triggerDB[dp].triggerAllowed[i]}` ); } continue; } if (c.parent && c.triggerParent && !c.parent.unload && !c.parent.sleep) { c.parent.onStateTriggerSuperDoNotOverride && await c.parent.onStateTriggerSuperDoNotOverride(dp, c); } else if (!c.unload) { c.onStateTriggerSuperDoNotOverride && await c.onStateTriggerSuperDoNotOverride(dp, c); } } else { this.log.debug(`Ignore trigger from state ${dp} no change!`); } } } else { this.log.debug(`Ignore trigger from state ${dp} ack is false!`); } } if (state.val === null || state.val === void 0 || typeof state.val !== "object") { if (dp.startsWith(this.adapter.namespace)) { const id = dp.replace(`${this.adapter.namespace}.`, ""); const libState = this.library.readdb(id); if (libState) { this.library.setdb(id, { ...libState, val: state.val, ts: state.ts, ack: state.ack }); } if (libState && libState.obj && libState.obj.common && libState.obj.common.write && this.adapter.controller) { for (const panel of this.adapter.controller.panels) { await panel.onStateChange(id, state); } } } if (dp.startsWith("system.host")) { this.adapter.controller && await this.adapter.controller.systemNotification.onStateChange(dp, state); } } } } async setStateAsync(item, val, writeable) { if (item.options.type === "state" || item.options.type === "triggered") { if (item.options.dp) { const ack = item.options.dp.startsWith(this.adapter.namespace); this.log.debug(`setStateAsync(${item.options.dp}, ${val}, ${ack})`); if (item.trueType() === "number" && typeof val === "string") { val = parseFloat(val); } else if (item.trueType() === "number" && typeof val === "boolean") { val = val ? 1 : 0; } else if (item.trueType() === "boolean") { val = !!val; } if (item.trueType() === "string") { val = String(val); } if (writeable) { await this.adapter.setForeignStateAsync(item.options.dp, val, ack); } else { this.log.error(`Forbidden write attempts on a read-only state! id: ${item.options.dp}`); } } } else if (item.options.type === "internal" || item.options.type === "internalState") { if (this.triggerDB[item.options.dp]) { await this.setInternalState(item.options.dp, val, false); } } } /** * Set a internal state and trigger * * @param id something like 'cmd/blabla' * @param val Value * @param ack false use value/ true use func * @param common optional for first call * @param func optional for first call * @returns true if set */ async setInternalState(id, val, ack = false, common = void 0, func = void 0) { var _a; if (this.triggerDB[id] !== void 0) { const f = this.triggerDB[id].f; const newState = { ...this.triggerDB[id].state, // if ack and function take value of function otherwise val val: ack && f ? (_a = await f(id, void 0)) != null ? _a : val : val, ack, ts: Date.now() }; await this.onStateChange(id, newState); f && await f(id, this.triggerDB[id].state); return true; } else if (common) { this.log.debug(`Add internal state ${id} with ${JSON.stringify(common)}`); this.triggerDB[id] = { state: { ts: Date.now(), val: null, ack, from: "", lc: Date.now() }, to: [], ts: Date.now(), subscribed: [], common, internal: true, f: func, triggerAllowed: [], change: [] }; } return false; } /** * Create dataitems from a json (deep) * * @param data Json with configuration to create dataitems * @param parent Page etc. * @param target optional target * @param path optional path * @param options so far only constant to use in getState().read * @returns then json with values dataitem or undefined */ async createDataItems(data, parent, target = {}, path = "data", options) { var _a; for (const i in data) { const d = data[i]; if (d === void 0) { continue; } if (typeof d === "object" && !("type" in d)) { target[i] = await this.createDataItems( d, parent, ((_a = target[i]) != null ? _a : Array.isArray(d)) ? [] : {}, `${path}.${i}`, options ); } else if (typeof d === "object" && "type" in d) { target[i] = data[i] !== void 0 ? new import_data_item.Dataitem( this.adapter, { ...d, name: `${this.name}.${parent.name}.${i}.${path}`, constants: options }, parent, this ) : void 0; if (target[i] !== void 0 && !await target[i].isValidAndInit()) { target[i] = void 0; } } } if (Object.keys(target).length === 0) { return void 0; } return target; } /** * Temporäes Datenobject zum speichern aller Enums und Objecte */ static TempObjectDB = { data: void 0, keys: [], enums: void 0 }; static tempObjectDBTimeout; static getTempObjectDB(adapter) { if (StatesControler.tempObjectDBTimeout) { adapter.clearTimeout(StatesControler.tempObjectDBTimeout); } StatesControler.tempObjectDBTimeout = adapter.setTimeout(() => { if (adapter.unload) { return; } StatesControler.tempObjectDBTimeout = void 0; StatesControler.TempObjectDB = { data: void 0, keys: [], enums: void 0 }; }, 1e4); return StatesControler.TempObjectDB; } /** * Filterfunktion umso genauer die Filter um so weniger Ressourcen werden verbraucht. * * @param dpInit string RegExp oder '' für aus; string wird mit include verwendet. * @param enums string, string[], RegExp als String übergeben oder ein String der mit include verwenden wird. * @returns 2 arrays keys: gefilterte keys und data: alle Objekte... */ async getFilteredObjects(dpInit, enums) { const tempObjectDB = StatesControler.getTempObjectDB(this.adapter); if (!tempObjectDB.data) { tempObjectDB.data = await this.adapter.getForeignObjectsAsync(`*`); if (!tempObjectDB.data) { throw new Error("getObjects fail. Critical Error!"); } tempObjectDB.keys = Object.keys(tempObjectDB.data); const temp = await this.adapter.getEnumsAsync(["rooms", "functions"]); tempObjectDB.enums = Object.assign(temp["enum.rooms"], temp["enum.functions"]); } const result = { data: tempObjectDB.data, keys: tempObjectDB.keys }; if (dpInit) { if (typeof dpInit !== "string") { result.keys = tempObjectDB.keys.filter((a) => a.match(dpInit) !== null); } else { result.keys = tempObjectDB.keys.filter((a) => a.includes(dpInit)); } } if (enums && tempObjectDB.enums) { if (typeof enums === "string") { enums = [enums]; } let r; for (const e of enums) { const regexp = (0, import_tools.getRegExp)(e); let t = []; for (const a in tempObjectDB.enums) { if (!regexp && a.includes(e) || regexp && a.match(regexp) !== null) { if (tempObjectDB.enums[a] && tempObjectDB.enums[a].common && tempObjectDB.enums[a].common.members) { t = t.concat(tempObjectDB.enums[a].common.members); } } } if (!r) { r = t; } else { r = r.filter((a) => t.indexOf(a) !== -1); } } result.keys = result.keys.filter((a) => r && r.some((b) => a.startsWith(b))); } return result; } /** * Retrieves the ID of a state automatically based on the provided parameters. * * @param dpInit - The initial data point, which can be a string or a regular expression. * @param role - The role of the state, which can be a single StateRole or an array of StateRoles. * @param enums - The enums associated with the state, which can be a single string or an array of strings. * @param regexp - The regular expression to match the state ID. * @param triggered - Whether the state is triggered. * @param writeable - Whether the state is writeable. * @param commonType - The common type of the state. * @returns A promise that resolves to the ID of the state if found, otherwise undefined. */ async getIdbyAuto(dpInit = "", role = "", enums = "", regexp, triggered, writeable, commonType) { const status = { ok: true }; let item; if (triggered) { item = { type: "triggered", role, dp: "", mode: "auto", regexp, writeable, commonType }; } else { item = { type: "state", role, dp: "", mode: "auto", regexp, writeable, commonType }; } const data = await this.getDataItemsFromAuto(dpInit, { item }, "", enums, status, true); if (status.ok && data && data.item && data.item.dp) { return item; } return void 0; } async getDataItemsFromAuto(dpInit, data, appendix, enums = "", status, ignoreMultiple = false) { if (dpInit === "" && enums === void 0) { return data; } const tempObjectDB = await this.getFilteredObjects(dpInit, enums); if (tempObjectDB.data) { for (const i in data) { const t = data[i]; if (t === void 0) { continue; } if (typeof t === "object" && !("type" in t)) { data[i] = await this.getDataItemsFromAuto(dpInit, t, appendix, enums, status); } else if (typeof t === "object" && "type" in t) { const d = t; let found = false; if (d.type !== "triggered" && d.type !== "state" || !d.mode || d.mode !== "auto") { continue; } if (tempObjectDB.keys.length === 0) { this.log.warn(`Dont find states for ${dpInit}!`); } for (const role of Array.isArray(d.role) ? d.role : [d.role]) { for (const commonType of Array.isArray(d.commonType) ? d.commonType : [d.commonType || ""]) { for (const id of tempObjectDB.keys) { const obj = tempObjectDB.data[id]; if (obj && obj.common && obj.type === "state" && (d.dp === "" || id.includes(d.dp)) && (role === "" || obj.common.role === role) && (!commonType || obj.common.type === commonType) && (!d.writeable || obj.common.write === d.writeable) && (!d.regexp || id.match(d.regexp) !== null)) { if (found) { if (!ignoreMultiple) { this.log.warn( `Found more as 1 state for role ${role} in ${dpInit} with .dp: ${d.dp ? d.dp.toString() : "empty"} and .regexp: ${d.regexp ? d.regexp.toString() : "empty"}` ); } break; } d.dp = id; d.mode = "done"; found = true; } } if (found) { break; } } if (found) { break; } } if (!found) { if (d.required) { status && (status.ok = false); this.log.warn( `No state found for role ${JSON.stringify(d.role)} in ${dpInit.toString()} with with .dp: ${d.dp ? d.dp.toString() : "empty"} and .regexp: ${d.regexp ? d.regexp.toString() : "empty"}` ); } data[i] = void 0; } } } } return data; } async getObjectAsync(id) { if (this.objectDatabase[id] !== void 0) { return this.objectDatabase[id]; } else if (this.triggerDB[id] !== void 0 && this.triggerDB[id].internal) { return { _id: "", type: "state", common: this.triggerDB[id].common, native: {} }; } const obj = await this.adapter.getForeignObjectAsync(id); this.objectDatabase[id] = obj != null ? obj : null; return obj != null ? obj : null; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { StatesControler }); //# sourceMappingURL=states-controller.js.map