UNPKG

iobroker.fritzdect

Version:
1,418 lines (1,381 loc) 141 kB
'use strict'; /* * Created with @iobroker/create-adapter v1.31.0 */ // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter const utils = require('@iobroker/adapter-core'); // Load your modules here, e.g.: // const fs = require("fs"); const Fritz = require('fritzdect-aha-nodejs').Fritz; /* let Fritz; (async () => { let fb = await import('fritzdect-aha-nodejs'); Fritz = fb.Fritz; })().catch((err) => console.error(err)); */ const parser = require('./lib/xml2json.js'); let polling; /* errorcodes hkr 0: kein Fehler 1: Keine Adaptierung möglich. Gerät korrekt am Heizkörper montiert? 2: Ventilhub zu kurz oder Batterieleistung zu schwach. Ventilstößel per Hand mehrmals öfnen und schließen oder neue Batterien einsetzen. 3: Keine Ventilbewegung möglich. Ventilstößel frei? 4: Die Installation wird gerade vorbereitet. 5: Der Heizkörperregler ist im Installationsmodus und kann auf das Heizungsventil montiert werden. 6: Der Heizkörperregler passt sich nun an den Hub des Heizungsventils an. */ /* errorcodes blind alert state Beim Rollladen als Bitmaske auszuwerten. 0000 0000 - Es liegt kein Fehler vor. 0000 0001 - Hindernisalarm, der Rollladen wird gestoppt und ein kleines Stück in entgegengesetzte Richtung bewegt. 0000 0010 - Temperaturalarm, Motor überhitzt. */ /* functionbitmask Bit 0: HAN-FUN Gerät Bit 2: Licht/Lampe Bit 4: Alarm-Sensor Bit 5: AVM Button Bit 6: AVM Heizkörperregler Bit 7: AVM Energie Messgerät Bit 8: Temperatursensor Bit 9: AVM Schaltsteckdose Bit 10: AVM DECT Repeater Bit 11: AVM Mikrofon Bit 13: HAN-FUN-Unit Bit 15: an-/ausschaltbares Gerät/Steckdose/Lampe/Aktor Bit 16: Gerät mit einstellbarem Dimm-, Höhen- bzw. Niveau-Level Bit 17: Lampe mit einstellbarer Farbe/Farbtemperatur Bit 18: Rollladen(Blind) - hoch, runter, stop und level 0% bis 100 % Bit 20: Luftfeuchtigkeitssensor Die Bits 5,6,7,9,10 und 11 werden nur von FRITZ!-Geräten verwendet und nicht von HANFUN- oder Zigbee-Geräten. */ /* HANFUN unittypes 256 = SIMPLE_ON_OFF_SWITCHABLE 257 = SIMPLE_ON_OFF_SWITCH 262 = AC_OUTLET 263 = AC_OUTLET_SIMPLE_POWER_METERING 264 = SIMPLE_LIGHT 265 = DIMMABLE_LIGHT 266 = DIMMER_SWITCH 273 = SIMPLE_BUTTON 277 = COLOR_BULB 278 = DIMMABLE_COLOR_BULB 281 = BLIND 282 = LAMELLAR 512 = SIMPLE_DETECTOR 513 = DOOR_OPEN_CLOSE_DETECTOR 514 = WINDOW_OPEN_CLOSE_DETECTOR 515 = MOTION_DETECTOR 518 = FLOOD_DETECTOR 519 = GLAS_BREAK_DETECTOR 520 = VIBRATION_DETECTOR 640 = SIREN */ /* HANFUN interfaces 256 = ALERT 277 = KEEP_ALIVE 512 = ON_OFF 513 = LEVEL_CTRL 514 = COLOR_CTRL 516 = OPEN_CLOSE ? detected with blinds, different alert -> status bits? 517 = OPEN_CLOSE_CONFIG ? detected with blinds 768 = ? 772 = SIMPLE_BUTTON 1024 = SUOTA-Update */ /* modes of DECT500 supported/current_mode 0 = nothing, because OFF or not present 1 = HueSaturation-Mode 2 = 3 = 4 = Colortemperature-Mode 5 = */ const settings = { Username: '', Password: '', Url: '', options: {}, intervall: 300, boosttime: 5, windowtime: 5, tsolldefault: 23, exclude_templates: false, exclude_routines: false }; class Fritzdect extends utils.Adapter { /** * @param {Partial<utils.AdapterOptions>} [options={}] */ constructor(options) { super({ ...options, name: 'fritzdect' }); 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)); this.systemConfig = {}; this.fritz = null; this.boosttime = 5; this.windowtime = 5; this.tsolldefault = 23; this.updatePromise = null; } /** * Is called when databases are connected and adapter received configuration. */ async onReady() { // Initialize your adapter here try { // Load user settings settings.Username = this.config.fritz_user; settings.Password = this.config.fritz_pw; settings.Url = this.config.fritz_ip; //settings.options = this.config.fritz_options; settings.intervall = this.config.fritz_interval; settings.boosttime = this.boosttime = this.config.fritz_boosttime; settings.windowtime = this.windowtime = this.config.fritz_windowtime; settings.tsolldefault = this.tsolldefault = this.config.fritz_tsolldefault; settings.fritz_writeonhyst = this.fritz_writeonhyst = this.config.fritz_writeonhyst; settings.exclude_templates = this.exclude_templates = this.config.fritz_exclude_templates; settings.exclude_routines = this.exclude_routines = this.config.fritz_exclude_routines; settings.exclude_stats = this.exclude_stats = this.config.fritz_exclude_stats; // The adapters config (in the instance object everything under the attribute "native") is accessible via // this.config: this.log.info('fritzdect entered ready'); const sysConf = await this.getForeignObjectAsync('system.config'); if (sysConf && sysConf.common) { this.systemConfig = sysConf.common; } else { throw `ioBroker system configuration not found.`; } // jsonUI should transfer PW decrypted if (settings.Username !== '' && settings.Password !== '') { this.getForeignObject('system.config', async (err) => { // Adapter is alive, make API call // Make a call to fritzboxAPI and get a list devices/groups and templates this.fritz = new Fritz( settings.Username, settings.Password, settings.Url || '', settings.options || {} ); this.log.info('fritzdect uses USER: ' + settings.Username); try { const login = await this.fritz.login_SID().catch((e) => this.errorHandlerApi(e)); if (login) { this.log.info('checking user permissions'); const resp = await this.fritz.check_SID().catch((e) => this.errorHandlerApi(e)); // wird zu try/catch error if (resp) { this.log.debug('raw perm =>' + JSON.stringify(resp)); try { let rights = ''; if (resp.rights.indexOf('ights') == -1) { rights = parser.xml2json(''.concat('<Rights>', resp.rights, '</Rights>')); } else { rights = parser.xml2json(resp.rights); } this.log.info('the rights are : ' + JSON.stringify(rights)); } catch (error) { this.log.error('error in permission xml2json ' + error); } } this.log.info('start creating global values '); await this.createGlobal(); this.log.info('finished creating global values'); this.log.info('start creating devices/groups'); await this.createDevices(this.fritz).catch((e) => this.errorHandlerAdapter(e)); this.log.info('finished creating devices/groups (if any)'); const templinfo = settings.exclude_templates ? 'not used ' : 'used'; this.log.info('templates are ' + templinfo + '(' + settings.exclude_templates + ')'); if (!settings.exclude_templates) { this.log.info('start creating templates '); await this.createTemplates(this.fritz).catch((e) => this.errorHandlerAdapter(e)); this.log.info('finished creating templates (if any) '); } const routineinfo = settings.exclude_routines ? 'not used ' : 'used'; this.log.info('routines are ' + routineinfo + '(' + settings.exclude_routines + ')'); if (!settings.exclude_routines) { this.log.info('start creating routines '); await this.createRoutines(this.fritz).catch((e) => this.errorHandlerAdapter(e)); this.log.info('finished creating routines (if any) '); } this.log.info('start initial updating devices/groups'); await this.updateDevices(this.fritz).catch((e) => this.errorHandlerAdapter(e)); this.log.info('finished initial updating devices/groups'); if (!polling && settings.intervall > 0) { this.log.info( 'going over to cyclic polling, messages to poll activity only in debug-mode ' ); polling = setInterval(async () => { // poll fritzbox try { this.log.debug('polling! fritzdect is alive with ' + settings.intervall + ' s'); this.update(); } catch (e) { this.log.warn(`[Polling] <== ${e}`); } }, (settings.intervall || 300) * 1000); } } else { this.log.error('login not possible, check user and permissions'); } } catch (error) { //from login this.log.warn( 'catched error in onReady (most likely no connection to FB or wrong credentials)' + error ); } if (err) { this.log.error('error getting system.config ' + err); } }); } else { this.log.error( '*** Adapter running, but doing nothing, credentials missing in Adaptper Settings !!! ***' ); } // in this template all states changes inside the adapters namespace are subscribed this.subscribeStates('*'); } catch (error) { this.log.error('[asyncOnReady()]' + error); return; } } /** * Is called when adapter shuts down - callback has to be called under any circumstances! * @param {() => void} callback */ async onUnload(callback) { try { // Here you must clear all timeouts or intervals that may still be active // clearTimeout(timeout1); // clearTimeout(timeout2); // ... // clearInterval(interval1); if (polling) clearInterval(polling); // await this.fritz.logout_SID().catch((e) => this.errorHandlerApi(e)); this.log.info('cleaned everything up...'); callback(); } catch (e) { this.log.error(e); callback(); } } // If you need to react to object changes, uncomment the following block and the corresponding line in the constructor. // You also need to subscribe to the objects with `this.subscribeObjects`, similar to `this.subscribeStates`. // /** // * Is called if a subscribed object changes // * @param {string} id // * @param {ioBroker.Object | null | undefined} obj // */ // onObjectChange(id, obj) { // if (obj) { // // The object was changed // this.log.info(`object ${id} changed: ${JSON.stringify(obj)}`); // } else { // // The object was deleted // this.log.info(`object ${id} deleted`); // } // } /** * Is called if a subscribed state changes * @param {string} id * @param {ioBroker.State | null | undefined} state */ async onStateChange(id, state) { if (state) { // The state was changed this.log.debug(`onStateChange => state ${id} changed: ${state.val} (ack = ${state.ack})`); if (!this.fritz) { this.fritz = new Fritz( settings.Username, settings.Password, settings.moreParam || '', settings.strictSsl || true ); try { const login = await this.fritz.login_SID(); if (login) { this.log.debug('login in stateChange success'); } else { this.log.error('login not possible, check user and permissions'); } } catch (error) { this.errorHandlerApi(error); } } //const fritz = new Fritz(settings.Username, settings.Password, settings.moreParam || '', settings.strictSsl || true); // you can use the ack flag to detect if it is status (true) or command (false) if (state && !state.ack && state.val !== null && id !== null) { this.log.debug('ack is not set! -> command'); //hier noch eine Abfrage ob das Gerät present=false hat und Fehlermeldung das man Nichterreichbares Gerät bedienen wiil const tmp = id.split('.'); const dp = tmp.pop(); const idx = tmp.pop(); //is the name after fritzdect.x. // devices or groups if (idx && idx !== null) { if (idx.startsWith('DECT_')) { // braucht man nicht wenn kein toggle in devices vorkommt id = idx.replace(/DECT_/g, ''); //Thermostat this.log.info('DECT ID: ' + id + ' identified for command (' + dp + ') : ' + state.val); if (dp === 'tsoll') { if (state.val < 8) { //kann gelöscht werden, wenn Temperaturvorwahl nicht zur Moduswahl benutzt werden soll await this.setStateAsync('DECT_' + id + '.hkrmode', { val: 1, ack: false }); //damit das Ventil auch regelt await this.fritz .setTempTarget(id, 'off') .then(() => { this.log.debug('Switched Mode' + id + ' to closed'); }) .catch((e) => this.errorHandlerApi(e)); } else if (state.val > 28) { //kann gelöscht werden, wenn Temperaturvorwahl nicht zur Moduswahl benutzt werden soll await this.setStateAsync('DECT_' + id + '.hkrmode', { val: 2, ack: false }); //damit das Ventil auch regelt (false= Befehl und nochmaliger Einsprung ) await this.fritz .setTempTarget(id, 'on') .then(() => { this.log.debug('Switched Mode' + id + ' to opened permanently'); }) .catch((e) => this.errorHandlerApi(e)); } else { await this.setStateAsync('DECT_' + id + '.hkrmode', { val: 0, ack: false }); //damit das Ventil auch regelt await this.fritz .setTempTarget(id, state.val) .then(() => { this.log.debug('Set target temp ' + id + state.val + ' °C'); this.setStateAsync('DECT_' + id + '.lasttarget', { val: state.val, ack: true }); //iobroker Tempwahl wird zum letzten Wert gespeichert this.setStateAsync('DECT_' + id + '.tsoll', { val: state.val, ack: true }); //iobroker Tempwahl wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } } else if (dp === 'hkrmode') { if (state.val === 0) { const targettemp = await this.getStateAsync('DECT_' + id + '.tsoll').catch((e) => { this.log.warn('problem getting the tsoll status ' + e); }); // oder hier die Verwendung von lasttarget if (targettemp && targettemp.val !== null) { if (targettemp.val) { let setTemp = targettemp.val; if (setTemp < 8) { await this.setStateAsync('DECT_' + id + '.tsoll', { val: 8, ack: true }); setTemp = 8; } else if (setTemp > 28) { await this.setStateAsync('DECT_' + id + '.tsoll', { val: 28, ack: true }); setTemp = 28; } await this.fritz .setTempTarget(id, setTemp) .then(() => { this.log.debug('Set target temp ' + id + ' ' + setTemp + ' °C'); this.setStateAsync('DECT_' + id + '.tsoll', { val: setTemp, ack: true }); //iobroker Tempwahl wird nochmal als Status geschrieben, da API-Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.operationmode', { val: 'Auto', ack: true }); //iobroker setzen des operationmode, da API Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } else { this.log.error('no data in targettemp for setting mode'); } } else { throw { error: ' targettemp is NULL ' }; } } else if (state.val === 1) { await this.fritz .setTempTarget(id, 'off') .then(() => { this.log.debug('Switched Mode' + id + ' to closed.'); this.setStateAsync('DECT_' + id + '.operationmode', { val: 'Off', ack: true }); //iobroker setzen des operationmode, da API Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } else if (state.val === 2) { await this.fritz .setTempTarget(id, 'on') .then(() => { this.log.debug('Switched Mode' + id + ' to opened permanently'); this.setStateAsync('DECT_' + id + '.operationmode', { val: 'On', ack: true }); //iobroker setzen des operationmode, da API Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } } //no need to check the state.val, it is a button if (dp === 'setmodeauto') { //zurücksetzen wegen toggle/button click await this.setStateAsync('DECT_' + id + '.setmodeauto', { val: false, ack: true }); const targettemp = await this.getStateAsync('DECT_' + id + '.tsoll').catch((e) => { this.log.warn('problem getting the tsoll status ' + e); }); // oder hier die Verwendung von lasttarget if (targettemp && targettemp.val !== null) { if (targettemp.val) { let setTemp = targettemp.val; if (setTemp < 8) { await this.setStateAsync('DECT_' + id + '.tsoll', { val: 8, ack: true }); setTemp = 8; } else if (setTemp > 28) { await this.setStateAsync('DECT_' + id + '.tsoll', { val: 28, ack: true }); setTemp = 28; } this.fritz .setTempTarget(id, setTemp) .then(() => { this.log.debug('Set target temp ' + id + ' ' + setTemp + ' °C'); this.setStateAsync('DECT_' + id + '.tsoll', { val: setTemp, ack: true }); //iobroker Tempwahl wird nochmal als Status geschrieben, da API-Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.operationmode', { val: 'Auto', ack: true }); //iobroker setzen des operationmode, da API Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.hkrmode', { val: 0, ack: true }); //iobroker setzen des hkrmode, da API Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } else { this.log.error('no data in targettemp for setting mode'); } } else { throw { error: ' targettemp is NULL ' }; } } if (dp === 'setmodeoff') { //zurücksetzen wegen toggle/button click await this.setStateAsync('DECT_' + id + '.setmodeoff', { val: false, ack: true }); await this.fritz .setTempTarget(id, 'off') .then(() => { this.log.debug('Switched Mode' + id + ' to closed.'); this.setStateAsync('DECT_' + id + '.operationmode', { val: 'Off', ack: true }); //iobroker setzen des operationmode, da API Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.hkrmode', { val: 1, ack: true }); //iobroker setzen des hkrmode, da API Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } if (dp === 'setmodeon') { //zurücksetzen wegen toggle/button click await this.setStateAsync('DECT_' + id + '.setmodeon', { val: false, ack: true }); await this.fritz .setTempTarget(id, 'on') .then(() => { this.log.debug('Switched Mode' + id + ' to opened permanently'); this.setStateAsync('DECT_' + id + '.operationmode', { val: 'On', ack: true }); //iobroker setzen des operationmode, da API Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.hkrmode', { val: 2, ack: true }); //iobroker setzen des hkrmode, da API Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } if (dp == 'boostactivetime') { this.log.debug( 'Nothing to send external, but the boost active time was defined for ' + state.val + ' min' ); } if (dp == 'boostactive') { if ( state.val === 0 || state.val === '0' || state.val === 'false' || state.val === false || state.val === 'off' || state.val === 'OFF' ) { this.fritz .setHkrBoost(id, 0) .then(() => { this.log.debug('Reset thermostat boost ' + id + ' to ' + state.val); this.setStateAsync('DECT_' + id + '.boostactive', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich //kein pauschales Setzen des Operationmode, da unbekannt wohin es dann geht const convTime = new Date(0); this.setStateAsync('DECT_' + id + '.boostactiveendtime', { val: String(convTime), ack: true }); }) .catch((e) => this.errorHandlerApi(e)); } else if ( state.val === 1 || state.val === '1' || state.val === 'true' || state.val === true || state.val === 'on' || state.val === 'ON' ) { const minutes = await this.getStateAsync( 'DECT_' + id + '.boostactivetime' ).catch((error) => { this.log.warn('DECT_' + +id + '.boostactivetime did not get state -> ' + error); }); if (minutes && minutes.val !== null) { let activetime = minutes.val; const jetzt = +new Date(); if (minutes.val > 1440) { activetime = 1440; } const ende = Math.floor(jetzt / 1000 + Number(activetime) * 60); //time for fritzbox is in seconds this.log.debug(' unix returned ' + ende + ' real ' + new Date(ende * 1000)); this.fritz .setHkrBoost(id, ende) .then((body) => { const endtime = new Date(Math.floor(body * 1000)); this.log.debug('window ' + body + ' reading to ' + endtime); this.log.debug( 'Set thermostat boost ' + id + ' to ' + state.val + ' until calculated ' + ende + ' ' + new Date(ende * 1000) ); this.setStateAsync('DECT_' + id + '.boostactive', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.boostactiveendtime', { val: String(endtime), ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.operationmode', { val: 'Boost', ack: true }); //iobroker setzen des operationmode, da API Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } else { throw { error: 'minutes were NULL' }; } } } if (dp == 'windowopenactivetime') { this.log.debug( 'Nothing to send external, but the window open active time was defined for ' + state.val + ' min' ); } if (dp == 'windowopenactiv') { if ( state.val === 0 || state.val === '0' || state.val === 'false' || state.val === false || state.val === 'off' || state.val === 'OFF' ) { this.fritz .setWindowOpen(id, 0) .then(() => { this.log.debug('Reset thermostat windowopen ' + id + ' to ' + state.val); this.setStateAsync('DECT_' + id + '.windowopenactiv', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich //keine Nachführung operationmode, da unbekannt wohin es geht const convTime = new Date(0); this.setStateAsync('DECT_' + id + '.windowopenactiveendtime', { val: String(convTime), ack: true }); }) .catch((e) => this.errorHandlerApi(e)); } else if ( state.val === 1 || state.val === '1' || state.val === 'true' || state.val === true || state.val === 'on' || state.val === 'ON' ) { const minutes = await this.getStateAsync( 'DECT_' + id + '.windowopenactivetime' ).catch((error) => { this.log.warn( 'DECT_' + +id + '.windowopenactivetime did not get state -> ' + error ); }); if (minutes && minutes.val !== null) { let activetime = minutes.val; const jetzt = +new Date(); if (minutes.val > 1440) { activetime = 1440; } const ende = Math.floor(jetzt / 1000 + Number(activetime) * 60); //time for fritzbox is in seconds this.log.debug(' unix ' + ende + ' real ' + new Date(ende * 1000)); this.fritz .setWindowOpen(id, ende) .then((body) => { const endtime = new Date(Math.floor(body * 1000)); this.log.debug('window ' + body + ' reading to ' + endtime); this.log.debug( 'Set thermostat windowopen ' + id + ' to ' + state.val + ' until calculated ' + ende + ' ' + new Date(ende * 1000) ); this.setStateAsync('DECT_' + id + '.windowopenactiv', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.windowopenactiveendtime', { val: String(endtime), ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich this.setStateAsync('DECT_' + id + '.operationmode', { val: 'WindowOpen', ack: true }); //iobroker setzen des operationmode, da API Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } else { throw { error: 'minutes were NULL' }; } } } // setswitch reicht scheinbar nicht bei simpleonoff, hier müsste irgendwie unterschieden werden ob DECT200 switch/state oder simpleonoff/state if (dp == 'state') { if ( state.val === 0 || state.val === '0' || state.val === 'false' || state.val === false || state.val === 'off' || state.val === 'OFF' ) { const switchtyp = await this.getStateAsync( 'DECT_' + id + '.switchtype' ).catch((error) => { this.log.warn('DECT_' + +id + '.switchtype did not get state -> ' + error); }); if (switchtyp && switchtyp.val !== null) { if (switchtyp.val === 'switch') { this.fritz .setSwitchOff(id) .then(() => { this.log.debug('Turned switch ' + id + ' off'); this.setStateAsync('DECT_' + id + '.state', { val: false, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } else { this.fritz .setSimpleOff(id) .then(() => { this.log.debug('Turned switch ' + id + ' off'); this.setStateAsync('DECT_' + id + '.state', { val: false, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } } else { throw { error: 'could not determine the type of switch (switch/simpleonoff)' }; } } else if ( state.val === 1 || state.val === '1' || state.val === 'true' || state.val === true || state.val === 'on' || state.val === 'ON' ) { const switchtyp = await this.getStateAsync( 'DECT_' + id + '.switchtype' ).catch((error) => { this.log.warn('DECT_' + +id + '.switchtype did not get state -> ' + error); }); if (switchtyp && switchtyp.val !== null) { if (switchtyp.val === 'switch') { this.fritz .setSwitchOn(id) .then(() => { this.log.debug('Turned switch ' + id + ' on'); this.setStateAsync('DECT_' + id + '.state', { val: true, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } else { this.fritz .setSimpleOn(id) .then(() => { this.log.debug('Turned switch ' + id + ' on'); this.setStateAsync('DECT_' + id + '.state', { val: true, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } } else { throw { error: 'could not determine the type of switch (switch/simpleonoff)' }; } } } if (dp == 'blindsclose') { this.fritz .setBlind(id, 'close') .then(async () => { this.log.debug('Started blind ' + id + ' to close'); await this.setStateAsync('DECT_' + id + '.blindsclose', { val: false, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } if (dp == 'blindsopen') { this.fritz .setBlind(id, 'open') .then(async () => { this.log.debug('Started blind ' + id + ' to open'); await this.setStateAsync('DECT_' + id + '.blindsopen', { val: false, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } if (dp == 'blindsstop') { this.fritz .setBlind(id, 'stop') .then(() => { this.log.debug('Set blind ' + id + ' to stop'); this.setStateAsync('DECT_' + id + '.blindsstop', { val: false, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } if (dp == 'level') { this.fritz .setLevel(id, state.val) .then(() => { this.log.debug('Set level' + id + ' to ' + state.val); this.setStateAsync('DECT_' + id + '.level', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } if (dp == 'levelpercentage') { this.fritz .setLevel(id, Math.floor(Number(state.val) / 100 * 255)) .then(() => { //level is in 0...255 this.log.debug('Set level %' + id + ' to ' + state.val); this.setStateAsync('DECT_' + id + '.levelpercentage', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } if (dp == 'hue') { const saturation = await this.getStateAsync('DECT_' + id + '.saturation').catch((error) => { this.log.warn('DECT_' + +id + '.saturation did not get state -> ' + error); }); if (saturation && saturation.val !== null) { // oder hier die Verwendung von lasttarget const setSaturation = saturation.val; if (setSaturation == '') { this.log.error( 'No saturation value exists when setting hue, please set saturation to a value ' ); } else { this.fritz .setColor(id, setSaturation, state.val) .then(() => { this.log.debug( 'Set lamp color hue ' + id + ' to ' + state.val + ' and saturation of ' + setSaturation ); this.setStateAsync('DECT_' + id + '.hue', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } } else { throw { error: 'minutes were NULL' }; } } if (dp == 'saturation') { const hue = await this.getStateAsync('DECT_' + id + '.hue').catch((error) => { this.log.warn('DECT_' + +id + '.hue did not get state -> ' + error); }); if (hue && hue.val !== null) { const setHue = hue.val; if (setHue == '') { this.log.error( 'No hue value exists when setting saturation, please set hue to a value ' ); } else { this.fritz .setColor(id, state.val, setHue) .then(() => { this.log.debug( 'Set lamp color saturation ' + id + ' to ' + state.val + ' and hue of ' + setHue ); this.setStateAsync('DECT_' + id + '.saturation', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } } else { throw { error: 'hue were NULL' }; } } if (dp == 'temperature') { this.fritz .setColorTemperature(id, state.val) .then(() => { this.log.debug('Set lamp color temperature ' + id + ' to ' + state.val); this.setStateAsync('DECT_' + id + '.temperature', { val: state.val, ack: true }); //iobroker State-Bedienung wird nochmal als Status geschrieben, da API-Aufruf erfolgreich }) .catch((e) => this.errorHandlerApi(e)); } } else if (idx.startsWith('template_')) { //must be fritzbox template id = idx.replace(/template_/g, ''); //template this.log.info('Template ID: ' + id + ' identified for command (' + dp + ') : ' + state.val); if (dp == 'toggle') { if ( state.val === 1 || state.val === '1' || state.val === 'true' || state.val === true || state.val === 'on' || state.val === 'ON' ) { this.fritz .applyTemplate(id) .then((sid) => { this.log.debug('cmd Toggle to template ' + id + ' on'); this.log.debug('response ' + sid); this.setStateAsync('template.lasttemplate', { val: sid, ack: true }); //when successfull toggle, the API returns the id of the template }) .catch((e) => this.errorHandlerApi(e)); } } } else if (idx.startsWith('routine_')) { //must be fritzbox routine id = idx.replace(/routine_/g, ''); //routine this.log.info('Routine ID: ' + id + ' identified for command (' + dp + ') : ' + state.val); if (dp == 'active') { if ( state.val === 1 || state.val === '1' || state.val === 'true' || state.val === true || state.val === 'on' || state.val === 'ON' ) { state.val = true; } this.fritz .setTriggerActive(id, state.val) .then((sid) => { this.log.debug('cmd Active to template ' + id + ' to ' + state.val); this.log.debug('response ' + sid); }) .catch((e) => this.errorHandlerApi(e)); } } } } //from if state&ack } else { // The state was deleted this.log.info(`state ${id} deleted`); } } // If you need to accept messages in your adapter, uncomment the following block and the corresponding line in the constructor. // /** // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ... // * Using this method requires "common.messagebox" property to be set to true in io-package.json // * @param {ioBroker.Message} obj // */ async onMessage(obj) { let wait = false; this.log.debug('messagebox received ' + JSON.stringify(obj)); try { if (typeof obj === 'object' && obj.message) { // if (obj) { if (obj.command === 'test') { // e.g. send email or pushover or whatever this.log.debug('msg with obj.command for test received'); // Send response in callback if required if (obj.callback) this.sendTo( obj.from, obj.command, 'Message received (sendTo works). This is not an indication that FB is reachable!', obj.callback ); } } else if (obj) { //my own messages for detectiung are without a message let result = ''; if (!this.fritz) { this.fritz = new Fritz( settings.Username, settings.Password, settings.moreParam || '', settings.strictSsl || true ); try { const login = await this.fritz.login_SID(); if (login) { this.log.debug('login in stateChange success'); } else { this.log.error('login not possible, check user and permissions'); } } catch (error) { this.errorHandlerApi(error); } } // const fritz = new Fritz(settings.Username, settings.Password, settings.moreParam || '', settings.strictSsl || true); let statfeedback = {}; switch (obj.command) { case 'update': try { await this.update(); if (obj.callback) { this.sendTo(obj.from, obj.command, { result: true }, obj.callback); } } catch (error) { this.log.warn('unable to get manual updates' + error); if (obj.callback) { this.sendTo( obj.from, obj.command, { result: false, error: 'unable to get manual updates' + error }, obj.callback ); } } wait = true; break; case 'devices': try { let xml = await this.fritz.getDeviceListInfos(); this.log.debug('devices' + xml); result = xml; if (obj.callback) { this.sendTo(obj.from, obj.command, { error: result }, obj.callback); } } catch (error) { this.log.warn('error calling in msgbox devices' + error); if (obj.callback) { this.sendTo( obj.from, obj.command, { error: 'unable to get devices' + error }, obj.callback ); } } wait = true; break; case 'groups': //eigentlich jetzt mit devices zusammen, da xml nicht geteilt wird try { let xml = await this.fritz.getDeviceListInfos(); this.log.debug('groups' + xml); result = xml; if (obj.callback) { this.sendTo(obj.from, obj.command, { error: result }, obj.callback); } } catch (error) { this.log.warn('error calling in msgbox groups' + error); if (obj.callback) { this.sendTo( obj.from, obj.command, { error: 'unable to get groups' + error }, obj.callback ); } } wait = true; break; case 'templates': try { let xml = await this.fritz.getTemplateListInfos(); this.log.debug('templates' + xml); result = xml; if (obj.callback) { this.sendTo(obj.from, obj.command, { error: result }, obj.callback); } } catch (error) { this.log.warn('error calling in msgbox templates' + error); if (obj.callback) { this.sendTo( obj.from, obj.command, { error: 'unable to get templates' + error }, obj.callback ); } } wait = true; break; case 'trigger': try { let xml = await this.fritz.getTriggerListInfos(); this.log.debug('trigger' + xml); result = xml; if (obj.callback) { this.sendTo(obj.from, obj.command, { error: result }, obj.callback); } } catch (error) { this.log.warn('error calling in msgbox trigger' + error); if (obj.callback) { this.sendTo( obj.from, obj.command, { error: 'unable to get trigger' + error }, obj.callback ); } } wait = true; break; case 'statistic': try { const deviceswithstat = await this.getStateAsync('global.statdevices').catch((e) => { this.log.warn('problem getting statdevices ' + e); }); if (deviceswithstat && deviceswithstat.val) { this.log.debug('msg statistics ' + deviceswithstat.val); let devstat = [].concat([], JSON.parse(String(deviceswithstat.val))); for (let i = 0; i < devstat.length; i++) { let stats = await this.fritz.getBasicDeviceStats(devstat[i]).catch((e) => { this.log.debug('error calling in msgbox'); throw { msg: 'issue getting statistics', function: 'onMessage', error: e }; }); this.log.debug('processed ' + devstat[i]); statfeedback[devstat[i]] = stats; } } result = JSON.stringify(statfeedback); if (obj.callback) { this.sendTo(obj.from, obj.command, { error: result }, obj.callback); } } catch (error) { this.log.warn('error calling in msgbox statistic' + error); if (obj.callback) { this.sendTo( obj.from, obj.command, { error: 'unable to get statistic' + error }, obj.callback ); } } wait = true; break; case 'color': try { let xml = await this.fritz.getColorDefaults(); this.log.debug('color' + xml); result = xml; if (obj.callback) { this.sendTo(obj.from, obj.command, { error: result }, obj.callback); } } catch (error) { this.log.warn('error calling in msgbox color' + error); if (obj.callback) { this.sendTo( obj.from, obj.command, { error: 'unable to get color' + error }, obj.callback ); } } wait = true; break; case 'rights': try { let xml = await this.fritz.getUserPermissions(); this.log.debug('rights' + xml); result = xml; if (obj.callback) { this.sendTo(obj.from, obj.command, { error: result }, obj.callback); } } catch (error) { this.log.warn('error calling in msgbox rights' + error); if (obj.callback) { this.sendTo( obj.from, obj.command, { error: 'unable to get rights' + error }, obj.callback ); } } wait = true; break; //idea for other statistics: call of message returns everything (loop over all devices) default: this.log.warn('Received unhandled message: ' + obj.command); break; } } if (!wait && obj.callback) { this.log.debug('messagebox landed in last evaluation wait=false and callback'); this.sendTo(obj.from, obj.command, obj.message, obj.callback); } } catch (e) { this.log.debug('try/catch messagebox error occured ' + e); } } decryptfc(key, value) { let result = ''; for (let i = 0; i < value.length; ++i) { result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i)); } return result; } errorHandlerApi(error) { try { this.log.error('--------------- error calling the fritzbox -----------'); this.log.error('API msg => ' + error.msg); this.log.error('API funct => ' + error.function); if (error == '0000000000000000') { this.log.error('Did not get session id -> invalid username or password?'); } else if (!error.response) { this.log.error('no response part in returned error message'); } else if (error.response.statusCode) { if (error.response.statusCode == 403) { this.log.error( 'no permission for this call (403), has user all the rights and access to fritzbox?' ); } else if (error.response.statusCode == 404) { this.log.error('call to API does not exist! (404)'); } else if (error.response.statusCode == 400) { this.log.error('bad request (400), ain correct?'); } else if (error.response.statusCode == 500) { this.log.error('internal fritzbox error (500)'); } else if (error.response.statusCode == 503) { this.log.error('service unavailable (503)'); } else if (error.response.statusCode == 303) { this.log.error('unknwon error (303)'); } else { this.log.error('statuscode not in errorHandlerApi of fritzdect'); } } this.log.error('API err => ' + JSON.stringify(error.error)); } catch (e) { this.log.error('catched error in function errorHandlerApi() ' + e); } } errorHandlerAdapter(error) { try { this.log.error('--------------- error calling the fritzbox -----------'); this.log.error('iob err => ' + error); //this.log.error('iob msg => ' + error.msg); //this.log.error('iob funct => ' + error.function); //this.log.error('iob err => ' + error.error); } catch (e) { this.log.error('try/catch error in function errorHandlerAdapter' + e); } } /** * @param {any[]} devicearray */ unifyDevicesUnits(devicearray) { let id = []; let etsiunit = []; let etsidelete = []; for (let i = 0; i < devicearray.length; i++) { id.push(devicearray[i].id); //if there is no identifier, then the dataset is useless (issue #598) if (!devicearray[i].identifier) { etsidelete.push(i); } if (devicearray[i]['etsiunitinfo']) { //prepare array with etsi units for later merge with etsidevice etsiunit.push(i); //setting the role of device if (Number(devicearray[i].etsiunitinfo.unittype) > 510) { devicearray[i]['role'] = 'sensor'; } else if (Number(devicearray[i].etsiunitinfo.unittype) > 280) { devicearray[i]['role'] = 'blinds'; } else if (Number(devicearray[i].etsiunitinfo.unittype) == 273) { devicearray[i]['role'] = 'sensor'; } else if (Number(devicearray[i].etsiunitinfo.unittype) > 263) { devicearray[i]['role'] = 'light'; } else if (Number(devicearray[i].etsiunitinfo.unittype) > 255) { devicearray[i]['role'] = 'switch'; } } else { //setting the role of device if (devicearray[i].switch) { devicearray[i]['role'] = 'switch'; } else if (devicearray[i].hkr) { devicearray[i]['role'] = 'thermo.heat'; } else if (devicearray[i].blind) { devicearray[i]['role'] = 'blinds'; } else if (devicearray[i].colortemperature) { devicearray[i]['role'] = 'light'; } else if (devicearray[i].levelcontrol) { devicearray[i]['role'] = 'light'; } else if (devicearray[i].temperature) { devicearray[i]['role'] = 'thermo'; } else if (devicearray[i].button) { devicearray[i]['role'] = 'sensor'; } else if (devicearray[i].alert) { devicearray[i]['role'] = 'sensor'; } else { devicearray[i]['role'] = 'etsi'; } //setting the switchtype if (devicearray[i].switch) { devicearray[i]['switchtype'] = 'switch'; } else if (devicearray[i].simpleonoff) { devicearray[i]['switchtype'] = 'simpleonoff'; } } } for (let etsiunitpos of etsiunit) { //find the matching etsidevice for etsiunit let etsidevpos = id.indexOf(devicearray[etsiunitpos]['etsiunitinfo']['etsideviceid']); //prepare array for deletion of etsidevices if (etsidelete.indexOf(etsidevpos) === -1 && etsidevpos !== -1) { etsidelete.push(etsidevpos); //merge etsidevice info into etsiunit for (let item in devicearray[etsidevpos]) { if (item !== 'id' && item !== 'identifier' && item !== 'functionbitmask' && item !== 'role') { devicearray[etsiunitpos][item] = devicearray[etsidevpos][item]; } } } } etsidelete.sort(); //delete the etsidevices for (let k = 0; k < etsidelete.length; k++) { devicearray.splice(etsidelete[k] - k, 1); } return devicearray; } async update() { if (!this.updatePromise) { this.updatePromise = this._update().finally(() => { this.updatePromise = null; }); } return this.updatePromise; } async _update() { await this.updateDevices(this.fritz).catch((e) => this.errorHandlerAdapter(e)); if (!settings.exclude_routines) { await this.updateRoutines(this.fritz).catch((e) => this.errorHandlerAdapter(e)); } if (!settings.exclude_stats) { const deviceswithstat = await this.getStateAsync('global.statdevices').catch((e) => { this.log.warn('problem getting statdevices ' + e); }); if (deviceswithstat && deviceswithstat.val) { this.log.debug('glob state ' + deviceswithstat.val); let devstat = [].concat([], JSON.parse(String(deviceswithstat.val))); for (let i = 0; i < devstat.length; i++) { this.log.debug('updating Stats of device ' + devstat[i]); await this.updateStats(devstat[i], this.fritz); } } } } async updateRoutines(fritz) { this.log.debug('__________________________'); this.log.debug('updating Routines '); try { const routineslistinfos = await fritz.getTriggerListInfos().catch((e) => this.errorHandlerApi(e)); //let typ = ''; //let role = ''; if (routineslistinfos) { let routines = parser.xml2json(routineslistinfos); routines = [].concat((routines.triggerlist || {}).trigger || []).map((trigger) => { return trigger; }); this.log.debug('__________________________'); this.log.debug('routines\n'); this.log.debug(JSON.stringify(routines)); if (routines.length) { this.log.debug('update routines ' + routines.length); await Promise.all( routines.map(async (routine) => { let active = routine.active == 0 ? false : true; let old = await this.getStateAsync( 'routine_' + routine.identifier.replace(/\s/g, '') + '.active' ).catch((error) => { this.log.warn( 'problem getting routine_' + routine.identifier.replace(/\s/g, '') +