From 01be72931e9df6ce6ab5fde8c40559eaa78dbd9f Mon Sep 17 00:00:00 2001 From: Acgua <95978245+Acgua@users.noreply.github.com> Date: Wed, 29 Mar 2023 20:04:27 +0200 Subject: [PATCH] Update --- README.md | 5 +- build/lib/interfaces.js.map | 2 +- build/lib/methods.js | 16 ----- build/lib/methods.js.map | 4 +- build/lib/mqtt-server.js | 14 ++-- build/lib/mqtt-server.js.map | 4 +- build/lib/restApi.js | 76 +++++++------------- build/lib/restApi.js.map | 4 +- build/main.js | 73 +++++++++++--------- build/main.js.map | 4 +- src/lib/adapter-config.d.ts | 11 ++- src/lib/interfaces.ts | 1 + src/lib/methods.ts | 15 +--- src/lib/mqtt-server.ts | 15 ++-- src/lib/restApi.ts | 115 ++++++++----------------------- src/main.ts | 130 ++++++++++++++++++----------------- 16 files changed, 206 insertions(+), 283 deletions(-) diff --git a/README.md b/README.md index 74248a6..e2da637 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,15 @@ Once this ioBroker.fully-mqtt adapter is tested accordingly and runs stable, I w ### **WORK IN PROGRESS** - (Acgua) Fixed starting MQTT server +- (Acgua) Code cleanup - (Acgua) Removed info log 'MQTT is not activated in adapter instance settings.' +- (Acgua) New object 'enabled' ('fully-mqtt.x.[Device Name].enabled') to indicate if the device is enabled in the adapter settings +- (Acgua) 'alive' state is set to null if device is not enabled (to not show connection/disconnection symbols in device object 'fully-mqtt.x.[Device Name]) ### 0.1.0 (2023-03-27) - (Acgua) **Breaking Change**: Using MQTT is required, and removed option to deactivate MQTT. Reasons: 1. requesting info thru REST API is redundant and simply not needed since MQTT is available, 2. It does not make sense to not use MQTT since it provides live updates of states. -- (Acgua) **Breaking Change**: Object 'mqttActivated' ('fully-mqtt.x.yyy.mqttActivated') is no longer used. Feel free to delete these objects if you update from a previous version as these will not be deleted automatically. +- (Acgua) **Breaking Change**: Object 'mqttActivated' ('fully-mqtt.x.[Device Name].mqttActivated') is no longer used. Feel free to delete these objects if you update from a previous version as these will not be deleted automatically. - (Acgua) Removed all REST API info requests, as MQTT use is now required - (Acgua) Code improvements diff --git a/build/lib/interfaces.js.map b/build/lib/interfaces.js.map index 5474b05..0d0e4bd 100644 --- a/build/lib/interfaces.js.map +++ b/build/lib/interfaces.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../src/lib/interfaces.ts"], - "sourcesContent": ["export interface IDevice {\r\n name: string; // e.g. \"Tablet Hallway Entry\"\r\n id: string; // e.g. \"Tablet-Hallway-Entry\" (meets ioBroker state convention)\r\n ip: string;\r\n restProtocol: 'http' | 'https';\r\n restPort: number;\r\n restPassword: string;\r\n lastSeen: number; // timestamp\r\n isAlive: true | false;\r\n mqttInfoObjectsCreated: true | false; // Set to true once first time creation initiated\r\n mqttInfoKeys: string[]; // Info keys from MQTT info, like 'batteryLevel', 'deviceID', ...\r\n}\r\n\r\nexport interface ICmds {\r\n readonly id: string;\r\n readonly name: string;\r\n readonly type: 'number' | 'boolean' | 'string';\r\n readonly cmdOn?: string;\r\n readonly cmdOff?: string;\r\n readonly mqttOn?: string;\r\n readonly mqttOff?: string;\r\n}\r\n\r\nexport interface IMqttDevice {\r\n ip?: string;\r\n lastTimeActive?: number;\r\n mqttFirstReceived?: true | false;\r\n isActive?: true | false;\r\n timeoutNoUpdate?: ioBroker.Timeout | null;\r\n previousInfoPublishTime?: number;\r\n}\r\n\r\nexport interface IConst {\r\n readonly mqttEvents: string[];\r\n readonly cmds: ICmds[];\r\n readonly cmdsSwitches: ICmds[];\r\n}\r\n"], + "sourcesContent": ["export interface IDevice {\n name: string; // e.g. \"Tablet Hallway Entry\"\n id: string; // e.g. \"Tablet-Hallway-Entry\" (meets ioBroker state convention)\n ip: string;\n enabled: true | false;\n restProtocol: 'http' | 'https';\n restPort: number;\n restPassword: string;\n lastSeen: number; // timestamp\n isAlive: true | false;\n mqttInfoObjectsCreated: true | false; // Set to true once first time creation initiated\n mqttInfoKeys: string[]; // Info keys from MQTT info, like 'batteryLevel', 'deviceID', ...\n}\n\nexport interface ICmds {\n readonly id: string;\n readonly name: string;\n readonly type: 'number' | 'boolean' | 'string';\n readonly cmdOn?: string;\n readonly cmdOff?: string;\n readonly mqttOn?: string;\n readonly mqttOff?: string;\n}\n\nexport interface IMqttDevice {\n ip?: string;\n lastTimeActive?: number;\n mqttFirstReceived?: true | false;\n isActive?: true | false;\n timeoutNoUpdate?: ioBroker.Timeout | null;\n previousInfoPublishTime?: number;\n}\n\nexport interface IConst {\n readonly mqttEvents: string[];\n readonly cmds: ICmds[];\n readonly cmdsSwitches: ICmds[];\n}\n"], "mappings": ";;;;;;;;;;;;;AAAA;AAAA;", "names": [] } diff --git a/build/lib/methods.js b/build/lib/methods.js index 09ad285..d8f4a55 100644 --- a/build/lib/methods.js +++ b/build/lib/methods.js @@ -25,22 +25,6 @@ __export(methods_exports, { wait: () => wait }); module.exports = __toCommonJS(methods_exports); -/** - * Methods and Tools - * @desc Methods and Tools - * @author Acgua - * @license Apache License 2.0 - * - * ---------------------------------------------------------------------------------------- - * How to implement this file in main.ts (see also https://stackoverflow.com/a/58459668) - * ---------------------------------------------------------------------------------------- - * 1. Add "this: InstanceWatcher" as first function parameter if you need access to "this" - * -> no need to provide this parameter when calling the method, though! - * 1. Add line like "import { err2Str, isEmpty } from './lib/methods';" - * 2. Add keyword "export" before "class InstanceWatcher extends utils.Adapter" - * 3. class InstanceWatcher: for each method, add line like: "public isEmpty = isEmpty.bind(this);" - * Note: use "private isEmpty..." and not "public", if you do not need to access method from this file - */ function err2Str(error) { if (error instanceof Error) { if (error.stack) diff --git a/build/lib/methods.js.map b/build/lib/methods.js.map index 8a266b7..6bf57c2 100644 --- a/build/lib/methods.js.map +++ b/build/lib/methods.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../src/lib/methods.ts"], - "sourcesContent": ["/**\n * Methods and Tools\n * @desc Methods and Tools\n * @author Acgua \n * @license Apache License 2.0\n *\n * ----------------------------------------------------------------------------------------\n * How to implement this file in main.ts (see also https://stackoverflow.com/a/58459668)\n * ----------------------------------------------------------------------------------------\n * 1. Add \"this: InstanceWatcher\" as first function parameter if you need access to \"this\"\n * -> no need to provide this parameter when calling the method, though!\n * 1. Add line like \"import { err2Str, isEmpty } from './lib/methods';\"\n * 2. Add keyword \"export\" before \"class InstanceWatcher extends utils.Adapter\"\n * 3. class InstanceWatcher: for each method, add line like: \"public isEmpty = isEmpty.bind(this);\"\n * Note: use \"private isEmpty...\" and not \"public\", if you do not need to access method from this file\n */\nimport { FullyMqtt } from '../main';\n// import { IDevice } from './interfaces';\n\n/**\n * Convert error to string\n * @param {*} error - any kind of thrown error\n * @returns string\n */\nexport function err2Str(error: any): string {\n if (error instanceof Error) {\n if (error.stack) return error.stack;\n if (error.message) return error.message;\n return JSON.stringify(error);\n } else {\n if (typeof error === 'string') return error;\n return JSON.stringify(error);\n }\n}\n\n/**\n * Clean device name for state\n * @param str - device name\n * @returns device name without forbidden chars, and without any dots.\n */\nexport function cleanDeviceName(this: FullyMqtt, str: string): string {\n let res = str.replace(this.FORBIDDEN_CHARS, ''); // https://github.com/ioBroker/ioBroker.js-controller/blob/master/packages/common/src/lib/common/tools.ts\n res = res.replace(/\\./g, ''); // remove any dots \".\"\n res = res.replace(/\\s{2,}/g, ' '); // replace multiple whitespaces with single space\n res = res.trim(); // removes whitespace from both ends\n res = res.replace(/\\s/g, '_'); // replace whitespaces with _\n if (res.replace(/_/g, '').length === 0) res = ''; // return empty str if just _ is left\n return res;\n}\n\n/**\n * Check if IP address is valid - https://stackoverflow.com/a/27434991\n * @param ip IP address\n * @returns true if valid, false if not\n */\nexport function isIpAddressValid(ip: string): true | false {\n const pattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;\n if (pattern.test(ip)) {\n return true;\n } else {\n return false;\n }\n}\n\n/**\n * Retrieve values from a CONFIG variable, example:\n * const CONF = [{car: 'bmw', color: 'black', hp: '250'}, {car: 'audi', color: 'blue', hp: '190'}]\n * To get the color of the Audi, use: getConfigValuePerKey(CONF, 'car', 'audi', 'color')\n * To find out which car has 190 hp, use: getConfigValuePerKey(CONF, 'hp', '190', 'car')\n * @param {object} config The configuration variable/constant\n * @param {string} key1 Key to look for.\n * @param {string | number} key1Value The value the key should have\n * @param {string} key2 The key which value we return\n * @returns {any} Returns the element's value, or number -1 of nothing found.\n */\nexport function getConfigValuePerKey(config: { [k: string]: any }[], key1: string, key1Value: string | number, key2: string): any {\n for (const lpConfDevice of config) {\n if (lpConfDevice[key1] === key1Value) {\n if (lpConfDevice[key2] === undefined) {\n return -1;\n } else {\n return lpConfDevice[key2];\n }\n }\n }\n return -1;\n}\n\n/**\n * Checks if an operand (variable, constant, object, ...) is considered as empty.\n * - empty: undefined; null; string|array|object, stringified and only with white space(s), and/or `><[]{}`\n * - NOT empty: not matching anything above; any function; boolean false; number -1\n * inspired by helper.js from SmartControl adapter\n */\nexport function isEmpty(toCheck: any): true | false {\n if (toCheck === null || typeof toCheck === 'undefined') return true;\n if (typeof toCheck === 'function') return false;\n let x = JSON.stringify(toCheck);\n x = x.replace(/\\s+/g, ''); // white space(s)\n x = x.replace(/\"+/g, ''); // \"\n x = x.replace(/'+/g, ''); // '\n x = x.replace(/\\[+/g, ''); // [\n x = x.replace(/\\]+/g, ''); // ]\n x = x.replace(/\\{+/g, ''); // {\n x = x.replace(/\\}+/g, ''); // }\n return x === '' ? true : false;\n}\n\n/**\n * async wait/pause\n * Actually not needed since a single line, but for the sake of using wait more easily\n * @param {number} ms - number of milliseconds to wait\n */\nexport async function wait(this: FullyMqtt, ms: number): Promise {\n try {\n await new Promise((w) => setTimeout(w, ms));\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n}\n"], - "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBO,SAAS,QAAQ,OAAoB;AACxC,MAAI,iBAAiB,OAAO;AACxB,QAAI,MAAM;AAAO,aAAO,MAAM;AAC9B,QAAI,MAAM;AAAS,aAAO,MAAM;AAChC,WAAO,KAAK,UAAU,KAAK;AAAA,EAC/B,OAAO;AACH,QAAI,OAAO,UAAU;AAAU,aAAO;AACtC,WAAO,KAAK,UAAU,KAAK;AAAA,EAC/B;AACJ;AAOO,SAAS,gBAAiC,KAAqB;AAClE,MAAI,MAAM,IAAI,QAAQ,KAAK,iBAAiB,EAAE;AAC9C,QAAM,IAAI,QAAQ,OAAO,EAAE;AAC3B,QAAM,IAAI,QAAQ,WAAW,GAAG;AAChC,QAAM,IAAI,KAAK;AACf,QAAM,IAAI,QAAQ,OAAO,GAAG;AAC5B,MAAI,IAAI,QAAQ,MAAM,EAAE,EAAE,WAAW;AAAG,UAAM;AAC9C,SAAO;AACX;AAOO,SAAS,iBAAiB,IAA0B;AACvD,QAAM,UAAU;AAChB,MAAI,QAAQ,KAAK,EAAE,GAAG;AAClB,WAAO;AAAA,EACX,OAAO;AACH,WAAO;AAAA,EACX;AACJ;AAaO,SAAS,qBAAqB,QAAgC,MAAc,WAA4B,MAAmB;AAC9H,aAAW,gBAAgB,QAAQ;AAC/B,QAAI,aAAa,UAAU,WAAW;AAClC,UAAI,aAAa,UAAU,QAAW;AAClC,eAAO;AAAA,MACX,OAAO;AACH,eAAO,aAAa;AAAA,MACxB;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;AAQO,SAAS,QAAQ,SAA4B;AAChD,MAAI,YAAY,QAAQ,OAAO,YAAY;AAAa,WAAO;AAC/D,MAAI,OAAO,YAAY;AAAY,WAAO;AAC1C,MAAI,IAAI,KAAK,UAAU,OAAO;AAC9B,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,OAAO,EAAE;AACvB,MAAI,EAAE,QAAQ,OAAO,EAAE;AACvB,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,SAAO,MAAM,KAAK,OAAO;AAC7B;AAOA,eAAsB,KAAsB,IAA2B;AACnE,MAAI;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAAA,EAC9C,SAAS,GAAP;AACE,SAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,EACJ;AACJ;", + "sourcesContent": ["/**\n * Methods and Tools\n */\n\nimport { FullyMqtt } from '../main';\n\n/**\n * Convert error to string\n * @param {*} error - any kind of thrown error\n * @returns string\n */\nexport function err2Str(error: any): string {\n if (error instanceof Error) {\n if (error.stack) return error.stack;\n if (error.message) return error.message;\n return JSON.stringify(error);\n } else {\n if (typeof error === 'string') return error;\n return JSON.stringify(error);\n }\n}\n\n/**\n * Clean device name for state\n * @param str - device name\n * @returns device name without forbidden chars, and without any dots.\n */\nexport function cleanDeviceName(this: FullyMqtt, str: string): string {\n let res = str.replace(this.FORBIDDEN_CHARS, ''); // https://github.com/ioBroker/ioBroker.js-controller/blob/master/packages/common/src/lib/common/tools.ts\n res = res.replace(/\\./g, ''); // remove any dots \".\"\n res = res.replace(/\\s{2,}/g, ' '); // replace multiple whitespaces with single space\n res = res.trim(); // removes whitespace from both ends\n res = res.replace(/\\s/g, '_'); // replace whitespaces with _\n if (res.replace(/_/g, '').length === 0) res = ''; // return empty str if just _ is left\n return res;\n}\n\n/**\n * Check if IP address is valid - https://stackoverflow.com/a/27434991\n * @param ip IP address\n * @returns true if valid, false if not\n */\nexport function isIpAddressValid(ip: string): true | false {\n const pattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;\n if (pattern.test(ip)) {\n return true;\n } else {\n return false;\n }\n}\n\n/**\n * Retrieve values from a CONFIG variable, example:\n * const CONF = [{car: 'bmw', color: 'black', hp: '250'}, {car: 'audi', color: 'blue', hp: '190'}]\n * To get the color of the Audi, use: getConfigValuePerKey(CONF, 'car', 'audi', 'color')\n * To find out which car has 190 hp, use: getConfigValuePerKey(CONF, 'hp', '190', 'car')\n * @param {object} config The configuration variable/constant\n * @param {string} key1 Key to look for.\n * @param {string | number} key1Value The value the key should have\n * @param {string} key2 The key which value we return\n * @returns {any} Returns the element's value, or number -1 of nothing found.\n */\nexport function getConfigValuePerKey(config: { [k: string]: any }[], key1: string, key1Value: string | number, key2: string): any {\n for (const lpConfDevice of config) {\n if (lpConfDevice[key1] === key1Value) {\n if (lpConfDevice[key2] === undefined) {\n return -1;\n } else {\n return lpConfDevice[key2];\n }\n }\n }\n return -1;\n}\n\n/**\n * Checks if an operand (variable, constant, object, ...) is considered as empty.\n * - empty: undefined; null; string|array|object, stringified and only with white space(s), and/or `><[]{}`\n * - NOT empty: not matching anything above; any function; boolean false; number -1\n * inspired by helper.js from SmartControl adapter\n */\nexport function isEmpty(toCheck: any): true | false {\n if (toCheck === null || typeof toCheck === 'undefined') return true;\n if (typeof toCheck === 'function') return false;\n let x = JSON.stringify(toCheck);\n x = x.replace(/\\s+/g, ''); // white space(s)\n x = x.replace(/\"+/g, ''); // \"\n x = x.replace(/'+/g, ''); // '\n x = x.replace(/\\[+/g, ''); // [\n x = x.replace(/\\]+/g, ''); // ]\n x = x.replace(/\\{+/g, ''); // {\n x = x.replace(/\\}+/g, ''); // }\n return x === '' ? true : false;\n}\n\n/**\n * async wait/pause\n * Actually not needed since a single line, but for the sake of using wait more easily\n * @param {number} ms - number of milliseconds to wait\n */\nexport async function wait(this: FullyMqtt, ms: number): Promise {\n try {\n await new Promise((w) => setTimeout(w, ms));\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n}\n"], + "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWO,SAAS,QAAQ,OAAoB;AACxC,MAAI,iBAAiB,OAAO;AACxB,QAAI,MAAM;AAAO,aAAO,MAAM;AAC9B,QAAI,MAAM;AAAS,aAAO,MAAM;AAChC,WAAO,KAAK,UAAU,KAAK;AAAA,EAC/B,OAAO;AACH,QAAI,OAAO,UAAU;AAAU,aAAO;AACtC,WAAO,KAAK,UAAU,KAAK;AAAA,EAC/B;AACJ;AAOO,SAAS,gBAAiC,KAAqB;AAClE,MAAI,MAAM,IAAI,QAAQ,KAAK,iBAAiB,EAAE;AAC9C,QAAM,IAAI,QAAQ,OAAO,EAAE;AAC3B,QAAM,IAAI,QAAQ,WAAW,GAAG;AAChC,QAAM,IAAI,KAAK;AACf,QAAM,IAAI,QAAQ,OAAO,GAAG;AAC5B,MAAI,IAAI,QAAQ,MAAM,EAAE,EAAE,WAAW;AAAG,UAAM;AAC9C,SAAO;AACX;AAOO,SAAS,iBAAiB,IAA0B;AACvD,QAAM,UAAU;AAChB,MAAI,QAAQ,KAAK,EAAE,GAAG;AAClB,WAAO;AAAA,EACX,OAAO;AACH,WAAO;AAAA,EACX;AACJ;AAaO,SAAS,qBAAqB,QAAgC,MAAc,WAA4B,MAAmB;AAC9H,aAAW,gBAAgB,QAAQ;AAC/B,QAAI,aAAa,UAAU,WAAW;AAClC,UAAI,aAAa,UAAU,QAAW;AAClC,eAAO;AAAA,MACX,OAAO;AACH,eAAO,aAAa;AAAA,MACxB;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;AAQO,SAAS,QAAQ,SAA4B;AAChD,MAAI,YAAY,QAAQ,OAAO,YAAY;AAAa,WAAO;AAC/D,MAAI,OAAO,YAAY;AAAY,WAAO;AAC1C,MAAI,IAAI,KAAK,UAAU,OAAO;AAC9B,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,OAAO,EAAE;AACvB,MAAI,EAAE,QAAQ,OAAO,EAAE;AACvB,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,SAAO,MAAM,KAAK,OAAO;AAC7B;AAOA,eAAsB,KAAsB,IAA2B;AACnE,MAAI;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAAA,EAC9C,SAAS,GAAP;AACE,SAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,EACJ;AACJ;", "names": [] } diff --git a/build/lib/mqtt-server.js b/build/lib/mqtt-server.js index e513b95..e6d0b30 100644 --- a/build/lib/mqtt-server.js +++ b/build/lib/mqtt-server.js @@ -45,7 +45,7 @@ class MqttServer { this.adapter.log.warn(`DEVELOPER: Port changed to ${this.port} as we are in DEV Environment! If you see this log message, please open an issue on Github.`); } this.server.listen(this.port, () => { - this.adapter.log.info(`[MQTT]\u{1F680} Server started and listening on port ${this.port}`); + this.adapter.log.info(`\u{1F680} MQTT Server started and is listening on port ${this.port}.`); }); this.aedes.authenticate = (client, username, password, callback) => { try { @@ -63,7 +63,7 @@ class MqttServer { if (!this.adapter.isIpAddressValid(ip)) ip === void 0; } - if (ip && !this.adapter.activeDeviceIPs.includes(ip)) { + if (ip && !Object.keys(this.adapter.fullys).includes(ip)) { this.adapter.log.error(`[MQTT] Client ${client.id} not authorized: ${ip} is not an active Fully device IP per adapter settings.`); this.notAuthorizedClients.push(client.id); callback(null, false); @@ -75,17 +75,17 @@ class MqttServer { this.devices[client.id].ip = ip; if (!this.adapter.config.mqttDoNotVerifyUserPw) { if (username !== this.adapter.config.mqttUser) { - this.adapter.log.warn(`[MQTT] Client ${ipMsg} Authorization rejected: received user name '${username}' does not match '${this.adapter.config.mqttUser}' in adapter settings.`); + this.adapter.log.warn(`MQTT Client ${ipMsg} Authorization rejected: received user name '${username}' does not match '${this.adapter.config.mqttUser}' in adapter settings.`); callback(null, false); return; } if (password.toString() !== this.adapter.config.mqttPassword) { - this.adapter.log.warn(`[MQTT] Client ${ipMsg} Authorization rejected: received password does not match with password in adapter settings.`); + this.adapter.log.warn(`MQTT Client ${ipMsg} Authorization rejected: received password does not match with password in adapter settings.`); callback(null, false); return; } } - this.adapter.log.info(`[MQTT]\u{1F511} Client ${ipMsg} successfully authenticated.`); + this.adapter.log.info(`\u{1F511} MQTT Client ${ipMsg} successfully authenticated.`); callback(null, true); } catch (e) { this.adapter.log.error(this.adapter.err2Str(e)); @@ -126,7 +126,7 @@ class MqttServer { } const ip = info.ip4; const devMsg = `${this.adapter.fullys[ip].name} (${ip})`; - if (!this.adapter.activeDeviceIPs.includes(ip)) { + if (!Object.keys(this.adapter.fullys).includes(ip)) { this.adapter.log.error(`[MQTT] Client ${devMsg} Packet rejected: IP is not allowed per adapter settings. ${client.id}`); return; } @@ -247,7 +247,7 @@ class MqttServer { this.devices[clientId].isActive = isAlive; const ip = (_a = this.devices[clientId]) == null ? void 0 : _a.ip; if (ip) { - this.adapter.onAliveChange("MQTT", ip, isAlive, msg); + this.adapter.onMqttAlive(ip, isAlive, msg); if (isAlive) { this.scheduleCheckIfStillActive(clientId); } else { diff --git a/build/lib/mqtt-server.js.map b/build/lib/mqtt-server.js.map index 8827480..bf5cad0 100644 --- a/build/lib/mqtt-server.js.map +++ b/build/lib/mqtt-server.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../src/lib/mqtt-server.ts"], - "sourcesContent": ["import Aedes from 'aedes';\nimport net from 'net';\nimport { FullyMqtt } from '../main';\nimport { IMqttDevice } from './interfaces';\n//import { inspect } from 'util';\n\nexport class MqttServer {\n private readonly adapter: FullyMqtt;\n private server: net.Server;\n private aedes: Aedes;\n public devices: { [mqttClientId: string]: IMqttDevice }; // {}\n private port = -1;\n private notAuthorizedClients: string[] = []; // to avoid multiple log lines\n\n /**\n * Constructor\n */\n public constructor(adapter: FullyMqtt) {\n this.adapter = adapter;\n //this.server = new net.Server();\n this.aedes = new Aedes();\n /** @ts-expect-error - https://github.com/moscajs/aedes/issues/801 */\n this.server = net.createServer(undefined, this.aedes.handle);\n this.devices = {}; // key = MQTT Client ID, property: IMqttDevice\n }\n\n /**\n * Listen\n */\n public start(): void {\n try {\n /**\n * Port\n */\n this.port = this.adapter.config.mqttPort;\n /**\n * #############################################################\n * For Developer only: change port if in dev environment\n * #############################################################\n */\n if (this.adapter.adapterDir.includes('/.dev-server/default/node_modules')) {\n this.port = 3012;\n this.adapter.log.warn(`DEVELOPER: Port changed to ${this.port} as we are in DEV Environment! If you see this log message, please open an issue on Github.`);\n }\n\n /**\n * Start Listening\n */\n this.server.listen(this.port, () => {\n this.adapter.log.info(`[MQTT]\uD83D\uDE80 Server started and listening on port ${this.port}`);\n });\n\n /**\n * Verify authorization\n * This fires first and before this.aedes.on('client', (client) ...\n * https://github.com/moscajs/aedes/blob/main/docs/Aedes.md#handler-authenticate-client-username-password-callback\n */\n this.aedes.authenticate = (client, username, password, callback) => {\n try {\n // If we saw client before and is not authorized\n if (this.notAuthorizedClients.includes(client.id)) {\n callback(null, false);\n return;\n }\n\n // Create device entry with id as key, if not yet existing\n if (!this.devices[client.id]) this.devices[client.id] = {};\n\n /**\n * Get IP\n * This rather complicated way is needed, see https://github.com/moscajs/aedes/issues/186\n * Not sure if this always works, but client.req was undefined in my test - which is suggested in https://github.com/moscajs/aedes/issues/527\n */\n let ip: string | undefined = undefined;\n if (client.conn && 'remoteAddress' in client.conn && typeof client.conn.remoteAddress === 'string') {\n const ipSource = client.conn.remoteAddress; // like: ::ffff:192.168.10.101\n this.adapter.log.debug(`[MQTT] client.conn.remoteAddress = \"${ipSource}\" - ${client.id}`);\n ip = ipSource.substring(ipSource.lastIndexOf(':') + 1); // get everything after last \":\"\n if (!this.adapter.isIpAddressValid(ip)) ip === undefined;\n }\n // Check if IP is an active device IP\n if (ip && !this.adapter.activeDeviceIPs.includes(ip)) {\n this.adapter.log.error(`[MQTT] Client ${client.id} not authorized: ${ip} is not an active Fully device IP per adapter settings.`);\n this.notAuthorizedClients.push(client.id);\n callback(null, false);\n return;\n }\n\n const ipMsg = ip ? `${this.adapter.fullys[ip].name} (${ip})` : `${client.id} (IP unknown)`;\n this.adapter.log.debug(`[MQTT] Client ${ipMsg} trys to authenticate...`);\n if (ip) this.devices[client.id].ip = ip;\n\n /**\n * Verify User and Password\n */\n if (!this.adapter.config.mqttDoNotVerifyUserPw) {\n // Username\n if (username !== this.adapter.config.mqttUser) {\n this.adapter.log.warn(`[MQTT] Client ${ipMsg} Authorization rejected: received user name '${username}' does not match '${this.adapter.config.mqttUser}' in adapter settings.`);\n callback(null, false);\n return;\n }\n // Password\n if (password.toString() !== this.adapter.config.mqttPassword) {\n this.adapter.log.warn(`[MQTT] Client ${ipMsg} Authorization rejected: received password does not match with password in adapter settings.`);\n callback(null, false);\n return;\n }\n }\n this.adapter.log.info(`[MQTT]\uD83D\uDD11 Client ${ipMsg} successfully authenticated.`);\n callback(null, true);\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n callback(null, false);\n }\n };\n\n /**\n * fired when a client connects\n */\n this.aedes.on('client', (client) => {\n try {\n if (!client) return;\n\n // Create device entry with id as key, if not yet existing (should have been set in this.aedes.authenticate already)\n if (!this.devices[client.id]) this.devices[client.id] = {};\n\n // IP\n const ip = this.devices[client.id].ip;\n const ipMsg = ip ? `${this.adapter.fullys[ip].name} (${ip})` : `${client.id} (IP unknown)`;\n\n this.adapter.log.debug(`[MQTT] Client ${ipMsg} connected to broker ${this.aedes.id}`);\n this.adapter.log.info(`[MQTT]\uD83D\uDD17 Client ${ipMsg} successfully connected.`);\n //this.adapter.log.debug(inspect(client)); //https://stackoverflow.com/a/31557814\n\n // set isAlive\n this.setIsAlive(client.id, true, 'client connected');\n\n // Schedule check if still alive\n this.scheduleCheckIfStillActive(client.id);\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n });\n\n /**\n * fired when a client publishes a message packet on the topic\n */\n this.aedes.on('publish', (packet, client) => {\n try {\n if (!client || !packet) return;\n\n this.setIsAlive(client.id, true, 'client published message');\n\n // Create device entry with id as key, if not yet existing\n if (!this.devices[client.id]) this.devices[client.id] = {};\n\n // QOS is always 1 per Fully documentation\n if (packet.qos !== 1) return;\n\n if (packet.retain) {\n /**\n * Device Info coming in...\n * Per fully documentation: The complete device info will be published every 60 seconds as fully/deviceInfo/[deviceId] topic (retaining, QOS=1).\n */\n\n // Payload as object\n const info = JSON.parse(packet.payload.toString());\n\n // Verification of device info packet\n // We don't use topic to check since we do not want to rely on user's input in Fully Browser \"MQTT Device Info Topic\" settings.\n if (!('startUrl' in info) && !('ip4' in info)) {\n this.adapter.log.error(`[MQTT] Packet rejected: ${info.ip4} - Info packet expected, but ip4 and startUrl is not defined in packet. ${info.deviceId}`);\n return;\n }\n\n // IP\n const ip = info.ip4;\n const devMsg = `${this.adapter.fullys[ip].name} (${ip})`;\n // Check IP - already done in this.aedes.authenticate, but just in case we were unable to get ip there\n if (!this.adapter.activeDeviceIPs.includes(ip)) {\n this.adapter.log.error(`[MQTT] Client ${devMsg} Packet rejected: IP is not allowed per adapter settings. ${client.id}`);\n return;\n }\n this.devices[client.id].ip = ip;\n\n // Slow down: Don't accept info event more often than x seconds\n // Per Fully doc, should not come in more often than 60s anyway...\n const prevTime = this.devices[client.id].previousInfoPublishTime;\n const limit = this.adapter.config.mqttPublishedInfoDelay * 1000; // milliseconds\n if (prevTime && prevTime !== 0) {\n if (Date.now() - prevTime < limit) {\n const diffMs = Date.now() - prevTime;\n this.adapter.log.silly(`[MQTT] ${devMsg} Packet rejected: Last packet came in ${diffMs}ms (${Math.round(diffMs / 1000)}s) ago...`);\n return;\n }\n }\n this.devices[client.id].previousInfoPublishTime = Date.now(); // set for future events\n\n /**\n * First time received device info incl. IP address etc.\n */\n if (!this.devices[client.id].mqttFirstReceived) {\n // show only once\n this.adapter.log.debug(`[MQTT] Client ${client.id} = ${this.adapter.fullys[ip].name} = ${ip}`);\n // set to true\n this.devices[client.id].mqttFirstReceived = true;\n }\n /**\n * Call Adapter function onMqttInfo()\n */\n const result = {\n clientId: client.id,\n ip: ip,\n topic: packet.topic,\n infoObj: info,\n };\n this.adapter.onMqttInfo(result);\n } else if (packet.qos === 1 && !packet.retain) {\n /**\n * Event coming in...\n * Per fully documentation: Events will be published as fully/event/[eventId]/[deviceId] topic (non-retaining, QOS=1).\n */\n // {\"deviceId\":\"xxxxxxxx-xxxxxxxx\",\"event\":\"screenOn\"}\n // NOTE: Device ID is different to client id, we actually disregard deviceId\n const msg = JSON.parse(packet.payload.toString());\n\n // Verification of event packet\n // We don't use topic to check since we do not want to rely on user's input in Fully Browser \"MQTT Event Topic\" settings.\n if (!('event' in msg)) {\n this.adapter.log.error(`[MQTT] Packet rejected: Event packet expected, but event is not defined in packet. ${client.id}`);\n return;\n }\n\n // Disregard first event once connected: mqttConnected\n if (msg.event === 'mqttConnected') {\n this.adapter.log.silly(`[MQTT] Client Publish Event: Disregard mqttConnected event - ${msg.deviceId}`);\n return;\n }\n\n // Get IP\n if (!this.devices[client.id]) {\n this.adapter.log.info(`[MQTT] Client Publish Event: Device ID and according IP not yet seen thru \"Publish Info\"`);\n this.adapter.log.info(`[MQTT] We wait until first info is published. ${msg.deviceId}`);\n return;\n }\n const ip = this.devices[client.id].ip ? this.devices[client.id].ip : '';\n if (ip === '' || typeof ip !== 'string') {\n this.adapter.log.debug(`[MQTT] Client Publish Event: IP address could not be determined. - Client ID: ${client.id}`);\n this.adapter.log.debug(`[MQTT] Please be patient until first MQTT info packet coming in (takes up to 1 minute)`);\n return; // Disregard since IP is unknown!\n }\n\n // Call function\n const result = {\n clientId: client.id,\n ip: ip,\n topic: packet.topic,\n cmd: msg.event,\n };\n if (!this.devices[client.id].mqttFirstReceived) {\n // show only once\n this.adapter.log.info(`[MQTT] \uD83D\uDD17 Client ${client.id} = ${this.adapter.fullys[ip].name} (${ip})`);\n this.devices[client.id].mqttFirstReceived = true;\n }\n /**\n * Call Adapter function onMqttEvent()\n */\n this.adapter.onMqttEvent(result);\n } else {\n // Ignore\n return;\n }\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n });\n\n /**\n * fired when a client disconnects\n */\n this.aedes.on('clientDisconnect', (client) => {\n const ip = this.devices[client.id].ip;\n const logMsgName = ip ? this.adapter.fullys[ip].name : client.id;\n if (this.adapter.config.mqttConnErrorsAsInfo) {\n this.adapter.log.info(`[MQTT] Client ${logMsgName} disconnected.`);\n } else {\n this.adapter.log.error(`[MQTT] Client ${logMsgName} disconnected.`);\n }\n this.setIsAlive(client.id, false, 'client disconnected');\n });\n\n /**\n * fired on client error\n */\n this.aedes.on('clientError', (client, e) => {\n if (this.notAuthorizedClients.includes(client.id)) return; // Error msg was already thrown in aedes.authenticate() before\n const ip = this.devices[client.id].ip;\n const logMsgName = ip ? this.adapter.fullys[ip].name : client.id;\n if (this.adapter.config.mqttConnErrorsAsInfo) {\n this.adapter.log.info(`[MQTT] ${logMsgName}: Client error - ${e.message}`);\n } else {\n this.adapter.log.error(`[MQTT]\uD83D\uDD25 ${logMsgName}: Client error - ${e.message}`);\n }\n this.adapter.log.debug(`[MQTT]\uD83D\uDD25 ${logMsgName}: Client error - stack: ${e.stack}`);\n this.setIsAlive(client.id, false, 'client error');\n });\n\n this.aedes.on('connectionError', (client, e) => {\n const ip = this.devices[client.id].ip;\n const logMsgName = ip ? this.adapter.fullys[ip].name : client.id;\n if (this.adapter.config.mqttConnErrorsAsInfo) {\n this.adapter.log.info(`[MQTT] ${logMsgName}: Connection error - ${e.message}`);\n } else {\n this.adapter.log.error(`[MQTT]\uD83D\uDD25 ${logMsgName}: Connection error - ${e.message}`);\n }\n this.adapter.log.debug(`[MQTT]\uD83D\uDD25 ${logMsgName}: Connection error - stack: ${e.stack}`);\n this.setIsAlive(client.id, false, 'connection error');\n });\n\n /**\n * fired on server error\n */\n this.server.on('error', (e: any) => {\n if (e instanceof Error && e.message.startsWith('listen EADDRINUSE')) {\n this.adapter.log.debug(`[MQTT] Cannot start server - ${e.message}`);\n this.adapter.log.error(`[MQTT]\uD83D\uDD25 Cannot start server - Port ${this.port} is already in use. Try a different port!`);\n } else {\n this.adapter.log.error(`[MQTT]\uD83D\uDD25 Cannot start server - ${e.message}`);\n }\n this.terminate();\n });\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n }\n\n /**\n * If Client is alive or not\n */\n private setIsAlive(clientId: string, isAlive: true | false, msg: string): void {\n if (isAlive) this.devices[clientId].lastTimeActive = Date.now();\n this.devices[clientId].isActive = isAlive;\n\n const ip = this.devices[clientId]?.ip;\n if (ip) {\n this.adapter.onAliveChange('MQTT', ip, isAlive, msg);\n if (isAlive) {\n this.scheduleCheckIfStillActive(clientId); // restart timer\n } else {\n // clear timer\n // @ts-expect-error \"Type 'null' is not assignable to type 'Timeout'.ts(2345)\" - we check for not being null via \"if\"\n if (this.devices[clientId].timeoutNoUpdate) this.adapter.clearTimeout(this.devices[clientId].timeoutNoUpdate);\n }\n } else {\n this.adapter.log.debug(`[MQTT] isAlive changed to ${isAlive}, but IP of client ${clientId} is still unknown.`);\n }\n }\n\n /**\n * Schedule: Check if MQTT topic was sent last x seconds ago\n * @param ip IP Address\n * @returns void\n */\n private async scheduleCheckIfStillActive(clientId: string): Promise {\n try {\n const ip = this.devices[clientId].ip;\n const ipMsg = ip ? `${this.adapter.fullys[ip].name} (${ip})` : `${clientId} (IP unknown)`;\n // this.adapter.log.debug(`[MQTT] ${ipMsg}: - Start scheduleCheckIfStillActive`);\n\n // @ts-expect-error \"Type 'null' is not assignable to type 'Timeout'.ts(2345)\" - we check for not being null via \"if\"\n if (this.devices[clientId].timeoutNoUpdate) this.adapter.clearTimeout(this.devices[clientId].timeoutNoUpdate);\n\n if (!this.devices[clientId]) this.devices[clientId] = {};\n\n const interval = 70 * 1000; // every 60s + 10s buffer\n this.devices[clientId].timeoutNoUpdate = this.adapter.setTimeout(async () => {\n try {\n const lastTimeActive = this.devices[clientId].lastTimeActive;\n if (!lastTimeActive) return;\n const diff = Date.now() - lastTimeActive;\n if (diff > 70000) {\n this.adapter.log.debug(`[MQTT] ${ipMsg} NOT ALIVE - last contact ${Math.round(diff / 1000)}s (${diff}ms) ago`);\n this.setIsAlive(clientId, false, 'client did not send message for more than 70 seconds');\n } else {\n this.adapter.log.warn(`[MQTT] ${ipMsg} Please open a issue on Github, this should never happen: scheduleCheckIfStillActive() timeout, and last contact was less than 70s ago.`);\n this.adapter.log.warn(`[MQTT] ${ipMsg} is alive - last contact ${Math.round(diff / 1000)}s (${diff}ms) ago`);\n this.setIsAlive(clientId, true, `alive check is successful (last contact: ${Math.round(diff / 1000)}s ago)`);\n }\n // Call function again since we are in callback of timeout\n this.scheduleCheckIfStillActive(clientId);\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n }, interval);\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n }\n\n /**\n * Terminate MQTT Server and close all...\n */\n public terminate(): void {\n this.adapter.log.info(`[MQTT] Disconnect all clients and close server`);\n // isAlive\n for (const clientId in this.devices) {\n // @ts-expect-error \"Type 'null' is not assignable to type 'Timeout'.ts(2345)\" - we check for not being null via \"if\"\n if (this.devices[clientId].timeoutNoUpdate) this.adapter.clearTimeout(this.devices[clientId].timeoutNoUpdate);\n this.setIsAlive(clientId, false, 'MQTT server was terminated');\n }\n\n if (this.aedes) {\n this.aedes.close(() => {\n this.adapter.log.debug('[MQTT] aedes.close() succeeded');\n if (this.server) {\n this.server.close(() => {\n this.adapter.log.debug('[MQTT] server.close() succeeded');\n });\n }\n });\n } else if (this.server) {\n this.server.close(() => {\n this.adapter.log.debug('[MQTT] server.close() succeeded');\n });\n }\n }\n}\n"], - "mappings": "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", + "sourcesContent": ["import Aedes from 'aedes';\nimport net from 'net';\nimport { FullyMqtt } from '../main';\nimport { IMqttDevice } from './interfaces';\n//import { inspect } from 'util';\n\nexport class MqttServer {\n private readonly adapter: FullyMqtt;\n private server: net.Server;\n private aedes: Aedes;\n public devices: { [mqttClientId: string]: IMqttDevice }; // {}\n private port = -1;\n private notAuthorizedClients: string[] = []; // to avoid multiple log lines\n\n /**\n * Constructor\n */\n public constructor(adapter: FullyMqtt) {\n this.adapter = adapter;\n //this.server = new net.Server();\n this.aedes = new Aedes();\n /** @ts-expect-error - https://github.com/moscajs/aedes/issues/801 */\n this.server = net.createServer(undefined, this.aedes.handle);\n this.devices = {}; // key = MQTT Client ID, property: IMqttDevice\n }\n\n /**\n * Listen\n */\n public start(): void {\n try {\n /**\n * Port\n */\n this.port = this.adapter.config.mqttPort;\n /**\n * #############################################################\n * For Developer only: change port if in dev environment\n * #############################################################\n */\n if (this.adapter.adapterDir.includes('/.dev-server/default/node_modules')) {\n this.port = 3012;\n this.adapter.log.warn(`DEVELOPER: Port changed to ${this.port} as we are in DEV Environment! If you see this log message, please open an issue on Github.`);\n }\n\n /**\n * Start Listening\n */\n this.server.listen(this.port, () => {\n this.adapter.log.info(`\uD83D\uDE80 MQTT Server started and is listening on port ${this.port}.`);\n });\n\n /**\n * Verify authorization\n * This fires first and before this.aedes.on('client', (client) ...\n * https://github.com/moscajs/aedes/blob/main/docs/Aedes.md#handler-authenticate-client-username-password-callback\n */\n this.aedes.authenticate = (client, username, password, callback) => {\n try {\n // If we saw client before and is not authorized\n if (this.notAuthorizedClients.includes(client.id)) {\n callback(null, false);\n return;\n }\n\n // Create device entry with id as key, if not yet existing\n if (!this.devices[client.id]) this.devices[client.id] = {};\n\n /**\n * Get IP\n * This rather complicated way is needed, see https://github.com/moscajs/aedes/issues/186\n * Not sure if this always works, but client.req was undefined in my test - which is suggested in https://github.com/moscajs/aedes/issues/527\n */\n let ip: string | undefined = undefined;\n if (client.conn && 'remoteAddress' in client.conn && typeof client.conn.remoteAddress === 'string') {\n const ipSource = client.conn.remoteAddress; // like: ::ffff:192.168.10.101\n this.adapter.log.debug(`[MQTT] client.conn.remoteAddress = \"${ipSource}\" - ${client.id}`);\n ip = ipSource.substring(ipSource.lastIndexOf(':') + 1); // get everything after last \":\"\n if (!this.adapter.isIpAddressValid(ip)) ip === undefined;\n }\n // Check if IP is an active device IP\n if (ip && !Object.keys(this.adapter.fullys).includes(ip)) {\n this.adapter.log.error(`[MQTT] Client ${client.id} not authorized: ${ip} is not an active Fully device IP per adapter settings.`);\n this.notAuthorizedClients.push(client.id);\n callback(null, false);\n return;\n }\n\n const ipMsg = ip ? `${this.adapter.fullys[ip].name} (${ip})` : `${client.id} (IP unknown)`;\n this.adapter.log.debug(`[MQTT] Client ${ipMsg} trys to authenticate...`);\n if (ip) this.devices[client.id].ip = ip;\n\n /**\n * Verify User and Password\n */\n if (!this.adapter.config.mqttDoNotVerifyUserPw) {\n // Username\n if (username !== this.adapter.config.mqttUser) {\n this.adapter.log.warn(`MQTT Client ${ipMsg} Authorization rejected: received user name '${username}' does not match '${this.adapter.config.mqttUser}' in adapter settings.`);\n callback(null, false);\n return;\n }\n // Password\n if (password.toString() !== this.adapter.config.mqttPassword) {\n this.adapter.log.warn(`MQTT Client ${ipMsg} Authorization rejected: received password does not match with password in adapter settings.`);\n callback(null, false);\n return;\n }\n }\n this.adapter.log.info(`\uD83D\uDD11 MQTT Client ${ipMsg} successfully authenticated.`);\n callback(null, true);\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n callback(null, false);\n }\n };\n\n /**\n * fired when a client connects\n */\n this.aedes.on('client', (client) => {\n try {\n if (!client) return;\n\n // Create device entry with id as key, if not yet existing (should have been set in this.aedes.authenticate already)\n if (!this.devices[client.id]) this.devices[client.id] = {};\n\n // IP\n const ip = this.devices[client.id].ip;\n const ipMsg = ip ? `${this.adapter.fullys[ip].name} (${ip})` : `${client.id} (IP unknown)`;\n\n this.adapter.log.debug(`[MQTT] Client ${ipMsg} connected to broker ${this.aedes.id}`);\n this.adapter.log.info(`[MQTT]\uD83D\uDD17 Client ${ipMsg} successfully connected.`);\n //this.adapter.log.debug(inspect(client)); //https://stackoverflow.com/a/31557814\n\n // set isAlive\n this.setIsAlive(client.id, true, 'client connected');\n\n // Schedule check if still alive\n this.scheduleCheckIfStillActive(client.id);\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n });\n\n /**\n * fired when a client publishes a message packet on the topic\n */\n this.aedes.on('publish', (packet, client) => {\n try {\n if (!client || !packet) return;\n\n this.setIsAlive(client.id, true, 'client published message');\n\n // Create device entry with id as key, if not yet existing\n if (!this.devices[client.id]) this.devices[client.id] = {};\n\n // QOS is always 1 per Fully documentation\n if (packet.qos !== 1) return;\n\n if (packet.retain) {\n /**\n * Device Info coming in...\n * Per fully documentation: The complete device info will be published every 60 seconds as fully/deviceInfo/[deviceId] topic (retaining, QOS=1).\n */\n\n // Payload as object\n const info = JSON.parse(packet.payload.toString());\n\n // Verification of device info packet\n // We don't use topic to check since we do not want to rely on user's input in Fully Browser \"MQTT Device Info Topic\" settings.\n if (!('startUrl' in info) && !('ip4' in info)) {\n this.adapter.log.error(`[MQTT] Packet rejected: ${info.ip4} - Info packet expected, but ip4 and startUrl is not defined in packet. ${info.deviceId}`);\n return;\n }\n\n // IP\n const ip = info.ip4;\n const devMsg = `${this.adapter.fullys[ip].name} (${ip})`;\n // Check IP - already done in this.aedes.authenticate, but just in case we were unable to get ip there\n if (!Object.keys(this.adapter.fullys).includes(ip)) {\n this.adapter.log.error(`[MQTT] Client ${devMsg} Packet rejected: IP is not allowed per adapter settings. ${client.id}`);\n return;\n }\n this.devices[client.id].ip = ip;\n\n // Slow down: Don't accept info event more often than x seconds\n // Per Fully doc, should not come in more often than 60s anyway...\n const prevTime = this.devices[client.id].previousInfoPublishTime;\n const limit = this.adapter.config.mqttPublishedInfoDelay * 1000; // milliseconds\n if (prevTime && prevTime !== 0) {\n if (Date.now() - prevTime < limit) {\n const diffMs = Date.now() - prevTime;\n this.adapter.log.silly(`[MQTT] ${devMsg} Packet rejected: Last packet came in ${diffMs}ms (${Math.round(diffMs / 1000)}s) ago...`);\n return;\n }\n }\n this.devices[client.id].previousInfoPublishTime = Date.now(); // set for future events\n\n /**\n * First time received device info incl. IP address etc.\n */\n if (!this.devices[client.id].mqttFirstReceived) {\n // show only once\n this.adapter.log.debug(`[MQTT] Client ${client.id} = ${this.adapter.fullys[ip].name} = ${ip}`);\n // set to true\n this.devices[client.id].mqttFirstReceived = true;\n }\n /**\n * Call Adapter function onMqttInfo()\n */\n const result = {\n clientId: client.id,\n ip: ip,\n topic: packet.topic,\n infoObj: info,\n };\n this.adapter.onMqttInfo(result);\n } else if (packet.qos === 1 && !packet.retain) {\n /**\n * Event coming in...\n * Per fully documentation: Events will be published as fully/event/[eventId]/[deviceId] topic (non-retaining, QOS=1).\n */\n // {\"deviceId\":\"xxxxxxxx-xxxxxxxx\",\"event\":\"screenOn\"}\n // NOTE: Device ID is different to client id, we actually disregard deviceId\n const msg = JSON.parse(packet.payload.toString());\n\n // Verification of event packet\n // We don't use topic to check since we do not want to rely on user's input in Fully Browser \"MQTT Event Topic\" settings.\n if (!('event' in msg)) {\n this.adapter.log.error(`[MQTT] Packet rejected: Event packet expected, but event is not defined in packet. ${client.id}`);\n return;\n }\n\n // Disregard first event once connected: mqttConnected\n if (msg.event === 'mqttConnected') {\n this.adapter.log.silly(`[MQTT] Client Publish Event: Disregard mqttConnected event - ${msg.deviceId}`);\n return;\n }\n\n // Get IP\n if (!this.devices[client.id]) {\n this.adapter.log.info(`[MQTT] Client Publish Event: Device ID and according IP not yet seen thru \"Publish Info\"`);\n this.adapter.log.info(`[MQTT] We wait until first info is published. ${msg.deviceId}`);\n return;\n }\n const ip = this.devices[client.id].ip ? this.devices[client.id].ip : '';\n if (ip === '' || typeof ip !== 'string') {\n this.adapter.log.debug(`[MQTT] Client Publish Event: IP address could not be determined. - Client ID: ${client.id}`);\n this.adapter.log.debug(`[MQTT] Please be patient until first MQTT info packet coming in (takes up to 1 minute)`);\n return; // Disregard since IP is unknown!\n }\n\n // Call function\n const result = {\n clientId: client.id,\n ip: ip,\n topic: packet.topic,\n cmd: msg.event,\n };\n if (!this.devices[client.id].mqttFirstReceived) {\n // show only once\n this.adapter.log.info(`[MQTT] \uD83D\uDD17 Client ${client.id} = ${this.adapter.fullys[ip].name} (${ip})`);\n this.devices[client.id].mqttFirstReceived = true;\n }\n /**\n * Call Adapter function onMqttEvent()\n */\n this.adapter.onMqttEvent(result);\n } else {\n // Ignore\n return;\n }\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n });\n\n /**\n * fired when a client disconnects\n */\n this.aedes.on('clientDisconnect', (client) => {\n const ip = this.devices[client.id].ip;\n const logMsgName = ip ? this.adapter.fullys[ip].name : client.id;\n if (this.adapter.config.mqttConnErrorsAsInfo) {\n this.adapter.log.info(`[MQTT] Client ${logMsgName} disconnected.`);\n } else {\n this.adapter.log.error(`[MQTT] Client ${logMsgName} disconnected.`);\n }\n this.setIsAlive(client.id, false, 'client disconnected');\n });\n\n /**\n * fired on client error\n */\n this.aedes.on('clientError', (client, e) => {\n if (this.notAuthorizedClients.includes(client.id)) return; // Error msg was already thrown in aedes.authenticate() before\n const ip = this.devices[client.id].ip;\n const logMsgName = ip ? this.adapter.fullys[ip].name : client.id;\n if (this.adapter.config.mqttConnErrorsAsInfo) {\n this.adapter.log.info(`[MQTT] ${logMsgName}: Client error - ${e.message}`);\n } else {\n this.adapter.log.error(`[MQTT]\uD83D\uDD25 ${logMsgName}: Client error - ${e.message}`);\n }\n this.adapter.log.debug(`[MQTT]\uD83D\uDD25 ${logMsgName}: Client error - stack: ${e.stack}`);\n this.setIsAlive(client.id, false, 'client error');\n });\n\n this.aedes.on('connectionError', (client, e) => {\n const ip = this.devices[client.id].ip;\n const logMsgName = ip ? this.adapter.fullys[ip].name : client.id;\n if (this.adapter.config.mqttConnErrorsAsInfo) {\n this.adapter.log.info(`[MQTT] ${logMsgName}: Connection error - ${e.message}`);\n } else {\n this.adapter.log.error(`[MQTT]\uD83D\uDD25 ${logMsgName}: Connection error - ${e.message}`);\n }\n this.adapter.log.debug(`[MQTT]\uD83D\uDD25 ${logMsgName}: Connection error - stack: ${e.stack}`);\n this.setIsAlive(client.id, false, 'connection error');\n });\n\n /**\n * fired on server error\n */\n this.server.on('error', (e: any) => {\n if (e instanceof Error && e.message.startsWith('listen EADDRINUSE')) {\n this.adapter.log.debug(`[MQTT] Cannot start server - ${e.message}`);\n this.adapter.log.error(`[MQTT]\uD83D\uDD25 Cannot start server - Port ${this.port} is already in use. Try a different port!`);\n } else {\n this.adapter.log.error(`[MQTT]\uD83D\uDD25 Cannot start server - ${e.message}`);\n }\n this.terminate();\n });\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n }\n\n /**\n * If Client is alive or not\n */\n private setIsAlive(clientId: string, isAlive: true | false, msg: string): void {\n if (isAlive) this.devices[clientId].lastTimeActive = Date.now();\n this.devices[clientId].isActive = isAlive;\n\n const ip = this.devices[clientId]?.ip;\n if (ip) {\n // Call Adapter function onMqttAliveChange()\n this.adapter.onMqttAlive(ip, isAlive, msg);\n if (isAlive) {\n this.scheduleCheckIfStillActive(clientId); // restart timer\n } else {\n // clear timer\n // @ts-expect-error \"Type 'null' is not assignable to type 'Timeout'.ts(2345)\" - we check for not being null via \"if\"\n if (this.devices[clientId].timeoutNoUpdate) this.adapter.clearTimeout(this.devices[clientId].timeoutNoUpdate);\n }\n } else {\n this.adapter.log.debug(`[MQTT] isAlive changed to ${isAlive}, but IP of client ${clientId} is still unknown.`);\n }\n }\n\n /**\n * Schedule: Check if MQTT topic was sent last x seconds ago\n * @param ip IP Address\n * @returns void\n */\n private async scheduleCheckIfStillActive(clientId: string): Promise {\n try {\n const ip = this.devices[clientId].ip;\n const ipMsg = ip ? `${this.adapter.fullys[ip].name} (${ip})` : `${clientId} (IP unknown)`;\n // this.adapter.log.debug(`[MQTT] ${ipMsg}: - Start scheduleCheckIfStillActive`);\n\n // @ts-expect-error \"Type 'null' is not assignable to type 'Timeout'.ts(2345)\" - we check for not being null via \"if\"\n if (this.devices[clientId].timeoutNoUpdate) this.adapter.clearTimeout(this.devices[clientId].timeoutNoUpdate);\n\n if (!this.devices[clientId]) this.devices[clientId] = {};\n\n const interval = 70 * 1000; // every 60s + 10s buffer\n this.devices[clientId].timeoutNoUpdate = this.adapter.setTimeout(async () => {\n try {\n const lastTimeActive = this.devices[clientId].lastTimeActive;\n if (!lastTimeActive) return;\n const diff = Date.now() - lastTimeActive;\n if (diff > 70000) {\n this.adapter.log.debug(`[MQTT] ${ipMsg} NOT ALIVE - last contact ${Math.round(diff / 1000)}s (${diff}ms) ago`);\n this.setIsAlive(clientId, false, 'client did not send message for more than 70 seconds');\n } else {\n this.adapter.log.warn(`[MQTT] ${ipMsg} Please open a issue on Github, this should never happen: scheduleCheckIfStillActive() timeout, and last contact was less than 70s ago.`);\n this.adapter.log.warn(`[MQTT] ${ipMsg} is alive - last contact ${Math.round(diff / 1000)}s (${diff}ms) ago`);\n this.setIsAlive(clientId, true, `alive check is successful (last contact: ${Math.round(diff / 1000)}s ago)`);\n }\n // Call function again since we are in callback of timeout\n this.scheduleCheckIfStillActive(clientId);\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n }, interval);\n } catch (e) {\n this.adapter.log.error(this.adapter.err2Str(e));\n return;\n }\n }\n\n /**\n * Terminate MQTT Server and close all...\n */\n public terminate(): void {\n this.adapter.log.info(`[MQTT] Disconnect all clients and close server`);\n // isAlive\n for (const clientId in this.devices) {\n // @ts-expect-error \"Type 'null' is not assignable to type 'Timeout'.ts(2345)\" - we check for not being null via \"if\"\n if (this.devices[clientId].timeoutNoUpdate) this.adapter.clearTimeout(this.devices[clientId].timeoutNoUpdate);\n this.setIsAlive(clientId, false, 'MQTT server was terminated');\n }\n\n if (this.aedes) {\n this.aedes.close(() => {\n this.adapter.log.debug('[MQTT] aedes.close() succeeded');\n if (this.server) {\n this.server.close(() => {\n this.adapter.log.debug('[MQTT] server.close() succeeded');\n });\n }\n });\n } else if (this.server) {\n this.server.close(() => {\n this.adapter.log.debug('[MQTT] server.close() succeeded');\n });\n }\n }\n}\n"], + "mappings": "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", "names": ["Aedes", "net"] } diff --git a/build/lib/restApi.js b/build/lib/restApi.js index 4ee1d73..c78fb2e 100644 --- a/build/lib/restApi.js +++ b/build/lib/restApi.js @@ -54,83 +54,59 @@ class RestApiFully { } else { finalUrlParam = "cmd=" + cmd; } - const result = await this.axiosGetInfoOrSendCmd(device, "sendCmd", cmd, finalUrlParam); - return result.status; + const result = await this.axiosSendCmd(device, cmd, finalUrlParam); + return result; } catch (e) { this.adapter.log.error(`[REST] ${device.name}: ${this.adapter.err2Str(e)}`); return false; } } - async axiosGetInfoOrSendCmd(device, what, cmd, urlParam) { + async axiosSendCmd(device, cmd, urlParam) { var _a, _b, _c; - const baseUrl = `${device.restProtocol}://${device.ip}:${device.restPort}/?password=${this.encodePassword(device.restPassword)}&type=json`; - let finalUrl = ""; - if (what === "getInfo") { - finalUrl = baseUrl + "&cmd=deviceInfo"; - } else { - finalUrl = baseUrl + "&" + urlParam; - } + const url = `${device.restProtocol}://${device.ip}:${device.restPort}/?password=${this.encodePassword(device.restPassword)}&type=json&${urlParam}`; const config = { method: "get", timeout: this.adapter.config.restTimeout }; try { - let urlHiddenPassword = finalUrl; + let urlHiddenPassword = url; urlHiddenPassword = urlHiddenPassword.replace(/password=.*&type/g, "password=(hidden)&type"); - this.adapter.log.debug(`[REST] ${device.name}: Start ${what} ${what === "sendCmd" ? '"' + cmd + '"' : ""}, URL: ${urlHiddenPassword}`); - const response = await import_axios.default.get(finalUrl, config); + this.adapter.log.debug(`[REST] ${device.name}: Start sending command ${cmd}, URL: ${urlHiddenPassword}`); + const response = await import_axios.default.get(url, config); if (response.status !== 200) { - this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === "sendCmd" ? cmd : ""} failed: ${response.status} - ${response.statusText}`); - this.adapter.onAliveChange("REST", device.ip, false, "${response.status} - ${response.statusText}"); - return { status: false }; + this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: ${response.status} - ${response.statusText}`); + return false; } if (!("status" in response)) { - this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === "sendCmd" ? cmd : ""} failed: Response received but it does not have key 'status'`); - this.adapter.onAliveChange("REST", device.ip, false, "response without status key"); - return { status: false }; + this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but it does not have key 'status'`); + return false; } if (!("data" in response)) { - this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === "sendCmd" ? cmd : ""} failed: Response received but it does not have key 'data'`); - this.adapter.onAliveChange("REST", device.ip, false, "response without data key"); - return { status: false }; - } - this.adapter.log.debug(`[REST] ${device.name}: ${what} response.data: ${JSON.stringify(response.data)}`); - if (what === "getInfo") { - this.adapter.onAliveChange("REST", device.ip, true, "information successfully received"); - if (!("deviceName" in response.data)) { - this.adapter.log.error(`[REST] ${device.name}: getInfo failed: Response data received, but data does not have key 'deviceName'`); - return { status: false }; - } - this.adapter.log.debug(`[REST] ${device.name}: getInfo was successful: Response = ${response.status} - ${response.statusText}`); - return { status: true, infoObj: response.data }; + this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but it does not have key 'data'`); + return false; } + this.adapter.log.debug(`[REST] ${device.name}: Sending command ${cmd} response.data: ${JSON.stringify(response.data)}`); if (!("status" in response.data)) { - this.adapter.onAliveChange("REST", device.ip, false, "response.data without status key"); - this.adapter.log.error(`[REST] ${device.name}: Sending ${what} failed: Response received but response.data does not have key 'status'`); - return { status: false }; + this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but response.data does not have key 'status'`); + return false; } switch (response.data.status) { case "OK": - this.adapter.log.debug(`[REST] ${device.name}: Sending ${what} successful: - Status = "${response.data.status}", Message = "${response.data.statustext}"`); - this.adapter.onAliveChange("REST", device.ip, true, "successfully received response"); - return { status: true }; + this.adapter.log.debug(`[REST] ${device.name}: Sending command ${cmd} successful: - Status = "${response.data.status}", Message = "${response.data.statustext}"`); + return true; case "Error": if (response.data.statustext === "Please login") { - this.adapter.log.error(`[REST] ${device.name}: Error: Remote Admin Password seems to be incorrect. Sending ${what} failed.`); - this.adapter.onAliveChange("REST", device.ip, false, "incorrect Remote Admin password"); + this.adapter.log.error(`[REST] ${device.name}: Error: Remote Admin Password seems to be incorrect. Sending command ${cmd} failed.`); } else { - this.adapter.log.error(`[REST] ${device.name}: Error: Sending cmd ${what} failed, received status text: ${response.data.statustext}`); - this.adapter.onAliveChange("REST", device.ip, false, `sending cmd ${what} failed`); + this.adapter.log.error(`[REST] ${device.name}: Error: Sending command ${cmd} failed, received status text: ${response.data.statustext}`); } - return { status: false }; + return false; default: - this.adapter.log.error(`[REST] ${device.name}: Undefined response.data.status = "${response.data.status}" when sending cmd ${what}: ${response.status} - ${response.statusText}`); - this.adapter.onAliveChange("REST", device.ip, false, "received undefined response.data.status"); - return { status: false }; + this.adapter.log.error(`[REST] ${device.name}: Undefined response.data.status = "${response.data.status}" when sending command ${cmd}: ${response.status} - ${response.statusText}`); + return false; } } catch (err) { - const errTxt = `[REST] ${device.name}: Sending ${what} failed`; - this.adapter.onAliveChange("REST", device.ip, false, `sending ${what} failed`); + const errTxt = `[REST] ${device.name}: Sending command ${cmd} failed`; if (import_axios.default.isAxiosError(err)) { if (!(err == null ? void 0 : err.response)) { this.adapter.log.warn(`${errTxt}: No response`); @@ -142,9 +118,9 @@ class RestApiFully { this.adapter.log.error(`${errTxt}: General Error`); } } else { - this.adapter.log.error(`[REST] ${device.name} Error: ${this.adapter.err2Str(err)}`); + this.adapter.log.error(`${errTxt}: Error: ${this.adapter.err2Str(err)}`); } - return { status: false }; + return false; } } encodePassword(pw) { diff --git a/build/lib/restApi.js.map b/build/lib/restApi.js.map index ca73ea4..465b7fd 100644 --- a/build/lib/restApi.js.map +++ b/build/lib/restApi.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../src/lib/restApi.ts"], - "sourcesContent": ["/**\r\n * REST API Class\r\n * Purpose: sending commands to Fully, since sending via MQTT is not supported by Fully.\r\n */\r\n\r\nimport axios from 'axios';\r\nimport { FullyMqtt } from '../main';\r\nimport { IDevice } from './interfaces';\r\n\r\n/**\r\n * @class RestApi\r\n * @desc To send commands via REST API to Fully Browser\r\n */\r\nexport class RestApiFully {\r\n /**\r\n * Constants and Variables\r\n */\r\n private readonly adapter: FullyMqtt;\r\n\r\n /**\r\n * Class Constructor\r\n * @param adapter - ioBroker adapter instance object\r\n */\r\n public constructor(adapter: FullyMqtt) {\r\n this.adapter = adapter;\r\n }\r\n\r\n /**\r\n * Get Info Object from Fully\r\n * @param ip - IP Address\r\n * @returns info object, or false in case of error\r\n */\r\n /* ----- NO LONGER USED since v0.1.0 ---\r\n public async getInfo(ip: string): Promise<{ [k: string]: any } | false> {\r\n try {\r\n const device = this.adapter.fullys[ip];\r\n const result = await this.axiosGetInfoOrSendCmd(device, 'getInfo');\r\n if (result.status && result.infoObj !== undefined) {\r\n return result.infoObj;\r\n } else {\r\n return false;\r\n }\r\n } catch (e) {\r\n this.adapter.log.error(`[REST] ${this.adapter.fullys[ip].name}: ${this.adapter.err2Str(e)}`);\r\n return false;\r\n }\r\n }\r\n */\r\n\r\n /**\r\n * Send a command to Fully\r\n * @param device - device object\r\n * @param cmd - 'loadStartURL', 'screenOn', etc.\r\n * @param val - state value\r\n * @returns true if successful, false if not\r\n */\r\n public async sendCmd(device: IDevice, cmd: string, val: any): Promise {\r\n try {\r\n interface ISendCmd {\r\n urlParameter: string;\r\n cleanSpaces?: true;\r\n encode?: true;\r\n }\r\n const cmds: { [k: string]: ISendCmd } = {\r\n textToSpeech: { urlParameter: 'cmd=textToSpeech&text=', cleanSpaces: true, encode: true },\r\n loadURL: { urlParameter: 'cmd=loadURL&url=', cleanSpaces: true, encode: true },\r\n startApplication: { urlParameter: 'cmd=startApplication&package=', cleanSpaces: true },\r\n screenBrightness: { urlParameter: 'cmd=setStringSetting&key=screenBrightness&value=' },\r\n setAudioVolume: { urlParameter: 'cmd=setAudioVolume&stream=3&level=' },\r\n };\r\n let finalUrlParam = '';\r\n if (cmd in cmds) {\r\n if (cmds[cmd].cleanSpaces) {\r\n val = val.toString().trim();\r\n val = val.replace(/\\s+/g, ' ');\r\n }\r\n if (cmds[cmd].encode) {\r\n val = val.toString().trim();\r\n val = encodeURIComponent(val);\r\n }\r\n finalUrlParam = cmds[cmd].urlParameter + val;\r\n } else {\r\n finalUrlParam = 'cmd=' + cmd;\r\n }\r\n\r\n const result = await this.axiosGetInfoOrSendCmd(device, 'sendCmd', cmd, finalUrlParam);\r\n return result.status;\r\n } catch (e) {\r\n this.adapter.log.error(`[REST] ${device.name}: ${this.adapter.err2Str(e)}`);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Axios: Get Device Info or Send Command\r\n * @param device - device object\r\n * @param what - 'getInfo' to get device info or 'sendCmd' to send a command\r\n * @param cmd - if 'sendCmd': Command like \"screenOff\"\r\n * @param urlParam - if 'sendCmd': URL parameter like \"cmd=screenOff\"\r\n * @returns if what='getInfo': false if error, device info object if true\r\n * if what='sendCmd': false if error, true if successful\r\n *\r\n * TODO: Remove 'getInfo' since no longer used from version 0.1.0\r\n *\r\n */\r\n private async axiosGetInfoOrSendCmd(device: IDevice, what: 'getInfo' | 'sendCmd', cmd?: string, urlParam?: string): Promise<{ status: true | false; infoObj?: { [k: string]: any } }> {\r\n // Base URL\r\n const baseUrl = `${device.restProtocol}://${device.ip}:${device.restPort}/?password=${this.encodePassword(device.restPassword)}&type=json`;\r\n let finalUrl = '';\r\n if (what === 'getInfo') {\r\n finalUrl = baseUrl + '&cmd=deviceInfo';\r\n } else {\r\n finalUrl = baseUrl + '&' + urlParam;\r\n }\r\n\r\n // Axios config\r\n const config = {\r\n method: 'get',\r\n timeout: this.adapter.config.restTimeout,\r\n };\r\n\r\n try {\r\n // Log\r\n let urlHiddenPassword = finalUrl;\r\n urlHiddenPassword = urlHiddenPassword.replace(/password=.*&type/g, 'password=(hidden)&type');\r\n this.adapter.log.debug(`[REST] ${device.name}: Start ${what} ${what === 'sendCmd' ? '\"' + cmd + '\"' : ''}, URL: ${urlHiddenPassword}`);\r\n\r\n // Axios: Send command\r\n const response = await axios.get(finalUrl, config);\r\n\r\n // Errors\r\n if (response.status !== 200) {\r\n this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === 'sendCmd' ? cmd : ''} failed: ${response.status} - ${response.statusText}`);\r\n this.adapter.onAliveChange('REST', device.ip, false, '${response.status} - ${response.statusText}'); // Update isAlive\r\n return { status: false };\r\n }\r\n if (!('status' in response)) {\r\n this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === 'sendCmd' ? cmd : ''} failed: Response received but it does not have key 'status'`);\r\n this.adapter.onAliveChange('REST', device.ip, false, 'response without status key'); // Update isAlive\r\n return { status: false };\r\n }\r\n if (!('data' in response)) {\r\n this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === 'sendCmd' ? cmd : ''} failed: Response received but it does not have key 'data'`);\r\n this.adapter.onAliveChange('REST', device.ip, false, 'response without data key'); // Update isAlive\r\n return { status: false };\r\n }\r\n this.adapter.log.debug(`[REST] ${device.name}: ${what} response.data: ${JSON.stringify(response.data)}`);\r\n\r\n // Handle Device Info\r\n if (what === 'getInfo') {\r\n this.adapter.onAliveChange('REST', device.ip, true, 'information successfully received'); // Update isAlive\r\n if (!('deviceName' in response.data)) {\r\n // we check if info object is ok by checking for deviceName, could also use any other key like screenOn etc.\r\n this.adapter.log.error(`[REST] ${device.name}: getInfo failed: Response data received, but data does not have key 'deviceName'`);\r\n return { status: false };\r\n }\r\n this.adapter.log.debug(`[REST] ${device.name}: getInfo was successful: Response = ${response.status} - ${response.statusText}`);\r\n return { status: true, infoObj: response.data };\r\n }\r\n\r\n // Handle all other commands\r\n if (!('status' in response.data)) {\r\n this.adapter.onAliveChange('REST', device.ip, false, 'response.data without status key'); // Update isAlive\r\n this.adapter.log.error(`[REST] ${device.name}: Sending ${what} failed: Response received but response.data does not have key 'status'`);\r\n return { status: false };\r\n }\r\n switch (response.data.status) {\r\n case 'OK':\r\n this.adapter.log.debug(`[REST] ${device.name}: Sending ${what} successful: - Status = \"${response.data.status}\", Message = \"${response.data.statustext}\"`);\r\n this.adapter.onAliveChange('REST', device.ip, true, 'successfully received response'); // Update isAlive\r\n return { status: true };\r\n case 'Error':\r\n if (response.data.statustext === 'Please login') {\r\n this.adapter.log.error(`[REST] ${device.name}: Error: Remote Admin Password seems to be incorrect. Sending ${what} failed.`);\r\n this.adapter.onAliveChange('REST', device.ip, false, 'incorrect Remote Admin password'); // Update isAlive\r\n } else {\r\n this.adapter.log.error(`[REST] ${device.name}: Error: Sending cmd ${what} failed, received status text: ${response.data.statustext}`);\r\n this.adapter.onAliveChange('REST', device.ip, false, `sending cmd ${what} failed`); // Update isAlive\r\n }\r\n return { status: false };\r\n default:\r\n // Unexpected\r\n this.adapter.log.error(`[REST] ${device.name}: Undefined response.data.status = \"${response.data.status}\" when sending cmd ${what}: ${response.status} - ${response.statusText}`);\r\n this.adapter.onAliveChange('REST', device.ip, false, 'received undefined response.data.status');\r\n return { status: false };\r\n }\r\n } catch (err) {\r\n const errTxt = `[REST] ${device.name}: Sending ${what} failed`;\r\n this.adapter.onAliveChange('REST', device.ip, false, `sending ${what} failed`); // Update isAlive\r\n if (axios.isAxiosError(err)) {\r\n if (!err?.response) {\r\n this.adapter.log.warn(`${errTxt}: No response`);\r\n } else if (err.response?.status === 400) {\r\n this.adapter.log.error('${errTxt}: Login Failed - Error 400 - ' + err.response?.statusText);\r\n } else if (err.response?.status) {\r\n this.adapter.log.error(`${errTxt}: ${err.response.status} - ${err.response.statusText}`);\r\n } else {\r\n this.adapter.log.error(`${errTxt}: General Error`);\r\n }\r\n } else {\r\n this.adapter.log.error(`[REST] ${device.name} Error: ${this.adapter.err2Str(err)}`);\r\n }\r\n return { status: false };\r\n }\r\n }\r\n\r\n /**\r\n * To encode a password to be sent to web server\r\n * Source: fixedEncodeURIComponent() from https://github.com/arteck/ioBroker.fullybrowser/blob/master/main.js\r\n * @param pw Password\r\n * @returns Encoded password\r\n */\r\n private encodePassword(pw: string): string {\r\n return encodeURIComponent(pw).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);\r\n }\r\n}\r\n"], - "mappings": "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", + "sourcesContent": ["/**\n * REST API Class\n * Purpose: sending commands to Fully, since sending via MQTT is not supported by Fully.\n */\n\nimport axios from 'axios';\nimport { FullyMqtt } from '../main';\nimport { IDevice } from './interfaces';\n\n/**\n * @class RestApi\n * @desc To send commands via REST API to Fully Browser\n */\nexport class RestApiFully {\n /**\n * Constants and Variables\n */\n private readonly adapter: FullyMqtt;\n\n /**\n * Class Constructor\n * @param adapter - ioBroker adapter instance object\n */\n public constructor(adapter: FullyMqtt) {\n this.adapter = adapter;\n }\n\n /**\n * Send a command to Fully\n * @param device - device object\n * @param cmd - 'loadStartURL', 'screenOn', etc.\n * @param val - state value\n * @returns true if successful, false if not\n */\n public async sendCmd(device: IDevice, cmd: string, val: any): Promise {\n try {\n interface ISendCmd {\n urlParameter: string;\n cleanSpaces?: true;\n encode?: true;\n }\n const cmds: { [k: string]: ISendCmd } = {\n textToSpeech: { urlParameter: 'cmd=textToSpeech&text=', cleanSpaces: true, encode: true },\n loadURL: { urlParameter: 'cmd=loadURL&url=', cleanSpaces: true, encode: true },\n startApplication: { urlParameter: 'cmd=startApplication&package=', cleanSpaces: true },\n screenBrightness: { urlParameter: 'cmd=setStringSetting&key=screenBrightness&value=' },\n setAudioVolume: { urlParameter: 'cmd=setAudioVolume&stream=3&level=' },\n };\n let finalUrlParam = '';\n if (cmd in cmds) {\n if (cmds[cmd].cleanSpaces) {\n val = val.toString().trim();\n val = val.replace(/\\s+/g, ' ');\n }\n if (cmds[cmd].encode) {\n val = val.toString().trim();\n val = encodeURIComponent(val);\n }\n finalUrlParam = cmds[cmd].urlParameter + val;\n } else {\n finalUrlParam = 'cmd=' + cmd;\n }\n\n const result = await this.axiosSendCmd(device, cmd, finalUrlParam);\n return result;\n } catch (e) {\n this.adapter.log.error(`[REST] ${device.name}: ${this.adapter.err2Str(e)}`);\n return false;\n }\n }\n\n /**\n * Axios: Send Command\n * @param device - device object\n * @param cmd - Command like \"screenOff\"\n * @param urlParam - URL parameter like \"cmd=screenOff\"\n * @returns false if error, true if successful\n */\n private async axiosSendCmd(device: IDevice, cmd: string, urlParam: string): Promise {\n // Base URL\n const url = `${device.restProtocol}://${device.ip}:${device.restPort}/?password=${this.encodePassword(device.restPassword)}&type=json&${urlParam}`;\n\n // Axios config\n const config = {\n method: 'get',\n timeout: this.adapter.config.restTimeout,\n };\n\n try {\n // Log\n let urlHiddenPassword = url;\n urlHiddenPassword = urlHiddenPassword.replace(/password=.*&type/g, 'password=(hidden)&type');\n this.adapter.log.debug(`[REST] ${device.name}: Start sending command ${cmd}, URL: ${urlHiddenPassword}`);\n\n // Axios: Send command\n const response = await axios.get(url, config);\n\n // Errors\n if (response.status !== 200) {\n this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: ${response.status} - ${response.statusText}`);\n return false;\n }\n if (!('status' in response)) {\n this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but it does not have key 'status'`);\n return false;\n }\n if (!('data' in response)) {\n this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but it does not have key 'data'`);\n return false;\n }\n this.adapter.log.debug(`[REST] ${device.name}: Sending command ${cmd} response.data: ${JSON.stringify(response.data)}`);\n\n if (!('status' in response.data)) {\n this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but response.data does not have key 'status'`);\n return false;\n }\n switch (response.data.status) {\n case 'OK':\n this.adapter.log.debug(`[REST] ${device.name}: Sending command ${cmd} successful: - Status = \"${response.data.status}\", Message = \"${response.data.statustext}\"`);\n return true;\n case 'Error':\n if (response.data.statustext === 'Please login') {\n this.adapter.log.error(`[REST] ${device.name}: Error: Remote Admin Password seems to be incorrect. Sending command ${cmd} failed.`);\n } else {\n this.adapter.log.error(`[REST] ${device.name}: Error: Sending command ${cmd} failed, received status text: ${response.data.statustext}`);\n }\n return false;\n default:\n // Unexpected\n this.adapter.log.error(`[REST] ${device.name}: Undefined response.data.status = \"${response.data.status}\" when sending command ${cmd}: ${response.status} - ${response.statusText}`);\n return false;\n }\n } catch (err) {\n const errTxt = `[REST] ${device.name}: Sending command ${cmd} failed`;\n if (axios.isAxiosError(err)) {\n if (!err?.response) {\n this.adapter.log.warn(`${errTxt}: No response`);\n } else if (err.response?.status === 400) {\n this.adapter.log.error('${errTxt}: Login Failed - Error 400 - ' + err.response?.statusText);\n } else if (err.response?.status) {\n this.adapter.log.error(`${errTxt}: ${err.response.status} - ${err.response.statusText}`);\n } else {\n this.adapter.log.error(`${errTxt}: General Error`);\n }\n } else {\n this.adapter.log.error(`${errTxt}: Error: ${this.adapter.err2Str(err)}`);\n }\n return false;\n }\n }\n\n /**\n * To encode a password to be sent to web server\n * Source: fixedEncodeURIComponent() from https://github.com/arteck/ioBroker.fullybrowser/blob/master/main.js\n * @param pw Password\n * @returns Encoded password\n */\n private encodePassword(pw: string): string {\n return encodeURIComponent(pw).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);\n }\n}\n"], + "mappings": "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", "names": ["axios"] } diff --git a/build/main.js b/build/main.js index b7fd0d3..faae40d 100644 --- a/build/main.js +++ b/build/main.js @@ -51,9 +51,9 @@ class FullyMqtt extends utils.Adapter { this.isIpAddressValid = import_methods.isIpAddressValid.bind(this); this.restApi_inst = new import_restApi.RestApiFully(this); this.fullys = {}; - this.disabledDeviceIds = []; - this.activeDeviceIPs = []; - this.onAliveChange_EverBeenCalledBefore = false; + this.fullysNotEnabled = {}; + this.fullysAll = {}; + this.onMqttAlive_EverBeenCalledBefore = false; this.on("ready", this.onReady.bind(this)); this.on("stateChange", this.onStateChange.bind(this)); this.on("unload", this.onUnload.bind(this)); @@ -67,20 +67,27 @@ class FullyMqtt extends utils.Adapter { this.log.error(`Adapter settings initialization failed. ---> Please check your adapter instance settings!`); return; } - this.mqtt_Server = new import_mqtt_server.MqttServer(this); - this.mqtt_Server.start(); for (const ip in this.fullys) { - await this.main(this.fullys[ip]); + const res = await this.createFullyDeviceObjects(this.fullys[ip]); + if (res) + await this.subscribeStatesAsync(this.fullys[ip].id + ".Commands.*"); + this.setState(this.fullys[ip].id + ".enabled", { val: true, ack: true }); + } + for (const ip in this.fullysNotEnabled) { + this.setState(this.fullysNotEnabled[ip].id + ".enabled", { val: false, ack: true }); + this.setState(this.fullysNotEnabled[ip].id + ".alive", { val: null, ack: true }); } + this.mqtt_Server = new import_mqtt_server.MqttServer(this); + this.mqtt_Server.start(); this.deleteRemovedDeviceObjects(); } catch (e) { this.log.error(this.err2Str(e)); return; } } - async main(device) { + async createFullyDeviceObjects(device) { try { - this.log.debug(`Start main() - ${device.name} (${device.ip})\u2026`); + this.log.debug(`Start createFullyDeviceObjects() - ${device.name} (${device.ip})\u2026`); await this.setObjectNotExistsAsync(device.id, { type: "device", common: { @@ -104,7 +111,8 @@ class FullyMqtt extends utils.Adapter { native: {} }); await this.setObjectNotExistsAsync(device.id + ".lastInfoUpdate", { type: "state", common: { name: "Last information update", desc: "Date/time of last information update from Fully Browser", type: "number", role: "value.time", read: true, write: false }, native: {} }); - await this.setObjectNotExistsAsync(device.id + ".Commands", { type: "channel", common: { name: "Commands (REST API)" }, native: {} }); + await this.setObjectNotExistsAsync(device.id + ".enabled", { type: "state", common: { name: "Is device enabled in adapter settings?", desc: "If this device is enabled in the adapter settings", type: "boolean", role: "indicator", read: true, write: false }, native: {} }); + await this.setObjectNotExistsAsync(device.id + ".Commands", { type: "channel", common: { name: "Commands" }, native: {} }); const allCommands = import_constants.CONST.cmds.concat(import_constants.CONST.cmdsSwitches); for (const cmdObj of allCommands) { let lpRole = ""; @@ -120,12 +128,12 @@ class FullyMqtt extends utils.Adapter { } await this.setObjectNotExistsAsync(device.id + ".Events", { type: "channel", common: { name: "MQTT Events" }, native: {} }); for (const event of import_constants.CONST.mqttEvents) { - await this.setObjectNotExistsAsync(device.id + ".Events." + event, { type: "state", common: { name: "MQTT Event: " + event, type: "boolean", role: "switch", read: true, write: false }, native: {} }); + await this.setObjectNotExistsAsync(device.id + ".Events." + event, { type: "state", common: { name: "Event: " + event, type: "boolean", role: "switch", read: true, write: false }, native: {} }); } - await this.subscribeStatesAsync(device.id + ".Commands.*"); + return true; } catch (e) { this.log.error(this.err2Str(e)); - return; + return false; } } async deleteRemovedDeviceObjects() { @@ -141,11 +149,11 @@ class FullyMqtt extends utils.Adapter { allObjectDeviceIds.push(deviceId); } } + const allConfigDeviceIds = []; + for (const ip in this.fullysAll) { + allConfigDeviceIds.push(this.fullysAll[ip].id); + } for (const id of allObjectDeviceIds) { - const allConfigDeviceIds = this.disabledDeviceIds; - for (const ip in this.fullys) { - allConfigDeviceIds.push(this.fullys[ip].id); - } if (!allConfigDeviceIds.includes(id)) { await this.delObjectAsync(id, { recursive: true }); this.log.info(`Cleanup: Deleted no longer defined device objects of '${id}'.`); @@ -182,6 +190,7 @@ class FullyMqtt extends utils.Adapter { name: "", id: "", ip: "", + enabled: false, mqttInfoObjectsCreated: false, mqttInfoKeys: [], restProtocol: "http", @@ -235,17 +244,17 @@ class FullyMqtt extends utils.Adapter { } else { finalDevice.restPassword = lpDevice.restPassword; } + finalDevice.enabled = lpDevice.enabled ? true : false; const logConfig = { ...finalDevice }; logConfig.restPassword = "(hidden)"; this.log.debug(`Final Config: ${JSON.stringify(logConfig)}`); + this.fullysAll[finalDevice.ip] = finalDevice; if (lpDevice.enabled) { this.fullys[finalDevice.ip] = finalDevice; - this.activeDeviceIPs.push(lpDevice.ip); this.log.info(`\u{1F5F8} ${finalDevice.name} (${finalDevice.ip}): Config successfully verified.`); } else { - this.disabledDeviceIds.push(finalDevice.id); - this.log.debug(`Device ${finalDevice.name} (${finalDevice.ip}) is not enabled, so skip it.`); - continue; + this.fullysNotEnabled[finalDevice.ip] = finalDevice; + this.log.info(`${finalDevice.name} (${finalDevice.ip}) is not enabled in adapter settings, so it will not be used by adapter.`); } } if (Object.keys(this.fullys).length === 0) { @@ -258,18 +267,18 @@ class FullyMqtt extends utils.Adapter { return false; } } - async onAliveChange(source, ip, isAlive, msg) { + async onMqttAlive(ip, isAlive, msg) { try { const prevIsAlive = this.fullys[ip].isAlive; this.fullys[ip].isAlive = isAlive; - const calledBefore = this.onAliveChange_EverBeenCalledBefore; - this.onAliveChange_EverBeenCalledBefore = true; + const calledBefore = this.onMqttAlive_EverBeenCalledBefore; + this.onMqttAlive_EverBeenCalledBefore = true; if (!calledBefore && isAlive === true || prevIsAlive !== isAlive) { this.setState(this.fullys[ip].id + ".alive", { val: isAlive, ack: true }); if (isAlive) { - this.log.info(`${this.fullys[ip].name} is alive (${source}: ${msg})`); + this.log.info(`${this.fullys[ip].name} is alive (MQTT: ${msg})`); } else { - this.log.warn(`${this.fullys[ip].name} is not alive! (${source}: ${msg})`); + this.log.warn(`${this.fullys[ip].name} is not alive! (MQTT: ${msg})`); } } else { } @@ -292,7 +301,7 @@ class FullyMqtt extends utils.Adapter { } async onMqttInfo(obj) { try { - this.log.debug(`[MQTT]\u{1F4E1} ${this.fullys[obj.ip].name} published info, topic: ${obj.topic}`); + this.log.debug(`[MQTT] ${this.fullys[obj.ip].name} published info, topic: ${obj.topic}`); const formerInfoKeysLength = this.fullys[obj.ip].mqttInfoKeys.length; const newInfoKeysAdded = []; for (const key in obj.infoObj) { @@ -387,7 +396,7 @@ class FullyMqtt extends utils.Adapter { throw `onStateChange() - ${stateId}: fullyCmd could not be determined!`; const sendCommand = await this.restApi_inst.sendCmd(fully, cmdToSend, stateObj.val); if (sendCommand) { - this.log.info(`${fully.name}: ${cmd} successfully set to ${stateObj.val}`); + this.log.info(`\u{1F5F8} ${fully.name}: Command ${cmd} successfully set to ${stateObj.val}`); if (switchConf !== void 0) { const onOrOffCmdVal = cmd === switchConf.cmdOn ? true : false; await this.setStateAsync(`${pth}.${switchConf.id}`, { val: onOrOffCmdVal, ack: true }); @@ -445,11 +454,13 @@ class FullyMqtt extends utils.Adapter { return -1; } } - onUnload(callback) { + async onUnload(callback) { try { - if (this.fullys) { - for (const ip in this.fullys) { - this.setState(this.fullys[ip].id + ".alive", { val: false, ack: true }); + if (this.fullysAll) { + for (const ip in this.fullysAll) { + if (await this.getObjectAsync(this.fullysAll[ip].id)) { + this.setState(this.fullysAll[ip].id + ".alive", { val: null, ack: true }); + } } } if (this.mqtt_Server) { diff --git a/build/main.js.map b/build/main.js.map index 4cb6a22..4094501 100644 --- a/build/main.js.map +++ b/build/main.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/main.ts"], - "sourcesContent": ["/**\n * -------------------------------------------------------------------\n * ioBroker Fully Browser MQTT Adapter\n * @github https://github.com/Acgua/ioBroker.fully-mqtt\n * @forum https://forum.iobroker.net/topic/63705/\n * @author Acgua \n * @license Apache License 2.0\n * -------------------------------------------------------------------\n */\n\n/**\n * For all imported NPM modules, open console, change dir for example to \"C:\\iobroker\\node_modules\\ioBroker.fully-mqtt\\\"\n * and execute \"npm install \", e.g., npm install axios\n */\nimport * as utils from '@iobroker/adapter-core';\nimport { CONST } from './lib/constants';\nimport { ICmds, IDevice } from './lib/interfaces';\nimport { cleanDeviceName, err2Str, getConfigValuePerKey, isEmpty, isIpAddressValid, wait } from './lib/methods';\nimport { MqttServer } from './lib/mqtt-server';\nimport { RestApiFully } from './lib/restApi';\n\n/**\n * Main ioBroker Adapter Class\n */\nexport class FullyMqtt extends utils.Adapter {\n // Imported methods from ./lib/methods\n public err2Str = err2Str.bind(this);\n public isEmpty = isEmpty.bind(this);\n public wait = wait.bind(this);\n public cleanDeviceName = cleanDeviceName.bind(this);\n public getConfigValuePerKey = getConfigValuePerKey.bind(this);\n public isIpAddressValid = isIpAddressValid.bind(this);\n // MQTT\n private mqtt_Server: MqttServer | undefined;\n\n // REST API\n private restApi_inst = new RestApiFully(this); // RestApi Class Instance\n\n /**\n * Active Fullys: IP as key, and object per IDevice\n * {\n * '192.168.10.20': {name: 'Tablet Kitchen', id:'Tablet-Kitchen', ip:'192.168.10.20', ...},\n * '192.168.10.30': {name: 'Tablet Hallway', id:'Tablet-Hallway', ip:'192.168.10.30', ...},\n * }\n * Use this.getFullyPerKey() to get fully object per provided key\n */\n public fullys: { [ip: string]: IDevice } = {};\n\n // array of device ids, which are not activated\n public disabledDeviceIds = [] as string[];\n // All active IP addresses\n public activeDeviceIPs = [] as string[]; // for MQTT server to verify IP\n\n // Has onAliveChange() ever been called before?\n private onAliveChange_EverBeenCalledBefore = false;\n\n /**\n * Constructor\n */\n public constructor(options: Partial = {}) {\n super({ ...options, name: 'fully-mqtt' });\n\n this.on('ready', this.onReady.bind(this));\n this.on('stateChange', this.onStateChange.bind(this));\n // this.on('objectChange', this.onObjectChange.bind(this));\n // this.on('message', this.onMessage.bind(this));\n this.on('unload', this.onUnload.bind(this));\n }\n\n /**\n * Is called when databases are connected and adapter received configuration.\n */\n private async onReady(): Promise {\n try {\n /**\n * Set the connection indicator to false during startup\n */\n this.setState('info.connection', { val: false, ack: true });\n\n /**\n * Init configuration\n */\n if (await this.initConfig()) {\n this.log.debug(`Adapter settings successfully verified and initialized.`);\n } else {\n this.log.error(`Adapter settings initialization failed. ---> Please check your adapter instance settings!`);\n return;\n }\n\n /**\n * Start MQTT Server\n */\n this.mqtt_Server = new MqttServer(this);\n this.mqtt_Server.start();\n\n /**\n * Call main() for each device\n */\n for (const ip in this.fullys) {\n await this.main(this.fullys[ip]);\n }\n\n /**\n * Delete device object tree(s) if deleted or renamed in config\n */\n this.deleteRemovedDeviceObjects();\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * main function for each Fully Browser Device\n * @param device Fully Browser Device Object\n */\n private async main(device: IDevice): Promise {\n try {\n this.log.debug(`Start main() - ${device.name} (${device.ip})\u2026`);\n\n /**\n * Create device object(s)\n */\n // Device and Info object\n await this.setObjectNotExistsAsync(device.id, {\n type: 'device',\n common: {\n name: device.name,\n //@ts-expect-error - Object \"statusStates\" is needed for status, error is: Object literal may only specify known properties, and 'statusStates' does not exist in type 'DeviceCommon'.ts(2345)\n statusStates: { onlineId: `${this.namespace}.${device.id}.alive` },\n },\n native: {},\n });\n await this.setObjectNotExistsAsync(device.id + '.Info', { type: 'channel', common: { name: 'Device Information' }, native: {} });\n\n // Alive and info update\n await this.setObjectNotExistsAsync(device.id + '.alive', {\n type: 'state',\n common: {\n name: 'Is Fully alive?',\n desc: 'If Fully Browser is alive or not',\n type: 'boolean',\n role: 'indicator.reachable',\n icon: 'data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iTXVpU3ZnSWNvbi1yb290IE11aVN2Z0ljb24tZm9udFNpemVNZWRpdW0gaWNvbk93biBjc3MtdnViYnV2IiBmb2N1c2FibGU9ImZhbHNlIiBhcmlhLWhpZGRlbj0idHJ1ZSIgdmlld0JveD0iMCAwIDI0IDI0IiBkYXRhLXRlc3RpZD0iV2lmaUljb24iPjxwYXRoIGQ9Im0xIDkgMiAyYzQuOTctNC45NyAxMy4wMy00Ljk3IDE4IDBsMi0yQzE2LjkzIDIuOTMgNy4wOCAyLjkzIDEgOXptOCA4IDMgMyAzLTNjLTEuNjUtMS42Ni00LjM0LTEuNjYtNiAwem0tNC00IDIgMmMyLjc2LTIuNzYgNy4yNC0yLjc2IDEwIDBsMi0yQzE1LjE0IDkuMTQgOC44NyA5LjE0IDUgMTN6Ij48L3BhdGg+PC9zdmc+',\n read: true,\n write: false,\n },\n native: {},\n });\n await this.setObjectNotExistsAsync(device.id + '.lastInfoUpdate', { type: 'state', common: { name: 'Last information update', desc: 'Date/time of last information update from Fully Browser', type: 'number', role: 'value.time', read: true, write: false }, native: {} });\n\n // REST API Commands Objects\n await this.setObjectNotExistsAsync(device.id + '.Commands', { type: 'channel', common: { name: 'Commands (REST API)' }, native: {} });\n const allCommands = CONST.cmds.concat(CONST.cmdsSwitches); // join both arrays\n for (const cmdObj of allCommands) {\n let lpRole = '';\n if (cmdObj.type === 'boolean') lpRole = 'button';\n if (cmdObj.type === 'string') lpRole = 'text';\n if (cmdObj.type === 'number') lpRole = 'value';\n if (cmdObj.cmdOn && cmdObj.cmdOff) lpRole = 'switch';\n await this.setObjectNotExistsAsync(device.id + '.Commands.' + cmdObj.id, { type: 'state', common: { name: 'Command: ' + cmdObj.name, type: cmdObj.type, role: lpRole, read: true, write: true }, native: {} });\n }\n\n // Create MQTT Events Objects\n // More states are created once a new Event is received.\n await this.setObjectNotExistsAsync(device.id + '.Events', { type: 'channel', common: { name: 'MQTT Events' }, native: {} });\n for (const event of CONST.mqttEvents) {\n await this.setObjectNotExistsAsync(device.id + '.Events.' + event, { type: 'state', common: { name: 'MQTT Event: ' + event, type: 'boolean', role: 'switch', read: true, write: false }, native: {} });\n }\n\n /**\n * REST API: Subscribe to command state changes\n */\n await this.subscribeStatesAsync(device.id + '.Commands.*');\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * Delete device objects if device was (a) renamed or (b) deleted from devices table in adapter settings.\n * However, do not delete if it was just set inactive in table.\n */\n private async deleteRemovedDeviceObjects(): Promise {\n try {\n // Get string array of all adapter objects: ['fully-mqtt.0.info', 'fully-mqtt.0.info.connection', ...];\n const adapterObjectsIds: string[] = Object.keys(await this.getAdapterObjectsAsync());\n\n // Get all existing fully device ids of iobroker adapter objects in array: 'fully-mqtt.0.Tablet-Kitchen' -> 'Tablet-Kitchen', 'fully-mqtt.0.Tablet-Hallway' -> 'Tablet-Hallway', etc.\n const allObjectDeviceIds: Array = [];\n for (const objectId of adapterObjectsIds) {\n const deviceId = objectId.split('.')[2]; // e.g. 'Tablet-Kitchen'\n // Ignore fully-mqtt.0.info tree (which includes fully-mqtt.0.info.connection, ...). Add more to ignore as needed in the future...\n if (['info'].includes(deviceId)) {\n this.log.silly(`Cleanup: Ignore non device related state ${objectId}.`);\n } else {\n if (!allObjectDeviceIds.includes(deviceId)) allObjectDeviceIds.push(deviceId);\n }\n }\n\n // process all adapter object devices ['Tablet-Kitchen', 'Tablet-Hallway', ...] accordingly\n for (const id of allObjectDeviceIds) {\n // We handle both disabled devices and enabled devices\n const allConfigDeviceIds = this.disabledDeviceIds; // add all disabled ids first\n // now add all active ones\n for (const ip in this.fullys) {\n allConfigDeviceIds.push(this.fullys[ip].id);\n }\n\n if (!allConfigDeviceIds.includes(id)) {\n await this.delObjectAsync(id, { recursive: true });\n this.log.info(`Cleanup: Deleted no longer defined device objects of '${id}'.`);\n }\n }\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * Verify adapter instance settings\n */\n private async initConfig(): Promise {\n try {\n /*************************\n * MQTT Fields\n *************************/\n if (this.isEmpty(this.config.mqttPort) || this.config.mqttPort < 1 || this.config.mqttPort > 65535) {\n this.log.warn(`Adapter instance settings: MQTT Port ${this.config.mqttPort} is not allowed, set to default of 1886`);\n this.config.mqttPort = 1886;\n }\n if (this.isEmpty(this.config.mqttPublishedInfoDelay) || this.config.mqttPublishedInfoDelay < 2 || this.config.mqttPublishedInfoDelay > 120) {\n this.log.warn(`Adapter instance settings: MQTT Publish Info Delay of ${this.config.mqttPublishedInfoDelay}s is not allowed, set to default of 30s`);\n this.config.mqttPublishedInfoDelay = 30;\n }\n\n /*************************\n * REST API Fields\n *************************/\n if (this.isEmpty(this.config.restTimeout) || this.config.restTimeout < 500 || this.config.restTimeout > 15000) {\n this.log.warn(`Adapter instance settings: REST API timeout of ${this.config.restTimeout} ms is not allowed, set to default of 6000ms`);\n this.config.restTimeout = 6000;\n }\n\n /*************************\n * Table Devices\n *************************/\n if (this.isEmpty(this.config.tableDevices)) {\n this.log.error(`No Fully devices defined in adapter instance settings!`);\n return false;\n }\n const deviceIds: string[] = []; // to check for duplicate device ids\n const deviceIPs: string[] = []; // to check for duplicate device IPs\n for (let i = 0; i < this.config.tableDevices.length; i++) {\n const lpDevice = this.config.tableDevices[i];\n const finalDevice: IDevice = {\n name: '',\n id: '',\n ip: '',\n mqttInfoObjectsCreated: false,\n mqttInfoKeys: [],\n restProtocol: 'http',\n restPort: 0,\n restPassword: '',\n lastSeen: 0, // timestamp\n isAlive: false,\n };\n\n // name\n if (this.isEmpty(lpDevice.name)) {\n this.log.error(`Provided device name \"${lpDevice.name}\" is empty!`);\n return false;\n }\n finalDevice.name = lpDevice.name.trim();\n\n // id\n finalDevice.id = this.cleanDeviceName(lpDevice.name);\n if (finalDevice.id.length < 1) {\n this.log.error(`Provided device name \"${lpDevice.name}\" is too short and/or has invalid characters!`);\n return false;\n }\n if (deviceIds.includes(finalDevice.id)) {\n this.log.error(`Device \"${finalDevice.name}\" -> id:\"${finalDevice.id}\" is used for more than once device.`);\n return false;\n } else {\n deviceIds.push(finalDevice.id);\n }\n\n // REST Protocol (http/https)\n if (lpDevice.restProtocol !== 'http' && lpDevice.restProtocol !== 'https') {\n this.log.warn(`${finalDevice.name}: REST API Protocol is empty, set to http as default.`);\n finalDevice.restProtocol = 'http';\n } else {\n finalDevice.restProtocol = lpDevice.restProtocol;\n }\n\n // IP Address\n if (!this.isIpAddressValid(lpDevice.ip)) {\n this.log.error(`${finalDevice.name}: Provided IP address \"${lpDevice.ip}\" is not valid!`);\n return false;\n }\n if (deviceIPs.includes(lpDevice.ip)) {\n this.log.error(`Device \"${finalDevice.name}\" -> IP:\"${lpDevice.ip}\" is used for more than once device.`);\n return false;\n } else {\n deviceIPs.push(lpDevice.ip);\n finalDevice.ip = lpDevice.ip;\n }\n\n // REST Port\n if (isNaN(lpDevice.restPort) || lpDevice.restPort < 0 || lpDevice.restPort > 65535) {\n this.log.error(`Adapter config Fully port number ${lpDevice.restPort} is not valid, should be >= 0 and < 65536.`);\n return false;\n } else {\n finalDevice.restPort = Math.round(lpDevice.restPort);\n }\n // REST Password\n if (isEmpty(lpDevice.restPassword)) {\n this.log.error(`Remote Admin (REST API) Password must not be empty!`);\n return false;\n } else {\n finalDevice.restPassword = lpDevice.restPassword;\n }\n\n const logConfig = { ...finalDevice }; // copy object using spread\n logConfig.restPassword = '(hidden)'; // do not show password in log !\n this.log.debug(`Final Config: ${JSON.stringify(logConfig)}`);\n if (lpDevice.enabled) {\n // Finalize\n this.fullys[finalDevice.ip] = finalDevice;\n this.activeDeviceIPs.push(lpDevice.ip); // global array for all active IPs\n this.log.info(`\uD83D\uDDF8 ${finalDevice.name} (${finalDevice.ip}): Config successfully verified.`);\n } else {\n // Skip if not enabled. (but we did verification anyway!)\n this.disabledDeviceIds.push(finalDevice.id);\n this.log.debug(`Device ${finalDevice.name} (${finalDevice.ip}) is not enabled, so skip it.`);\n continue;\n }\n }\n\n if (Object.keys(this.fullys).length === 0) {\n this.log.error(`No active devices with correct configuration found.`);\n return false;\n }\n return true;\n } catch (e) {\n this.log.error(this.err2Str(e));\n return false;\n }\n }\n\n /**\n * On Alive Changes\n * for both REST API and MQTT\n */\n public async onAliveChange(source: 'MQTT' | 'REST', ip: string, isAlive: true | false, msg: string): Promise {\n try {\n const prevIsAlive = this.fullys[ip].isAlive;\n this.fullys[ip].isAlive = isAlive;\n\n // Has this function ever been called before? If adapter is restarted, we ensure log, etc.\n const calledBefore = this.onAliveChange_EverBeenCalledBefore; // Keep old value\n this.onAliveChange_EverBeenCalledBefore = true; // Now it was called\n\n /***********\n * 1 - Fully Device\n ***********/\n // if alive status changed\n if ((!calledBefore && isAlive === true) || prevIsAlive !== isAlive) {\n // Set Device isAlive Status - we could also use setStateChanged()...\n this.setState(this.fullys[ip].id + '.alive', { val: isAlive, ack: true });\n\n // log\n if (isAlive) {\n this.log.info(`${this.fullys[ip].name} is alive (${source}: ${msg})`);\n } else {\n this.log.warn(`${this.fullys[ip].name} is not alive! (${source}: ${msg})`);\n }\n } else {\n // No change\n }\n\n /***********\n * 2 - Adapter Connection indicator\n ***********/\n let countAll = 0;\n let countAlive = 0;\n for (const lpIpAddr in this.fullys) {\n countAll++;\n if (this.fullys[lpIpAddr].isAlive) {\n countAlive++;\n }\n }\n let areAllAlive = false;\n if (countAll > 0 && countAll === countAlive) areAllAlive = true;\n this.setStateChanged('info.connection', { val: areAllAlive, ack: true });\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * MQTT: once new device info packet is coming in\n */\n public async onMqttInfo(obj: { clientId: string; ip: string; topic: string; infoObj: { [k: string]: any } }): Promise {\n try {\n // log\n this.log.debug(`[MQTT]\uD83D\uDCE1 ${this.fullys[obj.ip].name} published info, topic: ${obj.topic}`);\n //this.log.debug(`[MQTT] Client ${obj.ip} Publish Info: Details: ${JSON.stringify(obj.infoObj)}`);\n\n // Create info objects if not yet existing\n const formerInfoKeysLength: number = this.fullys[obj.ip].mqttInfoKeys.length;\n const newInfoKeysAdded: string[] = [];\n for (const key in obj.infoObj) {\n const val = obj.infoObj[key];\n const valType = typeof val;\n // only accept certain types\n if (valType !== 'string' && valType !== 'boolean' && valType !== 'object' && valType !== 'number') {\n this.log.warn(`[MQTT] ${this.fullys[obj.ip].name}: Unknown type ${valType} of key '${key}' in info object`);\n continue;\n }\n // Create info object if not yet seen - this check is used for increasing performance by not unnesserily call setObjectNotExistsAsync() every time new info package comes in\n if (!this.fullys[obj.ip].mqttInfoKeys.includes(key)) {\n this.fullys[obj.ip].mqttInfoKeys.push(key);\n newInfoKeysAdded.push(key);\n await this.setObjectNotExistsAsync(`${this.fullys[obj.ip].id}.Info.${key}`, { type: 'state', common: { name: 'Info: ' + key, type: valType, role: 'value', read: true, write: false }, native: {} });\n }\n }\n if (formerInfoKeysLength === 0) this.log.debug(`[MQTT] ${this.fullys[obj.ip].name}: Initially create states for ${newInfoKeysAdded.length} info items (if not yet existing)`);\n if (formerInfoKeysLength > 0 && newInfoKeysAdded.length > 0) this.log.info(`[MQTT] ${this.fullys[obj.ip].name}: Created new info object(s) as not seen before (if object(s) did not exist): ${newInfoKeysAdded.join(', ')}`);\n\n // Set info objects\n for (const key in obj.infoObj) {\n const newVal = typeof obj.infoObj[key] === 'object' ? JSON.stringify(obj.infoObj[key]) : obj.infoObj[key]; // https://forum.iobroker.net/post/628870 - https://forum.iobroker.net/post/960260\n if (this.config.mqttUpdateUnchangedObjects) {\n this.setState(`${this.fullys[obj.ip].id}.Info.${key}`, { val: newVal, ack: true });\n } else {\n this.setStateChanged(`${this.fullys[obj.ip].id}.Info.${key}`, { val: newVal, ack: true });\n }\n }\n this.setState(this.fullys[obj.ip].id + '.lastInfoUpdate', { val: Date.now(), ack: true });\n this.setState(this.fullys[obj.ip].id + '.alive', { val: true, ack: true });\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * MQTT: once new event packet is coming in\n */\n public async onMqttEvent(obj: { clientId: string; ip: string; topic: string; cmd: string }): Promise {\n try {\n // log\n this.log.debug(`[MQTT] \uD83D\uDCE1 ${this.fullys[obj.ip].name} published event, topic: ${obj.topic}, cmd: ${obj.cmd}`);\n\n /**\n * Set Event State\n */\n const pthEvent = `${this.fullys[obj.ip].id}.Events.${obj.cmd}`;\n if (!(await this.getObjectAsync(pthEvent))) {\n this.log.info(`[MQTT] ${this.fullys[obj.ip].name}: Event ${obj.cmd} received but state ${pthEvent} does not exist, so we create it first`);\n await this.setObjectNotExistsAsync(pthEvent, { type: 'state', common: { name: 'MQTT Event: ' + obj.cmd, type: 'boolean', role: 'switch', read: true, write: false }, native: {} });\n }\n this.setState(pthEvent, { val: true, ack: true });\n\n /**\n * Confirm Command state(s) with ack: true\n */\n const pthCmd = this.fullys[obj.ip].id + '.Commands';\n\n // Check if it is a switch with MQTT commands connected\n const idx = this.getIndexFromConf(CONST.cmdsSwitches, ['mqttOn', 'mqttOff'], obj.cmd);\n if (idx !== -1) {\n // We have a switch\n const conf = CONST.cmdsSwitches[idx]; // the found line from config array\n const onOrOffCmd = obj.cmd === conf.mqttOn ? true : false;\n await this.setStateAsync(`${pthCmd}.${conf.id}`, { val: onOrOffCmd, ack: true });\n await this.setStateAsync(`${pthCmd}.${conf.cmdOn}`, { val: onOrOffCmd, ack: true });\n await this.setStateAsync(`${pthCmd}.${conf.cmdOff}`, { val: !onOrOffCmd, ack: true });\n } else {\n // No switch\n const idx = this.getIndexFromConf(CONST.cmds, ['id'], obj.cmd);\n if (idx !== -1 && CONST.cmds[idx].type === 'boolean') {\n // We have a button, so set it to true\n await this.setStateAsync(`${pthCmd}.${obj.cmd}`, { val: true, ack: true });\n } else {\n this.log.silly(`[MQTT] ${this.fullys[obj.ip].name}: Event cmd ${obj.cmd} - no REST API command is existing, so skip confirmation with with ack:true`);\n }\n }\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * Called once a subscribed state changes.\n * Ready once subscribeStatesAsync() is called...\n * @param id - e.g. \"fully-mqtt.0.Tablet-Bathroom.Commands.screenSwitch\"\n * @param stateObj - e.g. { val: true, ack: false, ts: 123456789, q: 0, lc: 123456789 }\n */\n private async onStateChange(stateId: string, stateObj: ioBroker.State | null | undefined): Promise {\n try {\n if (!stateObj) return; // state was deleted, we disregard...\n if (stateObj.ack) return; // ignore ack:true\n const idSplit = stateId.split('.');\n const deviceId = idSplit[2]; // \"Tablet-Bathroom\"\n const channel = idSplit[3]; // \"Commands\"\n const cmd = idSplit[4]; // \"screenSwitch\"\n const pth = deviceId + '.' + channel; // Tablet-Bathroom.Commands\n /**\n * Commands\n */\n if (channel === 'Commands') {\n this.log.debug(`state ${stateId} changed: ${stateObj.val} (ack = ${stateObj.ack})`);\n // Get device object\n const fully = this.getFullyByKey('id', deviceId);\n if (!fully) throw `Fully object for deviceId '${deviceId}' not found!`;\n\n let cmdToSend: string | undefined = cmd; // Command to send to Fully\n let switchConf: undefined | ICmds = undefined; // Config line of switch\n\n /****************\n * Check if it is a switch state cmd, like 'screenSwitch'\n ****************/\n const idxSw = this.getIndexFromConf(CONST.cmdsSwitches, ['id'], cmd);\n if (idxSw !== -1) {\n // It is a switch\n switchConf = CONST.cmdsSwitches[idxSw]; // the found line from config array\n cmdToSend = stateObj.val ? switchConf.cmdOn : switchConf.cmdOff;\n } else {\n // Not a switch.\n // If val is false, we disregard, since it is a button only\n if (!stateObj.val) return;\n }\n if (!cmdToSend) throw `onStateChange() - ${stateId}: fullyCmd could not be determined!`;\n\n /**\n * Send Command\n */\n const sendCommand = await this.restApi_inst.sendCmd(fully, cmdToSend, stateObj.val);\n if (sendCommand) {\n this.log.info(`${fully.name}: ${cmd} successfully set to ${stateObj.val}`);\n /**\n * Confirm with ack:true\n */\n if (switchConf !== undefined) {\n // it is a switch\n const onOrOffCmdVal = cmd === switchConf.cmdOn ? true : false;\n await this.setStateAsync(`${pth}.${switchConf.id}`, { val: onOrOffCmdVal, ack: true });\n await this.setStateAsync(`${pth}.${switchConf.cmdOn}`, { val: onOrOffCmdVal, ack: true });\n await this.setStateAsync(`${pth}.${switchConf.cmdOff}`, { val: !onOrOffCmdVal, ack: true });\n } else {\n // No switch\n if (typeof stateObj.val === 'boolean') {\n const idx = this.getIndexFromConf(CONST.cmds, ['id'], cmd);\n if (idx !== -1) {\n if (CONST.cmds[idx].type === 'boolean') {\n // Is a button\n await this.setStateAsync(stateId, { val: true, ack: true });\n } else {\n // This should actually not happen, as we just define buttons in commands, but anyway\n this.log.warn(`${fully.name}: ${stateId} - val: ${stateObj.val} is boolean, but cmd ${cmd} is not defined in CONF`);\n await this.setStateAsync(stateId, { val: stateObj.val, ack: true });\n }\n } else {\n this.log.warn(`${fully.name}: ${stateId} - val: ${stateObj.val}, cmd ${cmd} is not defined in CONF`);\n }\n } else {\n // Non-boolean, so just set val with ack:true...\n await this.setStateAsync(stateId, { val: stateObj.val, ack: true });\n }\n }\n } else {\n // log, more log lines were already published by this.restApi_inst.sendCmd()\n this.log.debug(`${fully.name}: restApiSendCmd() was not successful (${stateId})`);\n }\n }\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * Get Fully Object per provided key and value\n * {\n * '192.168.10.20': {name: 'Tablet Kitchen', id:'Tablet-Kitchen', ip:'192.168.10.20', ...},\n * '192.168.10.30': {name: 'Tablet Hallway', id:'Tablet-Hallway', ip:'192.168.10.30', ...},\n * }\n * getFullyByKey('id', 'Tablet-Hallway') will return the second object...\n * @param keyId - e.g. 'id', 'name', ...\n * @param value - e.g. 'Tablet Hallway', ...\n * @returns - fully object or false if not found\n */\n private getFullyByKey(keyId: string, value: any): IDevice | false {\n for (const ip in this.fullys) {\n if (keyId in this.fullys[ip]) {\n const lpKeyId = keyId as string;\n // Wow, what a line. Due to: https://bobbyhadz.com/blog/typescript-element-implicitly-has-any-type-expression\n const lpVal = this.fullys[ip][lpKeyId as keyof (typeof this.fullys)[typeof ip]];\n if (lpVal === value) {\n return this.fullys[ip];\n }\n }\n }\n return false;\n }\n\n /**\n * Gets Index for given keys and a value\n * @param config - config like CONST.cmds\n * @param keys - like ['mqttOn','mqttOff']\n * @param cmd - like 'onScreensaverStart'\n * @returns Index (0-...), or -1 if not found\n */\n private getIndexFromConf(config: { [k: string]: any }[], keys: string[], cmd: string): number {\n try {\n let index = -1;\n for (const key of keys) {\n // Get array index\n index = config.findIndex((x: { [k: string]: any }) => x[key] === cmd);\n if (index !== -1) break;\n }\n return index;\n } catch (e) {\n this.log.error(this.err2Str(e));\n return -1;\n }\n }\n\n /**\n * Is called when adapter shuts down - callback has to be called under any circumstances!\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.fullys) {\n for (const ip in this.fullys) {\n // Set alive status to false\n this.setState(this.fullys[ip].id + '.alive', { val: false, ack: true });\n }\n }\n\n // Clear MQTT server timeouts\n if (this.mqtt_Server) {\n for (const clientId in this.mqtt_Server.devices) {\n // @ts-expect-error \"Type 'null' is not assignable to type 'Timeout'.ts(2345)\" - we check for not being null via \"if\"\n if (this.mqtt_Server.devices[clientId].timeoutNoUpdate) this.clearTimeout(this.mqtt_Server.devices[clientId].timeoutNoUpdate);\n }\n }\n\n // destroy MQTT Server\n if (this.mqtt_Server) {\n this.mqtt_Server.terminate();\n }\n\n callback();\n } catch (e) {\n callback();\n }\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial | undefined) => new FullyMqtt(options);\n} else {\n // otherwise start the instance directly\n (() => new FullyMqtt())();\n}\n"], - "mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,YAAuB;AACvB,uBAAsB;AAEtB,qBAAgG;AAChG,yBAA2B;AAC3B,qBAA6B;AAnB7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBO,MAAM,kBAAkB,MAAM,QAAQ;AAAA,EAmClC,YAAY,UAAyC,CAAC,GAAG;AAC5D,UAAM,EAAE,GAAG,SAAS,MAAM,aAAa,CAAC;AAlC5C,SAAO,UAAU,uBAAQ,KAAK,IAAI;AAClC,SAAO,UAAU,uBAAQ,KAAK,IAAI;AAClC,SAAO,OAAO,oBAAK,KAAK,IAAI;AAC5B,SAAO,kBAAkB,+BAAgB,KAAK,IAAI;AAClD,SAAO,uBAAuB,oCAAqB,KAAK,IAAI;AAC5D,SAAO,mBAAmB,gCAAiB,KAAK,IAAI;AAKpD,SAAQ,eAAe,IAAI,4BAAa,IAAI;AAU5C,SAAO,SAAoC,CAAC;AAG5C,SAAO,oBAAoB,CAAC;AAE5B,SAAO,kBAAkB,CAAC;AAG1B,SAAQ,qCAAqC;AAQzC,SAAK,GAAG,SAAS,KAAK,QAAQ,KAAK,IAAI,CAAC;AACxC,SAAK,GAAG,eAAe,KAAK,cAAc,KAAK,IAAI,CAAC;AAGpD,SAAK,GAAG,UAAU,KAAK,SAAS,KAAK,IAAI,CAAC;AAAA,EAC9C;AAAA,EAKA,MAAc,UAAyB;AACnC,QAAI;AAIA,WAAK,SAAS,mBAAmB,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AAK1D,UAAI,MAAM,KAAK,WAAW,GAAG;AACzB,aAAK,IAAI,MAAM,yDAAyD;AAAA,MAC5E,OAAO;AACH,aAAK,IAAI,MAAM,4FAA4F;AAC3G;AAAA,MACJ;AAKA,WAAK,cAAc,IAAI,8BAAW,IAAI;AACtC,WAAK,YAAY,MAAM;AAKvB,iBAAW,MAAM,KAAK,QAAQ;AAC1B,cAAM,KAAK,KAAK,KAAK,OAAO,GAAG;AAAA,MACnC;AAKA,WAAK,2BAA2B;AAAA,IACpC,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,IACJ;AAAA,EACJ;AAAA,EAMA,MAAc,KAAK,QAAgC;AAC/C,QAAI;AACA,WAAK,IAAI,MAAM,kBAAkB,OAAO,SAAS,OAAO,WAAM;AAM9D,YAAM,KAAK,wBAAwB,OAAO,IAAI;AAAA,QAC1C,MAAM;AAAA,QACN,QAAQ;AAAA,UACJ,MAAM,OAAO;AAAA,UAEb,cAAc,EAAE,UAAU,GAAG,KAAK,aAAa,OAAO,WAAW;AAAA,QACrE;AAAA,QACA,QAAQ,CAAC;AAAA,MACb,CAAC;AACD,YAAM,KAAK,wBAAwB,OAAO,KAAK,SAAS,EAAE,MAAM,WAAW,QAAQ,EAAE,MAAM,qBAAqB,GAAG,QAAQ,CAAC,EAAE,CAAC;AAG/H,YAAM,KAAK,wBAAwB,OAAO,KAAK,UAAU;AAAA,QACrD,MAAM;AAAA,QACN,QAAQ;AAAA,UACJ,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO;AAAA,QACX;AAAA,QACA,QAAQ,CAAC;AAAA,MACb,CAAC;AACD,YAAM,KAAK,wBAAwB,OAAO,KAAK,mBAAmB,EAAE,MAAM,SAAS,QAAQ,EAAE,MAAM,2BAA2B,MAAM,2DAA2D,MAAM,UAAU,MAAM,cAAc,MAAM,MAAM,OAAO,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;AAG3Q,YAAM,KAAK,wBAAwB,OAAO,KAAK,aAAa,EAAE,MAAM,WAAW,QAAQ,EAAE,MAAM,sBAAsB,GAAG,QAAQ,CAAC,EAAE,CAAC;AACpI,YAAM,cAAc,uBAAM,KAAK,OAAO,uBAAM,YAAY;AACxD,iBAAW,UAAU,aAAa;AAC9B,YAAI,SAAS;AACb,YAAI,OAAO,SAAS;AAAW,mBAAS;AACxC,YAAI,OAAO,SAAS;AAAU,mBAAS;AACvC,YAAI,OAAO,SAAS;AAAU,mBAAS;AACvC,YAAI,OAAO,SAAS,OAAO;AAAQ,mBAAS;AAC5C,cAAM,KAAK,wBAAwB,OAAO,KAAK,eAAe,OAAO,IAAI,EAAE,MAAM,SAAS,QAAQ,EAAE,MAAM,cAAc,OAAO,MAAM,MAAM,OAAO,MAAM,MAAM,QAAQ,MAAM,MAAM,OAAO,KAAK,GAAG,QAAQ,CAAC,EAAE,CAAC;AAAA,MACjN;AAIA,YAAM,KAAK,wBAAwB,OAAO,KAAK,WAAW,EAAE,MAAM,WAAW,QAAQ,EAAE,MAAM,cAAc,GAAG,QAAQ,CAAC,EAAE,CAAC;AAC1H,iBAAW,SAAS,uBAAM,YAAY;AAClC,cAAM,KAAK,wBAAwB,OAAO,KAAK,aAAa,OAAO,EAAE,MAAM,SAAS,QAAQ,EAAE,MAAM,iBAAiB,OAAO,MAAM,WAAW,MAAM,UAAU,MAAM,MAAM,OAAO,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;AAAA,MACzM;AAKA,YAAM,KAAK,qBAAqB,OAAO,KAAK,aAAa;AAAA,IAC7D,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,IACJ;AAAA,EACJ;AAAA,EAMA,MAAc,6BAA4C;AACtD,QAAI;AAEA,YAAM,oBAA8B,OAAO,KAAK,MAAM,KAAK,uBAAuB,CAAC;AAGnF,YAAM,qBAAoC,CAAC;AAC3C,iBAAW,YAAY,mBAAmB;AACtC,cAAM,WAAW,SAAS,MAAM,GAAG,EAAE;AAErC,YAAI,CAAC,MAAM,EAAE,SAAS,QAAQ,GAAG;AAC7B,eAAK,IAAI,MAAM,4CAA4C,WAAW;AAAA,QAC1E,OAAO;AACH,cAAI,CAAC,mBAAmB,SAAS,QAAQ;AAAG,+BAAmB,KAAK,QAAQ;AAAA,QAChF;AAAA,MACJ;AAGA,iBAAW,MAAM,oBAAoB;AAEjC,cAAM,qBAAqB,KAAK;AAEhC,mBAAW,MAAM,KAAK,QAAQ;AAC1B,6BAAmB,KAAK,KAAK,OAAO,IAAI,EAAE;AAAA,QAC9C;AAEA,YAAI,CAAC,mBAAmB,SAAS,EAAE,GAAG;AAClC,gBAAM,KAAK,eAAe,IAAI,EAAE,WAAW,KAAK,CAAC;AACjD,eAAK,IAAI,KAAK,yDAAyD,MAAM;AAAA,QACjF;AAAA,MACJ;AAAA,IACJ,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,IACJ;AAAA,EACJ;AAAA,EAKA,MAAc,aAAoC;AAC9C,QAAI;AAIA,UAAI,KAAK,QAAQ,KAAK,OAAO,QAAQ,KAAK,KAAK,OAAO,WAAW,KAAK,KAAK,OAAO,WAAW,OAAO;AAChG,aAAK,IAAI,KAAK,wCAAwC,KAAK,OAAO,iDAAiD;AACnH,aAAK,OAAO,WAAW;AAAA,MAC3B;AACA,UAAI,KAAK,QAAQ,KAAK,OAAO,sBAAsB,KAAK,KAAK,OAAO,yBAAyB,KAAK,KAAK,OAAO,yBAAyB,KAAK;AACxI,aAAK,IAAI,KAAK,yDAAyD,KAAK,OAAO,+DAA+D;AAClJ,aAAK,OAAO,yBAAyB;AAAA,MACzC;AAKA,UAAI,KAAK,QAAQ,KAAK,OAAO,WAAW,KAAK,KAAK,OAAO,cAAc,OAAO,KAAK,OAAO,cAAc,MAAO;AAC3G,aAAK,IAAI,KAAK,kDAAkD,KAAK,OAAO,yDAAyD;AACrI,aAAK,OAAO,cAAc;AAAA,MAC9B;AAKA,UAAI,KAAK,QAAQ,KAAK,OAAO,YAAY,GAAG;AACxC,aAAK,IAAI,MAAM,wDAAwD;AACvE,eAAO;AAAA,MACX;AACA,YAAM,YAAsB,CAAC;AAC7B,YAAM,YAAsB,CAAC;AAC7B,eAAS,IAAI,GAAG,IAAI,KAAK,OAAO,aAAa,QAAQ,KAAK;AACtD,cAAM,WAAW,KAAK,OAAO,aAAa;AAC1C,cAAM,cAAuB;AAAA,UACzB,MAAM;AAAA,UACN,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,wBAAwB;AAAA,UACxB,cAAc,CAAC;AAAA,UACf,cAAc;AAAA,UACd,UAAU;AAAA,UACV,cAAc;AAAA,UACd,UAAU;AAAA,UACV,SAAS;AAAA,QACb;AAGA,YAAI,KAAK,QAAQ,SAAS,IAAI,GAAG;AAC7B,eAAK,IAAI,MAAM,yBAAyB,SAAS,iBAAiB;AAClE,iBAAO;AAAA,QACX;AACA,oBAAY,OAAO,SAAS,KAAK,KAAK;AAGtC,oBAAY,KAAK,KAAK,gBAAgB,SAAS,IAAI;AACnD,YAAI,YAAY,GAAG,SAAS,GAAG;AAC3B,eAAK,IAAI,MAAM,yBAAyB,SAAS,mDAAmD;AACpG,iBAAO;AAAA,QACX;AACA,YAAI,UAAU,SAAS,YAAY,EAAE,GAAG;AACpC,eAAK,IAAI,MAAM,WAAW,YAAY,gBAAgB,YAAY,wCAAwC;AAC1G,iBAAO;AAAA,QACX,OAAO;AACH,oBAAU,KAAK,YAAY,EAAE;AAAA,QACjC;AAGA,YAAI,SAAS,iBAAiB,UAAU,SAAS,iBAAiB,SAAS;AACvE,eAAK,IAAI,KAAK,GAAG,YAAY,2DAA2D;AACxF,sBAAY,eAAe;AAAA,QAC/B,OAAO;AACH,sBAAY,eAAe,SAAS;AAAA,QACxC;AAGA,YAAI,CAAC,KAAK,iBAAiB,SAAS,EAAE,GAAG;AACrC,eAAK,IAAI,MAAM,GAAG,YAAY,8BAA8B,SAAS,mBAAmB;AACxF,iBAAO;AAAA,QACX;AACA,YAAI,UAAU,SAAS,SAAS,EAAE,GAAG;AACjC,eAAK,IAAI,MAAM,WAAW,YAAY,gBAAgB,SAAS,wCAAwC;AACvG,iBAAO;AAAA,QACX,OAAO;AACH,oBAAU,KAAK,SAAS,EAAE;AAC1B,sBAAY,KAAK,SAAS;AAAA,QAC9B;AAGA,YAAI,MAAM,SAAS,QAAQ,KAAK,SAAS,WAAW,KAAK,SAAS,WAAW,OAAO;AAChF,eAAK,IAAI,MAAM,oCAAoC,SAAS,oDAAoD;AAChH,iBAAO;AAAA,QACX,OAAO;AACH,sBAAY,WAAW,KAAK,MAAM,SAAS,QAAQ;AAAA,QACvD;AAEA,gBAAI,wBAAQ,SAAS,YAAY,GAAG;AAChC,eAAK,IAAI,MAAM,qDAAqD;AACpE,iBAAO;AAAA,QACX,OAAO;AACH,sBAAY,eAAe,SAAS;AAAA,QACxC;AAEA,cAAM,YAAY,EAAE,GAAG,YAAY;AACnC,kBAAU,eAAe;AACzB,aAAK,IAAI,MAAM,iBAAiB,KAAK,UAAU,SAAS,GAAG;AAC3D,YAAI,SAAS,SAAS;AAElB,eAAK,OAAO,YAAY,MAAM;AAC9B,eAAK,gBAAgB,KAAK,SAAS,EAAE;AACrC,eAAK,IAAI,KAAK,aAAM,YAAY,SAAS,YAAY,oCAAoC;AAAA,QAC7F,OAAO;AAEH,eAAK,kBAAkB,KAAK,YAAY,EAAE;AAC1C,eAAK,IAAI,MAAM,UAAU,YAAY,SAAS,YAAY,iCAAiC;AAC3F;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI,OAAO,KAAK,KAAK,MAAM,EAAE,WAAW,GAAG;AACvC,aAAK,IAAI,MAAM,qDAAqD;AACpE,eAAO;AAAA,MACX;AACA,aAAO;AAAA,IACX,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B,aAAO;AAAA,IACX;AAAA,EACJ;AAAA,EAMA,MAAa,cAAc,QAAyB,IAAY,SAAuB,KAA4B;AAC/G,QAAI;AACA,YAAM,cAAc,KAAK,OAAO,IAAI;AACpC,WAAK,OAAO,IAAI,UAAU;AAG1B,YAAM,eAAe,KAAK;AAC1B,WAAK,qCAAqC;AAM1C,UAAK,CAAC,gBAAgB,YAAY,QAAS,gBAAgB,SAAS;AAEhE,aAAK,SAAS,KAAK,OAAO,IAAI,KAAK,UAAU,EAAE,KAAK,SAAS,KAAK,KAAK,CAAC;AAGxE,YAAI,SAAS;AACT,eAAK,IAAI,KAAK,GAAG,KAAK,OAAO,IAAI,kBAAkB,WAAW,MAAM;AAAA,QACxE,OAAO;AACH,eAAK,IAAI,KAAK,GAAG,KAAK,OAAO,IAAI,uBAAuB,WAAW,MAAM;AAAA,QAC7E;AAAA,MACJ,OAAO;AAAA,MAEP;AAKA,UAAI,WAAW;AACf,UAAI,aAAa;AACjB,iBAAW,YAAY,KAAK,QAAQ;AAChC;AACA,YAAI,KAAK,OAAO,UAAU,SAAS;AAC/B;AAAA,QACJ;AAAA,MACJ;AACA,UAAI,cAAc;AAClB,UAAI,WAAW,KAAK,aAAa;AAAY,sBAAc;AAC3D,WAAK,gBAAgB,mBAAmB,EAAE,KAAK,aAAa,KAAK,KAAK,CAAC;AAAA,IAC3E,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,IACJ;AAAA,EACJ;AAAA,EAKA,MAAa,WAAW,KAAoG;AACxH,QAAI;AAEA,WAAK,IAAI,MAAM,mBAAY,KAAK,OAAO,IAAI,IAAI,+BAA+B,IAAI,OAAO;AAIzF,YAAM,uBAA+B,KAAK,OAAO,IAAI,IAAI,aAAa;AACtE,YAAM,mBAA6B,CAAC;AACpC,iBAAW,OAAO,IAAI,SAAS;AAC3B,cAAM,MAAM,IAAI,QAAQ;AACxB,cAAM,UAAU,OAAO;AAEvB,YAAI,YAAY,YAAY,YAAY,aAAa,YAAY,YAAY,YAAY,UAAU;AAC/F,eAAK,IAAI,KAAK,UAAU,KAAK,OAAO,IAAI,IAAI,sBAAsB,mBAAmB,qBAAqB;AAC1G;AAAA,QACJ;AAEA,YAAI,CAAC,KAAK,OAAO,IAAI,IAAI,aAAa,SAAS,GAAG,GAAG;AACjD,eAAK,OAAO,IAAI,IAAI,aAAa,KAAK,GAAG;AACzC,2BAAiB,KAAK,GAAG;AACzB,gBAAM,KAAK,wBAAwB,GAAG,KAAK,OAAO,IAAI,IAAI,WAAW,OAAO,EAAE,MAAM,SAAS,QAAQ,EAAE,MAAM,WAAW,KAAK,MAAM,SAAS,MAAM,SAAS,MAAM,MAAM,OAAO,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;AAAA,QACvM;AAAA,MACJ;AACA,UAAI,yBAAyB;AAAG,aAAK,IAAI,MAAM,UAAU,KAAK,OAAO,IAAI,IAAI,qCAAqC,iBAAiB,yCAAyC;AAC5K,UAAI,uBAAuB,KAAK,iBAAiB,SAAS;AAAG,aAAK,IAAI,KAAK,UAAU,KAAK,OAAO,IAAI,IAAI,qFAAqF,iBAAiB,KAAK,IAAI,GAAG;AAG3N,iBAAW,OAAO,IAAI,SAAS;AAC3B,cAAM,SAAS,OAAO,IAAI,QAAQ,SAAS,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI,IAAI,IAAI,QAAQ;AACrG,YAAI,KAAK,OAAO,4BAA4B;AACxC,eAAK,SAAS,GAAG,KAAK,OAAO,IAAI,IAAI,WAAW,OAAO,EAAE,KAAK,QAAQ,KAAK,KAAK,CAAC;AAAA,QACrF,OAAO;AACH,eAAK,gBAAgB,GAAG,KAAK,OAAO,IAAI,IAAI,WAAW,OAAO,EAAE,KAAK,QAAQ,KAAK,KAAK,CAAC;AAAA,QAC5F;AAAA,MACJ;AACA,WAAK,SAAS,KAAK,OAAO,IAAI,IAAI,KAAK,mBAAmB,EAAE,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,CAAC;AACxF,WAAK,SAAS,KAAK,OAAO,IAAI,IAAI,KAAK,UAAU,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC;AAAA,IAC7E,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,IACJ;AAAA,EACJ;AAAA,EAKA,MAAa,YAAY,KAAkF;AACvG,QAAI;AAEA,WAAK,IAAI,MAAM,oBAAa,KAAK,OAAO,IAAI,IAAI,gCAAgC,IAAI,eAAe,IAAI,KAAK;AAK5G,YAAM,WAAW,GAAG,KAAK,OAAO,IAAI,IAAI,aAAa,IAAI;AACzD,UAAI,CAAE,MAAM,KAAK,eAAe,QAAQ,GAAI;AACxC,aAAK,IAAI,KAAK,UAAU,KAAK,OAAO,IAAI,IAAI,eAAe,IAAI,0BAA0B,gDAAgD;AACzI,cAAM,KAAK,wBAAwB,UAAU,EAAE,MAAM,SAAS,QAAQ,EAAE,MAAM,iBAAiB,IAAI,KAAK,MAAM,WAAW,MAAM,UAAU,MAAM,MAAM,OAAO,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC;AAAA,MACrL;AACA,WAAK,SAAS,UAAU,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC;AAKhD,YAAM,SAAS,KAAK,OAAO,IAAI,IAAI,KAAK;AAGxC,YAAM,MAAM,KAAK,iBAAiB,uBAAM,cAAc,CAAC,UAAU,SAAS,GAAG,IAAI,GAAG;AACpF,UAAI,QAAQ,IAAI;AAEZ,cAAM,OAAO,uBAAM,aAAa;AAChC,cAAM,aAAa,IAAI,QAAQ,KAAK,SAAS,OAAO;AACpD,cAAM,KAAK,cAAc,GAAG,UAAU,KAAK,MAAM,EAAE,KAAK,YAAY,KAAK,KAAK,CAAC;AAC/E,cAAM,KAAK,cAAc,GAAG,UAAU,KAAK,SAAS,EAAE,KAAK,YAAY,KAAK,KAAK,CAAC;AAClF,cAAM,KAAK,cAAc,GAAG,UAAU,KAAK,UAAU,EAAE,KAAK,CAAC,YAAY,KAAK,KAAK,CAAC;AAAA,MACxF,OAAO;AAEH,cAAMA,OAAM,KAAK,iBAAiB,uBAAM,MAAM,CAAC,IAAI,GAAG,IAAI,GAAG;AAC7D,YAAIA,SAAQ,MAAM,uBAAM,KAAKA,MAAK,SAAS,WAAW;AAElD,gBAAM,KAAK,cAAc,GAAG,UAAU,IAAI,OAAO,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC;AAAA,QAC7E,OAAO;AACH,eAAK,IAAI,MAAM,UAAU,KAAK,OAAO,IAAI,IAAI,mBAAmB,IAAI,gFAAgF;AAAA,QACxJ;AAAA,MACJ;AAAA,IACJ,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,IACJ;AAAA,EACJ;AAAA,EAQA,MAAc,cAAc,SAAiB,UAA4D;AACrG,QAAI;AACA,UAAI,CAAC;AAAU;AACf,UAAI,SAAS;AAAK;AAClB,YAAM,UAAU,QAAQ,MAAM,GAAG;AACjC,YAAM,WAAW,QAAQ;AACzB,YAAM,UAAU,QAAQ;AACxB,YAAM,MAAM,QAAQ;AACpB,YAAM,MAAM,WAAW,MAAM;AAI7B,UAAI,YAAY,YAAY;AACxB,aAAK,IAAI,MAAM,SAAS,oBAAoB,SAAS,cAAc,SAAS,MAAM;AAElF,cAAM,QAAQ,KAAK,cAAc,MAAM,QAAQ;AAC/C,YAAI,CAAC;AAAO,gBAAM,8BAA8B;AAEhD,YAAI,YAAgC;AACpC,YAAI,aAAgC;AAKpC,cAAM,QAAQ,KAAK,iBAAiB,uBAAM,cAAc,CAAC,IAAI,GAAG,GAAG;AACnE,YAAI,UAAU,IAAI;AAEd,uBAAa,uBAAM,aAAa;AAChC,sBAAY,SAAS,MAAM,WAAW,QAAQ,WAAW;AAAA,QAC7D,OAAO;AAGH,cAAI,CAAC,SAAS;AAAK;AAAA,QACvB;AACA,YAAI,CAAC;AAAW,gBAAM,qBAAqB;AAK3C,cAAM,cAAc,MAAM,KAAK,aAAa,QAAQ,OAAO,WAAW,SAAS,GAAG;AAClF,YAAI,aAAa;AACb,eAAK,IAAI,KAAK,GAAG,MAAM,SAAS,2BAA2B,SAAS,KAAK;AAIzE,cAAI,eAAe,QAAW;AAE1B,kBAAM,gBAAgB,QAAQ,WAAW,QAAQ,OAAO;AACxD,kBAAM,KAAK,cAAc,GAAG,OAAO,WAAW,MAAM,EAAE,KAAK,eAAe,KAAK,KAAK,CAAC;AACrF,kBAAM,KAAK,cAAc,GAAG,OAAO,WAAW,SAAS,EAAE,KAAK,eAAe,KAAK,KAAK,CAAC;AACxF,kBAAM,KAAK,cAAc,GAAG,OAAO,WAAW,UAAU,EAAE,KAAK,CAAC,eAAe,KAAK,KAAK,CAAC;AAAA,UAC9F,OAAO;AAEH,gBAAI,OAAO,SAAS,QAAQ,WAAW;AACnC,oBAAM,MAAM,KAAK,iBAAiB,uBAAM,MAAM,CAAC,IAAI,GAAG,GAAG;AACzD,kBAAI,QAAQ,IAAI;AACZ,oBAAI,uBAAM,KAAK,KAAK,SAAS,WAAW;AAEpC,wBAAM,KAAK,cAAc,SAAS,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC;AAAA,gBAC9D,OAAO;AAEH,uBAAK,IAAI,KAAK,GAAG,MAAM,SAAS,kBAAkB,SAAS,2BAA2B,4BAA4B;AAClH,wBAAM,KAAK,cAAc,SAAS,EAAE,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC;AAAA,gBACtE;AAAA,cACJ,OAAO;AACH,qBAAK,IAAI,KAAK,GAAG,MAAM,SAAS,kBAAkB,SAAS,YAAY,4BAA4B;AAAA,cACvG;AAAA,YACJ,OAAO;AAEH,oBAAM,KAAK,cAAc,SAAS,EAAE,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC;AAAA,YACtE;AAAA,UACJ;AAAA,QACJ,OAAO;AAEH,eAAK,IAAI,MAAM,GAAG,MAAM,8CAA8C,UAAU;AAAA,QACpF;AAAA,MACJ;AAAA,IACJ,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B;AAAA,IACJ;AAAA,EACJ;AAAA,EAaQ,cAAc,OAAe,OAA6B;AAC9D,eAAW,MAAM,KAAK,QAAQ;AAC1B,UAAI,SAAS,KAAK,OAAO,KAAK;AAC1B,cAAM,UAAU;AAEhB,cAAM,QAAQ,KAAK,OAAO,IAAI;AAC9B,YAAI,UAAU,OAAO;AACjB,iBAAO,KAAK,OAAO;AAAA,QACvB;AAAA,MACJ;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAAA,EASQ,iBAAiB,QAAgC,MAAgB,KAAqB;AAC1F,QAAI;AACA,UAAI,QAAQ;AACZ,iBAAW,OAAO,MAAM;AAEpB,gBAAQ,OAAO,UAAU,CAAC,MAA4B,EAAE,SAAS,GAAG;AACpE,YAAI,UAAU;AAAI;AAAA,MACtB;AACA,aAAO;AAAA,IACX,SAAS,GAAP;AACE,WAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;AAC9B,aAAO;AAAA,IACX;AAAA,EACJ;AAAA,EAKQ,SAAS,UAA4B;AACzC,QAAI;AACA,UAAI,KAAK,QAAQ;AACb,mBAAW,MAAM,KAAK,QAAQ;AAE1B,eAAK,SAAS,KAAK,OAAO,IAAI,KAAK,UAAU,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AAAA,QAC1E;AAAA,MACJ;AAGA,UAAI,KAAK,aAAa;AAClB,mBAAW,YAAY,KAAK,YAAY,SAAS;AAE7C,cAAI,KAAK,YAAY,QAAQ,UAAU;AAAiB,iBAAK,aAAa,KAAK,YAAY,QAAQ,UAAU,eAAe;AAAA,QAChI;AAAA,MACJ;AAGA,UAAI,KAAK,aAAa;AAClB,aAAK,YAAY,UAAU;AAAA,MAC/B;AAEA,eAAS;AAAA,IACb,SAAS,GAAP;AACE,eAAS;AAAA,IACb;AAAA,EACJ;AACJ;AAEA,IAAI,QAAQ,SAAS,QAAQ;AAEzB,SAAO,UAAU,CAAC,YAAuD,IAAI,UAAU,OAAO;AAClG,OAAO;AAEH,GAAC,MAAM,IAAI,UAAU,GAAG;AAC5B;", + "sourcesContent": ["/**\n * -------------------------------------------------------------------\n * ioBroker Fully Browser MQTT Adapter\n * @github https://github.com/Acgua/ioBroker.fully-mqtt\n * @forum https://forum.iobroker.net/topic/63705/\n * @author Acgua \n * @license Apache License 2.0\n * -------------------------------------------------------------------\n */\n\n/**\n * For all imported NPM modules, open console, change dir for example to \"C:\\iobroker\\node_modules\\ioBroker.fully-mqtt\\\"\n * and execute \"npm install \", e.g., npm install axios\n */\nimport * as utils from '@iobroker/adapter-core';\nimport { CONST } from './lib/constants';\nimport { ICmds, IDevice } from './lib/interfaces';\nimport { cleanDeviceName, err2Str, getConfigValuePerKey, isEmpty, isIpAddressValid, wait } from './lib/methods';\nimport { MqttServer } from './lib/mqtt-server';\nimport { RestApiFully } from './lib/restApi';\n\n/**\n * Main ioBroker Adapter Class\n */\nexport class FullyMqtt extends utils.Adapter {\n // Imported methods from ./lib/methods\n public err2Str = err2Str.bind(this);\n public isEmpty = isEmpty.bind(this);\n public wait = wait.bind(this);\n public cleanDeviceName = cleanDeviceName.bind(this);\n public getConfigValuePerKey = getConfigValuePerKey.bind(this);\n public isIpAddressValid = isIpAddressValid.bind(this);\n\n // MQTT Server\n private mqtt_Server: MqttServer | undefined;\n\n // REST API\n private restApi_inst = new RestApiFully(this);\n\n /**\n * Fullys: IP as key, and object per IDevice\n * {\n * '192.168.10.20': {name: 'Tablet Kitchen', id:'Tablet-Kitchen', ip:'192.168.10.20', ...},\n * '192.168.10.30': {name: 'Tablet Hallway', id:'Tablet-Hallway', ip:'192.168.10.30', ...},\n * }\n * Note: we can use this.getFullyPerKey() to get fully object per provided key\n */\n public fullys: { [ip: string]: IDevice } = {}; // enabled Fullys only\n public fullysNotEnabled: { [ip: string]: IDevice } = {}; // not enabled Fullys only\n public fullysAll: { [ip: string]: IDevice } = {}; // enabled and not enabled Fullys\n\n // Has onMqttAlive() ever been called before?\n private onMqttAlive_EverBeenCalledBefore = false;\n\n /**\n * Constructor\n */\n public constructor(options: Partial = {}) {\n super({ ...options, name: 'fully-mqtt' });\n this.on('ready', this.onReady.bind(this));\n this.on('stateChange', this.onStateChange.bind(this));\n this.on('unload', this.onUnload.bind(this));\n }\n\n /**\n * Is called when databases are connected and adapter received configuration.\n */\n private async onReady(): Promise {\n try {\n /**\n * Set the connection indicator to false during startup\n */\n this.setState('info.connection', { val: false, ack: true });\n\n /**\n * Verify and init configuration\n */\n if (await this.initConfig()) {\n this.log.debug(`Adapter settings successfully verified and initialized.`);\n } else {\n this.log.error(`Adapter settings initialization failed. ---> Please check your adapter instance settings!`);\n return;\n }\n\n for (const ip in this.fullys) {\n // Create Fully device objects\n const res = await this.createFullyDeviceObjects(this.fullys[ip]);\n // REST API: Subscribe to command state changes\n if (res) await this.subscribeStatesAsync(this.fullys[ip].id + '.Commands.*');\n // Set enabled state\n this.setState(this.fullys[ip].id + '.enabled', { val: true, ack: true });\n }\n // Not enabled fullys: 1. Enabled state to false; 2. alive to null\n for (const ip in this.fullysNotEnabled) {\n this.setState(this.fullysNotEnabled[ip].id + '.enabled', { val: false, ack: true });\n this.setState(this.fullysNotEnabled[ip].id + '.alive', { val: null, ack: true });\n }\n\n /**\n * Start MQTT Server\n */\n this.mqtt_Server = new MqttServer(this);\n this.mqtt_Server.start();\n\n /**\n * Delete device object tree(s) if deleted or renamed in config\n */\n this.deleteRemovedDeviceObjects();\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * Create Fully Browser Device ioBroker state objects\n * @param device Fully Browser Device Object\n * @returns true if successful, false if error\n */\n private async createFullyDeviceObjects(device: IDevice): Promise {\n try {\n this.log.debug(`Start createFullyDeviceObjects() - ${device.name} (${device.ip})\u2026`);\n\n /**\n * Create device object(s)\n */\n // Device and Info object\n await this.setObjectNotExistsAsync(device.id, {\n type: 'device',\n common: {\n name: device.name,\n //@ts-expect-error - Object \"statusStates\" is needed for status, error is: Object literal may only specify known properties, and 'statusStates' does not exist in type 'DeviceCommon'.ts(2345)\n statusStates: { onlineId: `${this.namespace}.${device.id}.alive` },\n },\n native: {},\n });\n await this.setObjectNotExistsAsync(device.id + '.Info', { type: 'channel', common: { name: 'Device Information' }, native: {} });\n\n // Alive\n await this.setObjectNotExistsAsync(device.id + '.alive', {\n type: 'state',\n common: {\n name: 'Is Fully alive?',\n desc: 'If Fully Browser is alive or not',\n type: 'boolean',\n role: 'indicator.reachable',\n icon: 'data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iTXVpU3ZnSWNvbi1yb290IE11aVN2Z0ljb24tZm9udFNpemVNZWRpdW0gaWNvbk93biBjc3MtdnViYnV2IiBmb2N1c2FibGU9ImZhbHNlIiBhcmlhLWhpZGRlbj0idHJ1ZSIgdmlld0JveD0iMCAwIDI0IDI0IiBkYXRhLXRlc3RpZD0iV2lmaUljb24iPjxwYXRoIGQ9Im0xIDkgMiAyYzQuOTctNC45NyAxMy4wMy00Ljk3IDE4IDBsMi0yQzE2LjkzIDIuOTMgNy4wOCAyLjkzIDEgOXptOCA4IDMgMyAzLTNjLTEuNjUtMS42Ni00LjM0LTEuNjYtNiAwem0tNC00IDIgMmMyLjc2LTIuNzYgNy4yNC0yLjc2IDEwIDBsMi0yQzE1LjE0IDkuMTQgOC44NyA5LjE0IDUgMTN6Ij48L3BhdGg+PC9zdmc+',\n read: true,\n write: false,\n },\n native: {},\n });\n // Last info update, and if enabled in adapter settings\n await this.setObjectNotExistsAsync(device.id + '.lastInfoUpdate', { type: 'state', common: { name: 'Last information update', desc: 'Date/time of last information update from Fully Browser', type: 'number', role: 'value.time', read: true, write: false }, native: {} });\n await this.setObjectNotExistsAsync(device.id + '.enabled', { type: 'state', common: { name: 'Is device enabled in adapter settings?', desc: 'If this device is enabled in the adapter settings', type: 'boolean', role: 'indicator', read: true, write: false }, native: {} });\n\n // REST API Commands Objects\n await this.setObjectNotExistsAsync(device.id + '.Commands', { type: 'channel', common: { name: 'Commands' }, native: {} });\n const allCommands = CONST.cmds.concat(CONST.cmdsSwitches); // join both arrays\n for (const cmdObj of allCommands) {\n let lpRole = '';\n if (cmdObj.type === 'boolean') lpRole = 'button';\n if (cmdObj.type === 'string') lpRole = 'text';\n if (cmdObj.type === 'number') lpRole = 'value';\n if (cmdObj.cmdOn && cmdObj.cmdOff) lpRole = 'switch';\n await this.setObjectNotExistsAsync(device.id + '.Commands.' + cmdObj.id, { type: 'state', common: { name: 'Command: ' + cmdObj.name, type: cmdObj.type, role: lpRole, read: true, write: true }, native: {} });\n }\n\n // Create MQTT Events Objects\n // More states are created once a new Event is received.\n await this.setObjectNotExistsAsync(device.id + '.Events', { type: 'channel', common: { name: 'MQTT Events' }, native: {} });\n for (const event of CONST.mqttEvents) {\n await this.setObjectNotExistsAsync(device.id + '.Events.' + event, { type: 'state', common: { name: 'Event: ' + event, type: 'boolean', role: 'switch', read: true, write: false }, native: {} });\n }\n return true;\n } catch (e) {\n this.log.error(this.err2Str(e));\n return false;\n }\n }\n\n /**\n * Delete device objects if device was (a) renamed or (b) deleted from devices table in adapter settings.\n * However, do not delete if it was just set inactive in table.\n */\n private async deleteRemovedDeviceObjects(): Promise {\n try {\n // Get string array of all adapter objects: ['fully-mqtt.0.info', 'fully-mqtt.0.info.connection', ...];\n const adapterObjectsIds: string[] = Object.keys(await this.getAdapterObjectsAsync());\n\n // Get all existing fully device ids of iobroker adapter objects in array: 'fully-mqtt.0.Tablet-Kitchen' -> 'Tablet-Kitchen', 'fully-mqtt.0.Tablet-Hallway' -> 'Tablet-Hallway', etc.\n const allObjectDeviceIds: Array = [];\n for (const objectId of adapterObjectsIds) {\n const deviceId = objectId.split('.')[2]; // e.g. 'Tablet-Kitchen'\n // Ignore fully-mqtt.0.info tree (which includes fully-mqtt.0.info.connection, ...). Add more to ignore as needed in the future...\n if (['info'].includes(deviceId)) {\n this.log.silly(`Cleanup: Ignore non device related state ${objectId}.`);\n } else {\n if (!allObjectDeviceIds.includes(deviceId)) allObjectDeviceIds.push(deviceId);\n }\n }\n\n // Get all adapter configuration device ids (enabled and disabled), like ['Tablet-Kitchen', 'Tablet-Hallway', ...]\n const allConfigDeviceIds: string[] = [];\n for (const ip in this.fullysAll) {\n allConfigDeviceIds.push(this.fullysAll[ip].id);\n }\n // Delete\n for (const id of allObjectDeviceIds) {\n if (!allConfigDeviceIds.includes(id)) {\n await this.delObjectAsync(id, { recursive: true });\n this.log.info(`Cleanup: Deleted no longer defined device objects of '${id}'.`);\n }\n }\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * Verify adapter instance settings\n */\n private async initConfig(): Promise {\n try {\n /*************************\n * MQTT Fields\n *************************/\n if (this.isEmpty(this.config.mqttPort) || this.config.mqttPort < 1 || this.config.mqttPort > 65535) {\n this.log.warn(`Adapter instance settings: MQTT Port ${this.config.mqttPort} is not allowed, set to default of 1886`);\n this.config.mqttPort = 1886;\n }\n if (this.isEmpty(this.config.mqttPublishedInfoDelay) || this.config.mqttPublishedInfoDelay < 2 || this.config.mqttPublishedInfoDelay > 120) {\n this.log.warn(`Adapter instance settings: MQTT Publish Info Delay of ${this.config.mqttPublishedInfoDelay}s is not allowed, set to default of 30s`);\n this.config.mqttPublishedInfoDelay = 30;\n }\n\n /*************************\n * REST API Fields\n *************************/\n if (this.isEmpty(this.config.restTimeout) || this.config.restTimeout < 500 || this.config.restTimeout > 15000) {\n this.log.warn(`Adapter instance settings: REST API timeout of ${this.config.restTimeout} ms is not allowed, set to default of 6000ms`);\n this.config.restTimeout = 6000;\n }\n\n /*************************\n * Table Devices\n *************************/\n if (this.isEmpty(this.config.tableDevices)) {\n this.log.error(`No Fully devices defined in adapter instance settings!`);\n return false;\n }\n const deviceIds: string[] = []; // to check for duplicate device ids\n const deviceIPs: string[] = []; // to check for duplicate device IPs\n for (let i = 0; i < this.config.tableDevices.length; i++) {\n const lpDevice = this.config.tableDevices[i];\n const finalDevice: IDevice = {\n name: '',\n id: '',\n ip: '',\n enabled: false,\n mqttInfoObjectsCreated: false,\n mqttInfoKeys: [],\n restProtocol: 'http',\n restPort: 0,\n restPassword: '',\n lastSeen: 0, // timestamp\n isAlive: false,\n };\n\n // name\n if (this.isEmpty(lpDevice.name)) {\n this.log.error(`Provided device name \"${lpDevice.name}\" is empty!`);\n return false;\n }\n finalDevice.name = lpDevice.name.trim();\n\n // id\n finalDevice.id = this.cleanDeviceName(lpDevice.name);\n if (finalDevice.id.length < 1) {\n this.log.error(`Provided device name \"${lpDevice.name}\" is too short and/or has invalid characters!`);\n return false;\n }\n if (deviceIds.includes(finalDevice.id)) {\n this.log.error(`Device \"${finalDevice.name}\" -> id:\"${finalDevice.id}\" is used for more than once device.`);\n return false;\n } else {\n deviceIds.push(finalDevice.id);\n }\n\n // REST Protocol (http/https)\n if (lpDevice.restProtocol !== 'http' && lpDevice.restProtocol !== 'https') {\n this.log.warn(`${finalDevice.name}: REST API Protocol is empty, set to http as default.`);\n finalDevice.restProtocol = 'http';\n } else {\n finalDevice.restProtocol = lpDevice.restProtocol;\n }\n\n // IP Address\n if (!this.isIpAddressValid(lpDevice.ip)) {\n this.log.error(`${finalDevice.name}: Provided IP address \"${lpDevice.ip}\" is not valid!`);\n return false;\n }\n if (deviceIPs.includes(lpDevice.ip)) {\n this.log.error(`Device \"${finalDevice.name}\" -> IP:\"${lpDevice.ip}\" is used for more than once device.`);\n return false;\n } else {\n deviceIPs.push(lpDevice.ip);\n finalDevice.ip = lpDevice.ip;\n }\n\n // REST Port\n if (isNaN(lpDevice.restPort) || lpDevice.restPort < 0 || lpDevice.restPort > 65535) {\n this.log.error(`Adapter config Fully port number ${lpDevice.restPort} is not valid, should be >= 0 and < 65536.`);\n return false;\n } else {\n finalDevice.restPort = Math.round(lpDevice.restPort);\n }\n // REST Password\n if (isEmpty(lpDevice.restPassword)) {\n this.log.error(`Remote Admin (REST API) Password must not be empty!`);\n return false;\n } else {\n finalDevice.restPassword = lpDevice.restPassword;\n }\n\n // Enabled status\n finalDevice.enabled = lpDevice.enabled ? true : false;\n\n // Debug log of config\n const logConfig = { ...finalDevice }; // copy object using spread\n logConfig.restPassword = '(hidden)'; // do not show password in log !\n this.log.debug(`Final Config: ${JSON.stringify(logConfig)}`);\n\n // Finalize\n this.fullysAll[finalDevice.ip] = finalDevice;\n if (lpDevice.enabled) {\n this.fullys[finalDevice.ip] = finalDevice;\n this.log.info(`\uD83D\uDDF8 ${finalDevice.name} (${finalDevice.ip}): Config successfully verified.`);\n } else {\n this.fullysNotEnabled[finalDevice.ip] = finalDevice;\n this.log.info(`${finalDevice.name} (${finalDevice.ip}) is not enabled in adapter settings, so it will not be used by adapter.`);\n }\n }\n\n if (Object.keys(this.fullys).length === 0) {\n this.log.error(`No active devices with correct configuration found.`);\n return false;\n }\n return true;\n } catch (e) {\n this.log.error(this.err2Str(e));\n return false;\n }\n }\n\n /**\n * On Alive Changes\n * MQTT is being used only, REST API not.\n */\n public async onMqttAlive(ip: string, isAlive: true | false, msg: string): Promise {\n try {\n const prevIsAlive = this.fullys[ip].isAlive;\n this.fullys[ip].isAlive = isAlive;\n\n // Has this function ever been called before? If adapter is restarted, we ensure log, etc.\n const calledBefore = this.onMqttAlive_EverBeenCalledBefore; // Keep old value\n this.onMqttAlive_EverBeenCalledBefore = true; // Now it was called\n\n /***********\n * 1 - Fully Device\n ***********/\n // if alive status changed\n if ((!calledBefore && isAlive === true) || prevIsAlive !== isAlive) {\n // Set Device isAlive Status - we could also use setStateChanged()...\n this.setState(this.fullys[ip].id + '.alive', { val: isAlive, ack: true });\n\n // log\n if (isAlive) {\n this.log.info(`${this.fullys[ip].name} is alive (MQTT: ${msg})`);\n } else {\n this.log.warn(`${this.fullys[ip].name} is not alive! (MQTT: ${msg})`);\n }\n } else {\n // No change\n }\n\n /***********\n * 2 - Adapter Connection indicator\n ***********/\n let countAll = 0;\n let countAlive = 0;\n for (const lpIpAddr in this.fullys) {\n countAll++;\n if (this.fullys[lpIpAddr].isAlive) {\n countAlive++;\n }\n }\n let areAllAlive = false;\n if (countAll > 0 && countAll === countAlive) areAllAlive = true;\n this.setStateChanged('info.connection', { val: areAllAlive, ack: true });\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * MQTT: once new device info packet is coming in\n */\n public async onMqttInfo(obj: { clientId: string; ip: string; topic: string; infoObj: { [k: string]: any } }): Promise {\n try {\n // log\n this.log.debug(`[MQTT] ${this.fullys[obj.ip].name} published info, topic: ${obj.topic}`);\n //this.log.debug(`[MQTT] Client ${obj.ip} Publish Info: Details: ${JSON.stringify(obj.infoObj)}`);\n\n // Create info objects if not yet existing\n const formerInfoKeysLength: number = this.fullys[obj.ip].mqttInfoKeys.length;\n const newInfoKeysAdded: string[] = [];\n for (const key in obj.infoObj) {\n const val = obj.infoObj[key];\n const valType = typeof val;\n // only accept certain types\n if (valType !== 'string' && valType !== 'boolean' && valType !== 'object' && valType !== 'number') {\n this.log.warn(`[MQTT] ${this.fullys[obj.ip].name}: Unknown type ${valType} of key '${key}' in info object`);\n continue;\n }\n // Create info object if not yet seen - this check is used for increasing performance by not unnesserily call setObjectNotExistsAsync() every time new info package comes in\n if (!this.fullys[obj.ip].mqttInfoKeys.includes(key)) {\n this.fullys[obj.ip].mqttInfoKeys.push(key);\n newInfoKeysAdded.push(key);\n await this.setObjectNotExistsAsync(`${this.fullys[obj.ip].id}.Info.${key}`, { type: 'state', common: { name: 'Info: ' + key, type: valType, role: 'value', read: true, write: false }, native: {} });\n }\n }\n if (formerInfoKeysLength === 0) this.log.debug(`[MQTT] ${this.fullys[obj.ip].name}: Initially create states for ${newInfoKeysAdded.length} info items (if not yet existing)`);\n if (formerInfoKeysLength > 0 && newInfoKeysAdded.length > 0) this.log.info(`[MQTT] ${this.fullys[obj.ip].name}: Created new info object(s) as not seen before (if object(s) did not exist): ${newInfoKeysAdded.join(', ')}`);\n\n // Set info objects\n for (const key in obj.infoObj) {\n const newVal = typeof obj.infoObj[key] === 'object' ? JSON.stringify(obj.infoObj[key]) : obj.infoObj[key]; // https://forum.iobroker.net/post/628870 - https://forum.iobroker.net/post/960260\n if (this.config.mqttUpdateUnchangedObjects) {\n this.setState(`${this.fullys[obj.ip].id}.Info.${key}`, { val: newVal, ack: true });\n } else {\n this.setStateChanged(`${this.fullys[obj.ip].id}.Info.${key}`, { val: newVal, ack: true });\n }\n }\n this.setState(this.fullys[obj.ip].id + '.lastInfoUpdate', { val: Date.now(), ack: true });\n this.setState(this.fullys[obj.ip].id + '.alive', { val: true, ack: true });\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * MQTT: once new event packet is coming in\n */\n public async onMqttEvent(obj: { clientId: string; ip: string; topic: string; cmd: string }): Promise {\n try {\n // log\n this.log.debug(`[MQTT] \uD83D\uDCE1 ${this.fullys[obj.ip].name} published event, topic: ${obj.topic}, cmd: ${obj.cmd}`);\n\n /**\n * Set Event State\n */\n const pthEvent = `${this.fullys[obj.ip].id}.Events.${obj.cmd}`;\n if (!(await this.getObjectAsync(pthEvent))) {\n this.log.info(`[MQTT] ${this.fullys[obj.ip].name}: Event ${obj.cmd} received but state ${pthEvent} does not exist, so we create it first`);\n await this.setObjectNotExistsAsync(pthEvent, { type: 'state', common: { name: 'MQTT Event: ' + obj.cmd, type: 'boolean', role: 'switch', read: true, write: false }, native: {} });\n }\n this.setState(pthEvent, { val: true, ack: true });\n\n /**\n * Confirm Command state(s) with ack: true\n */\n const pthCmd = this.fullys[obj.ip].id + '.Commands';\n\n // Check if it is a switch with MQTT commands connected\n const idx = this.getIndexFromConf(CONST.cmdsSwitches, ['mqttOn', 'mqttOff'], obj.cmd);\n if (idx !== -1) {\n // We have a switch\n const conf = CONST.cmdsSwitches[idx]; // the found line from config array\n const onOrOffCmd = obj.cmd === conf.mqttOn ? true : false;\n await this.setStateAsync(`${pthCmd}.${conf.id}`, { val: onOrOffCmd, ack: true });\n await this.setStateAsync(`${pthCmd}.${conf.cmdOn}`, { val: onOrOffCmd, ack: true });\n await this.setStateAsync(`${pthCmd}.${conf.cmdOff}`, { val: !onOrOffCmd, ack: true });\n } else {\n // No switch\n const idx = this.getIndexFromConf(CONST.cmds, ['id'], obj.cmd);\n if (idx !== -1 && CONST.cmds[idx].type === 'boolean') {\n // We have a button, so set it to true\n await this.setStateAsync(`${pthCmd}.${obj.cmd}`, { val: true, ack: true });\n } else {\n this.log.silly(`[MQTT] ${this.fullys[obj.ip].name}: Event cmd ${obj.cmd} - no REST API command is existing, so skip confirmation with with ack:true`);\n }\n }\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * Called once a subscribed state changes.\n * Ready once subscribeStatesAsync() is called...\n * @param id - e.g. \"fully-mqtt.0.Tablet-Bathroom.Commands.screenSwitch\"\n * @param stateObj - e.g. { val: true, ack: false, ts: 123456789, q: 0, lc: 123456789 }\n */\n private async onStateChange(stateId: string, stateObj: ioBroker.State | null | undefined): Promise {\n try {\n if (!stateObj) return; // state was deleted, we disregard...\n if (stateObj.ack) return; // ignore ack:true\n const idSplit = stateId.split('.');\n const deviceId = idSplit[2]; // \"Tablet-Bathroom\"\n const channel = idSplit[3]; // \"Commands\"\n const cmd = idSplit[4]; // \"screenSwitch\"\n const pth = deviceId + '.' + channel; // Tablet-Bathroom.Commands\n /**\n * Commands\n */\n if (channel === 'Commands') {\n this.log.debug(`state ${stateId} changed: ${stateObj.val} (ack = ${stateObj.ack})`);\n // Get device object\n const fully = this.getFullyByKey('id', deviceId);\n if (!fully) throw `Fully object for deviceId '${deviceId}' not found!`;\n\n let cmdToSend: string | undefined = cmd; // Command to send to Fully\n let switchConf: undefined | ICmds = undefined; // Config line of switch\n\n /****************\n * Check if it is a switch state cmd, like 'screenSwitch'\n ****************/\n const idxSw = this.getIndexFromConf(CONST.cmdsSwitches, ['id'], cmd);\n if (idxSw !== -1) {\n // It is a switch\n switchConf = CONST.cmdsSwitches[idxSw]; // the found line from config array\n cmdToSend = stateObj.val ? switchConf.cmdOn : switchConf.cmdOff;\n } else {\n // Not a switch.\n // If val is false, we disregard, since it is a button only\n if (!stateObj.val) return;\n }\n if (!cmdToSend) throw `onStateChange() - ${stateId}: fullyCmd could not be determined!`;\n\n /**\n * Send Command\n */\n const sendCommand = await this.restApi_inst.sendCmd(fully, cmdToSend, stateObj.val);\n if (sendCommand) {\n this.log.info(`\uD83D\uDDF8 ${fully.name}: Command ${cmd} successfully set to ${stateObj.val}`);\n /**\n * Confirm with ack:true\n */\n if (switchConf !== undefined) {\n // it is a switch\n const onOrOffCmdVal = cmd === switchConf.cmdOn ? true : false;\n await this.setStateAsync(`${pth}.${switchConf.id}`, { val: onOrOffCmdVal, ack: true });\n await this.setStateAsync(`${pth}.${switchConf.cmdOn}`, { val: onOrOffCmdVal, ack: true });\n await this.setStateAsync(`${pth}.${switchConf.cmdOff}`, { val: !onOrOffCmdVal, ack: true });\n } else {\n // No switch\n if (typeof stateObj.val === 'boolean') {\n const idx = this.getIndexFromConf(CONST.cmds, ['id'], cmd);\n if (idx !== -1) {\n if (CONST.cmds[idx].type === 'boolean') {\n // Is a button\n await this.setStateAsync(stateId, { val: true, ack: true });\n } else {\n // This should actually not happen, as we just define buttons in commands, but anyway\n this.log.warn(`${fully.name}: ${stateId} - val: ${stateObj.val} is boolean, but cmd ${cmd} is not defined in CONF`);\n await this.setStateAsync(stateId, { val: stateObj.val, ack: true });\n }\n } else {\n this.log.warn(`${fully.name}: ${stateId} - val: ${stateObj.val}, cmd ${cmd} is not defined in CONF`);\n }\n } else {\n // Non-boolean, so just set val with ack:true...\n await this.setStateAsync(stateId, { val: stateObj.val, ack: true });\n }\n }\n } else {\n // log, more log lines were already published by this.restApi_inst.sendCmd()\n this.log.debug(`${fully.name}: restApiSendCmd() was not successful (${stateId})`);\n }\n }\n } catch (e) {\n this.log.error(this.err2Str(e));\n return;\n }\n }\n\n /**\n * Get Fully Object per provided key and value\n * {\n * '192.168.10.20': {name: 'Tablet Kitchen', id:'Tablet-Kitchen', ip:'192.168.10.20', ...},\n * '192.168.10.30': {name: 'Tablet Hallway', id:'Tablet-Hallway', ip:'192.168.10.30', ...},\n * }\n * getFullyByKey('id', 'Tablet-Hallway') will return the second object...\n * @param keyId - e.g. 'id', 'name', ...\n * @param value - e.g. 'Tablet Hallway', ...\n * @returns - fully object or false if not found\n */\n private getFullyByKey(keyId: string, value: any): IDevice | false {\n for (const ip in this.fullys) {\n if (keyId in this.fullys[ip]) {\n const lpKeyId = keyId as string;\n // Wow, what a line. Due to: https://bobbyhadz.com/blog/typescript-element-implicitly-has-any-type-expression\n const lpVal = this.fullys[ip][lpKeyId as keyof (typeof this.fullys)[typeof ip]];\n if (lpVal === value) {\n return this.fullys[ip];\n }\n }\n }\n return false;\n }\n\n /**\n * Gets Index for given keys and a value\n * @param config - config like CONST.cmds\n * @param keys - like ['mqttOn','mqttOff']\n * @param cmd - like 'onScreensaverStart'\n * @returns Index (0-...), or -1 if not found\n */\n private getIndexFromConf(config: { [k: string]: any }[], keys: string[], cmd: string): number {\n try {\n let index = -1;\n for (const key of keys) {\n // Get array index\n index = config.findIndex((x: { [k: string]: any }) => x[key] === cmd);\n if (index !== -1) break;\n }\n return index;\n } catch (e) {\n this.log.error(this.err2Str(e));\n return -1;\n }\n }\n\n /**\n * Is called when adapter shuts down - callback has to be called under any circumstances!\n */\n private async onUnload(callback: () => void): Promise {\n try {\n // All Fullys: Set alive status to null\n if (this.fullysAll) {\n for (const ip in this.fullysAll) {\n // We check first if object exists, as there were errors in log on when updating adpater via Github (related to missing objects)\n if (await this.getObjectAsync(this.fullysAll[ip].id)) {\n this.setState(this.fullysAll[ip].id + '.alive', { val: null, ack: true });\n }\n }\n }\n\n // Clear MQTT server timeouts\n if (this.mqtt_Server) {\n for (const clientId in this.mqtt_Server.devices) {\n // @ts-expect-error \"Type 'null' is not assignable to type 'Timeout'.ts(2345)\" - we check for not being null via \"if\"\n if (this.mqtt_Server.devices[clientId].timeoutNoUpdate) this.clearTimeout(this.mqtt_Server.devices[clientId].timeoutNoUpdate);\n }\n }\n\n // destroy MQTT Server\n if (this.mqtt_Server) {\n this.mqtt_Server.terminate();\n }\n\n callback();\n } catch (e) {\n callback();\n }\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial | undefined) => new FullyMqtt(options);\n} else {\n // otherwise start the instance directly\n (() => new FullyMqtt())();\n}\n"], + "mappings": "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", "names": ["idx"] } diff --git a/src/lib/adapter-config.d.ts b/src/lib/adapter-config.d.ts index 127b6d0..ec5273c 100644 --- a/src/lib/adapter-config.d.ts +++ b/src/lib/adapter-config.d.ts @@ -4,7 +4,16 @@ declare global { namespace ioBroker { interface AdapterConfig { - tableDevices: any[]; + tableDevices: [ + { + enabled: boolean; + name: string; + restProtocol: string; + ip: string; + restPort: number; + restPassword: string; + }, + ]; mqttUser: string; mqttPassword: string; mqttPort: number; diff --git a/src/lib/interfaces.ts b/src/lib/interfaces.ts index 1f96497..a4ee633 100644 --- a/src/lib/interfaces.ts +++ b/src/lib/interfaces.ts @@ -2,6 +2,7 @@ export interface IDevice { name: string; // e.g. "Tablet Hallway Entry" id: string; // e.g. "Tablet-Hallway-Entry" (meets ioBroker state convention) ip: string; + enabled: true | false; restProtocol: 'http' | 'https'; restPort: number; restPassword: string; diff --git a/src/lib/methods.ts b/src/lib/methods.ts index fbbddae..118c395 100644 --- a/src/lib/methods.ts +++ b/src/lib/methods.ts @@ -1,21 +1,8 @@ /** * Methods and Tools - * @desc Methods and Tools - * @author Acgua - * @license Apache License 2.0 - * - * ---------------------------------------------------------------------------------------- - * How to implement this file in main.ts (see also https://stackoverflow.com/a/58459668) - * ---------------------------------------------------------------------------------------- - * 1. Add "this: InstanceWatcher" as first function parameter if you need access to "this" - * -> no need to provide this parameter when calling the method, though! - * 1. Add line like "import { err2Str, isEmpty } from './lib/methods';" - * 2. Add keyword "export" before "class InstanceWatcher extends utils.Adapter" - * 3. class InstanceWatcher: for each method, add line like: "public isEmpty = isEmpty.bind(this);" - * Note: use "private isEmpty..." and not "public", if you do not need to access method from this file */ + import { FullyMqtt } from '../main'; -// import { IDevice } from './interfaces'; /** * Convert error to string diff --git a/src/lib/mqtt-server.ts b/src/lib/mqtt-server.ts index 94bf768..f77d911 100644 --- a/src/lib/mqtt-server.ts +++ b/src/lib/mqtt-server.ts @@ -47,7 +47,7 @@ export class MqttServer { * Start Listening */ this.server.listen(this.port, () => { - this.adapter.log.info(`[MQTT]🚀 Server started and listening on port ${this.port}`); + this.adapter.log.info(`🚀 MQTT Server started and is listening on port ${this.port}.`); }); /** @@ -79,7 +79,7 @@ export class MqttServer { if (!this.adapter.isIpAddressValid(ip)) ip === undefined; } // Check if IP is an active device IP - if (ip && !this.adapter.activeDeviceIPs.includes(ip)) { + if (ip && !Object.keys(this.adapter.fullys).includes(ip)) { this.adapter.log.error(`[MQTT] Client ${client.id} not authorized: ${ip} is not an active Fully device IP per adapter settings.`); this.notAuthorizedClients.push(client.id); callback(null, false); @@ -96,18 +96,18 @@ export class MqttServer { if (!this.adapter.config.mqttDoNotVerifyUserPw) { // Username if (username !== this.adapter.config.mqttUser) { - this.adapter.log.warn(`[MQTT] Client ${ipMsg} Authorization rejected: received user name '${username}' does not match '${this.adapter.config.mqttUser}' in adapter settings.`); + this.adapter.log.warn(`MQTT Client ${ipMsg} Authorization rejected: received user name '${username}' does not match '${this.adapter.config.mqttUser}' in adapter settings.`); callback(null, false); return; } // Password if (password.toString() !== this.adapter.config.mqttPassword) { - this.adapter.log.warn(`[MQTT] Client ${ipMsg} Authorization rejected: received password does not match with password in adapter settings.`); + this.adapter.log.warn(`MQTT Client ${ipMsg} Authorization rejected: received password does not match with password in adapter settings.`); callback(null, false); return; } } - this.adapter.log.info(`[MQTT]🔑 Client ${ipMsg} successfully authenticated.`); + this.adapter.log.info(`🔑 MQTT Client ${ipMsg} successfully authenticated.`); callback(null, true); } catch (e) { this.adapter.log.error(this.adapter.err2Str(e)); @@ -179,7 +179,7 @@ export class MqttServer { const ip = info.ip4; const devMsg = `${this.adapter.fullys[ip].name} (${ip})`; // Check IP - already done in this.aedes.authenticate, but just in case we were unable to get ip there - if (!this.adapter.activeDeviceIPs.includes(ip)) { + if (!Object.keys(this.adapter.fullys).includes(ip)) { this.adapter.log.error(`[MQTT] Client ${devMsg} Packet rejected: IP is not allowed per adapter settings. ${client.id}`); return; } @@ -347,7 +347,8 @@ export class MqttServer { const ip = this.devices[clientId]?.ip; if (ip) { - this.adapter.onAliveChange('MQTT', ip, isAlive, msg); + // Call Adapter function onMqttAliveChange() + this.adapter.onMqttAlive(ip, isAlive, msg); if (isAlive) { this.scheduleCheckIfStillActive(clientId); // restart timer } else { diff --git a/src/lib/restApi.ts b/src/lib/restApi.ts index a57edfe..fc9dd25 100644 --- a/src/lib/restApi.ts +++ b/src/lib/restApi.ts @@ -25,28 +25,6 @@ export class RestApiFully { this.adapter = adapter; } - /** - * Get Info Object from Fully - * @param ip - IP Address - * @returns info object, or false in case of error - */ - /* ----- NO LONGER USED since v0.1.0 --- - public async getInfo(ip: string): Promise<{ [k: string]: any } | false> { - try { - const device = this.adapter.fullys[ip]; - const result = await this.axiosGetInfoOrSendCmd(device, 'getInfo'); - if (result.status && result.infoObj !== undefined) { - return result.infoObj; - } else { - return false; - } - } catch (e) { - this.adapter.log.error(`[REST] ${this.adapter.fullys[ip].name}: ${this.adapter.err2Str(e)}`); - return false; - } - } - */ - /** * Send a command to Fully * @param device - device object @@ -83,8 +61,8 @@ export class RestApiFully { finalUrlParam = 'cmd=' + cmd; } - const result = await this.axiosGetInfoOrSendCmd(device, 'sendCmd', cmd, finalUrlParam); - return result.status; + const result = await this.axiosSendCmd(device, cmd, finalUrlParam); + return result; } catch (e) { this.adapter.log.error(`[REST] ${device.name}: ${this.adapter.err2Str(e)}`); return false; @@ -92,26 +70,15 @@ export class RestApiFully { } /** - * Axios: Get Device Info or Send Command + * Axios: Send Command * @param device - device object - * @param what - 'getInfo' to get device info or 'sendCmd' to send a command - * @param cmd - if 'sendCmd': Command like "screenOff" - * @param urlParam - if 'sendCmd': URL parameter like "cmd=screenOff" - * @returns if what='getInfo': false if error, device info object if true - * if what='sendCmd': false if error, true if successful - * - * TODO: Remove 'getInfo' since no longer used from version 0.1.0 - * + * @param cmd - Command like "screenOff" + * @param urlParam - URL parameter like "cmd=screenOff" + * @returns false if error, true if successful */ - private async axiosGetInfoOrSendCmd(device: IDevice, what: 'getInfo' | 'sendCmd', cmd?: string, urlParam?: string): Promise<{ status: true | false; infoObj?: { [k: string]: any } }> { + private async axiosSendCmd(device: IDevice, cmd: string, urlParam: string): Promise { // Base URL - const baseUrl = `${device.restProtocol}://${device.ip}:${device.restPort}/?password=${this.encodePassword(device.restPassword)}&type=json`; - let finalUrl = ''; - if (what === 'getInfo') { - finalUrl = baseUrl + '&cmd=deviceInfo'; - } else { - finalUrl = baseUrl + '&' + urlParam; - } + const url = `${device.restProtocol}://${device.ip}:${device.restPort}/?password=${this.encodePassword(device.restPassword)}&type=json&${urlParam}`; // Axios config const config = { @@ -121,72 +88,50 @@ export class RestApiFully { try { // Log - let urlHiddenPassword = finalUrl; + let urlHiddenPassword = url; urlHiddenPassword = urlHiddenPassword.replace(/password=.*&type/g, 'password=(hidden)&type'); - this.adapter.log.debug(`[REST] ${device.name}: Start ${what} ${what === 'sendCmd' ? '"' + cmd + '"' : ''}, URL: ${urlHiddenPassword}`); + this.adapter.log.debug(`[REST] ${device.name}: Start sending command ${cmd}, URL: ${urlHiddenPassword}`); // Axios: Send command - const response = await axios.get(finalUrl, config); + const response = await axios.get(url, config); // Errors if (response.status !== 200) { - this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === 'sendCmd' ? cmd : ''} failed: ${response.status} - ${response.statusText}`); - this.adapter.onAliveChange('REST', device.ip, false, '${response.status} - ${response.statusText}'); // Update isAlive - return { status: false }; + this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: ${response.status} - ${response.statusText}`); + return false; } if (!('status' in response)) { - this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === 'sendCmd' ? cmd : ''} failed: Response received but it does not have key 'status'`); - this.adapter.onAliveChange('REST', device.ip, false, 'response without status key'); // Update isAlive - return { status: false }; + this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but it does not have key 'status'`); + return false; } if (!('data' in response)) { - this.adapter.log.error(`[REST] ${device.name}: ${what} ${what === 'sendCmd' ? cmd : ''} failed: Response received but it does not have key 'data'`); - this.adapter.onAliveChange('REST', device.ip, false, 'response without data key'); // Update isAlive - return { status: false }; - } - this.adapter.log.debug(`[REST] ${device.name}: ${what} response.data: ${JSON.stringify(response.data)}`); - - // Handle Device Info - if (what === 'getInfo') { - this.adapter.onAliveChange('REST', device.ip, true, 'information successfully received'); // Update isAlive - if (!('deviceName' in response.data)) { - // we check if info object is ok by checking for deviceName, could also use any other key like screenOn etc. - this.adapter.log.error(`[REST] ${device.name}: getInfo failed: Response data received, but data does not have key 'deviceName'`); - return { status: false }; - } - this.adapter.log.debug(`[REST] ${device.name}: getInfo was successful: Response = ${response.status} - ${response.statusText}`); - return { status: true, infoObj: response.data }; + this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but it does not have key 'data'`); + return false; } + this.adapter.log.debug(`[REST] ${device.name}: Sending command ${cmd} response.data: ${JSON.stringify(response.data)}`); - // Handle all other commands if (!('status' in response.data)) { - this.adapter.onAliveChange('REST', device.ip, false, 'response.data without status key'); // Update isAlive - this.adapter.log.error(`[REST] ${device.name}: Sending ${what} failed: Response received but response.data does not have key 'status'`); - return { status: false }; + this.adapter.log.error(`[REST] ${device.name}: Sending command ${cmd} failed: Response received but response.data does not have key 'status'`); + return false; } switch (response.data.status) { case 'OK': - this.adapter.log.debug(`[REST] ${device.name}: Sending ${what} successful: - Status = "${response.data.status}", Message = "${response.data.statustext}"`); - this.adapter.onAliveChange('REST', device.ip, true, 'successfully received response'); // Update isAlive - return { status: true }; + this.adapter.log.debug(`[REST] ${device.name}: Sending command ${cmd} successful: - Status = "${response.data.status}", Message = "${response.data.statustext}"`); + return true; case 'Error': if (response.data.statustext === 'Please login') { - this.adapter.log.error(`[REST] ${device.name}: Error: Remote Admin Password seems to be incorrect. Sending ${what} failed.`); - this.adapter.onAliveChange('REST', device.ip, false, 'incorrect Remote Admin password'); // Update isAlive + this.adapter.log.error(`[REST] ${device.name}: Error: Remote Admin Password seems to be incorrect. Sending command ${cmd} failed.`); } else { - this.adapter.log.error(`[REST] ${device.name}: Error: Sending cmd ${what} failed, received status text: ${response.data.statustext}`); - this.adapter.onAliveChange('REST', device.ip, false, `sending cmd ${what} failed`); // Update isAlive + this.adapter.log.error(`[REST] ${device.name}: Error: Sending command ${cmd} failed, received status text: ${response.data.statustext}`); } - return { status: false }; + return false; default: // Unexpected - this.adapter.log.error(`[REST] ${device.name}: Undefined response.data.status = "${response.data.status}" when sending cmd ${what}: ${response.status} - ${response.statusText}`); - this.adapter.onAliveChange('REST', device.ip, false, 'received undefined response.data.status'); - return { status: false }; + this.adapter.log.error(`[REST] ${device.name}: Undefined response.data.status = "${response.data.status}" when sending command ${cmd}: ${response.status} - ${response.statusText}`); + return false; } } catch (err) { - const errTxt = `[REST] ${device.name}: Sending ${what} failed`; - this.adapter.onAliveChange('REST', device.ip, false, `sending ${what} failed`); // Update isAlive + const errTxt = `[REST] ${device.name}: Sending command ${cmd} failed`; if (axios.isAxiosError(err)) { if (!err?.response) { this.adapter.log.warn(`${errTxt}: No response`); @@ -198,9 +143,9 @@ export class RestApiFully { this.adapter.log.error(`${errTxt}: General Error`); } } else { - this.adapter.log.error(`[REST] ${device.name} Error: ${this.adapter.err2Str(err)}`); + this.adapter.log.error(`${errTxt}: Error: ${this.adapter.err2Str(err)}`); } - return { status: false }; + return false; } } diff --git a/src/main.ts b/src/main.ts index ecba1c6..e6533c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,40 +30,35 @@ export class FullyMqtt extends utils.Adapter { public cleanDeviceName = cleanDeviceName.bind(this); public getConfigValuePerKey = getConfigValuePerKey.bind(this); public isIpAddressValid = isIpAddressValid.bind(this); - // MQTT + + // MQTT Server private mqtt_Server: MqttServer | undefined; // REST API - private restApi_inst = new RestApiFully(this); // RestApi Class Instance + private restApi_inst = new RestApiFully(this); /** - * Active Fullys: IP as key, and object per IDevice + * Fullys: IP as key, and object per IDevice * { * '192.168.10.20': {name: 'Tablet Kitchen', id:'Tablet-Kitchen', ip:'192.168.10.20', ...}, * '192.168.10.30': {name: 'Tablet Hallway', id:'Tablet-Hallway', ip:'192.168.10.30', ...}, * } - * Use this.getFullyPerKey() to get fully object per provided key + * Note: we can use this.getFullyPerKey() to get fully object per provided key */ - public fullys: { [ip: string]: IDevice } = {}; - - // array of device ids, which are not activated - public disabledDeviceIds = [] as string[]; - // All active IP addresses - public activeDeviceIPs = [] as string[]; // for MQTT server to verify IP + public fullys: { [ip: string]: IDevice } = {}; // enabled Fullys only + public fullysNotEnabled: { [ip: string]: IDevice } = {}; // not enabled Fullys only + public fullysAll: { [ip: string]: IDevice } = {}; // enabled and not enabled Fullys - // Has onAliveChange() ever been called before? - private onAliveChange_EverBeenCalledBefore = false; + // Has onMqttAlive() ever been called before? + private onMqttAlive_EverBeenCalledBefore = false; /** * Constructor */ public constructor(options: Partial = {}) { super({ ...options, name: 'fully-mqtt' }); - this.on('ready', this.onReady.bind(this)); this.on('stateChange', this.onStateChange.bind(this)); - // this.on('objectChange', this.onObjectChange.bind(this)); - // this.on('message', this.onMessage.bind(this)); this.on('unload', this.onUnload.bind(this)); } @@ -78,7 +73,7 @@ export class FullyMqtt extends utils.Adapter { this.setState('info.connection', { val: false, ack: true }); /** - * Init configuration + * Verify and init configuration */ if (await this.initConfig()) { this.log.debug(`Adapter settings successfully verified and initialized.`); @@ -87,19 +82,26 @@ export class FullyMqtt extends utils.Adapter { return; } + for (const ip in this.fullys) { + // Create Fully device objects + const res = await this.createFullyDeviceObjects(this.fullys[ip]); + // REST API: Subscribe to command state changes + if (res) await this.subscribeStatesAsync(this.fullys[ip].id + '.Commands.*'); + // Set enabled state + this.setState(this.fullys[ip].id + '.enabled', { val: true, ack: true }); + } + // Not enabled fullys: 1. Enabled state to false; 2. alive to null + for (const ip in this.fullysNotEnabled) { + this.setState(this.fullysNotEnabled[ip].id + '.enabled', { val: false, ack: true }); + this.setState(this.fullysNotEnabled[ip].id + '.alive', { val: null, ack: true }); + } + /** * Start MQTT Server */ this.mqtt_Server = new MqttServer(this); this.mqtt_Server.start(); - /** - * Call main() for each device - */ - for (const ip in this.fullys) { - await this.main(this.fullys[ip]); - } - /** * Delete device object tree(s) if deleted or renamed in config */ @@ -111,12 +113,13 @@ export class FullyMqtt extends utils.Adapter { } /** - * main function for each Fully Browser Device + * Create Fully Browser Device ioBroker state objects * @param device Fully Browser Device Object + * @returns true if successful, false if error */ - private async main(device: IDevice): Promise { + private async createFullyDeviceObjects(device: IDevice): Promise { try { - this.log.debug(`Start main() - ${device.name} (${device.ip})…`); + this.log.debug(`Start createFullyDeviceObjects() - ${device.name} (${device.ip})…`); /** * Create device object(s) @@ -133,7 +136,7 @@ export class FullyMqtt extends utils.Adapter { }); await this.setObjectNotExistsAsync(device.id + '.Info', { type: 'channel', common: { name: 'Device Information' }, native: {} }); - // Alive and info update + // Alive await this.setObjectNotExistsAsync(device.id + '.alive', { type: 'state', common: { @@ -147,10 +150,12 @@ export class FullyMqtt extends utils.Adapter { }, native: {}, }); + // Last info update, and if enabled in adapter settings await this.setObjectNotExistsAsync(device.id + '.lastInfoUpdate', { type: 'state', common: { name: 'Last information update', desc: 'Date/time of last information update from Fully Browser', type: 'number', role: 'value.time', read: true, write: false }, native: {} }); + await this.setObjectNotExistsAsync(device.id + '.enabled', { type: 'state', common: { name: 'Is device enabled in adapter settings?', desc: 'If this device is enabled in the adapter settings', type: 'boolean', role: 'indicator', read: true, write: false }, native: {} }); // REST API Commands Objects - await this.setObjectNotExistsAsync(device.id + '.Commands', { type: 'channel', common: { name: 'Commands (REST API)' }, native: {} }); + await this.setObjectNotExistsAsync(device.id + '.Commands', { type: 'channel', common: { name: 'Commands' }, native: {} }); const allCommands = CONST.cmds.concat(CONST.cmdsSwitches); // join both arrays for (const cmdObj of allCommands) { let lpRole = ''; @@ -165,16 +170,12 @@ export class FullyMqtt extends utils.Adapter { // More states are created once a new Event is received. await this.setObjectNotExistsAsync(device.id + '.Events', { type: 'channel', common: { name: 'MQTT Events' }, native: {} }); for (const event of CONST.mqttEvents) { - await this.setObjectNotExistsAsync(device.id + '.Events.' + event, { type: 'state', common: { name: 'MQTT Event: ' + event, type: 'boolean', role: 'switch', read: true, write: false }, native: {} }); + await this.setObjectNotExistsAsync(device.id + '.Events.' + event, { type: 'state', common: { name: 'Event: ' + event, type: 'boolean', role: 'switch', read: true, write: false }, native: {} }); } - - /** - * REST API: Subscribe to command state changes - */ - await this.subscribeStatesAsync(device.id + '.Commands.*'); + return true; } catch (e) { this.log.error(this.err2Str(e)); - return; + return false; } } @@ -199,15 +200,13 @@ export class FullyMqtt extends utils.Adapter { } } - // process all adapter object devices ['Tablet-Kitchen', 'Tablet-Hallway', ...] accordingly + // Get all adapter configuration device ids (enabled and disabled), like ['Tablet-Kitchen', 'Tablet-Hallway', ...] + const allConfigDeviceIds: string[] = []; + for (const ip in this.fullysAll) { + allConfigDeviceIds.push(this.fullysAll[ip].id); + } + // Delete for (const id of allObjectDeviceIds) { - // We handle both disabled devices and enabled devices - const allConfigDeviceIds = this.disabledDeviceIds; // add all disabled ids first - // now add all active ones - for (const ip in this.fullys) { - allConfigDeviceIds.push(this.fullys[ip].id); - } - if (!allConfigDeviceIds.includes(id)) { await this.delObjectAsync(id, { recursive: true }); this.log.info(`Cleanup: Deleted no longer defined device objects of '${id}'.`); @@ -259,6 +258,7 @@ export class FullyMqtt extends utils.Adapter { name: '', id: '', ip: '', + enabled: false, mqttInfoObjectsCreated: false, mqttInfoKeys: [], restProtocol: 'http', @@ -324,19 +324,22 @@ export class FullyMqtt extends utils.Adapter { finalDevice.restPassword = lpDevice.restPassword; } + // Enabled status + finalDevice.enabled = lpDevice.enabled ? true : false; + + // Debug log of config const logConfig = { ...finalDevice }; // copy object using spread logConfig.restPassword = '(hidden)'; // do not show password in log ! this.log.debug(`Final Config: ${JSON.stringify(logConfig)}`); + + // Finalize + this.fullysAll[finalDevice.ip] = finalDevice; if (lpDevice.enabled) { - // Finalize this.fullys[finalDevice.ip] = finalDevice; - this.activeDeviceIPs.push(lpDevice.ip); // global array for all active IPs this.log.info(`🗸 ${finalDevice.name} (${finalDevice.ip}): Config successfully verified.`); } else { - // Skip if not enabled. (but we did verification anyway!) - this.disabledDeviceIds.push(finalDevice.id); - this.log.debug(`Device ${finalDevice.name} (${finalDevice.ip}) is not enabled, so skip it.`); - continue; + this.fullysNotEnabled[finalDevice.ip] = finalDevice; + this.log.info(`${finalDevice.name} (${finalDevice.ip}) is not enabled in adapter settings, so it will not be used by adapter.`); } } @@ -353,16 +356,16 @@ export class FullyMqtt extends utils.Adapter { /** * On Alive Changes - * for both REST API and MQTT + * MQTT is being used only, REST API not. */ - public async onAliveChange(source: 'MQTT' | 'REST', ip: string, isAlive: true | false, msg: string): Promise { + public async onMqttAlive(ip: string, isAlive: true | false, msg: string): Promise { try { const prevIsAlive = this.fullys[ip].isAlive; this.fullys[ip].isAlive = isAlive; // Has this function ever been called before? If adapter is restarted, we ensure log, etc. - const calledBefore = this.onAliveChange_EverBeenCalledBefore; // Keep old value - this.onAliveChange_EverBeenCalledBefore = true; // Now it was called + const calledBefore = this.onMqttAlive_EverBeenCalledBefore; // Keep old value + this.onMqttAlive_EverBeenCalledBefore = true; // Now it was called /*********** * 1 - Fully Device @@ -374,9 +377,9 @@ export class FullyMqtt extends utils.Adapter { // log if (isAlive) { - this.log.info(`${this.fullys[ip].name} is alive (${source}: ${msg})`); + this.log.info(`${this.fullys[ip].name} is alive (MQTT: ${msg})`); } else { - this.log.warn(`${this.fullys[ip].name} is not alive! (${source}: ${msg})`); + this.log.warn(`${this.fullys[ip].name} is not alive! (MQTT: ${msg})`); } } else { // No change @@ -408,7 +411,7 @@ export class FullyMqtt extends utils.Adapter { public async onMqttInfo(obj: { clientId: string; ip: string; topic: string; infoObj: { [k: string]: any } }): Promise { try { // log - this.log.debug(`[MQTT]📡 ${this.fullys[obj.ip].name} published info, topic: ${obj.topic}`); + this.log.debug(`[MQTT] ${this.fullys[obj.ip].name} published info, topic: ${obj.topic}`); //this.log.debug(`[MQTT] Client ${obj.ip} Publish Info: Details: ${JSON.stringify(obj.infoObj)}`); // Create info objects if not yet existing @@ -544,7 +547,7 @@ export class FullyMqtt extends utils.Adapter { */ const sendCommand = await this.restApi_inst.sendCmd(fully, cmdToSend, stateObj.val); if (sendCommand) { - this.log.info(`${fully.name}: ${cmd} successfully set to ${stateObj.val}`); + this.log.info(`🗸 ${fully.name}: Command ${cmd} successfully set to ${stateObj.val}`); /** * Confirm with ack:true */ @@ -636,12 +639,15 @@ export class FullyMqtt extends utils.Adapter { /** * Is called when adapter shuts down - callback has to be called under any circumstances! */ - private onUnload(callback: () => void): void { + private async onUnload(callback: () => void): Promise { try { - if (this.fullys) { - for (const ip in this.fullys) { - // Set alive status to false - this.setState(this.fullys[ip].id + '.alive', { val: false, ack: true }); + // All Fullys: Set alive status to null + if (this.fullysAll) { + for (const ip in this.fullysAll) { + // We check first if object exists, as there were errors in log on when updating adpater via Github (related to missing objects) + if (await this.getObjectAsync(this.fullysAll[ip].id)) { + this.setState(this.fullysAll[ip].id + '.alive', { val: null, ack: true }); + } } }