UNPKG

iobroker.fb-checkpresence

Version:

The adapter checks the presence of family members over the fritzbox. You must fill in the name of the family member and the mac-address of the used device. The comment is optional and you can enable or disable the family member. The datapoint based on the

1,077 lines (1,019 loc) 138 kB
'use strict'; /* * Created with @iobroker/create-adapter vunknown */ // The adapter-core module gives you access to the core ioBroker functions const utils = require('@iobroker/adapter-core'); // load your modules here, e.g.: //const dateFormat = require('dateformat'); // own libraries const fb = require('./lib/fb'); const obj = require('./lib/objects'); const dateFormat = require('./lib/dateformat/dateformat'); class Warn extends Error { constructor(message) { super(message); this.name = 'Warning'; this.toString = function () { return `${this.name}: ${this.message}`; }; } } class FbCheckpresence extends utils.Adapter { /** * @param {Partial<utils.AdapterOptions>} options - adapter options */ constructor(options) { super({ ...options, name: 'fb-checkpresence', }); //events this.on('ready', this.onReady.bind(this)); //this.on('objectChange', this.onObjectChange); this.on('stateChange', this.onStateChange.bind(this)); this.on('message', this.onMessage.bind(this)); this.on('unload', this.onUnload.bind(this)); //constants this.urn = 'urn:dslforum-org:service:'; this.HTML = '<table class="mdui-table"><thead><tr><th>Name</th><th>Status</th><th>Kommt</th><th>Geht</th></tr></thead><tbody>'; this.HTML_HISTORY = '<table class="mdui-table"><thead><tr><th>Status</th><th>Date</th></tr></thead><tbody>'; this.HTML_END = '</body></table>'; this.HTML_GUEST = '<table class="mdui-table"><thead><tr><th>Hostname</th><th>IPAddress</th><th>MACAddress</th></tr></thead><tbody>'; this.HTML_FB = '<table class="mdui-table"><thead><tr><th>Hostname</th><th>IPAddress</th><th>MACAddress</th><th>Active</th><th>Type</th></tr></thead><tbody>'; this.FORBIDDEN_CHARS = /[\][.*,;'"`<>\\?\s]+/g; this.errorCntMax = 10; this.allDevices = []; this.adapterStates = []; this.memberStates = []; this.historyAlive = { val: 'false' }; //false; this.hosts = null; this.jsonTab; this.htmlTab; this.enabled = true; this.errorCnt = 0; this.Fb = null; this.tout = null; this.suppressMesg = false; this.suppressMesgEmptyHostname = false; this.suppressArr = []; this.triggerActive = false; } /** * @param {any | string} error - error object * @param {string} title - error name */ errorHandler(error, title) { //if(adapter === null) adapter = this; if (typeof error === 'string') { if (error === undefined) { this.log.warn(`${title} error unknown`); } else { this.log.warn(`${title}: ${error}`); } } else if (typeof error === 'object') { if (error instanceof TypeError) { if (error.name === undefined || error.message === undefined) { this.log.warn(title); // + ' ' + JSON.stringify(error)); } else { this.log.warn(`${title} ${error.name}: ${error.message}`); } } else if (error instanceof Error) { if (error.message == 'NoSuchEntryInArray' && this.suppressMesg == false) { this.log.warn( `${title} ${error.name}: ${ error.message } -> please check entry (mac, ip, hostname) in configuration! It is not listed in the Fritzbox device list`, ); this.suppressMesg = true; } if (error.message.includes('EHOSTUNREACH')) { this.log.warn( `${title} ${error.name}: ${ error.message } -> please check fritzbox connection! Ip-address in configuration correct?`, ); } if (error.message != 'NoSuchEntryInArray' && !error.message.includes('EHOSTUNREACH')) { if (error.name === undefined || error.message === undefined) { this.log.warn(title); // + ' ' + JSON.stringify(error)); } else { this.log.warn(`${title} ${error.name}: ${error.message}`); } } } else { this.log.warn(`${title} ${error.name}: ${error.message}`); } } } /** * @param {number | undefined} milliseconds - delay in milli seconds */ _sleep(milliseconds) { return new Promise(resolve => setTimeout(resolve, milliseconds)); } createHTMLTableRow(dataArray) { return `<tr>${dataArray.map(data => `<td>${data}</td>`).join('')}</tr>`; } getAdapterState(id) { try { const ind = this.adapterStates.findIndex(x => x.id == id); if (ind != -1) { return true; } return false; } catch (error) { this.errorHandler(error, 'getAdapterState: '); } } /** * @param {string | any[]} items - fb-device objects of the adapter */ async resyncFbObjects(items) { try { // Get all fb-device objects of this adapter if (items && this.config.syncfbdevices == true) { const devices = await this.getDevicesAsync(); for (const id in devices) { if (devices[id] != undefined && devices[id].common != undefined) { const dName = devices[id].common.name; const shortName = dName.replace('fb-devices.', ''); let found = false; if (dName.includes('fb-devices.')) { for (let i = 0; i < items.length; i++) { let hostName = items[i]['HostName']; hostName = hostName.replace(this.FORBIDDEN_CHARS, '-'); if (shortName == hostName) { found = true; break; } } if (found == false && !dName.includes('whitelist')) { const states = await this.getStatesAsync(`${dName}.*`); for (const idS in states) { await this.delObjectAsync(idS); await this.delStateAsync(idS); } const ch = await this.getChannelsOfAsync(); for (const c in ch) { if (ch[c]._id.includes(`${dName}.`)) { await this.delObjectAsync(ch[c]._id); } } await this.delObjectAsync(devices[id]._id); this.log.info(`device <${devices[id]._id}> successfully deleted`); } } } } const adapterObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); if (adapterObj) { adapterObj.native.syncfbdevices = false; this.config.syncfbdevices = false; await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, adapterObj); this.log.info('fb-devices synchronized successfully'); } else { this.log.info('resyncFbObjects: could not clear the resync checkbox! '); } } } catch (error) { this.errorHandler(error, 'resyncFbObjects: '); } } async checkDevices() { try { if (this && this.Fb && (this.config.fbdevices === true || this.config.guestinfo === true)) { await this.Fb.getDeviceList(); if (this.Fb && this.Fb.deviceList) { if (this.config.fbdevices === true) { this.setDeviceStates(); } await this.getAllFbObjects(); if (this.hosts) { //this.log.warn('devicelist2: ' + JSON.stringify(this.hosts)); if (this.config.enableWl == true) { await this.getWlBlInfo(); } await this.getDeviceInfo(); if ( this.Fb.GETMESHPATH != null && this.Fb.GETMESHPATH == true && this.config.meshinfo == true ) { await this.Fb.getMeshList(); } if (this.Fb.meshList && this.config.meshinfo == true) { await this.getMeshInfo(); } } } } } catch (error) { this.errorHandler(error, 'checkDevices: '); } } async connCheck() { try { if (this && this.Fb && (await this.Fb.connectionCheck())) { await this.setStateChangedAsync('info.connection', { val: true, ack: true }); } else { await this.setStateChangedAsync('info.connection', { val: false, ack: true }); } await this.setStateChangedAsync('info.lastUpdate', { val: new Date().toString(), ack: true }); } catch (error) { this.errorHandler(error, 'connCheck: '); } } /** * @param {number} cnt1 - loop counter family members * @param {number} cnt2 - loop counter fb-devices * @param {number | undefined} int1 - max count family members * @param {number | undefined} int2 - max count fb-devices */ async loop(cnt1, cnt2, int1, int2) { while (this.enabled === true) { this.tout = setTimeout(() => { this.log.error('cycle error! Adapter restarted'); this.startAdapter(); }, 300000); let time = null; let work = null; try { await this._sleep(1000); cnt1++; cnt2++; work = process.hrtime(); cnt1 < cnt2 && cnt1 >= int1 ? await this.connCheck() : cnt2 >= int2 ? await this.connCheck() : null; if (cnt1 >= int1) { cnt1 = 0; await this.checkPresence(false); time = process.hrtime(work); this.log.debug(`loop family ends after ${time} s`); } if (cnt2 >= int2) { cnt2 = 0; if (this.config.extip === true) { await this.setStateChangedAsync('info.extIp', { val: await this.Fb.getExtIp(), ack: true }); await this.setStateChangedAsync('info.extIpv6', { val: await this.Fb.getExtIpv6(), ack: true }); await this.setStateChangedAsync('info.extIpv6Prf', { val: await this.Fb.getExtIpv6Prefix(), ack: true, }); } if (this.config.guestinfo === true) { await this.setStateChangedAsync('guest.wlan', { val: await this.Fb.getGuestWlan(), ack: true }); } if (this.config.qrcode === true) { await this.setStateChangedAsync('guest.wlanQR', { val: await this.Fb.getGuestQR(), ack: true }); } if (this.Fb.GETPATH != null && this.Fb.GETPATH == true) { this.checkDevices(); } time = process.hrtime(work); this.log.debug(`loop main ends after ${time} s`); } } catch (error) { this.errorHandler(error, 'loop: '); } finally { clearTimeout(this.tout); this.tout = null; } } } async setDeviceStates() { try { const deviceList = this.Fb.deviceList; if (this.config.compatibility == true) { await this.setStateChangedAsync('devices', { val: deviceList.length, ack: true }); } await this.setStateChangedAsync('fb-devices.count', { val: deviceList.length, ack: true }); let inActiveHosts = deviceList.filter(host => host.Active == '0'); await this.setStateChangedAsync('fb-devices.inactive', { val: inActiveHosts.length, ack: true }); inActiveHosts = null; let activeHosts = deviceList.filter(host => host.Active == '1'); await this.setStateChangedAsync('fb-devices.active', { val: activeHosts.length, ack: true }); if (this.config.compatibility == true) { await this.setStateChangedAsync('activeDevices', { val: activeHosts.length, ack: true }); } activeHosts = null; } catch (error) { this.errorHandler(error, 'setDeviceStates: '); } } async getAllFbObjects() { try { const items = this.Fb.deviceList; if (items === null || items === false) { return null; } // Get all fb-device objects of this adapter let hosts = []; let devices = await this.getDevicesAsync(); let fbDevices = devices.filter(x => x._id.includes(`${this.namespace}` + '.fb-devices.')); for (let i = 0; i < items.length; i++) { let hostName = items[i]['HostName']; hostName = hostName.replace(this.FORBIDDEN_CHARS, '-'); if (hostName === null || hostName == '') { if (this.suppressMesgEmptyHostname === false) { this.log.warn( `getAllFbObjects: Hostname of fritzbox device ${items[i]['MACAddress']} is empty!`, ); this.log.warn( 'getAllFbObjects: To delete this device in the fritzbox please add the device manually in the fritzbox with a hostname and the shown mac-address. After a reboot you can hopefully delete the device.', ); this.suppressMesgEmptyHostname = true; } continue; } const host = fbDevices.filter( x => x._id.replace(`${this.namespace}` + '.fb-devices.', '') === hostName, ); let item = items.filter(x => x.HostName == items[i].HostName); let itemActive = items.filter(x => x.HostName == items[i].HostName && x.Active == '1'); if (!host || host.length == 0) { //new devices if (!item || item.length == 1) { const device = { status: 'new', dp: `fb-devices.${hostName}`, hn: hostName, hnOrg: items[i]['HostName'], mac: items[i]['MACAddress'], ip: items[i]['IPAddress'], active: items[i]['Active'] == 1 ? true : false, data: items[i], interfaceType: items[i]['InterfaceType'], speed: parseInt(items[i]['X_AVM-DE_Speed']), guest: items[i]['X_AVM-DE_Guest'] == 0 ? false : true, }; hosts.push(device); } if (!item || item.length > 1) { let mac = ''; let ip = ''; let active = false; for (let it = 0; it < item.length; it++) { mac += mac == '' ? item[it].MACAddress : `, ${item[it].MACAddress}`; ip += ip == '' ? item[it].IPAddress : `, ${item[it].IPAddress}`; } if (itemActive && itemActive.length > 0) { active = true; } const device = { status: 'unchanged', dp: `fb-devices.${hostName}`, hn: hostName, hnOrg: items[i]['HostName'], mac: mac, ip: ip, active: active, data: active == true ? itemActive[0] : items[i], interfaceType: active == true ? itemActive[0]['InterfaceType'] : items[i]['InterfaceType'], speed: active == true ? parseInt(itemActive[0]['X_AVM-DE_Speed']) : parseInt(items[i]['X_AVM-DE_Speed']), guest: active == true ? itemActive[0]['X_AVM-DE_Guest'] == 0 ? false : true : items[i]['X_AVM-DE_Guest'] == 0 ? false : true, }; const temp = hosts.filter(x => x.hn == hostName); if (temp.length == 0) { hosts.push(device); } } } else { if (!item || item.length == 1) { const device = { status: 'unchanged', dp: `fb-devices.${hostName}`, hn: hostName, hnOrg: items[i]['HostName'], mac: items[i]['MACAddress'], ip: items[i]['IPAddress'], active: items[i]['Active'] == 1 ? true : false, data: items[i], interfaceType: items[i]['InterfaceType'], speed: parseInt(items[i]['X_AVM-DE_Speed']), guest: items[i]['X_AVM-DE_Guest'] == 0 ? false : true, }; hosts.push(device); } if (!item || item.length > 1) { let mac = ''; let ip = ''; let active = false; for (let it = 0; it < item.length; it++) { mac += mac == '' ? item[it].MACAddress : `, ${item[it].MACAddress}`; ip += ip == '' ? item[it].IPAddress : `, ${item[it].IPAddress}`; } if (itemActive && itemActive.length > 0) { active = true; } const device = { status: 'unchanged', dp: `fb-devices.${hostName}`, hn: hostName, hnOrg: items[i]['HostName'], mac: mac, ip: ip, active: active, data: active == true ? itemActive[0] : items[i], interfaceType: active == true ? itemActive[0]['InterfaceType'] : items[i]['InterfaceType'], speed: active == true ? parseInt(itemActive[0]['X_AVM-DE_Speed']) : parseInt(items[i]['X_AVM-DE_Speed']), guest: active == true ? itemActive[0]['X_AVM-DE_Guest'] == 0 ? false : true : items[i]['X_AVM-DE_Guest'] == 0 ? false : true, }; const temp = hosts.filter(x => x.hn == hostName); if (temp.length == 0) { hosts.push(device); } } } item = null; itemActive = null; } for (const id in fbDevices) { if (fbDevices[id] != undefined && fbDevices[id].common != undefined) { const dName = fbDevices[id].common.name; const shortName = dName.replace('fb-devices.', ''); const shortNameOrg = fbDevices[id].common.desc; //shortNameOrg = shortNameOrg.replace('-', '.'); let host = items.filter(x => x.HostName === shortName); if (host && host.length == 0) { let host = items.filter(x => x.HostName === shortNameOrg); if (host && host.length == 0) { const device = { status: 'old', dp: `fb-devices.${shortName}`, hn: shortName, hnOrg: shortNameOrg, mac: '', //await this.getStateAsync('fb-devices.' + shortName + '.macaddress').val, ip: '', //await this.getStateAsync('fb-devices.' + shortName + '.ipaddress').val, active: false, data: null, interfaceType: '', speed: 0, guest: false, }; hosts.push(device); } host = null; } host = null; } } let newObjs = hosts.filter(host => host.status == 'new'); if (newObjs) { await obj.createFbDeviceObjects(this, this.adapterStates, newObjs, this.enabled); } devices = null; newObjs = null; fbDevices = null; this.hosts = hosts; hosts = null; return true; } catch (error) { this.errorHandler(error, 'getAllFbObjects: '); return null; } } async stopAdapter() { try { const adapterObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); adapterObj.common.enabled = false; // Adapter ausschalten await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, adapterObj); } catch (error) { this.errorHandler(error, 'stopAdapter: '); } } async startAdapter() { try { const adapterObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); adapterObj.common.enabled = true; // Adapter ausschalten await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, adapterObj); } catch (error) { this.errorHandler(error, 'startAdapter: '); } } /** * Is called when databases are connected and adapter received configuration. */ async onReady() { try { // Initialize your adapter here const membersFiltered = this.config.familymembers.filter(x => x.enabled == true); const familyGroups = this.removeDuplicates(membersFiltered); if (this.config.username === '') { throw new Error('username is empty! You have to define a username! Adapter shuts down!'); } this.Fb = await fb.Fb.init( { host: this.config.ipaddress, uid: this.config.username, pwd: this.config.password, }, this.config.ssl, this, ); if (this.Fb.services === null) { this.log.error('Can not get services! Adapter stops'); this.stopAdapter(); } //Logging of adapter start this.log.info( `start ` + `${this.namespace}` + `: ${this.Fb.modelName} version: ${this.Fb.version} ip-address: "${ this.config.ipaddress }" - interval devices: ${this.config.interval} s` + ` - interval members: ${this.config.intervalFamily} s`, ); this.config.username === '' || this.config.username === null ? this.log.warn('please insert a user for full functionality') : this.log.debug(`configuration user: <${this.config.username}>`); this.log.debug(`configuration history: <${this.config.history}>`); this.log.debug(`configuration dateformat: <${this.config.dateformat}>`); this.log.debug(`configuration familymembers count: ${membersFiltered.length}`); this.log.debug(`configuration familymembers: ${JSON.stringify(this.config.familymembers)}`); this.log.debug(`configuration fb-devices ${this.config.fbdevices}`); this.log.debug(`configuration mesh info: ${this.config.meshinfo}`); this.log.debug(`configuration whitelist: ${this.config.enableWl}`); this.log.debug(`configuration compatibility: ${this.config.compatibility}`); this.log.debug(`configuration ssl: ${this.config.ssl}`); this.log.debug(`configuration qr code: ${this.config.qrcode}`); this.log.debug(`configuration guest info: ${this.config.guestinfo}`); this.log.debug(`configuration external ip address: ${this.config.extip}`); this.log.debug(`configuration filter delay: ${this.config.delay}`); const mesg = this.Fb.connection == null ? '-' : this.Fb.connection; this.log.info(`configuration default connection: ${mesg}`); this.Fb.suportedServices.forEach(element => { element.enabled ? this.log.info(`${element.name} is supported`) : this.log.warn(`${element.name} is not supported! Feature is deactivated!`); }); //Configuration changes if needed let adapterObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); let adapterObjChanged = false; //for changes if (this.config.interval_seconds === false) { //Workaround: Switch interval to seconds this.log.warn('Interval changed to seconds!'); adapterObj.native.interval_seconds = true; adapterObj.native.interval = adapterObj.native.interval * 60; adapterObjChanged = true; } //if interval <= 0 than set to 1 if (this.config.interval <= 0) { adapterObj.native.interval = 60; adapterObjChanged = true; this.config.interval = 60; this.log.warn('interval is less than 1. Set to 60 s'); } //if interval <= 9 than set to 10 if (this.config.intervalFamily <= 9) { adapterObj.native.intervalFamily = 10; adapterObjChanged = true; this.config.intervalFamily = 10; this.log.warn('interval is less than 10. Set to 10s.'); } //create new configuration items -> workaround for older versions for (let i = 0; i < this.config.familymembers.length; i++) { if (this.config.familymembers[i].usefilter == undefined) { this.log.warn( `${this.config.familymembers[i].familymember} usefilter is undefined! Changed to false`, ); adapterObj.native.familymembers[i].usefilter = false; adapterObjChanged = true; } if (this.config.familymembers[i].group == undefined) { this.log.warn(`${this.config.familymembers[i].familymember} group is undefined! Changed to ""`); adapterObj.native.familymembers[i].group = ''; adapterObjChanged = true; } if (this.config.familymembers[i].devicename == undefined) { this.log.warn( `${this.config.familymembers[i].familymember} devicename is undefined! Changed to ""`, ); adapterObj.native.familymembers[i].devicename = ''; adapterObjChanged = true; } if (this.config.familymembers[i].usage == undefined) { if (this.config.familymembers[i].useip == true) { adapterObj.native.familymembers[i].usage = 'IP'; } if (this.config.familymembers[i].usename == true) { adapterObj.native.familymembers[i].usage = 'Hostname'; } if (this.config.familymembers[i].usename == false && this.config.familymembers[i].useip == false) { adapterObj.native.familymembers[i].usage = 'MAC'; } if (adapterObj.native.familymembers[i].usage == undefined) { adapterObj.native.familymembers[i].usage = 'MAC'; } this.log.warn( `${this.config.familymembers[i].familymember} usage is undefined! Changed to ${ adapterObj.native.familymembers[i].usage }`, ); adapterObjChanged = true; } } //suppress messages from family members after one occurence for (let i = 0; i < this.config.familymembers.length; i++) { if (this.config.familymembers[i].enabled === true) { this.suppressArr[i] = { name: this.config.familymembers[i].familymember, suppress: false, hostname: this.config.familymembers[i].devicename, mac: this.config.familymembers[i].macaddress, ip: this.config.familymembers[i].ipaddress, }; } } if (adapterObjChanged === true) { //Save changes this.log.info('some familymember attributes were changed! Please check the configuration'); this.log.info('Adapter restarts'); await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, adapterObj); } const intDev = this.config.interval; const intFamily = this.config.intervalFamily; if (this.config.compatibility === true) { this.log.warn( 'In a future version some states are not more existent. Please disable the compatibility mode in the adapter settings by unchecking the box.', ); this.log.warn('With this change in the adapter settings the new folder familyMembers will be created'); this.log.warn('Then you should manually delete the old family member data points!'); } //Create global objects await obj.createGlobalObjects( this, this.adapterStates, this.HTML + this.HTML_END, this.HTML_GUEST + this.HTML_END, this.enabled, ); this.Fb.suportedServices.forEach(async element => { await this.setState(`info.${element.id}`, { val: element.enabled, ack: true }); }); await this.setState('reboot', { val: false, ack: true }); await this.setState('reconnect', { val: false, ack: true }); if (this.config.extip === false) { //await this.setStateChangedAsync('info.extIp', { val: '', ack: true }); } //If history is enbled, check if history adapter is running. if (this.config.history != '') { this.historyAlive = await this.getForeignStateAsync(`system.adapter.${this.config.history}.alive`); if (this.historyAlive.val === false) { for (let to = 0; to < 6; to++) { this.log.warn('history adapter is not alive! Waiting 10s'); await this._sleep(10000); this.historyAlive = await this.getForeignStateAsync( `system.adapter.${this.config.history}.alive`, ); if (this.historyAlive.val === true) { break; } } if (this.historyAlive.val === false) { this.stopAdapter(); } } } await obj.createMemberObjects( this, membersFiltered, familyGroups, this.adapterStates, this.memberStates, this.config, this.HTML_HISTORY + this.HTML_END, this.enabled, this.historyAlive.val, ); //create Fb devices if (this.Fb.GETPATH != null && this.Fb.GETPATH == true && this.config.fbdevices == true) { await this.Fb.getDeviceList(); if (this.Fb.deviceList != null) { await this.getAllFbObjects(); //this.log.warn('devicelist2: ' + JSON.stringify(this.hosts)); const res = await obj.createFbDeviceObjects(this, this.adapterStates, this.hosts, this.enabled); if (res === true) { this.log.info('createFbDeviceObjects finished successfully'); } } else { this.log.error('createFbDeviceObjects -> ' + "can't read devices from fritzbox! Adapter stops"); adapterObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); adapterObj.common.enabled = false; // Adapter ausschalten await this.setForeignObject(`system.adapter.${this.namespace}`, adapterObj); } await this.resyncFbObjects(this.Fb.deviceList); } // states changes inside the adapters namespace are subscribed if (this.Fb.SETENABLE === true && this.Fb.WLAN3INFO === true && this.config.guestinfo === true) { this.subscribeStates(`${this.namespace}` + '.guest.wlan'); } if (this.Fb.DISALLOWWANACCESSBYIP === true && this.Fb.GETWANACCESSBYIP === true) { this.subscribeStates(`${this.namespace}` + '.fb-devices.*.disabled'); } if (this.Fb.REBOOT === true) { this.subscribeStates(`${this.namespace}` + '.reboot'); } if (this.Fb.RECONNECT === true) { this.subscribeStates(`${this.namespace}` + '.reconnect'); } this.log.info('states successfully subscribed'); this.loop(intFamily - 1, intDev - 3, intFamily, intDev); //values must be less than intFamily or intDev this.log.info('loop successfully started'); } catch (error) { this.errorHandler(error, 'onReady: '); this.stopAdapter(); } } /** * Is called when adapter shuts down - callback has to be called under any circumstances! * * @param callback - callback */ async onUnload(callback) { try { this.enabled = false; if (this.Fb != null) { this.Fb.exitRequest(); } this.tout && clearTimeout(this.tout); this.setState('info.connection', { val: false, ack: true }); this.log.info('cleaned everything up ...'); callback && callback(); //setTimeout(callback, 1000); } catch (e) { this.log.error(`onUnload: ${e.name} ${e.message}`); callback && callback(); } } /** * 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.debug(`object ${id} changed: ${JSON.stringify(obj)}`); } else { // The object was deleted this.log.debug(`object ${id} deleted`); } }*/ /** * Is called if a subscribed state changes * * @param id - id of the changed state * @param state - name of the changed state */ async onStateChange(id, state) { try { if (state) { //Enable/disable Guest WLAN if ( id == `${this.namespace}` + '.guest.wlan' && this.Fb.SETENABLE == true && state.ack === false && this.Fb.WLAN3INFO === true && this.config.guestinfo === true ) { this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`); const val = state.val ? '1' : '0'; const soapResult = await this.Fb.soapAction( '/upnp/control/wlanconfig3', 'urn:dslforum-org:service:' + 'WLANConfiguration:3', 'SetEnable', [[1, 'NewEnable', val]], ); //if (guestwlan['status'] == 200 || guestwlan['result'] == true) { if (soapResult) { await this.Fb.getGuestWlan(id, this.config.qrcode); //if(state.val == wlanStatus) this.setState('guest.wlan', { val: wlanStatus, ack: true }); } else { throw { name: `onStateChange ${id}`, message: `Can not change state${JSON.stringify(soapResult)}`, }; } } //Enable/disable Internet if ( id.includes('disabled') && !state.ack && this.Fb.DISALLOWWANACCESSBYIP && this.Fb.GETWANACCESSBYIP ) { this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`); // IP-Adresse des Geräts holen const ipId = `${id.replace('.disabled', '')}.ipaddress`; const ipaddress = await this.getStateAsync(ipId); const val = state.val ? '1' : '0'; if (!ipaddress || ipaddress.val === '') { throw { name: `onStateChange ${id}`, message: 'Cannot change state. IP address is empty!' }; } const soapResult = await this.Fb.soapAction( '/upnp/control/x_hostfilter', 'urn:dslforum-org:service:' + 'X_AVM-DE_HostFilter:1', 'DisallowWANAccessByIP', [ [1, 'NewIPv4Address', ipaddress.val], [2, 'NewDisallow', val], ], ); if (!soapResult) { throw { name: `onStateChange ${id}`, message: `Cannot change state: ${JSON.stringify(soapResult)}`, }; } // Kurz warten und Zugriffsstatus überprüfen await this._sleep(1000); const newDisallow = (await this.Fb.getWanAccess(ipaddress)) !== '0'; if (newDisallow === state.val) { this.setState(id, { val: newDisallow, ack: true }); this.log.info(`state ${id} changed: ${state.val} (ack = true)`); } else { this.log.warn(`state ${id} changed: ${state.val} (ack = false)`); } } //Reboot Fritzbox if (id == `${this.namespace}` + '.reboot' && this.Fb.REBOOT === true) { this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`); if (state.val === true) { const soapResult = await this.Fb.soapAction( '/upnp/control/deviceconfig', 'urn:dslforum-org:service:' + 'DeviceConfig:1', 'Reboot', null, ); //if (reboot['status'] == 200 || reboot['result'] == true) { if (soapResult) { this.setState(`${this.namespace}` + '.reboot', { val: false, ack: true }); } else { this.setState(`${this.namespace}` + '.reboot', { val: false, ack: true }); throw Error(`reboot failure! ${JSON.stringify(soapResult)}`); } } } //Reconnect if (id == `${this.namespace}` + '.reconnect' && this.Fb.RECONNECT === true) { this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`); if (state.val === true) { let soapResult = null; if ( this.Fb && this.Fb.RECONNECT && this.Fb.RECONNECT == true && this.Fb.connection == '1.WANPPPConnection.1' ) { soapResult = await this.Fb.soapAction( '/upnp/control/wanpppconn1', 'urn:dslforum-org:service:' + 'WANPPPConnection:1', 'ForceTermination', null, ); } else { soapResult = await this.Fb.soapAction( '/upnp/control/wanipconnection1', 'urn:dslforum-org:service:' + 'WANIPConnection:1', 'ForceTermination', null, ); } if (soapResult) { this.setState(`${this.namespace}` + '.reconnect', { val: false, ack: true }); } else { this.setState(`${this.namespace}` + '.reconnect', { val: false, ack: true }); throw `reconnect failure! ${JSON.stringify(soapResult)}`; } } } } else { // The state was deleted this.log.debug(`state ${id} deleted`); } } catch (error) { if (error.message == 'DisconnectInProgress') { this.setState(`${this.namespace}` + '.reconnect', { val: false, ack: true }); this.log.info('Fritzbox reconnect in progress'); await this._sleep(5000); } else { this.log.error(`onStateChange: ${error.name} ${error.message}`); } } } /** * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ... * Using this method requires "common.message" property to be set to true in io-package.json * * @param obj - message object */ async onMessage(obj) { try { //this.log.debug(`[MSSG] Received: ${JSON.stringify(obj)}`); if (!obj) { return; } if (typeof obj === 'object' && obj.message) { const reply = result => { this.sendTo(obj.from, obj.command, JSON.stringify(result), obj.callback); }; switch (obj.command) { case 'triggerPresence': { this.log.info('triggerPresence'); if (this.triggerActive === false) { this.triggerActive = true; await this.checkPresence(true); await this._sleep(10000); this.triggerActive = false; reply(true); } else { reply(false); } return true; } case 'getDevices': { this.allDevices = []; let onlyActive, reread; if (typeof obj.message === 'object') { onlyActive = obj.message.onlyActive; reread = obj.message.reread; } if (!obj.callback) { return false; } if (!reread && this.allDevices.length > 0 && this.allDevices.onlyActive === onlyActive) { reply(this.allDevices); return true; } this.allDevices.onlyActive = onlyActive; if (this.Fb && this.Fb.GETPATH && this.Fb.GETPATH == true) { await this.Fb.getDeviceList(); if (this.Fb.deviceList == null) { reply(this.allDevices); return; } for (let i = 0; i < this.Fb.deviceList.length; i++) { const active = this.Fb.deviceList[i]['Active']; if (!onlyActive || active) { this.allDevices.push({ name: this.Fb.deviceList[i]['HostName'], ip: this.Fb.deviceList[i]['IPAddress'], mac: this.Fb.deviceList[i]['MACAddress'], active: active, }); } } } reply(this.allDevices); return true; } case 'addDeviceToWhitelist': { let hostName = obj.message.hostname; if (this.Fb) { //check, if the mac is in the fritzbox devicelist const host = this.hosts.filter(x => x.hnOrg == hostName); //find fb-device if (host && host.length == 0) { throw new Error( `sendTo addDeviceToWhitelist: Cannot find device ${hostName} in fritzbox!`, ); } const macs = host[0].mac.split(', '); let count = 0; for (let m = 0; m < macs.length; m++) { if (obj.message.mac == macs[m]) { count++; } } if (count == 0) { throw new Error( `sendTo addDeviceToWhitelist: Cannot find device ${hostName} with ${obj.message.mac} in fritzbox!`, ); } hostName = hostName.replace(this.FORBIDDEN_CHARS, '-'); //check, if the mac is always in whitelist let adapterObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); const wl = adapterObj.native.whitelist.filter(x => x.white_macaddress