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.
986 lines (985 loc) • 35 kB
JavaScript
;
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("./data-item");
var import_library = require("./library");
var import_tools = require("../const/tools");
class StatesControler extends import_library.BaseClass {
triggerDB = {};
// Performance-optimized lookup maps
targetToTriggerMap = /* @__PURE__ */ new Map();
activeTriggerCount = /* @__PURE__ */ new Map();
/**
* Holds subscriptions created while the subscription system is temporarily blocked.
*
* - `undefined`: blocking is not active (normal mode, new subscriptions are applied immediately).
* - `string[]`: blocking is active; new subscriptions are collected here until the block is lifted.
*/
blockedSubscriptions;
deletePageInterval;
stateDB = {};
objectDatabase = {};
intervalObjectDatabase;
timespan;
constructor(adapter, name = "", timespan = 500) {
super(adapter, name || "StatesDB");
this.timespan = timespan;
this.deletePageInterval = this.adapter.setInterval(async () => {
void this.deletePageLoop();
}, 18e4);
this.intervalObjectDatabase = this.adapter.setInterval(() => {
if (this.unload || this.adapter.unload) {
return;
}
this.objectDatabase = {};
this.cleanupStateDB();
}, 18e4);
}
deletePageLoop = async (f) => {
var _a, _b, _c, _d, _e;
const removeIds = [];
for (const id in this.triggerDB) {
const entry = this.triggerDB[id];
const removeIdx = [];
if (f && entry.f === f) {
removeIds.push(id);
continue;
}
for (let i = 0; i < entry.to.length; i++) {
const it = entry.to[i];
if ((it == null ? void 0 : it.unload) || ((_a = it == null ? void 0 : it.parent) == null ? void 0 : _a.unload) || ((_c = (_b = it == null ? void 0 : it.parent) == null ? void 0 : _b.basePanel) == null ? void 0 : _c.unload) || ((_e = (_d = it == null ? void 0 : it.parent) == null ? void 0 : _d.parent) == null ? void 0 : _e.unload)) {
removeIdx.push(i);
}
}
if (removeIdx.length > 0) {
removeIdx.sort((a, b) => b - a);
const arrayProperties = ["to", "subscribed", "triggerAllowed", "change"];
for (const idx of removeIdx) {
for (const prop of arrayProperties) {
const arr = entry[prop];
if (arr && Array.isArray(arr) && idx >= 0 && idx < arr.length) {
arr.splice(idx, 1);
}
}
}
for (const idx of removeIdx) {
const target = entry.to[idx];
if (target) {
this.removeFromTargetMap(target, id);
}
}
}
if (entry.to.length === 0 && !entry.internal) {
removeIds.push(id);
}
}
this.blockedSubscriptions = [];
for (const id of removeIds) {
if (!id.startsWith(this.adapter.namespace)) {
if (!id.startsWith(this.adapter.namespace)) {
await this.adapter.unsubscribeForeignStatesAsync(id);
}
}
delete this.triggerDB[id];
this.activeTriggerCount.delete(id);
}
while (this.blockedSubscriptions && this.blockedSubscriptions.length > 0) {
for (let idx = this.blockedSubscriptions.length - 1; idx >= 0; idx--) {
if (idx >= this.blockedSubscriptions.length) {
continue;
}
const id = this.blockedSubscriptions[idx];
if (!id.startsWith(this.adapter.namespace)) {
await this.adapter.subscribeForeignStatesAsync(id);
}
this.blockedSubscriptions.splice(idx, 1);
}
}
this.blockedSubscriptions = void 0;
};
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);
}
this.targetToTriggerMap.clear();
this.activeTriggerCount.clear();
}
// Performance optimization helper methods
addToTargetMap(target, triggerId) {
if (!this.targetToTriggerMap.has(target)) {
this.targetToTriggerMap.set(target, /* @__PURE__ */ new Set());
}
this.targetToTriggerMap.get(target).add(triggerId);
}
removeFromTargetMap(target, triggerId) {
const triggers = this.targetToTriggerMap.get(target);
if (triggers) {
triggers.delete(triggerId);
if (triggers.size === 0) {
this.targetToTriggerMap.delete(target);
}
}
}
incrementActiveTrigger(triggerId) {
const current = this.activeTriggerCount.get(triggerId) || 0;
this.activeTriggerCount.set(triggerId, current + 1);
}
decrementActiveTrigger(triggerId) {
const current = this.activeTriggerCount.get(triggerId) || 0;
if (current <= 1) {
this.activeTriggerCount.delete(triggerId);
} else {
this.activeTriggerCount.set(triggerId, current - 1);
}
}
/**
* Cleanup expired entries from stateDB to prevent memory leaks
* OPTIMIERT: Regelmäßige Bereinigung des Cache
*/
cleanupStateDB() {
const now = Date.now();
const expiredIds = [];
for (const id in this.stateDB) {
const entry = this.stateDB[id];
const age = now - entry.ts;
if (age > this.timespan * 2) {
expiredIds.push(id);
}
}
for (const id of expiredIds) {
delete this.stateDB[id];
}
if (expiredIds.length > 0 && this.adapter.config.debugLogStates) {
this.log.debug(`Cleaned up ${expiredIds.length} expired stateDB entries`);
}
}
/**
* Registriert einen Trigger auf einen *fremden* State (nicht im eigenen Namespace)
* und initialisiert die Trigger-Datenbank inkl. Abo & aktuellem Wert.
*
* Hinweise:
* - Eigene States (im Adapter-Namespace) sind hier verboten.
* - Bei bereits existierendem Eintrag wird der Empfänger nur ergänzt.
*
* @param id Fremd-State-ID
* @param from Auslösende/abonniert-werdende Klasse
* @param internal true = interner Trigger (kein Fremd-Abo erwartet)
* @param trigger ob dieser Empfänger durch Änderungen ausgelöst werden darf
* @param change optional: 'ts' → löse auch ohne Wert-/Ack-Änderung (Zeitstempel)
*/
async setTrigger(id, from, internal = false, trigger = true, change) {
if (id.startsWith(this.adapter.namespace) && !(id.includes(".alarm.") && (id.endsWith(".approve") || id.endsWith(".status")))) {
this.log.warn(`Id: ${id} refers to the adapter's own namespace, this is not allowed!`);
return;
}
const existing = this.triggerDB[id];
if (existing) {
const idx = existing.to.indexOf(from);
if (idx === -1) {
existing.to.push(from);
existing.subscribed.push(false);
existing.triggerAllowed.push(trigger);
existing.change.push(change != null ? change : "ne");
this.addToTargetMap(from, id);
if (this.adapter.config.debugLogStates) {
this.log.debug(`Add a trigger for ${from.name} to ${id}`);
}
}
return;
}
if (internal) {
this.log.error("setInternal Trigger too early");
return;
}
this.triggerDB[id] = {
state: { val: null, ack: false, ts: 0, from: "", lc: 0 },
to: [from],
ts: Date.now(),
subscribed: [false],
common: { name: id, type: "number", role: "state", write: false, read: true },
triggerAllowed: [trigger],
change: [change != null ? change : "ne"],
internal: false
};
try {
const obj = await this.getObjectAsync(id);
if (!obj || obj.type !== "state" || !obj.common) {
delete this.triggerDB[id];
throw new Error(`Got invalid object for ${id}`);
}
this.triggerDB[id].common = obj.common;
if (this.unload || this.adapter.unload) {
return;
}
if (this.blockedSubscriptions) {
if (!this.blockedSubscriptions.includes(id)) {
this.blockedSubscriptions.push(id);
}
} else {
if (!id.startsWith(this.adapter.namespace)) {
await this.adapter.subscribeForeignStatesAsync(id);
}
}
if (this.adapter.config.debugLogStates) {
this.log.debug(`Set a new trigger for ${from.basePanel.name}.${from.name} to ${id}`);
}
this.addToTargetMap(from, id);
const state = await this.adapter.getForeignStateAsync(id);
if (state) {
this.triggerDB[id].state = state;
}
if (this.stateDB[id]) {
delete this.stateDB[id];
}
} catch (err) {
delete this.triggerDB[id];
throw err;
}
}
/**
* Activate the triggers of a pageItem for self or parent. First subscribes to the state.
* OPTIMIERT: Verwende performance maps für bessere Effizienz
*
* @param to Page
*/
async activateTrigger(to) {
if (!to) {
return;
}
const triggerIds = this.targetToTriggerMap.get(to);
if (triggerIds) {
for (const id of triggerIds) {
const entry = this.triggerDB[id];
if (!entry) {
continue;
}
const index = entry.to.indexOf(to);
if (index === -1) {
continue;
}
if (entry.subscribed[index]) {
continue;
}
if (!entry.triggerAllowed[index]) {
continue;
}
const hasActiveSubscription = entry.subscribed.some((a) => a);
if (!hasActiveSubscription) {
entry.subscribed[index] = true;
this.incrementActiveTrigger(id);
if (!id.startsWith(this.adapter.namespace)) {
await this.adapter.subscribeForeignStatesAsync(id);
}
const state = await this.adapter.getForeignStateAsync(id);
if (state) {
entry.state = state;
}
} else {
entry.subscribed[index] = true;
this.incrementActiveTrigger(id);
}
}
}
for (const id in this.triggerDB) {
const entry = this.triggerDB[id];
const index = entry.to.findIndex((a) => a.parent && a.parent === to);
if (index === -1) {
continue;
}
if (entry.subscribed[index]) {
continue;
}
if (!entry.triggerAllowed[index]) {
continue;
}
const hasActiveSubscription = entry.subscribed.some((a) => a);
if (!hasActiveSubscription) {
entry.subscribed[index] = true;
this.incrementActiveTrigger(id);
if (!id.startsWith(this.adapter.namespace)) {
await this.adapter.subscribeForeignStatesAsync(id);
}
const state = await this.adapter.getForeignStateAsync(id);
if (state) {
entry.state = state;
}
} else {
entry.subscribed[index] = true;
this.incrementActiveTrigger(id);
}
}
}
/**
* Deactivate the triggers of a pageItem for self or parent page. Last unsubscribes to the state.
* OPTIMIERT: Verwende performance maps und effizientere Suche
*
* @param to Page
*/
async deactivateTrigger(to) {
if (to.neverDeactivateTrigger) {
return;
}
const triggerIds = this.targetToTriggerMap.get(to);
if (triggerIds) {
for (const id of triggerIds) {
const entry = this.triggerDB[id];
if (!entry || entry.internal) {
continue;
}
const index = entry.to.indexOf(to);
if (index === -1 || !entry.subscribed[index]) {
continue;
}
const indexParent = entry.to.findIndex((a) => a.parent && a.parent === to);
if (indexParent !== -1 && entry.subscribed[indexParent]) {
continue;
}
entry.subscribed[index] = false;
this.decrementActiveTrigger(id);
if (this.adapter.config.debugLogStates) {
this.log.debug(`Deactivate trigger from ${to.name} to ${id}`);
}
const hasActiveSubscriptions = entry.subscribed.some((a) => a);
if (!hasActiveSubscriptions) {
if (this.blockedSubscriptions) {
const idx = this.blockedSubscriptions.indexOf(id);
if (idx !== -1) {
this.blockedSubscriptions.splice(idx, 1);
}
}
if (!id.startsWith(this.adapter.namespace)) {
await this.adapter.unsubscribeForeignStatesAsync(id);
}
}
}
}
for (const id in this.triggerDB) {
const entry = this.triggerDB[id];
if (entry.internal) {
continue;
}
const index = entry.to.indexOf(to);
if (index === -1 || !entry.subscribed[index]) {
continue;
}
const triggerIdsForTarget = this.targetToTriggerMap.get(to);
if (triggerIdsForTarget && triggerIdsForTarget.has(id)) {
continue;
}
const indexParent = entry.to.findIndex((a) => a.parent && a.parent === to);
if (indexParent !== -1 && entry.subscribed[indexParent]) {
continue;
}
entry.subscribed[index] = false;
this.decrementActiveTrigger(id);
if (this.adapter.config.debugLogStates) {
this.log.debug(`Deactivate trigger from ${to.name} to ${id}`);
}
const hasActiveSubscriptions = entry.subscribed.some((a) => a);
if (!hasActiveSubscriptions) {
if (this.blockedSubscriptions) {
const idx = this.blockedSubscriptions.indexOf(id);
if (idx !== -1) {
this.blockedSubscriptions.splice(idx, 1);
}
}
if (!id.startsWith(this.adapter.namespace)) {
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
* OPTIMIERT: Verbesserte Cache-Strategie und Performance
*
* @param id state id with namespace
* @param internal if the state is internal
* @returns nsPanelState or null
*/
async getState(id, internal = false) {
const triggerEntry = this.triggerDB[id];
if (triggerEntry && (triggerEntry.internal || triggerEntry.subscribed.some((a) => a))) {
let state = null;
const f = triggerEntry.f;
if (f) {
state = {
...triggerEntry.state,
val: await f(id, void 0)
};
} else {
state = triggerEntry.state;
}
return state;
}
const cachedEntry = this.stateDB[id];
if (cachedEntry) {
const age = Date.now() - cachedEntry.ts;
if (age < this.timespan) {
return cachedEntry.state;
}
delete this.stateDB[id];
}
if (id.includes("/")) {
internal = true;
}
if (!internal) {
try {
const state = await this.adapter.getForeignStateAsync(id);
if (state != null) {
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;
}
if (state === null) {
return null;
}
} catch (e) {
this.log.error(`Error 1005: ${typeof e === "string" ? e.replaceAll("Error: ", "") : e}`);
return null;
}
}
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 null;
}
let j = void 0;
if (force) {
const obj = await this.adapter.getForeignObjectAsync(id);
if (obj && obj.common && obj.common.states) {
j = obj.common.states;
if (this.triggerDB[id] !== void 0 && this.triggerDB[id].common.states) {
this.triggerDB[id].common.states = j;
}
if (this.stateDB[id] !== void 0 && this.stateDB[id].common.states) {
this.stateDB[id].common.states = j;
}
}
} 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 null;
}
if (Array.isArray(j)) {
const a = {};
j.forEach((e, i) => a[String(i)] = e);
j = a;
}
return j;
}
isStateValue(v) {
return v === null || v === void 0 || typeof v === "string" || typeof v === "number" || typeof v === "boolean";
}
/**
* Handle incoming state changes from ioBroker.
*
* Performance-optimized version for high-frequency calls (1000s per minute).
* Responsibilities:
* - Update the internal triggerDB entry for the given datapoint.
* - Decide whether the state change should trigger dependent classes.
* - Forward primitive values to library cache and panels if required.
* - Forward system.host changes to systemNotification.
*
* Notes:
* - Active subscriptions (visible & neverDeactivateTrigger) are checked later, not here.
* - Object values are ignored (only primitive values are cached/forwarded).
*
* @param dp Datapoint ID (internal/external state id)
* @param state New ioBroker state object or null/undefined
*/
async onStateChange(dp, state) {
var _a, _b, _c, _d;
if (!dp || !state) {
return;
}
const adapterNamespace = this.adapter.namespace;
const debugLogEnabled = this.adapter.config.debugLogStates;
const startsWithUserdata = dp.startsWith("0_userdata.0");
const startsWithAlias = dp.startsWith("alias.0");
const startsWithNamespace = dp.startsWith(adapterNamespace);
const entry = this.triggerDB[dp];
if (entry == null ? void 0 : entry.state) {
if (debugLogEnabled) {
this.log.debug(`Trigger from ${dp} with state ${JSON.stringify(state)}`);
}
const oldVal = entry.state.val;
const oldAck = entry.state.ack;
const oldTs = entry.state.ts;
const oldFrom = entry.state.from;
const oldLc = entry.state.lc;
entry.ts = Date.now();
entry.state = state;
const isSystemOrAlias = startsWithUserdata || startsWithAlias || !state.ack && startsWithNamespace;
const mayTrigger = state.ack || entry.internal || isSystemOrAlias;
if (mayTrigger) {
const to = entry.to;
const toLength = to.length;
if (toLength === 0) {
return;
}
const changes = entry.change || [];
const subscribed = entry.subscribed || [];
const allowed = entry.triggerAllowed || [];
const hasValChange = oldVal !== state.val;
const hasAckChange = oldAck !== state.ack;
let oldState;
for (let i = 0; i < toLength; i++) {
const target = to[i];
if (target.unload) {
continue;
}
const hasChange = hasValChange || hasAckChange || changes[i] === "ts";
if (!hasChange) {
if (debugLogEnabled) {
this.log.debug(`Ignore trigger from state ${dp} no change!`);
}
continue;
}
if (!oldState) {
oldState = {
val: oldVal,
ack: oldAck,
from: oldFrom,
ts: oldTs,
lc: oldLc
};
}
await target.onStateChange(dp, { old: oldState, new: state });
const isSubscribed = target.neverDeactivateTrigger || subscribed[i];
const isAllowed = allowed[i];
if (!isSubscribed || !isAllowed) {
if (debugLogEnabled && i === toLength - 1) {
this.log.debug(`Ignore trigger from state ${dp} not subscribed or not allowed!`);
this.log.debug(
`c: ${target.name} !c.neverDeactivateTrigger: ${!target.neverDeactivateTrigger} && !subscribed[${i}]: ${!subscribed[i]} || !allowed[${i}]: ${!allowed[i]}`
);
}
continue;
}
const parent = target.parent;
if (parent && target.triggerParent && !parent.unload && !parent.sleep) {
await ((_a = parent.onStateTriggerSuperDoNotOverride) == null ? void 0 : _a.call(parent, dp, target));
} else {
await ((_b = target.onStateTriggerSuperDoNotOverride) == null ? void 0 : _b.call(target, dp, target));
}
}
} else if (debugLogEnabled) {
this.log.debug(`Ignore trigger from state ${dp} ack is false!`);
}
} else if (this.stateDB[dp]) {
const stateEntry = this.stateDB[dp];
stateEntry.state = state;
stateEntry.ts = state.ts;
}
const v = state.val;
if (v !== null && v !== void 0 && typeof v === "object") {
return;
}
if (!state.ack && startsWithNamespace) {
const namespacePrefix = `${adapterNamespace}.`;
const id = dp.slice(namespacePrefix.length);
const libState = this.library.readdb(id);
if (libState) {
this.library.setdb(id, {
...libState,
val: this.isStateValue(state.val) ? state.val : null,
ts: state.ts,
ack: state.ack
});
if (((_d = (_c = libState.obj) == null ? void 0 : _c.common) == null ? void 0 : _d.write) && this.adapter.controller) {
const panels = this.adapter.controller.panels;
for (const panel of panels) {
await panel.onStateChange(id, state);
}
}
}
}
if (dp.startsWith("system.host") && this.adapter.controller) {
await this.adapter.controller.systemNotification.onStateChange(dp, state);
}
}
async setState(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(`setState(${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) {
try {
await this.adapter.setForeignStateAsync(item.options.dp, val, ack);
} catch (e) {
this.log.error(
`Cannot write state ${item.options.dp} with value ${val}: ${typeof e === "string" ? e : e.message}`
);
item.writeable = false;
item.common.write = false;
throw e;
}
} 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) {
if (this.adapter.config.debugLogStates) {
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);
}
if (!adapter.unload) {
StatesControler.tempObjectDBTimeout = adapter.setTimeout(() => {
if (adapter.unload) {
return;
}
StatesControler.tempObjectDBTimeout = void 0;
StatesControler.TempObjectDB = { data: void 0, keys: [], enums: void 0 };
}, 2e4);
}
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 = { ...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 options.
*
* @param options - Configuration for the automatic state lookup.
* @param options.dpInit - The initial data point, either a string or a regular expression.
* @param options.role - The expected role of the state, either a single StateRole or an array of roles.
* @param options.enums - One or more enum IDs that the state should belong to.
* @param options.regexp - A regular expression to match the state ID.
* @param options.triggered - If true, the returned object will be of type "triggered" instead of "state".
* @param options.writeable - If true, only writeable states will be considered.
* @param options.commonType - The expected common type of the state, either a single type or an array of types.
* @returns A promise that resolves to a `DataItemsOptions` object if a matching state is found,
* otherwise `undefined`.
*/
async getIdbyAuto(options) {
const { dpInit, role = "", enums = "", regexp, triggered, writeable, commonType = "" } = options;
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 finds states for ${dpInit} dpinit is ${typeof 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