From 762973940ac266178c45e8b91fb4b70c10b69a0e Mon Sep 17 00:00:00 2001 From: Schmakus Date: Thu, 6 Mar 2025 10:53:38 +0100 Subject: [PATCH 1/2] upgrade to jsonConfig --- README.md | 9 + admin/admin.d.ts | 93 +++++ admin/jsonConfig.json | 57 ++- admin/tsconfig.json | 9 + io-package.json | 424 +++++++++++++++++++ main.js | 918 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1509 insertions(+), 1 deletion(-) create mode 100644 admin/admin.d.ts create mode 100644 admin/tsconfig.json diff --git a/README.md b/README.md index d7d3772..00684c5 100644 --- a/README.md +++ b/README.md @@ -94,12 +94,21 @@ onFile('doorbird.0', 'TakeSnapshot_1.jpg', true, (id, fileName, size, fileData, Placeholder for the next version (at the beginning of the line): ### **WORK IN PROGRESS** --> +<<<<<<< HEAD ### 3.0.0 (2025-03-03) NodeJS >= 20.x and js-controller >= 6 is required - (@klein0r) Migrated to json config - (@klein0r) Updated documentation and dependencies +======= + +### **WORK IN PROGRESS** + +- (Schmakus) migrated to admin 5 UI (jsonConfig) +- (Schmakus) Removed Wizard +- (Schmakus) changed async methods +>>>>>>> a9e541f (upgrade to jsonConfig) ### 2.0.0 (2024-09-02) diff --git a/admin/admin.d.ts b/admin/admin.d.ts new file mode 100644 index 0000000..41f934f --- /dev/null +++ b/admin/admin.d.ts @@ -0,0 +1,93 @@ +declare let systemDictionary: Record>; + +declare let load: (settings: Record, onChange: (hasChanges: boolean) => void) => void; +declare let save: (callback: (settings: Record) => void) => void; + +// make load and save exist on the window object +interface Window { + load: typeof load; + save: typeof save; +} + +declare const instance: number; +declare const adapter: string; +/** Translates text */ +declare function _(text: string, arg1?: string, arg2?: string, arg3?: string): string; +declare const socket: ioBrokerSocket; +declare function sendTo( + instance: any | null, + command: string, + message: any, + callback: (result: SendToResult) => void | Promise, +): void; + +interface SendToResult { + error?: string | Error; + result?: any; +} + +// tslint:disable-next-line:class-name +interface ioBrokerSocket { + emit( + command: "subscribeObjects", + pattern: string, + callback?: (err?: string) => void | Promise, + ): void; + emit( + command: "subscribeStates", + pattern: string, + callback?: (err?: string) => void | Promise, + ): void; + emit( + command: "unsubscribeObjects", + pattern: string, + callback?: (err?: string) => void | Promise, + ): void; + emit( + command: "unsubscribeStates", + pattern: string, + callback?: (err?: string) => void | Promise, + ): void; + + emit( + event: "getObjectView", + view: "system", + type: "device", + options: ioBroker.GetObjectViewParams, + callback: ( + err: string | undefined, + result?: any, + ) => void | Promise, + ): void; + emit( + event: "getStates", + callback: ( + err: string | undefined, + result?: Record, + ) => void, + ): void; + emit( + event: "getState", + id: string, + callback: (err: string | undefined, result?: ioBroker.State) => void, + ): void; + emit( + event: "setState", + id: string, + state: unknown, + callback: (err: string | undefined, result?: any) => void, + ): void; + + on(event: "objectChange", handler: ioBroker.ObjectChangeHandler): void; + on(event: "stateChange", handler: ioBroker.StateChangeHandler): void; + removeEventHandler( + event: "objectChange", + handler: ioBroker.ObjectChangeHandler, + ): void; + removeEventHandler( + event: "stateChange", + handler: ioBroker.StateChangeHandler, + ): void; + + // TODO: other events +} diff --git a/admin/jsonConfig.json b/admin/jsonConfig.json index aa6a751..fe40066 100644 --- a/admin/jsonConfig.json +++ b/admin/jsonConfig.json @@ -1,4 +1,5 @@ { +<<<<<<< HEAD "i18n": true, "type": "tabs", "tabsStyle": { @@ -84,4 +85,58 @@ } } } -} \ No newline at end of file +} +======= + "i18n": true, + "type": "panel", + "items": { + "adapterAddress": { + "type": "ip", + "label": "IP of Adapter (ioBroker). e.g. 192.168.1.100", + "sm": 3, + "md": 3, + "lg": 3, + "onlyIp4": true, + "listenOnAllPorts": true + }, + "adapterport": { + "type": "number", + "label": "Port of Adapter (ioBroker). e.g. 8100", + "sm": 3, + "md": 3, + "lg": 3, + "default": 8100 + }, + "birdip": { + "type": "text", + "label": "IP of DoorBird Device. e.g. 192.168.1.100", + "sm": 3, + "md": 3, + "lg": 3, + "newLine": true + }, + "birdid": { + "type": "text", + "label": "Device ID of DoorBird. e.g. ghfxxx", + "sm": 3, + "md": 3, + "lg": 3 + }, + "birduser": { + "type": "text", + "label": "Username e.g. ghfxxx0001", + "sm": 3, + "md": 3, + "lg": 3, + "newLine": true + }, + "birdpw": { + "type": "password", + "label": "Password", + "sm": 3, + "md": 3, + "lg": 3 + } + } +} +>>>>>>> a9e541f (upgrade to jsonConfig) diff --git a/admin/tsconfig.json b/admin/tsconfig.json new file mode 100644 index 0000000..7608306 --- /dev/null +++ b/admin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "include": [ + "./admin.d.ts", + "./**/*.js", + // include the adapter-config definition if it exists + "../src/lib/adapter-config.d.ts" + ] +} diff --git a/io-package.json b/io-package.json index a9e4c15..e31a28e 100644 --- a/io-package.json +++ b/io-package.json @@ -1,4 +1,5 @@ { +<<<<<<< HEAD "common": { "name": "doorbird", "version": "3.0.0", @@ -442,4 +443,427 @@ "native": {} } ] +======= + "common": { + "name": "doorbird", + "version": "2.0.0", + "news": { + "2.0.0": { + "en": "update dependencies", + "de": "aktualisierung der abhängigkeiten", + "ru": "обновление зависимостей", + "pt": "dependências", + "nl": "afhankelijkheden bijwerken", + "fr": "mettre à jour les dépendances", + "it": "aggiornamento dipendenze", + "es": "dependencias de actualización", + "pl": "aktualizacji zależności", + "uk": "оновлення залежності", + "zh-cn": "更新依赖关系" + }, + "1.7.0": { + "en": "Dependencies have been updated", + "de": "Abhängigkeiten wurden aktualisiert", + "ru": "Зависимость обновлена", + "pt": "As dependências foram atualizadas", + "nl": "Afhankelijkheden zijn bijgewerkt", + "fr": "Les dépendances ont été actualisées", + "it": "Le dipendenze sono state aggiornate", + "es": "Se han actualizado las dependencias", + "pl": "Zaktualizowano zależności", + "uk": "Залежність було оновлено", + "zh-cn": "依赖关系已更新" + }, + "1.6.0": { + "en": "Adapter requires node.js >= 18 and Admin >=6 now\nDependencies have been updated", + "de": "Adapter benötigt node.js >= 18 und Admin >=6 jetzt\nAbhängigkeiten wurden aktualisiert", + "ru": "Адаптер требует node.js >= 18 и Admin >=6\nЗависимость обновлена", + "pt": "Adapter requer node.js >= 18 e Admin >=6 agora\nAs dependências foram atualizadas", + "nl": "Voor adapter zijn node.js < 18 en Admin > 6 nu vereist\nAfhankelijkheden zijn bijgewerkt", + "fr": "Adapter nécessite node.js >= 18 et Admin >=6 maintenant\nLes dépendances ont été actualisées", + "it": "Adattatore richiede node.js >= 18 e Admin >=6 ora\nLe dipendenze sono state aggiornate", + "es": "Adaptador requiere node.js √≥= 18 y Admin >=6 ahora\nSe han actualizado las dependencias", + "pl": "Adapter wymaga node.js > = 18 i Admin > = 6\nZaktualizowano zależności", + "uk": "Адаптер вимагає node.js >= 18 і Admin >=6 тепер\nЗалежність було оновлено", + "zh-cn": "适配器需要节点.js QQ 18 和 Admin 6 现在\n依赖关系已更新" + }, + "1.5.0": { + "en": "update dependencies", + "de": "aktualisierung der abhängigkeiten", + "ru": "обновление зависимостей", + "pt": "dependências", + "nl": "afhankelijkheden bijwerken", + "fr": "mettre à jour les dépendances", + "it": "aggiornamento dipendenze", + "es": "dependencias de actualización", + "pl": "aktualizacji zależności", + "uk": "оновлення залежності", + "zh-cn": "更新依赖关系" + }, + "1.4.1": { + "en": "Creation of favorites and schedules only once after starting the adapter and successfully connecting to DoorBird. (Another step to solve \"Maximum call stack size exceeded\" problem.)", + "de": "Erstellung von Favoriten und Zeitplänen nur einmal nach dem Start des Adapters und erfolgreicher Verbindung zur DoorBird. (Ein weiterer Schritt, um \"Maximum Call Stack Größe überschritten\" Problem zu lösen.)", + "ru": "Создание фаворитов и графиков только один раз после запуска адаптера и успешного подключения к DoorBird. (Еще один шаг к решению проблемы \"Максимальный размер стека вызовов превысил\")", + "pt": "Criação de favoritos e horários só uma vez depois de iniciar o adaptador e se conectar com sucesso ao DoorBird. (Outra etapa para resolver o problema \"Tamanho máximo da pilha de chamada excedeu\".)", + "nl": "Creatie van favorieten en schema's slechts één keer na het starten van de adapter en het succesvol verbinden met DoorBird. (Een andere stap om \"Maximale aanroep stack grootte overschreden\" probleem op te lossen.)", + "fr": "Création des favoris et des horaires seulement une fois après le démarrage de l'adaptateur et la connexion réussie à DoorBird. (Une autre étape pour résoudre le problème « Taille maximale de la pile d'appel dépassée ».)", + "it": "Creazione di preferiti e programmi solo una volta dopo l'avvio dell'adattatore e la connessione con successo a DoorBird. (Un altro passo per risolvere il problema \"Maximum call stack size superato\".)", + "es": "Creación de favoritos y horarios sólo una vez después de iniciar el adaptador y conectarse con éxito a DoorBird. (Otro paso para resolver \"El tamaño de la pila de llamadas Maxum superó\" problema.)", + "pl": "Tworzenie ulubionych i harmonogramów tylko raz po uruchomieniu adaptera i pomyślnym połączeniu z DoorBird. (Kolejny krok do rozwiązania \"Maksymalny rozmiar stosu wywołania przekroczony\" problem.)", + "uk": "Створення фаворитів і графіків тільки після запуску адаптера і вдало підключених до DoorBird. (Іншим кроком для вирішення проблеми «Максимум виклику, розмір яких перевищує».)", + "zh-cn": "在启动适配器并成功连接到DoorBird后,只创建一次最爱和时间表. (另一步骤解决\"最大调用堆栈大小超过\"问题" + }, + "1.4.0": { + "en": "update dependencies (security updates)", + "de": "Aktualisierung von Abhängigkeiten (Sicherheits Updates)", + "ru": "обновление зависимостей (защищенные обновления)", + "pt": "dependências de atualização (atualizações seguras)", + "nl": "update afhankelijkheden (veilige updates)", + "fr": "mise à jour des dépendances (mises à jour sécurisées)", + "it": "aggiornamento dipendenze (aggiornamento sicuro)", + "es": "dependencias de actualización (actualizaciones seguras)", + "pl": "aktualizacji zależności (bezpieczne aktualizacje)", + "uk": "оновлення залежності", + "zh-cn": "更新依赖性( 安全更新)" + }, + "1.3.1": { + "en": "update dependencies", + "de": "Aktualisierung der Abhängigkeiten", + "ru": "обновление зависимостей", + "pt": "dependências", + "nl": "afhankelijkheden bijwerken", + "fr": "mettre à jour les dépendances", + "it": "aggiornamento dipendenze", + "es": "dependencias de actualización", + "pl": "aktualizacji zależności", + "uk": "оновлення залежності", + "zh-cn": "更新依赖关系" + } + }, + "titleLang": { + "en": "DoorBird", + "de": "DoorBird", + "ru": "DoorBird", + "pt": "DoorBird", + "nl": "DoorBird", + "fr": "DoorBird", + "it": "DoorBird", + "es": "DoorBird", + "pl": "DoorBird", + "zh-cn": "DoorBird", + "uk": "DoorBird" + }, + "desc": { + "en": "Connects DoorBird doorbells to ioBroker", + "de": "Verbindet DoorBird-Türklingeln mit ioBroker", + "ru": "Подключает дверные звонки DoorBird к ioBroker", + "pt": "Conecta as campainhas DoorBird ao ioBroker", + "nl": "Verbindt DoorBird deurbellen met ioBroker", + "fr": "Connecte les sonnettes DoorBird à ioBroker", + "it": "Collega i campanelli DoorBird a ioBroker", + "es": "Conecta los timbres DoorBird a ioBroker", + "pl": "Łączy dzwonki DoorBird z ioBroker", + "zh-cn": "将 DoorBird 门铃连接到 ioBroker", + "uk": "Підключає дверні дзвінки DoorBird до ioBroker" + }, + "authors": ["BuZZy1337 ", "Schmakus "], + "keywords": ["doorbird", "VIS", "GUI"], + "tier": 3, + "licenseInformation": { + "license": "MIT", + "type": "free" + }, + "platform": "Javascript/Node.js", + "icon": "doorbird.png", + "messagebox": true, + "enabled": true, + "extIcon": "https://raw.githubusercontent.com/iobroker-community-adapters/ioBroker.doorbird/master/admin/doorbird.png", + "readme": "https://github.com/iobroker-community-adapters/ioBroker.doorbird/blob/master/README.md", + "loglevel": "info", + "mode": "daemon", + "type": "iot-systems", + "compact": true, + "connectionType": "local", + "dataSource": "push", + "adminUI": { + "config": "json" + }, + "messages": [ + { + "condition": { + "operand": "and", + "rules": ["oldVersion<1.0.0", "newVersion>=1.0.0"] + }, + "title": { + "en": "Breaking change by usage of snapshots", + "de": "Änderung bei Verwendung von Snapshots", + "ru": "Перерыв изменения с помощью снимков", + "pt": "Quebrando a mudança pelo uso de instantâneos", + "nl": "Verandering door het gebruik van foto's", + "fr": "Changement de rupture par utilisation de snapshots", + "it": "Cambiamento di rottura mediante l'utilizzo di snapshot", + "es": "Cambio de ruptura por el uso de instantáneas", + "pl": "Zmiana strzałów poprzez użycie strzałów", + "uk": "Зміна розривів за допомогою знімків", + "zh-cn": "D. 使用绑架器的断裂变化" + }, + "text": { + "en": "Storage location of the snapshots has been moved to the ioBroker files!", + "de": "Der Speicherort der Snapshots wurde in die ioBroker Dateien verschoben!", + "ru": "Место хранения снимков было перемещено в файлы ioBroker!", + "pt": "Localização de armazenamento dos instantâneos foi movido para os arquivos ioBroker!", + "nl": "De locatie van de foto's is verplaatst naar de ioBroker bestanden!", + "fr": "L'emplacement de stockage des snapshots a été déplacé vers les fichiers ioBroker!", + "it": "La posizione di archiviazione delle snapshot è stata spostata nei file ioBroker!", + "es": "Ubicación de almacenamiento de las instantáneas se ha trasladado a los archivos ioBroker!", + "pl": "Lokalizacja postrzałów została przeniesiona do plików ioBroker!", + "uk": "Місце зберігання знімків було перенесено на файли ioBroker!", + "zh-cn": "偷窃者地点已移到OioBroker案!" + }, + "level": "warn", + "buttons": ["agree", "cancel"] + } + ], + "dependencies": [ + { + "js-controller": ">=5.0.19" + } + ], + "globalDependencies": [ + { + "admin": ">=6.0.0" + } + ] + }, + "native": { + "adapterport": 8100, + "adapterAddress": "", + "birdip": "", + "birdid": "", + "birduser": "", + "birdpw": "", + "listenOnAllInterfaces": false + }, + "objects": [], + "instanceObjects": [ + { + "_id": "Light", + "type": "state", + "common": { + "name": "Doorbird Light", + "type": "boolean", + "role": "button", + "read": false, + "write": true + }, + "native": {} + }, + { + "_id": "Restart", + "type": "state", + "common": { + "name": { + "en": "Restart DoorBird", + "de": "Neustart DoorBird", + "ru": "Рестарт DoorBird", + "pt": "Reinicie DoorBird", + "nl": "Herstart DoorBird", + "fr": "Restart DoorBird", + "it": "Riavviare DoorBird", + "es": "Restart DoorBird", + "pl": "Restart DoorBird", + "uk": "Решта DoorBird", + "zh-cn": "恢复原状 DoorBird" + }, + "type": "boolean", + "role": "button", + "read": false, + "write": true + }, + "native": {} + }, + { + "_id": "Doorbell", + "type": "channel", + "common": { + "name": { + "en": "Doorbell", + "de": "Türklingel", + "ru": "Дверной звонок", + "pt": "Campainha de porta", + "nl": "Deur", + "fr": "Doorbell", + "it": "Campana porta", + "es": "Doorbell", + "pl": "Doorbell", + "uk": "Рушники", + "zh-cn": "Doorbell" + } + }, + "native": {} + }, + { + "_id": "Motion", + "type": "channel", + "common": { + "name": { + "en": "Motion detection", + "de": "Bewegungserkennung", + "ru": "Обнаружение движения", + "pt": "Detecção de movimento", + "nl": "Motie detectie", + "fr": "Détection des mouvements", + "it": "Rilevamento del movimento", + "es": "Detección de mociones", + "pl": "Wykrywanie", + "uk": "Визначення руху", + "zh-cn": "B. 侦查" + } + }, + "native": {} + }, + { + "_id": "Motion.trigger", + "type": "state", + "common": { + "role": "indicator", + "name": { + "en": "Motion detected", + "de": "Bewegung erkannt", + "ru": "Движение обнаружено", + "pt": "Movimento detectado", + "nl": "Motie ontdekt", + "fr": "Motion détectée", + "it": "Rilevamento del movimento", + "es": "Moción detectada", + "pl": "Wykryć", + "uk": "Виявлено рух", + "zh-cn": "目 录" + }, + "type": "boolean", + "read": true, + "write": false, + "def": false + }, + "native": {} + }, + { + "_id": "", + "type": "meta", + "common": { + "name": { + "en": "JPG Files", + "de": "JPG Dateien", + "ru": "ДЖП Файл", + "pt": "JPG Arquivo", + "nl": "JPG Veld", + "fr": "JPG Fichier", + "it": "JPG File", + "es": "JPG Archivo", + "pl": "JPG File", + "uk": "JPG Головна", + "zh-cn": "J. 导 言 导 言" + }, + "type": "meta.user" + }, + "native": {} + }, + { + "_id": "TakeSnapshot", + "type": "state", + "common": { + "name": { + "en": "Get Snapshot", + "de": "Schnappschuss holen", + "ru": "Получить снимок", + "pt": "Instantâneo", + "nl": "Haal Snapshot", + "fr": "Snapshot", + "it": "Indossando una cinghia", + "es": "Captura", + "pl": "Get Snapshot (ang.)", + "uk": "Отримати Snapshot", + "zh-cn": "Get Steshot" + }, + "type": "boolean", + "role": "button", + "read": false, + "write": true + }, + "native": {} + }, + { + "_id": "info", + "type": "channel", + "common": { + "name": "Information" + }, + "native": {} + }, + { + "_id": "info.connection", + "type": "state", + "common": { + "role": "indicator.connected", + "name": { + "en": "Connected to DoorBird Device", + "de": "Mit DoorBird verbunden", + "ru": "Подключен к устройству DoorBird", + "pt": "Conectado ao dispositivo DoorBird", + "nl": "Verbinding met Device", + "fr": "Connecté à DoorBird Device", + "it": "Collegato al dispositivo DoorBird", + "es": "Conectado al dispositivo DoorBird", + "pl": "Oficjalna strona DoorBird Device", + "uk": "Підключається до дверних приладів", + "zh-cn": "受托人" + }, + "type": "boolean", + "read": true, + "write": false, + "def": false + }, + "native": {} + }, + { + "_id": "info.firmware", + "type": "state", + "common": { + "role": "", + "name": "DoorBird Firmware version", + "type": "string", + "read": true, + "write": false, + "def": "" + }, + "native": {} + }, + { + "_id": "info.build", + "type": "state", + "common": { + "role": "", + "name": "DoorBird BuildNumber", + "type": "string", + "read": true, + "write": false, + "def": "" + }, + "native": {} + }, + { + "_id": "info.type", + "type": "state", + "common": { + "role": "", + "name": "DoorBird Type", + "type": "string", + "read": true, + "write": false, + "def": "" + }, + "native": {} + } + ] +>>>>>>> a9e541f (upgrade to jsonConfig) } diff --git a/main.js b/main.js index 38a3940..b6b6eeb 100644 --- a/main.js +++ b/main.js @@ -21,6 +21,7 @@ let COUNT_SCHEDULES = 0; let adapterStarted = false; class Doorbird extends utils.Adapter { +<<<<<<< HEAD /** * @param [options] */ @@ -949,6 +950,923 @@ class Doorbird extends utils.Adapter { this.log.warn(`Error on migrate. Error: ${error}`); } } +======= + /** + * @param {Partial} [options={}] + */ + constructor(options) { + super({ + ...options, + name: 'doorbird', + }); + this.on('ready', this.onReady.bind(this)); + this.on('stateChange', this.onStateChange.bind(this)); + this.on('message', this.onMessage.bind(this)); + this.on('unload', this.onUnload.bind(this)); + + this.sockets = []; + this.bellCount = 0; + this.birdpw = ''; + this.scheduleState = {}; + this.motionState = {}; + this.doorbellsArray = []; // Contains all Doorbell IDs + this.favoriteState = {}; // {'ID of Doorbell/Motion/RFID': 'ID of Favorite'} + + this.authorized = false; + + this.wizard = false; + this.wizardTimeout = null; + } + + /** + * Is called when databases are connected and adapter received configuration. + */ + async onReady() { + // Reset the connection indicator during startup + await this.setState('info.connection', false, true); + + this.birdpw = this.config.birdpw ? this.encrypt(this.config.birdpw) : ''; + this.log.debug(`Entcrypted Password: ${this.birdpw}`); + await Promise.all([this.migrateAsync(), this.mainAsync()]); + } + + async mainAsync() { + if (this.config.birdip && this.birdpw && this.config.birduser) { + this.testBirdAsync(); // no await because code should run parallel + } + + try { + udpserver.on('listening', () => { + const address = udpserver.address(); + this.log.debug('Adapter listening on IP: ' + address + ' - UDP Port 35344'); + }); + } catch (error) { + this.log.error(`address already in use ${this.config.adapterAddress}:${this.config.adapterport}: ${error}`); + } + + // udpserver.bind(35344); + if (this.config.adapterAddress) { + try { + let ip; + let msg; + + if (this.config.listenOnAllInterfaces) { + ip = '0.0.0.0'; + msg = `Server gestartet auf allen Interfaces auf Port ${this.config.adapterport || 8100}`; + } else { + ip = this.config.adapterAddress; + msg = `Server gestartet auf Port ${this.config.adapterport || 8100} und IP ${ + this.config.adapterAddress + }`; + } + + this.server = http.createServer(async (req, res) => { + if (res.socket && res.socket.remoteAddress) { + const remoteAddress = res.socket.remoteAddress.replace(/^.*:/, ''); + if (remoteAddress === this.config.birdip || this.config.listenOnAllInterfaces) { + res.writeHead(204, { 'Content-Type': 'text/plain' }); + if (req.url == '/motion') { + this.log.debug('Received Motion-alert from Doorbird!'); + await Promise.all([ + this.setState('Motion.trigger', true, true), + this.downloadFileAsync('Motion'), + ]); + + this.setTimeout(async () => { + await this.setState('Motion.trigger', false, true); + }, 2500); + } + if (req.url && req.url.indexOf('ring') != -1) { + const id = req.url.substring(req.url.indexOf('?') + 1, req.url.length); + this.log.debug('Received Ring-alert (ID: ' + id + ') from Doorbird!'); + await Promise.all([ + this.setState('Doorbell.' + id + '.trigger', true, true), + this.downloadFileAsync(`Doorbell${id}`), + ]); + + this.setTimeout(async () => { + await this.setState('Doorbell.' + id + '.trigger', false, true); + }, 2000); + } + res.end(); + } + } else { + res.writeHead(401, { 'Content-Type': 'text/plain' }); + res.end(); + } + }); + + this.server.listen(this.config.adapterport || 8081, ip, () => { + this.log.debug(msg); + }); + } catch (e) { + this.log.warn('There was an Error starting the HTTP Server! (' + e + ')'); + } + } else { + this.log.warn('You need to set the Adapteraddress in the Instance-Settings!!'); + } + + this.subscribeStates('*'); + } + + /** + * Is called if a subscribed state changes + * @param {string} id + * @param {ioBroker.State | null | undefined} state + */ + async onStateChange(id, state) { + if (!state || state.ack) return; + const comp = id.split('.'); + + if (!this.authorized) { + this.log.warn('Cannot do that because not authorized! Check you config!'); + return; + } + + switch (comp[2]) { + case 'Restart': + this.log.debug('Trying to restart DoorBird Device..'); + await this.restartDoorbirdAsync(); + break; + case 'TakeSnapshot': + this.log.info('Trying to take snapshot..'); + await this.downloadFileAsync('TakeSnapshot'); + break; + case 'Light': + this.log.info('Trying to turn on light..'); + await this.turnOnLightAsync(); + break; + case 'Relays': + await this.relaysAsync(comp[3]); + break; + } + } + /** + * @param {ioBroker.Message} obj + */ + async onMessage(obj) { + if (typeof obj === 'object' && obj.message) { + if (obj.command === 'wizard' && !this.wizard) { + this.startWizard(obj); + this.wizard = true; + } + } + } + + /** + * Generate URL + * @param {'restart' | 'schedule' | 'info' | 'favorites' | 'image' | 'light-on'} command + */ + buildURL(command) { + return ( + 'http://' + + this.config.birdip + + '/bha-api/' + + command + + '.cgi?http-user=' + + this.config.birduser + + '&http-password=' + + this.birdpw + ); + } + + /** + * Check Doorbird Connection + * @async + * @returns {Promise} + */ + async testBirdAsync() { + try { + const url = this.buildURL('schedule'); + const response = await Axios.get(url); + + if (response.status === 401) { + this.authorized = false; + await this.setState('info.connection', false, true); + this.log.warn('Whooops.. DoorBird says User ' + this.config.birduser + ' is unauthorized!!'); + this.log.warn('Check Username + Password and enable the "API-Operator" Permission for the User!!'); + } else if (response.status === 200) { + this.authorized = true; + await this.setState('info.connection', true, true); + this.log.debug('Authorization with User ' + this.config.birduser + ' successful!'); + if (!adapterStarted) { + adapterStarted = true; + await this.getInfoAsync(); + } + } + } catch (error) { + if (error.code === 'EHOSTUNREACH') { + this.authorized = false; + await this.setState('info.connection', false, true); + this.log.warn('DoorBird Device is offline!!'); + } else if (error.code === 'ECONNABORTED') { + this.authorized = false; + await this.setState('info.connection', false, true); + this.log.warn('Error in testBird() Request timed out: ' + error); + } else { + this.authorized = false; + await this.setState('info.connection', false, true); + this.log.warn('Error in testBird() Request: ' + error); + } + } + + if (this.birdConCheck) this.clearTimeout(this.birdConCheck), (this.birdConCheck = null); + this.birdConCheck = this.setTimeout(async () => { + this.log.debug(`Refresh connection check...`); + this.birdConCheck = null; + await this.testBirdAsync(); + }, 180000); + } + + /** + * Get Doorbird Info + * @async + * @returns {Promise} + */ + async getInfoAsync() { + if (this.authorized) { + try { + const url = this.buildURL('info'); + const response = await Axios.get(url); + + if (response.status === 200) { + const info = response.data; + await this.setState('info.firmware', info.BHA.VERSION[0].FIRMWARE, true); + await this.setState('info.build', info.BHA.VERSION[0].BUILD_NUMBER, true); + await this.setState('info.type', info.BHA.VERSION[0]['DEVICE-TYPE'], true); + + const relays = info.BHA.VERSION[0].RELAYS; + + for (const value of relays) { + //create channel + await this.createObjectsAsync('Relays', 'channel', { + name: { + en: 'Available Relays', + de: 'Verfügbare Relais', + ru: 'Доступные реле', + pt: 'Relés disponíveis', + nl: 'Available Relay', + fr: 'Relais disponibles', + it: 'Relè disponibili', + es: 'Relés disponibles', + pl: 'Dostępny Relay', + uk: 'Доступні реле', + 'zh-cn': '现有费用', + }, + }); + //create State + await this.createObjectsAsync(`Relays.${value}`, 'state', { + name: { + en: 'Activate relay', + de: 'Relais aktivieren', + ru: 'Активировать реле', + pt: 'Activar o relé', + nl: 'Activeer relay', + fr: 'Activer le relais', + it: 'Attiva relè', + es: 'Activar el relé', + pl: 'Aktywacja', + uk: 'Активувати реле', + 'zh-cn': '法 律 法 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 重', + }, + type: 'boolean', + role: 'button', + read: false, + write: true, + desc: `ID: ${value}`, + }); + } + } + } catch (error) { + this.log.warn('Error in getInfo(): ' + error); + } + + await this.checkFavoritesAsync(); + } else { + this.log.warn('Execution of getInfo not allowed because not authorized!'); + } + } + + /** + * Check Favorites + * @async + * @returns {Promise} + */ + async checkFavoritesAsync() { + this.log.debug('Checking favorites on DoorBird Device..'); + try { + const url = this.buildURL('favorites'); + const response = await Axios.get(url); + + if (response.status === 200) { + const favorites = response.data; + if (!favorites.http || typeof favorites.http !== 'object') { + this.log.warn('It seems that the list of HTTP Favorites is empty!'); + await this.createFavoritesAsync(0); + return; + } + for (const [key, obj] of Object.entries(favorites.http)) { + if ( + obj.title.includes('ioBroker ' + this.namespace) && + !obj.value.includes(this.config.adapterAddress + ':' + this.config.adapterport) + ) { + this.log.warn( + `The Favorite ID '${key}' contains a wrong URL ('${obj.value}').. I will update that..`, + ); + + await this.updateFavoriteAsync(key, obj); + return; + } + + if (obj.value.includes(this.config.adapterAddress + ':' + this.config.adapterport)) { + this.log.debug( + `Found a Favorite that belongs to me.. (ID: '${key}') ('${obj.title}': '${obj.value}')`, + ); + + const favoriteKey = obj.title.split(' ')[2]; + const favoriteUrl = obj.value; + + this.favoriteState[favoriteKey] = { + ID: key, + URL: obj.value, + }; + + const duplicate = Object.values(this.favoriteState).find( + (item) => item.URL === favoriteUrl && item.ID !== key, + ); + + if (duplicate) { + this.log.warn(`Found a duplicate favorite! (ID : '${key}') URL ${obj.value}`); + + if (InterimSolutionForDeletionOfDuplicates) { + this.log.warn( + `deleting duplicates is currently in dev-mode. Please delete duplicates yourself via the Doorbird app if necessary.`, + ); + } else { + this.log.debug(`Trying to delete the duplicate..`); + + const deleteUrl = `http://${this.config.birdip}/bha-api/favorites.cgi?http-user=${this.config.birduser}&http-password=${this.birdpw}&action=remove&type=http&id=${key}`; + + try { + const deleteResponse = await Axios.get(deleteUrl); + + if (deleteResponse.status === 200) { + delete this.favoriteState[favoriteKey]; + this.log.debug(`Deleted the duplicate (ID: '${key}') successfully!`); + // toDelete + this.log.debug(`favoriteState: ${JSON.stringify(this.favoriteState)}`); + } else { + this.log.warn(`I was unable to delete the duplicate! (ID: ${key})`); + } + } catch (error) { + this.log.error( + `An error occurred while trying to delete the duplicate (ID: ${key}): ${error}`, + ); + } + } + } + } + } + + if (Object.keys(this.favoriteState).length === 0) { + this.log.debug('I did not find any Favorite on the DoorBird Device that belongs to me.'); + } else { + this.log.debug(`Result of Favorites: ${JSON.stringify(this.favoriteState)}`); + } + + await this.getSchedulesAsync(); + return; + } + } catch (error) { + this.log.warn(`Error in checkFavoritesAsync(): ${error}`); + } + } + + /** + * Update Favorites + * @async + * @param {number | string} key + * @param {object} obj + * @returns {Promise} + */ + async updateFavoriteAsync(key, obj) { + const newURL = obj.value.replace( + /(http:\/\/)(.*)(\/.*)/, + '$1' + this.config.adapterAddress + ':' + this.config.adapterport + '$3', + ); + + try { + const url = + 'http://' + + this.config.birdip + + '/bha-api/favorites.cgi?http-user=' + + this.config.birduser + + '&http-password=' + + this.birdpw + + '&action=save&type=http&id=' + + key + + '&title=' + + obj.title + + '&value=' + + newURL; + + const response = await Axios.get(url); + + if (response.status === 200) { + this.log.debug('Favorite Updated successfully..'); + await this.checkFavoritesAsync(); + } else { + this.log.warn(`There was an error while updating the Favorite! ${response.status}`); + } + } catch (error) { + if (error.code === 'ECONNABORTED') { + this.log.warn('Error in testBird() Request timed out: ' + error); + } else { + this.log.warn('There was an error while updating the Favorite! (' + error + ')'); + } + } + } + + /** + * Get Schedules + * @async + * @returns {Promise} + */ + async getSchedulesAsync() { + this.log.debug(`[ getSchedulesAsync ] Schedule call count: ${COUNT_SCHEDULES}`); + COUNT_SCHEDULES++; + + try { + const url = this.buildURL('schedule'); + const response = await Axios.get(url); + + if (response.status === 200) { + try { + const schedules = response.data; + this.bellCount = 0; + this.log.debug(`Following schedules found: ${JSON.stringify(schedules)}`); + this.log.debug('Looping through the Schedules..'); + + for (let i = 0; i < schedules.length && i < MAX_SCHEDULES; i++) { + if (schedules[i].input === 'doorbell') { + this.bellCount++; + this.log.debug('Detected a Doorbell Schedule!'); + this.scheduleState[schedules[i].param] = schedules[i].output; + this.log.debug(`The Param of the actual Schedule is: ${schedules[i].param}`); + this.doorbellsArray.push(schedules[i].param); + this.log.debug(`The Output contains ${schedules[i].output.length} entries!`); + + for (let k = 0; k < schedules[i].output.length; k++) { + this.log.debug(`Entry "${k}" is: ${JSON.stringify(schedules[i].output[k])}`); + } + + this.log.debug(`Counted ${this.bellCount} Doorbells.`); + } + + if (schedules[i].input === 'motion') { + this.log.debug('Detected Motion Schedule!'); + this.motionState.output = schedules[i].output; + this.log.debug(`The Output contains ${schedules[i].output.length} entries!`); + + for (let k = 0; k < schedules[i].output.length; k++) { + this.log.debug(`Entry "${k}" is: ${JSON.stringify(schedules[i].output[k])}`); + } + } + } + + await this.createFavoritesAsync(0); + } catch (error) { + this.log.warn(`Error in Parsing Schedules: ${error}. Error-Stack: ${error.stack}`); + } + } + } catch (error) { + this.log.warn('Error in getSchedules(): ' + error); + } + } + + /** + * @async + * @param {number} i + * @param {boolean} [action = false] + * @param {boolean} [motion = false] + * @returns {Promise} + */ + async createFavoritesAsync(i, action, motion) { + this.log.debug('Checking if we need to create any favorites..'); + if (i < this.doorbellsArray.length && !motion) { + this.log.debug('Cheking if Favorite for Doorbell ID ' + this.doorbellsArray[i] + ' exists.'); + if (this.favoriteState[this.doorbellsArray[i]]) { + this.log.debug(`Favorite for Doorbell ID ${this.doorbellsArray[i]} exists!`); + i++; + await this.createFavoritesAsync(i, false, false); + return; + } + try { + this.log.debug(`Favorite for Doorbell ID ${this.doorbellsArray[i]} has to be created!`); + const createUrl = + 'http://' + + this.config.birdip + + '/bha-api/favorites.cgi?http-user=' + + this.config.birduser + + '&http-password=' + + this.birdpw + + '&action=save&type=http&title=ioBroker ' + + this.namespace + + ' ' + + this.doorbellsArray[i] + + ' Ring&value=http://' + + this.config.adapterAddress + + ':' + + this.config.adapterport + + '/ring?' + + this.doorbellsArray[i]; + const response = await Axios.get(createUrl); + + if (response.status === 200) { + i++; + this.log.debug(`Favorite created successfully..`); + await this.createFavoritesAsync(i, true, false); + return; + } + } catch (error) { + this.log.warn(`Error in creating Favorite: ${error}`); + } + } + if (typeof this.favoriteState['Motion'] === 'undefined' && !motion) { + try { + const url = + 'http://' + + this.config.birdip + + '/bha-api/favorites.cgi?http-user=' + + this.config.birduser + + '&http-password=' + + this.birdpw + + '&action=save&type=http&title=ioBroker ' + + this.namespace + + ' Motion&value=http://' + + this.config.adapterAddress + + ':' + + this.config.adapterport + + '/motion'; + + const response = await Axios.get(url); + + if (response.status === 200) { + this.log.debug('Favorite for Motionsensor created.'); + await this.createFavoritesAsync(i, true, true); + return; + } + } catch (error) { + this.log.warn('Error creating favorite for Motionsensor: ' + error); + return; + } + } + if (action) { + this.log.debug('Finished creating Favorites.. Checking again - just to be sure!'); + //toDelete + this.log.debug(`favoriteState before check again: ${JSON.stringify(this.favoriteState)}`); + await this.checkFavoritesAsync(); + } else { + this.log.debug('Favorites checked successfully. No actions needed!'); + await this.createSchedulesAsync(); + } + } + + /** + * Create Schedules + * @async + * @returns {Promise} + */ + async createSchedulesAsync() { + this.log.debug('Checking if we need to create Schedules on DoorBird Device..'); + + for (const key of Object.keys(this.scheduleState)) { + let actionNeeded = true; + for (let i = 0; i < this.scheduleState[key].length; i++) { + if (this.scheduleState[key][i].event !== 'http') { + continue; + } + if (this.scheduleState[key][i].param === this.favoriteState[key]['ID']) { + actionNeeded = false; + } + } + + if (actionNeeded) { + this.log.debug('We need to create a Schedule for Doorbell ID: ' + key); + const scheduleArray = this.scheduleState[key]; + scheduleArray.push({ + event: 'http', + param: this.favoriteState[key]['ID'], + enabled: '1', + schedule: { weekdays: [{ from: '79200', to: '79199' }] }, + }); + + this.toCreate = { input: 'doorbell', param: key, output: scheduleArray }; + + try { + const createUrl = this.buildURL('schedule'); + const response = await Axios.post(createUrl, this.toCreate); + + if (response.status === 200) { + this.log.debug('Schedule created successfully!'); + } else { + this.log.warn( + `There was an Error while creating the schedule. Status: ${response.status}, Text: ${response.statusText}`, + ); + } + } catch (error) { + this.log.warn(`Error in creating schedule: ${error}`); + } + } else { + this.log.debug('Okay we dont need to create any Doorbell-Schedules..'); + } + } + + for (const value of this.doorbellsArray) { + await this.createObjectsAsync(`Doorbell.${value}`, 'device', { + name: { + en: 'Doorbell', + de: 'Türklingel', + // ... + }, + desc: `ID: ${value}`, + }); + + // create State + await this.createObjectsAsync(`Doorbell.${value}.trigger`, 'state', { + role: 'indicator', + name: "Doorbell ID '" + value + "' pressed", + type: 'boolean', + read: true, + write: false, + def: false, + }); + } + + await this.createMotionScheduleAsync(); + } + + /** + * Create Motion Schedule + * @async + * @returns {Promise} + */ + async createMotionScheduleAsync() { + for (const key of Object.keys(this.motionState)) { + let actionNeeded = true; + for (let i = 0; i < this.motionState[key].length; i++) { + if (this.motionState[key][i].event !== 'http') { + continue; + } + if (this.motionState[key][i].param === this.favoriteState['Motion']['ID']) { + actionNeeded = false; + } + } + + if (actionNeeded) { + this.log.debug('We need to create a Schedule for the Motion Sensor!'); + const motionArray = this.motionState[key]; + motionArray.push({ + event: 'http', + param: this.favoriteState['Motion']['ID'], + enabled: '1', + schedule: { weekdays: [{ from: '79200', to: '79199' }] }, + }); + + const toCreate = { input: 'motion', param: '', output: motionArray }; + + try { + const createUrl = this.buildURL('schedule'); + const response = await Axios.post(createUrl, toCreate); + + if (response.status === 200) { + this.log.debug('Schedule for Motion Sensor set successfully!'); + } else { + this.log.warn( + `There was an Error while setting the Motion schedule. (Statuscode: ${response.status}), (Statustext: ${response.statusText}`, + ); + } + } catch (error) { + this.log.warn(`Error in setting Motion schedule: ${error}`); + } + } + } + } + + /** + * Is called when adapter shuts down - callback has to be called under any circumstances! + * @param {() => void} callback + */ + onUnload(callback) { + try { + if (this.birdConCheck) this.clearTimeout(this.birdConCheck), (this.birdConCheck = null); + if (this.server) + this.server.close(() => { + this.log.debug(`Server closed`); + }); + for (let i = 0; i < this.sockets.length; i++) { + this.sockets[i].destroy(); + } + this.sockets = []; + callback(); + } catch (error) { + this.log.error(`Error in onUnload: ${error}`); + callback(); + } + } + + /** + * Wizard to find Doorbird + * @async + * @param {object} msg + */ + async startWizard(msg) { + this.wizard = true; + const wizData = []; + const wizServer = dgram.createSocket('udp4'); + const adapter = this; + + wizServer.on('listening', function () { + const address = wizServer.address(); + adapter.log.debug(`Wizard listening on IP: ${address.address} - UDP Port 6524`); + }); + + wizServer.on('message', (message, remote) => { + if (remote.address && message.length > 25 && wizData.length == 0) { + wizData.push(remote.address); + } + if (wizData[0] == remote.address && message.length < 25) { + wizData.push(message.toString('utf-8').split(':')[1]); + this.sendTo(msg.from, msg.command, wizData, msg.callback); + this.wizard = false; + (() => { + if (this.wizardTimeout) { + this.clearTimeout(this.wizardTimeout); + this.wizardTimeout = null; + } + })(); + if (this.wizardTimeout) this.clearTimeout(this.wizardTimeout); + wizServer.close(); + } + }); + + wizServer.bind(6524); + if (this.wizardTimeout) this.clearTimeout(this.wizardTimeout); + this.wizardTimeout = this.setTimeout(function () { + wizServer.close(); + this.wizard = false; + adapter.log.debug('Wizard timeout!'); + }, 60000); + } + + /** + * Turn on light + * @async + */ + async turnOnLightAsync() { + try { + const url = this.buildURL('light-on'); + const response = await Axios.get(url); + + if (response.status === 200) { + this.log.debug('Light successfully triggered!'); + } else { + this.log.warn(`Could not trigger light. (Got Statuscode ${response.status})`); + } + } catch (error) { + this.log.warn('Error in triggering Light: ' + error); + } + } + + /** + * Restart Doorbird + * @async + */ + async restartDoorbirdAsync() { + try { + const url = this.buildURL('restart'); + const response = await Axios.get(url); + + if (response.status === 200) { + this.log.debug('DoorBird Device is now restarting!!'); + } else if (response.status === 503) { + this.log.warn('DoorBird denied restart! (Device is busy and cannot restart now!)'); + } else { + this.log.warn('DoorBird denied restart! (Statuscode: ' + response.status + ')'); + } + } catch (error) { + this.log.warn('DoorBird denied restart: ' + error); + } + } + + /** + * Trigger Relays + * @async + * @param {string} comp + */ + async relaysAsync(comp) { + try { + const url = + 'http://' + + this.config.birdip + + '/bha-api/open-door.cgi?http-user=' + + this.config.birduser + + '&http-password=' + + this.birdpw + + '&r=' + + comp; + + const response = await Axios.get(url); + + if (response.status === 200) { + this.log.debug('Relay ' + comp + ' triggered successfully!'); + } else if (response.status === 204) { + this.log.warn('Could not trigger relay ' + comp + '! (Insufficient permissions)'); + } else { + this.log.warn('Could not trigger relay ' + comp + '. (Got Statuscode ' + response.status + ')'); + } + } catch (error) { + this.log.warn('Error in triggering Relay: ' + error); + } + } + + /** + * Download File from Doorbird + * @async + * @param {string} cmd + */ + async downloadFileAsync(cmd) { + try { + const url = this.buildURL('image'); + const response = await Axios.get(url, { responseType: 'stream' }); + const chunks = []; + + response.data.on('data', (chunk) => { + chunks.push(chunk); + }); + + response.data.on('end', async () => { + const fileData = Buffer.concat(chunks); + await this.writeFileAsync(this.namespace, `${cmd}_1.jpg`, fileData); + this.log.debug('Snapshot saved successfully!'); + }); + } catch (error) { + this.log.warn(`Error in downloading file: ${error}`); + } + } + + /** + * Create datapoint and update datapoints + * @async + * @param {string} dp path to datapoint + * @param {'device' | 'channel' | 'state' | 'meta'} type type of object + * @param {object} common common part of object + * @param {boolean} [foreign = false] set adapter states = false; set foreign states = true + */ + async createObjectsAsync(dp, type, common, foreign) { + const obj = !foreign ? await this.getObjectAsync(dp) : await this.getForeignObjectAsync(dp); + + if (!obj) { + const obj = { + _id: dp, + type: type, + common: common, + native: {}, + }; + await (foreign ? this.setForeignObjectAsync(dp, obj) : this.setObjectAsync(dp, obj)); + this.log.debug(`Object: ${dp} created.`); + } else { + if (JSON.stringify(obj.common) !== JSON.stringify(common) || !('native' in obj)) { + obj.common = common; + obj.native = obj.native ?? {}; + await (foreign ? this.setForeignObjectAsync(dp, obj) : this.setObjectAsync(dp, obj)); + this.log.debug(`Object: ${dp} updated.`); + } + } + } + + /** + * Migrate versions above 1.0.1 + * @async + */ + async migrateAsync() { + try { + let migrate = false; + const arrayOfStates = await this.getStatesOfAsync(); + for (const obj of arrayOfStates) { + if (obj._id.includes('.snapshot') && obj.type === 'state') { + await this.delObjectAsync(obj._id); + migrate = true; + } + } + if (migrate) { + this.log.info(`Version migrated. Snapshot states deleted.`); + } + } catch (error) { + this.log.warn(`Error on migrate. Error: ${error}`); + } + } +>>>>>>> a9e541f (upgrade to jsonConfig) } if (require.main !== module) { From 1bceb7072a22aa1c73e700cd13eb6ec4a92fcddd Mon Sep 17 00:00:00 2001 From: Schmakus Date: Thu, 6 Mar 2025 10:58:56 +0100 Subject: [PATCH 2/2] updates --- README.md | 9 - admin/jsonConfig.json | 55 --- main.js | 918 ------------------------------------------ 3 files changed, 982 deletions(-) diff --git a/README.md b/README.md index 00684c5..d7d3772 100644 --- a/README.md +++ b/README.md @@ -94,21 +94,12 @@ onFile('doorbird.0', 'TakeSnapshot_1.jpg', true, (id, fileName, size, fileData, Placeholder for the next version (at the beginning of the line): ### **WORK IN PROGRESS** --> -<<<<<<< HEAD ### 3.0.0 (2025-03-03) NodeJS >= 20.x and js-controller >= 6 is required - (@klein0r) Migrated to json config - (@klein0r) Updated documentation and dependencies -======= - -### **WORK IN PROGRESS** - -- (Schmakus) migrated to admin 5 UI (jsonConfig) -- (Schmakus) Removed Wizard -- (Schmakus) changed async methods ->>>>>>> a9e541f (upgrade to jsonConfig) ### 2.0.0 (2024-09-02) diff --git a/admin/jsonConfig.json b/admin/jsonConfig.json index fe40066..bc556ce 100644 --- a/admin/jsonConfig.json +++ b/admin/jsonConfig.json @@ -1,5 +1,4 @@ { -<<<<<<< HEAD "i18n": true, "type": "tabs", "tabsStyle": { @@ -86,57 +85,3 @@ } } } -======= - "i18n": true, - "type": "panel", - "items": { - "adapterAddress": { - "type": "ip", - "label": "IP of Adapter (ioBroker). e.g. 192.168.1.100", - "sm": 3, - "md": 3, - "lg": 3, - "onlyIp4": true, - "listenOnAllPorts": true - }, - "adapterport": { - "type": "number", - "label": "Port of Adapter (ioBroker). e.g. 8100", - "sm": 3, - "md": 3, - "lg": 3, - "default": 8100 - }, - "birdip": { - "type": "text", - "label": "IP of DoorBird Device. e.g. 192.168.1.100", - "sm": 3, - "md": 3, - "lg": 3, - "newLine": true - }, - "birdid": { - "type": "text", - "label": "Device ID of DoorBird. e.g. ghfxxx", - "sm": 3, - "md": 3, - "lg": 3 - }, - "birduser": { - "type": "text", - "label": "Username e.g. ghfxxx0001", - "sm": 3, - "md": 3, - "lg": 3, - "newLine": true - }, - "birdpw": { - "type": "password", - "label": "Password", - "sm": 3, - "md": 3, - "lg": 3 - } - } -} ->>>>>>> a9e541f (upgrade to jsonConfig) diff --git a/main.js b/main.js index b6b6eeb..38a3940 100644 --- a/main.js +++ b/main.js @@ -21,7 +21,6 @@ let COUNT_SCHEDULES = 0; let adapterStarted = false; class Doorbird extends utils.Adapter { -<<<<<<< HEAD /** * @param [options] */ @@ -950,923 +949,6 @@ class Doorbird extends utils.Adapter { this.log.warn(`Error on migrate. Error: ${error}`); } } -======= - /** - * @param {Partial} [options={}] - */ - constructor(options) { - super({ - ...options, - name: 'doorbird', - }); - this.on('ready', this.onReady.bind(this)); - this.on('stateChange', this.onStateChange.bind(this)); - this.on('message', this.onMessage.bind(this)); - this.on('unload', this.onUnload.bind(this)); - - this.sockets = []; - this.bellCount = 0; - this.birdpw = ''; - this.scheduleState = {}; - this.motionState = {}; - this.doorbellsArray = []; // Contains all Doorbell IDs - this.favoriteState = {}; // {'ID of Doorbell/Motion/RFID': 'ID of Favorite'} - - this.authorized = false; - - this.wizard = false; - this.wizardTimeout = null; - } - - /** - * Is called when databases are connected and adapter received configuration. - */ - async onReady() { - // Reset the connection indicator during startup - await this.setState('info.connection', false, true); - - this.birdpw = this.config.birdpw ? this.encrypt(this.config.birdpw) : ''; - this.log.debug(`Entcrypted Password: ${this.birdpw}`); - await Promise.all([this.migrateAsync(), this.mainAsync()]); - } - - async mainAsync() { - if (this.config.birdip && this.birdpw && this.config.birduser) { - this.testBirdAsync(); // no await because code should run parallel - } - - try { - udpserver.on('listening', () => { - const address = udpserver.address(); - this.log.debug('Adapter listening on IP: ' + address + ' - UDP Port 35344'); - }); - } catch (error) { - this.log.error(`address already in use ${this.config.adapterAddress}:${this.config.adapterport}: ${error}`); - } - - // udpserver.bind(35344); - if (this.config.adapterAddress) { - try { - let ip; - let msg; - - if (this.config.listenOnAllInterfaces) { - ip = '0.0.0.0'; - msg = `Server gestartet auf allen Interfaces auf Port ${this.config.adapterport || 8100}`; - } else { - ip = this.config.adapterAddress; - msg = `Server gestartet auf Port ${this.config.adapterport || 8100} und IP ${ - this.config.adapterAddress - }`; - } - - this.server = http.createServer(async (req, res) => { - if (res.socket && res.socket.remoteAddress) { - const remoteAddress = res.socket.remoteAddress.replace(/^.*:/, ''); - if (remoteAddress === this.config.birdip || this.config.listenOnAllInterfaces) { - res.writeHead(204, { 'Content-Type': 'text/plain' }); - if (req.url == '/motion') { - this.log.debug('Received Motion-alert from Doorbird!'); - await Promise.all([ - this.setState('Motion.trigger', true, true), - this.downloadFileAsync('Motion'), - ]); - - this.setTimeout(async () => { - await this.setState('Motion.trigger', false, true); - }, 2500); - } - if (req.url && req.url.indexOf('ring') != -1) { - const id = req.url.substring(req.url.indexOf('?') + 1, req.url.length); - this.log.debug('Received Ring-alert (ID: ' + id + ') from Doorbird!'); - await Promise.all([ - this.setState('Doorbell.' + id + '.trigger', true, true), - this.downloadFileAsync(`Doorbell${id}`), - ]); - - this.setTimeout(async () => { - await this.setState('Doorbell.' + id + '.trigger', false, true); - }, 2000); - } - res.end(); - } - } else { - res.writeHead(401, { 'Content-Type': 'text/plain' }); - res.end(); - } - }); - - this.server.listen(this.config.adapterport || 8081, ip, () => { - this.log.debug(msg); - }); - } catch (e) { - this.log.warn('There was an Error starting the HTTP Server! (' + e + ')'); - } - } else { - this.log.warn('You need to set the Adapteraddress in the Instance-Settings!!'); - } - - this.subscribeStates('*'); - } - - /** - * Is called if a subscribed state changes - * @param {string} id - * @param {ioBroker.State | null | undefined} state - */ - async onStateChange(id, state) { - if (!state || state.ack) return; - const comp = id.split('.'); - - if (!this.authorized) { - this.log.warn('Cannot do that because not authorized! Check you config!'); - return; - } - - switch (comp[2]) { - case 'Restart': - this.log.debug('Trying to restart DoorBird Device..'); - await this.restartDoorbirdAsync(); - break; - case 'TakeSnapshot': - this.log.info('Trying to take snapshot..'); - await this.downloadFileAsync('TakeSnapshot'); - break; - case 'Light': - this.log.info('Trying to turn on light..'); - await this.turnOnLightAsync(); - break; - case 'Relays': - await this.relaysAsync(comp[3]); - break; - } - } - /** - * @param {ioBroker.Message} obj - */ - async onMessage(obj) { - if (typeof obj === 'object' && obj.message) { - if (obj.command === 'wizard' && !this.wizard) { - this.startWizard(obj); - this.wizard = true; - } - } - } - - /** - * Generate URL - * @param {'restart' | 'schedule' | 'info' | 'favorites' | 'image' | 'light-on'} command - */ - buildURL(command) { - return ( - 'http://' + - this.config.birdip + - '/bha-api/' + - command + - '.cgi?http-user=' + - this.config.birduser + - '&http-password=' + - this.birdpw - ); - } - - /** - * Check Doorbird Connection - * @async - * @returns {Promise} - */ - async testBirdAsync() { - try { - const url = this.buildURL('schedule'); - const response = await Axios.get(url); - - if (response.status === 401) { - this.authorized = false; - await this.setState('info.connection', false, true); - this.log.warn('Whooops.. DoorBird says User ' + this.config.birduser + ' is unauthorized!!'); - this.log.warn('Check Username + Password and enable the "API-Operator" Permission for the User!!'); - } else if (response.status === 200) { - this.authorized = true; - await this.setState('info.connection', true, true); - this.log.debug('Authorization with User ' + this.config.birduser + ' successful!'); - if (!adapterStarted) { - adapterStarted = true; - await this.getInfoAsync(); - } - } - } catch (error) { - if (error.code === 'EHOSTUNREACH') { - this.authorized = false; - await this.setState('info.connection', false, true); - this.log.warn('DoorBird Device is offline!!'); - } else if (error.code === 'ECONNABORTED') { - this.authorized = false; - await this.setState('info.connection', false, true); - this.log.warn('Error in testBird() Request timed out: ' + error); - } else { - this.authorized = false; - await this.setState('info.connection', false, true); - this.log.warn('Error in testBird() Request: ' + error); - } - } - - if (this.birdConCheck) this.clearTimeout(this.birdConCheck), (this.birdConCheck = null); - this.birdConCheck = this.setTimeout(async () => { - this.log.debug(`Refresh connection check...`); - this.birdConCheck = null; - await this.testBirdAsync(); - }, 180000); - } - - /** - * Get Doorbird Info - * @async - * @returns {Promise} - */ - async getInfoAsync() { - if (this.authorized) { - try { - const url = this.buildURL('info'); - const response = await Axios.get(url); - - if (response.status === 200) { - const info = response.data; - await this.setState('info.firmware', info.BHA.VERSION[0].FIRMWARE, true); - await this.setState('info.build', info.BHA.VERSION[0].BUILD_NUMBER, true); - await this.setState('info.type', info.BHA.VERSION[0]['DEVICE-TYPE'], true); - - const relays = info.BHA.VERSION[0].RELAYS; - - for (const value of relays) { - //create channel - await this.createObjectsAsync('Relays', 'channel', { - name: { - en: 'Available Relays', - de: 'Verfügbare Relais', - ru: 'Доступные реле', - pt: 'Relés disponíveis', - nl: 'Available Relay', - fr: 'Relais disponibles', - it: 'Relè disponibili', - es: 'Relés disponibles', - pl: 'Dostępny Relay', - uk: 'Доступні реле', - 'zh-cn': '现有费用', - }, - }); - //create State - await this.createObjectsAsync(`Relays.${value}`, 'state', { - name: { - en: 'Activate relay', - de: 'Relais aktivieren', - ru: 'Активировать реле', - pt: 'Activar o relé', - nl: 'Activeer relay', - fr: 'Activer le relais', - it: 'Attiva relè', - es: 'Activar el relé', - pl: 'Aktywacja', - uk: 'Активувати реле', - 'zh-cn': '法 律 法 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 重', - }, - type: 'boolean', - role: 'button', - read: false, - write: true, - desc: `ID: ${value}`, - }); - } - } - } catch (error) { - this.log.warn('Error in getInfo(): ' + error); - } - - await this.checkFavoritesAsync(); - } else { - this.log.warn('Execution of getInfo not allowed because not authorized!'); - } - } - - /** - * Check Favorites - * @async - * @returns {Promise} - */ - async checkFavoritesAsync() { - this.log.debug('Checking favorites on DoorBird Device..'); - try { - const url = this.buildURL('favorites'); - const response = await Axios.get(url); - - if (response.status === 200) { - const favorites = response.data; - if (!favorites.http || typeof favorites.http !== 'object') { - this.log.warn('It seems that the list of HTTP Favorites is empty!'); - await this.createFavoritesAsync(0); - return; - } - for (const [key, obj] of Object.entries(favorites.http)) { - if ( - obj.title.includes('ioBroker ' + this.namespace) && - !obj.value.includes(this.config.adapterAddress + ':' + this.config.adapterport) - ) { - this.log.warn( - `The Favorite ID '${key}' contains a wrong URL ('${obj.value}').. I will update that..`, - ); - - await this.updateFavoriteAsync(key, obj); - return; - } - - if (obj.value.includes(this.config.adapterAddress + ':' + this.config.adapterport)) { - this.log.debug( - `Found a Favorite that belongs to me.. (ID: '${key}') ('${obj.title}': '${obj.value}')`, - ); - - const favoriteKey = obj.title.split(' ')[2]; - const favoriteUrl = obj.value; - - this.favoriteState[favoriteKey] = { - ID: key, - URL: obj.value, - }; - - const duplicate = Object.values(this.favoriteState).find( - (item) => item.URL === favoriteUrl && item.ID !== key, - ); - - if (duplicate) { - this.log.warn(`Found a duplicate favorite! (ID : '${key}') URL ${obj.value}`); - - if (InterimSolutionForDeletionOfDuplicates) { - this.log.warn( - `deleting duplicates is currently in dev-mode. Please delete duplicates yourself via the Doorbird app if necessary.`, - ); - } else { - this.log.debug(`Trying to delete the duplicate..`); - - const deleteUrl = `http://${this.config.birdip}/bha-api/favorites.cgi?http-user=${this.config.birduser}&http-password=${this.birdpw}&action=remove&type=http&id=${key}`; - - try { - const deleteResponse = await Axios.get(deleteUrl); - - if (deleteResponse.status === 200) { - delete this.favoriteState[favoriteKey]; - this.log.debug(`Deleted the duplicate (ID: '${key}') successfully!`); - // toDelete - this.log.debug(`favoriteState: ${JSON.stringify(this.favoriteState)}`); - } else { - this.log.warn(`I was unable to delete the duplicate! (ID: ${key})`); - } - } catch (error) { - this.log.error( - `An error occurred while trying to delete the duplicate (ID: ${key}): ${error}`, - ); - } - } - } - } - } - - if (Object.keys(this.favoriteState).length === 0) { - this.log.debug('I did not find any Favorite on the DoorBird Device that belongs to me.'); - } else { - this.log.debug(`Result of Favorites: ${JSON.stringify(this.favoriteState)}`); - } - - await this.getSchedulesAsync(); - return; - } - } catch (error) { - this.log.warn(`Error in checkFavoritesAsync(): ${error}`); - } - } - - /** - * Update Favorites - * @async - * @param {number | string} key - * @param {object} obj - * @returns {Promise} - */ - async updateFavoriteAsync(key, obj) { - const newURL = obj.value.replace( - /(http:\/\/)(.*)(\/.*)/, - '$1' + this.config.adapterAddress + ':' + this.config.adapterport + '$3', - ); - - try { - const url = - 'http://' + - this.config.birdip + - '/bha-api/favorites.cgi?http-user=' + - this.config.birduser + - '&http-password=' + - this.birdpw + - '&action=save&type=http&id=' + - key + - '&title=' + - obj.title + - '&value=' + - newURL; - - const response = await Axios.get(url); - - if (response.status === 200) { - this.log.debug('Favorite Updated successfully..'); - await this.checkFavoritesAsync(); - } else { - this.log.warn(`There was an error while updating the Favorite! ${response.status}`); - } - } catch (error) { - if (error.code === 'ECONNABORTED') { - this.log.warn('Error in testBird() Request timed out: ' + error); - } else { - this.log.warn('There was an error while updating the Favorite! (' + error + ')'); - } - } - } - - /** - * Get Schedules - * @async - * @returns {Promise} - */ - async getSchedulesAsync() { - this.log.debug(`[ getSchedulesAsync ] Schedule call count: ${COUNT_SCHEDULES}`); - COUNT_SCHEDULES++; - - try { - const url = this.buildURL('schedule'); - const response = await Axios.get(url); - - if (response.status === 200) { - try { - const schedules = response.data; - this.bellCount = 0; - this.log.debug(`Following schedules found: ${JSON.stringify(schedules)}`); - this.log.debug('Looping through the Schedules..'); - - for (let i = 0; i < schedules.length && i < MAX_SCHEDULES; i++) { - if (schedules[i].input === 'doorbell') { - this.bellCount++; - this.log.debug('Detected a Doorbell Schedule!'); - this.scheduleState[schedules[i].param] = schedules[i].output; - this.log.debug(`The Param of the actual Schedule is: ${schedules[i].param}`); - this.doorbellsArray.push(schedules[i].param); - this.log.debug(`The Output contains ${schedules[i].output.length} entries!`); - - for (let k = 0; k < schedules[i].output.length; k++) { - this.log.debug(`Entry "${k}" is: ${JSON.stringify(schedules[i].output[k])}`); - } - - this.log.debug(`Counted ${this.bellCount} Doorbells.`); - } - - if (schedules[i].input === 'motion') { - this.log.debug('Detected Motion Schedule!'); - this.motionState.output = schedules[i].output; - this.log.debug(`The Output contains ${schedules[i].output.length} entries!`); - - for (let k = 0; k < schedules[i].output.length; k++) { - this.log.debug(`Entry "${k}" is: ${JSON.stringify(schedules[i].output[k])}`); - } - } - } - - await this.createFavoritesAsync(0); - } catch (error) { - this.log.warn(`Error in Parsing Schedules: ${error}. Error-Stack: ${error.stack}`); - } - } - } catch (error) { - this.log.warn('Error in getSchedules(): ' + error); - } - } - - /** - * @async - * @param {number} i - * @param {boolean} [action = false] - * @param {boolean} [motion = false] - * @returns {Promise} - */ - async createFavoritesAsync(i, action, motion) { - this.log.debug('Checking if we need to create any favorites..'); - if (i < this.doorbellsArray.length && !motion) { - this.log.debug('Cheking if Favorite for Doorbell ID ' + this.doorbellsArray[i] + ' exists.'); - if (this.favoriteState[this.doorbellsArray[i]]) { - this.log.debug(`Favorite for Doorbell ID ${this.doorbellsArray[i]} exists!`); - i++; - await this.createFavoritesAsync(i, false, false); - return; - } - try { - this.log.debug(`Favorite for Doorbell ID ${this.doorbellsArray[i]} has to be created!`); - const createUrl = - 'http://' + - this.config.birdip + - '/bha-api/favorites.cgi?http-user=' + - this.config.birduser + - '&http-password=' + - this.birdpw + - '&action=save&type=http&title=ioBroker ' + - this.namespace + - ' ' + - this.doorbellsArray[i] + - ' Ring&value=http://' + - this.config.adapterAddress + - ':' + - this.config.adapterport + - '/ring?' + - this.doorbellsArray[i]; - const response = await Axios.get(createUrl); - - if (response.status === 200) { - i++; - this.log.debug(`Favorite created successfully..`); - await this.createFavoritesAsync(i, true, false); - return; - } - } catch (error) { - this.log.warn(`Error in creating Favorite: ${error}`); - } - } - if (typeof this.favoriteState['Motion'] === 'undefined' && !motion) { - try { - const url = - 'http://' + - this.config.birdip + - '/bha-api/favorites.cgi?http-user=' + - this.config.birduser + - '&http-password=' + - this.birdpw + - '&action=save&type=http&title=ioBroker ' + - this.namespace + - ' Motion&value=http://' + - this.config.adapterAddress + - ':' + - this.config.adapterport + - '/motion'; - - const response = await Axios.get(url); - - if (response.status === 200) { - this.log.debug('Favorite for Motionsensor created.'); - await this.createFavoritesAsync(i, true, true); - return; - } - } catch (error) { - this.log.warn('Error creating favorite for Motionsensor: ' + error); - return; - } - } - if (action) { - this.log.debug('Finished creating Favorites.. Checking again - just to be sure!'); - //toDelete - this.log.debug(`favoriteState before check again: ${JSON.stringify(this.favoriteState)}`); - await this.checkFavoritesAsync(); - } else { - this.log.debug('Favorites checked successfully. No actions needed!'); - await this.createSchedulesAsync(); - } - } - - /** - * Create Schedules - * @async - * @returns {Promise} - */ - async createSchedulesAsync() { - this.log.debug('Checking if we need to create Schedules on DoorBird Device..'); - - for (const key of Object.keys(this.scheduleState)) { - let actionNeeded = true; - for (let i = 0; i < this.scheduleState[key].length; i++) { - if (this.scheduleState[key][i].event !== 'http') { - continue; - } - if (this.scheduleState[key][i].param === this.favoriteState[key]['ID']) { - actionNeeded = false; - } - } - - if (actionNeeded) { - this.log.debug('We need to create a Schedule for Doorbell ID: ' + key); - const scheduleArray = this.scheduleState[key]; - scheduleArray.push({ - event: 'http', - param: this.favoriteState[key]['ID'], - enabled: '1', - schedule: { weekdays: [{ from: '79200', to: '79199' }] }, - }); - - this.toCreate = { input: 'doorbell', param: key, output: scheduleArray }; - - try { - const createUrl = this.buildURL('schedule'); - const response = await Axios.post(createUrl, this.toCreate); - - if (response.status === 200) { - this.log.debug('Schedule created successfully!'); - } else { - this.log.warn( - `There was an Error while creating the schedule. Status: ${response.status}, Text: ${response.statusText}`, - ); - } - } catch (error) { - this.log.warn(`Error in creating schedule: ${error}`); - } - } else { - this.log.debug('Okay we dont need to create any Doorbell-Schedules..'); - } - } - - for (const value of this.doorbellsArray) { - await this.createObjectsAsync(`Doorbell.${value}`, 'device', { - name: { - en: 'Doorbell', - de: 'Türklingel', - // ... - }, - desc: `ID: ${value}`, - }); - - // create State - await this.createObjectsAsync(`Doorbell.${value}.trigger`, 'state', { - role: 'indicator', - name: "Doorbell ID '" + value + "' pressed", - type: 'boolean', - read: true, - write: false, - def: false, - }); - } - - await this.createMotionScheduleAsync(); - } - - /** - * Create Motion Schedule - * @async - * @returns {Promise} - */ - async createMotionScheduleAsync() { - for (const key of Object.keys(this.motionState)) { - let actionNeeded = true; - for (let i = 0; i < this.motionState[key].length; i++) { - if (this.motionState[key][i].event !== 'http') { - continue; - } - if (this.motionState[key][i].param === this.favoriteState['Motion']['ID']) { - actionNeeded = false; - } - } - - if (actionNeeded) { - this.log.debug('We need to create a Schedule for the Motion Sensor!'); - const motionArray = this.motionState[key]; - motionArray.push({ - event: 'http', - param: this.favoriteState['Motion']['ID'], - enabled: '1', - schedule: { weekdays: [{ from: '79200', to: '79199' }] }, - }); - - const toCreate = { input: 'motion', param: '', output: motionArray }; - - try { - const createUrl = this.buildURL('schedule'); - const response = await Axios.post(createUrl, toCreate); - - if (response.status === 200) { - this.log.debug('Schedule for Motion Sensor set successfully!'); - } else { - this.log.warn( - `There was an Error while setting the Motion schedule. (Statuscode: ${response.status}), (Statustext: ${response.statusText}`, - ); - } - } catch (error) { - this.log.warn(`Error in setting Motion schedule: ${error}`); - } - } - } - } - - /** - * Is called when adapter shuts down - callback has to be called under any circumstances! - * @param {() => void} callback - */ - onUnload(callback) { - try { - if (this.birdConCheck) this.clearTimeout(this.birdConCheck), (this.birdConCheck = null); - if (this.server) - this.server.close(() => { - this.log.debug(`Server closed`); - }); - for (let i = 0; i < this.sockets.length; i++) { - this.sockets[i].destroy(); - } - this.sockets = []; - callback(); - } catch (error) { - this.log.error(`Error in onUnload: ${error}`); - callback(); - } - } - - /** - * Wizard to find Doorbird - * @async - * @param {object} msg - */ - async startWizard(msg) { - this.wizard = true; - const wizData = []; - const wizServer = dgram.createSocket('udp4'); - const adapter = this; - - wizServer.on('listening', function () { - const address = wizServer.address(); - adapter.log.debug(`Wizard listening on IP: ${address.address} - UDP Port 6524`); - }); - - wizServer.on('message', (message, remote) => { - if (remote.address && message.length > 25 && wizData.length == 0) { - wizData.push(remote.address); - } - if (wizData[0] == remote.address && message.length < 25) { - wizData.push(message.toString('utf-8').split(':')[1]); - this.sendTo(msg.from, msg.command, wizData, msg.callback); - this.wizard = false; - (() => { - if (this.wizardTimeout) { - this.clearTimeout(this.wizardTimeout); - this.wizardTimeout = null; - } - })(); - if (this.wizardTimeout) this.clearTimeout(this.wizardTimeout); - wizServer.close(); - } - }); - - wizServer.bind(6524); - if (this.wizardTimeout) this.clearTimeout(this.wizardTimeout); - this.wizardTimeout = this.setTimeout(function () { - wizServer.close(); - this.wizard = false; - adapter.log.debug('Wizard timeout!'); - }, 60000); - } - - /** - * Turn on light - * @async - */ - async turnOnLightAsync() { - try { - const url = this.buildURL('light-on'); - const response = await Axios.get(url); - - if (response.status === 200) { - this.log.debug('Light successfully triggered!'); - } else { - this.log.warn(`Could not trigger light. (Got Statuscode ${response.status})`); - } - } catch (error) { - this.log.warn('Error in triggering Light: ' + error); - } - } - - /** - * Restart Doorbird - * @async - */ - async restartDoorbirdAsync() { - try { - const url = this.buildURL('restart'); - const response = await Axios.get(url); - - if (response.status === 200) { - this.log.debug('DoorBird Device is now restarting!!'); - } else if (response.status === 503) { - this.log.warn('DoorBird denied restart! (Device is busy and cannot restart now!)'); - } else { - this.log.warn('DoorBird denied restart! (Statuscode: ' + response.status + ')'); - } - } catch (error) { - this.log.warn('DoorBird denied restart: ' + error); - } - } - - /** - * Trigger Relays - * @async - * @param {string} comp - */ - async relaysAsync(comp) { - try { - const url = - 'http://' + - this.config.birdip + - '/bha-api/open-door.cgi?http-user=' + - this.config.birduser + - '&http-password=' + - this.birdpw + - '&r=' + - comp; - - const response = await Axios.get(url); - - if (response.status === 200) { - this.log.debug('Relay ' + comp + ' triggered successfully!'); - } else if (response.status === 204) { - this.log.warn('Could not trigger relay ' + comp + '! (Insufficient permissions)'); - } else { - this.log.warn('Could not trigger relay ' + comp + '. (Got Statuscode ' + response.status + ')'); - } - } catch (error) { - this.log.warn('Error in triggering Relay: ' + error); - } - } - - /** - * Download File from Doorbird - * @async - * @param {string} cmd - */ - async downloadFileAsync(cmd) { - try { - const url = this.buildURL('image'); - const response = await Axios.get(url, { responseType: 'stream' }); - const chunks = []; - - response.data.on('data', (chunk) => { - chunks.push(chunk); - }); - - response.data.on('end', async () => { - const fileData = Buffer.concat(chunks); - await this.writeFileAsync(this.namespace, `${cmd}_1.jpg`, fileData); - this.log.debug('Snapshot saved successfully!'); - }); - } catch (error) { - this.log.warn(`Error in downloading file: ${error}`); - } - } - - /** - * Create datapoint and update datapoints - * @async - * @param {string} dp path to datapoint - * @param {'device' | 'channel' | 'state' | 'meta'} type type of object - * @param {object} common common part of object - * @param {boolean} [foreign = false] set adapter states = false; set foreign states = true - */ - async createObjectsAsync(dp, type, common, foreign) { - const obj = !foreign ? await this.getObjectAsync(dp) : await this.getForeignObjectAsync(dp); - - if (!obj) { - const obj = { - _id: dp, - type: type, - common: common, - native: {}, - }; - await (foreign ? this.setForeignObjectAsync(dp, obj) : this.setObjectAsync(dp, obj)); - this.log.debug(`Object: ${dp} created.`); - } else { - if (JSON.stringify(obj.common) !== JSON.stringify(common) || !('native' in obj)) { - obj.common = common; - obj.native = obj.native ?? {}; - await (foreign ? this.setForeignObjectAsync(dp, obj) : this.setObjectAsync(dp, obj)); - this.log.debug(`Object: ${dp} updated.`); - } - } - } - - /** - * Migrate versions above 1.0.1 - * @async - */ - async migrateAsync() { - try { - let migrate = false; - const arrayOfStates = await this.getStatesOfAsync(); - for (const obj of arrayOfStates) { - if (obj._id.includes('.snapshot') && obj.type === 'state') { - await this.delObjectAsync(obj._id); - migrate = true; - } - } - if (migrate) { - this.log.info(`Version migrated. Snapshot states deleted.`); - } - } catch (error) { - this.log.warn(`Error on migrate. Error: ${error}`); - } - } ->>>>>>> a9e541f (upgrade to jsonConfig) } if (require.main !== module) {