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.

768 lines (767 loc) • 27.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var controller_exports = {}; __export(controller_exports, { Controller: () => Controller }); module.exports = __toCommonJS(controller_exports); var Library = __toESM(require("./library")); var import_states_controller = require("./states-controller"); var Panel = __toESM(require("./panel")); var import_definition = require("../const/definition"); var import_system_notifications = require("../classes/system-notifications"); var import_tools = require("../const/tools"); var import_Color = require("../const/Color"); var import_pageTrash = require("../pages/tools/pageTrash"); var definition = __toESM(require("../const/definition")); class Controller extends Library.BaseClass { mqttClient; statesControler; panels = []; minuteLoopTimeout; dateUpdateTimeout; dailyIntervalTimeout; dataCache = {}; options; globalPanelInfo = { availableTftFirmwareVersion: {}, availableTasmotaFirmwareVersion: "" }; systemNotification; constructor(adapter, options) { super(adapter, options.name); switch (this.adapter.config.colorTheme) { case 1: import_Color.Color.setTheme(import_Color.Color.topicalTheme); break; case 2: import_Color.Color.setTheme(import_Color.Color.technicalTheme); break; case 3: import_Color.Color.setTheme(import_Color.Color.sunsetTheme); break; case 4: import_Color.Color.setTheme(import_Color.Color.volcanoTheme); break; case 5: { const custom = this.buildCustomColorTheme(); import_Color.Color.setTheme(custom); this.adapter.log.debug(`Custom ColorTheme angewendet: ${JSON.stringify(custom)}`); break; } case 0: default: import_Color.Color.setTheme(import_Color.Color.defaultTheme); break; } this.adapter.controller = this; this.mqttClient = options.mqttClient; this.statesControler = new import_states_controller.StatesControler(this.adapter); this.systemNotification = new import_system_notifications.SystemNotifications(this.adapter); this.options = options; if (this.adapter.mqttServer) { this.adapter.mqttServer.controller = this; } void this.init(options.panels); this.log.debug(`${this.name} created`); } minuteLoop = async () => { const now = /* @__PURE__ */ new Date(); const minute = now.getMinutes(); try { if (minute === 0) { for (const panel of this.panels) { panel.sendDimmode(); } } if (minute % 5 === 1) { for (const panel of this.panels) { panel.requestStatusTasmota(); } } const currentTime = await this.getCurrentTime(); await this.statesControler.setInternalState("///time", currentTime, true); const currentTimeString = await this.getCurrentTimeString(); await this.statesControler.setInternalState("///timeString", currentTimeString, true); await this.adapter.delay(10); } catch (e) { this.log.error(`minuteLoop error: ${e instanceof Error ? e.message : JSON.stringify(e)}`); } const next = new Date(now); next.setSeconds(0, 10); next.setMinutes(now.getMinutes() + 1); const diff = next.getTime() - Date.now(); if (this.unload || this.adapter.unload) { return; } this.minuteLoopTimeout = this.adapter.setTimeout(() => this.minuteLoop(), diff); }; /** * Loop every hour * Update Date every day at hour 0 * check every 8 hours for new online version * * @returns void */ hourLoop = async () => { const now = /* @__PURE__ */ new Date(); const next = new Date(now); const hourNow = now.getHours(); next.setHours(hourNow + 1, 0, 1, 200); const diff = next.getTime() - now.getTime(); try { if (hourNow === 0) { const currentTime = await this.getCurrentTime(); this.log.debug(`Set current Date with time: ${new Date(currentTime).toString()}`); await this.statesControler.setInternalState("///date", currentTime, true); } } catch (err) { this.log.error(`dateUpdateLoop failed: ${err}`); } if (hourNow % 8 === 0) { await this.checkOnlineVersion(); } if (hourNow % 6 === 0) { await this.getTrashDaten(); } if (this.unload || this.adapter.unload) { return; } this.dateUpdateTimeout = this.adapter.setTimeout(() => this.hourLoop(), diff); }; getCurrentTime = async () => { return new Promise((resolve) => resolve(Date.now())); }; getCurrentTimeString = async () => { return new Promise( (resolve) => resolve((/* @__PURE__ */ new Date()).toLocaleString("de-DE", { hour: "2-digit", minute: "2-digit" })) ); }; onLibraryStateChange = async (id, state) => { var _a; if (!id.startsWith("panels.") || state.ack) { return; } const endsWith = id.split(".").slice(-2).join("."); switch (endsWith) { case "cmd.activated": { if (state.val === true) { const panel = this.panels.find((p) => p.name === id.split(".").slice(1, -2).join(".")); if (panel) { break; } const mac = id.split(".").slice(1, 2).join("."); const topic = (_a = this.adapter.config.panels.find((p) => p.id === mac)) == null ? void 0 : _a.topic; const panelConfig = this.options.panels.find((p) => p.topic === topic); if (panelConfig) { await this.addPanel(structuredClone(panelConfig)); } } else if (state.val === false) { const panel = this.panels.find((p) => p.name === id.split(".").slice(1, -2).join(".")); if (panel) { await this.removePanel(panel); } } await this.library.writedp( `panels.${this.name}.cmd.activated`, !!state.val, definition.genericStateObjects.panel.panels.cmd.activated ); break; } default: break; } }; /** * Handles internal commands based on the provided id and state. * * @param id - The identifier for the internal command. * @param _state - The state associated with the command. * @returns The value of the internal state or null if not applicable. */ onInternalCommand = async (id, _state) => { if (!id.startsWith("///")) { return null; } const token = id.split("///").pop(); switch (token) { case "cmd/NotificationCustomID": { if (!_state || typeof _state.val === "object") { break; } await this.library.writedp(`pagePopup.id`, _state.val, import_definition.genericStateObjects.panel.panels.pagePopup.id); break; } case "cmd/NotificationCustomRight": { if (!_state || typeof _state.val === "object") { break; } await this.library.writedp( `pagePopup.buttonRight`, _state.val, import_definition.genericStateObjects.panel.panels.pagePopup.buttonRight ); break; } case "cmd/NotificationCustomLeft": { if (!_state || typeof _state.val === "object") { break; } await this.library.writedp( `pagePopup.buttonLeft`, _state.val, import_definition.genericStateObjects.panel.panels.pagePopup.buttonLeft ); break; } case "cmd/NotificationCustomMid": { if (!_state || typeof _state.val === "object") { break; } await this.library.writedp( `pagePopup.buttonMid`, _state.val, import_definition.genericStateObjects.panel.panels.pagePopup.buttonMid ); break; } case "AdapterStoppedBoolean": case "AdapterNoConnectionBoolean": case "AdapterNoConnection": case "AdapterStopped": { if (this.dataCache[token] && this.dataCache[token].time < Date.now() - 5e3) { delete this.dataCache[token]; } let save = false; if (!this.dataCache[token]) { this.dataCache[token] = { time: Date.now(), data: {} }; save = true; } let list; if (save) { list = await this.adapter.getObjectViewAsync("system", "instance", { startkey: `system.adapter`, endkey: `system.adapter}` }); this.dataCache[token].data[`system#view.instance`] = list; } else { list = this.dataCache[token].data[`system#view.instance`]; } if (!list || !list.token) { return null; } let total = 0; let withProblems = 0; for (const item of list.rows) { const obj = item.value; if (!obj.common.enabled || obj.common.mode !== "daemon") { continue; } if (token === "AdapterStopped" || token === "AdapterStoppedBoolean") { let state; if (save) { state = await this.adapter.getForeignStateAsync(`${item.id}.alive`); this.dataCache[token].data[`${item.id}.alive`] = state; } else { state = this.dataCache[token].data[`${item.id}.alive`]; } if (state && !state.val) { withProblems++; if (token === "AdapterStoppedBoolean") { return true; } } total++; } else if (token === "AdapterNoConnection" || token === "AdapterNoConnectionBoolean") { const nID = item.id.split(".").slice(2).join("."); let state; if (save) { state = await this.adapter.getForeignStateAsync(`${nID}.info.connection`); this.dataCache[token].data[`${nID}.info.connection`] = state; } else { state = this.dataCache[token].data[`${nID}.info.connection`]; } if (state && !state.val) { withProblems++; if (token === "AdapterNoConnectionBoolean") { return true; } } total++; } } if (token === "AdapterNoConnectionBoolean" || token === "AdapterStoppedBoolean") { return false; } return `(${withProblems}/${total})`; } } return null; }; async init(panels) { await this.statesControler.setInternalState( "///time", await this.getCurrentTime(), true, { name: "", type: "number", role: "value.time", read: true, write: false }, this.getCurrentTime ); await this.statesControler.setInternalState( "///timeString", await this.getCurrentTimeString(), true, { name: "", type: "string", role: "text", read: true, write: false }, this.getCurrentTimeString ); await this.statesControler.setInternalState( "///date", await this.getCurrentTime(), true, { name: "", type: "number", role: "value.time", read: true, write: false }, this.getCurrentTime ); await this.statesControler.setInternalState( `///AdapterNoConnection`, "", true, (0, import_tools.getInternalDefaults)("string", "text", false), this.onInternalCommand ); await this.statesControler.setInternalState( `///AdapterStopped`, "", true, (0, import_tools.getInternalDefaults)("string", "text", false), this.onInternalCommand ); await this.statesControler.setInternalState( `///AdapterNoConnectionBoolean`, true, true, (0, import_tools.getInternalDefaults)("boolean", "indicator", false), this.onInternalCommand ); await this.statesControler.setInternalState( `///AdapterStoppedBoolean`, true, true, (0, import_tools.getInternalDefaults)("boolean", "indicator", false), this.onInternalCommand ); await this.statesControler.setInternalState( `///cmd/NotificationCustomLeft`, "", true, (0, import_tools.getInternalDefaults)("string", "text", false), this.onInternalCommand ); await this.statesControler.setInternalState( `///cmd/NotificationCustomMid`, "", true, (0, import_tools.getInternalDefaults)("string", "text", false), this.onInternalCommand ); await this.statesControler.setInternalState( `///cmd/NotificationCustomRight`, "", true, (0, import_tools.getInternalDefaults)("string", "text", false), this.onInternalCommand ); await this.statesControler.setInternalState( `///cmd/NotificationCustomID`, "", true, (0, import_tools.getInternalDefaults)("string", "text", false), this.onInternalCommand ); await this.library.writedp(`panels`, void 0, import_definition.genericStateObjects.panel._channel); await this.library.writedp(`pagePopup`, void 0, import_definition.genericStateObjects.panel.panels.pagePopup._channel); for (const key of Object.keys(import_definition.genericStateObjects.panel.panels.pagePopup)) { if (key !== "_channel") { await this.library.writedp( `pagePopup.${key}`, void 0, import_definition.genericStateObjects.panel.panels.pagePopup[key] ); } } void this.systemNotification.init(); const tasks = []; for (const panelConfig of panels) { if (panelConfig === void 0) { continue; } tasks.push(this.addPanel(structuredClone(panelConfig))); } await Promise.all(tasks); void this.minuteLoop(); void this.hourLoop(); await this.checkOnlineVersion(); await this.getTrashDaten(true); this.log.info(`${this.panels.length} Panels initialized`); } addPanel = async (panel) => { let index = this.panels.findIndex((p) => p.topic === panel.topic); if (index !== -1) { this.adapter.testSuccessful = false; this.adapter.log.error(`Panel ${panel.name} with topic ${panel.topic} already exists`); return false; } index = this.adapter.config.panels.findIndex((p) => p.topic === panel.topic); if (index === -1) { this.adapter.testSuccessful = false; this.adapter.log.error(`Panel ${panel.name} with topic ${panel.topic} not found in config`); return false; } panel.name = this.adapter.config.panels[index].id; const state = this.library.readdb(`panels.${panel.name}.cmd.activated`); if (state && state.val === false) { this.log.info(`Panel ${panel.name} is deactivated, skipping initialization.`); return false; } panel.friendlyName = this.adapter.config.panels[index].name; panel.controller = this; panel.scriptName = panel.scriptName || "missing"; const o = await this.adapter.getForeignObjectAsync(this.adapter.namespace); if ((panel == null ? void 0 : panel.topic) && o && o.native && o.native.navigation) { if (o.native.navigation[panel.topic] && o.native.navigation[panel.topic].useNavigation) { panel.navigation = o.native.navigation[panel.topic].data; } } const newPanel = await Panel.Panel.create(this.adapter, panel); if (await newPanel.isValid()) { await newPanel.init(); this.panels.push(newPanel); newPanel.initDone = true; this.log.debug(`Panel ${newPanel.name} created`); return true; } await newPanel.setStatus("error"); await newPanel.delete(); this.adapter.testSuccessful = false; this.log.error(`Panel ${panel.name} has a invalid configuration.`); return false; }; removePanel = async (panel) => { const index = this.panels.findIndex((p) => p.topic === panel.topic); if (index !== -1) { this.panels.splice(index, 1); await panel.delete(); this.log.info(`Panel ${panel.topic} removed!`); } else { this.log.error(`Panel ${panel.topic} not found`); } }; mqttClientConnected = (id) => { if (id === this.mqttClient.clientId) { return true; } const index = this.panels.findIndex((p) => id.startsWith(this.library.cleandp(p.friendlyName))); if (index !== -1) { void this.panels[index].setStatus("connected"); if (this.panels[index].initDone) { this.panels[index].restartLoops(); return true; } } return false; }; async setGlobalNotificationDismiss(id) { if (!id) { return; } for (const panel of this.panels) { if (panel.screenSaver) { await panel.screenSaver.setGlobalNotificationDismiss(id); } } } async setGlobalAlarmStatus(name, status) { for (const panel of this.panels) { for (const page of panel.pages) { if ((page == null ? void 0 : page.card) === "cardAlarm" && "isGlobal" in page && page.isGlobal && page.name === name) { await page.setStatusGlobal(status); } } } } async delete() { this.unload = true; if (this.minuteLoopTimeout) { this.adapter.clearTimeout(this.minuteLoopTimeout); } if (this.dateUpdateTimeout) { this.adapter.clearTimeout(this.dateUpdateTimeout); } if (this.dailyIntervalTimeout) { this.adapter.clearInterval(this.dailyIntervalTimeout); } if (this.systemNotification) { await this.systemNotification.delete(); } if (this.statesControler) { await this.statesControler.delete(); } const tasks = []; for (const a of this.panels) { if (a) { tasks.push(a.delete()); } } await Promise.all(tasks); this.panels = []; this.dataCache = {}; await super.delete(); } async setPopupNotification(data) { let temp; try { temp = typeof data === "string" ? JSON.parse(data) : data; } catch { this.log.error("setPopupNotification: Invalid data format, must be valid JSON or object"); return; } this.log.debug(`setPopupNotification called with data: ${JSON.stringify(temp)}`); const details = { id: typeof temp.id === "string" ? temp.id : "missing", priority: typeof temp.priority === "number" ? temp.priority : 50, alwaysOn: typeof temp.alwaysOn === "boolean" ? temp.alwaysOn : true, type: typeof temp.type === "string" ? temp.type : "information", global: temp.global !== false, headline: typeof temp.headline === "string" ? temp.headline : "Missing Headline", text: typeof temp.text === "string" ? temp.text : "Missing Text", buttonLeft: typeof temp.buttonLeft === "string" ? temp.buttonLeft : "", buttonMid: typeof temp.buttonMid === "string" ? temp.buttonMid : "", buttonRight: typeof temp.buttonRight === "string" ? temp.buttonRight : "", icon: typeof temp.icon === "string" ? temp.icon : void 0, iconColor: temp.iconColor != null ? (0, import_tools.getRGBFromValue)(temp.iconColor) : void 0, textSize: typeof temp.textSize === "string" ? temp.textSize : typeof temp.textSize === "number" ? String(temp.textSize) : void 0, colorHeadline: temp.colorHeadline != null ? (0, import_tools.getRGBFromValue)(temp.colorHeadline) : void 0, colorText: temp.colorText != null ? (0, import_tools.getRGBFromValue)(temp.colorText) : void 0, colorButtonLeft: temp.colorButtonLeft != null ? (0, import_tools.getRGBFromValue)(temp.colorButtonLeft) : void 0, colorButtonMid: temp.colorButtonMid != null ? (0, import_tools.getRGBFromValue)(temp.colorButtonMid) : void 0, colorButtonRight: temp.colorButtonRight != null ? (0, import_tools.getRGBFromValue)(temp.colorButtonRight) : void 0, buzzer: !temp.buzzer || !(temp.buzzer === true || typeof temp.buzzer === "string") ? false : temp.buzzer }; let panels = []; if (!temp.panel) { panels = this.panels; } else { if (!Array.isArray(temp.panel)) { temp.panel = [temp.panel]; } for (const pName of temp.panel) { const panel = this.panels.find((p) => p.name === pName || p.friendlyName === pName); if (!panel) { this.log.error(`setPopupMessage: Panel ${pName} not found`); continue; } panels.push(panel); } } temp.global = details.global && panels.length > 1; for (const panel of panels) { await this.statesControler.setInternalState( `${panel.name}/cmd/popupNotificationCustom`, JSON.stringify(details), false ); } } async notificationToPanel() { if (!this.panels) { return; } await this.statesControler.setInternalState("///Notifications", true, true); } checkOnlineVersion = async () => { await this.writeTFTVersion(); }; async writeTFTVersion() { try { const result = await this.adapter.getVersionsJson(); const data = result; if (!data) { this.log.error("No version data received."); return; } this.globalPanelInfo.availableTftFirmwareVersion = result; this.globalPanelInfo.availableTasmotaFirmwareVersion = result.tasmota ? result.tasmota.trim() : ""; for (const panel of this.panels) { await panel.writeInfo(); } } catch { } } async getTasmotaVersion() { var _a, _b; try { const result = await this.adapter.getVersionsJson(); if (!result) { this.log.error("No version data received."); return ""; } const TasmotaVersionOnline = typeof result.tasmota === "string" ? result.tasmota.trim() : ""; this.globalPanelInfo.availableTasmotaFirmwareVersion = TasmotaVersionOnline; for (const panel of this.panels) { panel.info.tasmota.onlineVersion = this.globalPanelInfo.availableTasmotaFirmwareVersion; await panel.writeInfo(); } } catch { } return (_b = (_a = this.globalPanelInfo) == null ? void 0 : _a.availableTasmotaFirmwareVersion) != null ? _b : ""; } /** * Baut ein zusammengefasstes Farb-Objekt aus allen Color-Accordion Arrays. * Nimmt Color.defaultTheme als Basis und überschreibt nur definierte Werte. * */ buildCustomColorTheme() { const cfg = this.adapter.config; const result = { ...import_Color.Color.defaultTheme }; const merge = (arr) => { if (!Array.isArray(arr) || !arr[0]) { return; } for (const [k, v] of Object.entries(arr[0])) { if (typeof v === "string" && v.trim() !== "" && /^col[A-Z]/.test(k)) { const keyNoPrefix = k.replace(/^col/, ""); const kTemp = keyNoPrefix.length ? keyNoPrefix.charAt(0).toLowerCase() + keyNoPrefix.slice(1) : keyNoPrefix; if (import_Color.Color.isHex(v)) { const colRgb = import_Color.Color.ConvertHexToRgb(v); if (import_Color.Color.isRGB(colRgb) && kTemp in result) { result[kTemp] = colRgb; } } else { this.log.debug(`Color property ${k} with value ${v} is not valid and will be ignored.`); } } } }; merge(cfg.colorStates); merge(cfg.colorNavigation); merge(cfg.colorWeatherIcon); merge(cfg.colorDisplay); merge(cfg.colorWeatherForecast); merge(cfg.colorScreensaver); merge(cfg.colorCardMedia); return result; } async getTrashDaten(init = false) { try { if (!this.adapter.config.pageConfig) { this.log.debug("No pageConfig available, skipping trash data update."); return; } const trashEntries = this.adapter.config.pageConfig.filter((e) => e.card === "cardTrash"); this.log.debug(`Found ${trashEntries.length} trash card configurations.`); for (const entry of trashEntries) { try { const state = entry.trashState || ""; let result; if (entry.trashImport) { if (!state) { this.log.warn(`No trash state defined for entry: ${entry.uniqueName}`); continue; } const daten = await this.adapter.getForeignStateAsync(state); if (!daten || !daten.val || daten.val === null || daten.val === "") { this.log.warn(`Trash state ${state} has no data .`); continue; } this.log.debug(`Processing trash data from state ${state} for entry ${entry.uniqueName}`); result = await (0, import_pageTrash.getTrashDataFromState)(daten.val, entry); } else { if (!entry.trashFile || typeof entry.trashFile !== "string" || entry.trashFile.trim() === "") { this.log.warn(`No trash .ics-file defined for entry: ${entry.uniqueName}`); continue; } this.log.debug( `Processing trash data from file ${entry.trashFile} for entry ${entry.uniqueName}` ); result = await (0, import_pageTrash.getTrashDataFromFile)(entry, this.adapter); } if (result.error) { this.log.error( `Error processing trash data for ${entry.uniqueName}: ${JSON.stringify(result.error)}` ); continue; } if (init) { await this.statesControler.setInternalState( `///pageTrash_${entry.uniqueName}`, "", false, (0, import_tools.getInternalDefaults)("string", "text", false) ); this.log.debug(`Set InternalState for ///pageTrash_${entry.uniqueName}`); await this.adapter.delay(10); } await this.statesControler.setInternalState( `///pageTrash_${entry.uniqueName}`, result.messages, false ); this.log.debug(`count of trash messages : ${result.messages.length}`); this.log.debug(`Trash message for entry ${entry.uniqueName}: ${JSON.stringify(result.messages)}`); } catch (error) { this.log.error(`Error processing trash entry ${entry.uniqueName}: ${error.message}`); } } } catch (error) { this.log.error(`getTrashDaten error: ${error.message}`); } } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Controller }); //# sourceMappingURL=controller.js.map