UNPKG

iobroker.fhem

Version:
930 lines (928 loc) 162 kB
/* jshint -W097 */ /* jshint strict:false */ /* jslint node: true */ 'use strict'; const utils = require('@iobroker/adapter-core'); const adapterName = require('./package.json').name.split('.').pop(); const Telnet = require('./lib/telnet'); // Telnet sessions let telnetOut = null; // read config and write values let telnetIn = null; // receive events let adapter; let connected = false; const eventIOB = []; const delObj = []; const eventQueue = []; const setStateQueue = []; const setStateLogQueue = []; let fhemIN = {}; let fhemINs = {}; let fhemIgnore = {}; let fhemIgnoreConfig = {}; let infoObjects = {}; const fhemObjects = {}; const functions = {}; let lastNameQueue; let lastNameTS = '0'; let iobroker = false; let firstRun = true; let synchro = true; let debug = false; let aktivQueue = false; let aktiv = false; const buildDate = '26.12.23'; const linkREADME = 'https://github.com/iobroker-community-adapters/ioBroker.fhem/blob/master/docs/de/README.md'; const tsStart = Date.now(); let t = '> '; // info.Debug let debugNAME = []; let logDevelop; let numEvent = 0; let timeEvent = 0; let numWriteOut = 0; let timeWriteOut = 0; let numWriteValue = 0; let timeWriteValue = 0; // info.Configurations let syncUpdate; let syncUpdateIOBin; let advancedFunction; let autoRole; let autoFunction; let autoRoom; let autoConfigFHEM; let autoSmartName; let autoName; let autoType; let autoStates; let autoRest; let oldState; let deleteUnusedObjects; let logNoInfo; let onlySyncNAME = []; let onlySyncTYPE = []; const onlySyncRoomS = ['ioBroker', 'ioB_OUT']; let onlySyncRoom = []; const ignoreObjectsInternalsTYPES = []; let ignoreObjectsInternalsTYPE = []; const ignoreObjectsInternalsNAMES = ['info']; let ignoreObjectsInternalsNAME = []; const ignoreObjectsAttributesRoomS = []; let ignoreObjectsAttributesRoom = []; const ignorePossibleSetsS = ['getConfig', 'etRegRaw', 'egBulk', 'regSet', 'deviceMsg', 'CommandAccepted']; let ignorePossibleSets = []; const ignoreReadingsS = ['currentTrackPositionSimulated', 'currentTrackPositionSimulatedSec']; let ignoreReadings = []; const allowedAttributesS = ['room', 'alias', 'comment']; let allowedAttributes = []; const allowedInternalsS = ['TYPE', 'NAME']; let allowedInternals = []; const allowedIOBinS = []; let allowedIOBin = []; let allowedIOBinExclude = []; let allowedIOBinExcludeS = []; const logEventFHEMexcludeS = []; let logEventFHEMexclude = []; // info.Settings let logCheckObject; let logUpdateChannel; let logCreateChannel; let logDeleteChannel; let logEventIOB; let logEventFHEM; let logEventFHEMglobal; let logEventFHEMreading; let logEventFHEMstate; let logUnhandledEventFHEM; let logIgnoreConfigurations; // parseObject const dimPossibleSets = ['pct', 'brightness', 'dim', 'pos']; const volumePossibleSets = ['Volume', 'volume', 'GroupVolume']; const temperaturePossibleSets = ['desired-temp']; const Utemperature = ['temperature', 'measured-temp', 'desired-temp', 'degrees', 'box_cputemp', 'temp_c', 'cpu_temp', 'cpu_temp_avg']; const rgbPossibleSets = ['rgb']; const Rindicator = ['reachable', 'presence', 'battery', 'Activity', 'present', 'batteryState', 'online']; //const function startAdapter(options) { options = options || {}; Object.assign(options, {name: adapterName}); adapter = new utils.Adapter(options); // is called when adapter shuts down - callback has to be called under any circumstances! adapter.on('unload', callback => { try { connected = false; // stop all timers Object.keys(adapter.__timeouts).forEach(name => { adapter.__timeouts[name] && clearTimeout(adapter.__timeouts[name]); adapter.__timeouts[name] = null; adapter.log.debug(`adapter.on.unload: clearTimeout ${name}`); }); adapter.setState('info.connection', false, true); adapter.setState('info.Info.alive', false, true); if (telnetOut) { telnetOut.destroy(); telnetOut = null; } if (telnetIn) { telnetIn.destroy(); telnetIn = null; } callback(); } catch (e) { callback(); } }); // is called if a subscribed state changes adapter.on('stateChange', (id, state) => { let fn = '[stateChange] '; let ts = Date.now(); // you can use the ack flag to detect if it is status (true) or command (false) if (!state) { adapter.log.debug(`${fn}no state - ${id}`); return; } let val = state.val; let ack = state.ack; logDebug(fn, id, `stateChange (in): ${id} ${val} ${JSON.stringify(state)}`, ''); if (!id.startsWith(adapter.namespace)) { let idFHEM = convertNameIob(fn, id); if (fhemIN[idFHEM]) { if (val !== fhemINs[idFHEM].val || syncUpdateIOBin) { eventIOB.push({ command: 'writeOut', id: idFHEM, val: val, ts: ts }); fhemINs[idFHEM] = { id: id, val: val }; if (!firstRun) checkQueue(fn); return; } else { return; } } } if (ack) return; // no ack and from adapter ? if (!state.ack && id.startsWith(adapter.namespace)) { if (id === `${adapter.namespace}.info.resync`) { logWarn(fn, '----- request restart adapter'); eventIOB.push({ command: 'resync' }); checkQueue(fn); return; } else if (fhemObjects[id] || id.startsWith(adapter.namespace + '.info')) { logDebug(fn, id, `stateChange(write): ${id} ${val} ${JSON.stringify(state)}`, 'D'); eventIOB.push({ command: 'write', id: id, val: val, ts: ts }); checkQueue(fn); return; } else { logStateChange(fn, id, val, 'stateChange - no match !state.ack && id.startsWith(adapter.namespace) id: ' + id, 'neg'); return; } } else { logDebug(fn + t, id, `stateChange: ${id} | ${val} | ack: ${ack}`, 'D'); return; } logStateChange(fn, id, val, `stateChange - no match ${JSON.stringify(state)}`, 'neg'); }); // Some message was sent to adapter instance over message box. Used by email, pushover, text2speech, ... adapter.on('message', obj => { if (typeof obj === 'object' && obj.message) { if (obj.command === 'send') { // e.g. send email or pushover or whatever console.log('send command'); // Send response in callback if required if (obj.callback) { adapter.sendTo(obj.from, obj.command, 'Message received', obj.callback); } } } }); // is called when databases are connected and adapter received configuration. // start here! adapter.on('ready', main); adapter.__timeouts = {}; return adapter; } //========================================================================================================================================== start function firstCheck(ff, cb) { let fn = ff + '[firstCheck] '; logDebug(fn, '', 'start', 'D'); adapter.setState('info.resync', false, true); getSetting(fn, 'info.Debug.logDevelop', value => { logDevelop = value; getSetting(fn, 'info.Configurations.logNoInfo', value => { logNoInfo = value; logDebug(fn, '', 'end', 'D'); cb(); }); }); } // STEP 01 function myObjects(ff, cb) { let fn = ff + '[myObjects] '; logDebug(fn, '', 'start', 'D'); let id; const newPoints = [ // info.Commands {_id: 'info.Commands.lastCommand', type: 'state', common: {name: 'Last command to FHEM', type: 'string', read: true, write: false, role: 'text'}, native: {}}, {_id: 'info.Commands.resultFHEM', type: 'state', common: {name: 'Result of FHEM', type: 'string', read: true, write: false, role: 'text'}, native: {}}, {_id: 'info.Commands.sendFHEM', type: 'state', common: {name: 'Command to FHEM', type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Commands.createSwitch', type: 'state', common: {name: 'Create dummy as switch in room FHEM (NAME room)', type: 'string', read: true, write: true, role: 'state'}, native: {}}, // info.Configurations {_id: 'info.Configurations.autoConfigFHEM', type: 'state', common: {name: 'FUNCTION - allow special configurations FHEM', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.autoFunction', type: 'state', common: {name: 'FUNCTION - auto create function of object (use Adapter Material)', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.autoRoom', type: 'state', common: {name: 'FUNCTION - auto create room of channel (use Adapter Material)', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.autoRole', type: 'state', common: {name: 'FUNCTION - auto create role of object (use Adapter Material)', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.autoSmartName', type: 'state', common: {name: 'FUNCTION - (fhem.0) auto create SmartName of object (Adapter Cloud/IoT)', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.autoName', type: 'state', common: {name: 'FUNCTION - auto create name of object', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.autoType', type: 'state', common: {name: 'FUNCTION - auto create type of object', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.autoStates', type: 'state', common: {name: 'FUNCTION - auto create states of object', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.autoRest', type: 'state', common: {name: 'FUNCTION - auto create read,write,min,max,unit of object', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.deleteUnusedObjects', type: 'state', common: {name: 'FUNCTION - delete unused objects automatically', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.oldState', type: 'state', common: {name: 'FUNCTION - old version of state with true/false', type: 'boolean', read: true, write: true, role: 'switch', def: false}, native: {}}, {_id: 'info.Configurations.allowedIOBin', type: 'state', common: {name: 'SYNC - allowed objects send2FHEM', type: 'string', read: true, write: true, role: 'state'}, native: {default: '.'}}, {_id: 'info.Configurations.allowedIOBinExclude', type: 'state', common: {name: 'SYNC - exclude allowedIOBin', type: 'string', read: true, write: true, role: 'state'}, native: {default: '.'}}, {_id: 'info.Configurations.ignoreObjectsInternalsTYPE', type: 'state', common: {name: `SYNC - ignore device(s) TYPE (default: ${ignoreObjectsInternalsTYPES})`, type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.ignoreObjectsInternalsNAME', type: 'state', common: {name: `SYNC - ignore device(s) NAME (default: ${ignoreObjectsInternalsNAMES})`, type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.ignoreObjectsAttributesroom', type: 'state', common: {name: `SYNC - ignore device(s) of room(s) (default: ${ignoreObjectsAttributesRoomS})`, type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.allowedAttributes', type: 'state', common: {name: `SYNC - allowed Attributes (default: ${allowedAttributesS})`, type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.allowedInternals', type: 'state', common: {name: `SYNC - allowed Internals (default: ${allowedInternalsS})`, type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.ignoreReadings', type: 'state', common: {name: `SYNC - ignore Readings (default: ${ignoreReadingsS})`, type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.ignorePossibleSets', type: 'state', common: {name: `SYNC - ignore PossibleSets (default: ${ignorePossibleSetsS})`, type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.onlySyncRoom', type: 'state', common: {name: 'SYNC - only sync device(s) if room exist (default: ' + onlySyncRoomS + ')', type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.onlySyncNAME', type: 'state', common: {name: 'SYNC - only sync device(s) NAME', type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.onlySyncTYPE', type: 'state', common: {name: 'SYNC - only sync device(s) TYPE', type: 'string', read: true, write: true, role: 'state'}, native: {}}, {_id: 'info.Configurations.logNoInfo', type: 'state', common: {name: 'FUNCTION - no LOG info', type: 'boolean', read: true, write: true, role: 'switch', def: false}, native: {}}, {_id: 'info.Configurations.advancedFunction', type: 'state', common: {name: 'FUNCTION - advanced', type: 'boolean', read: true, write: true, role: 'switch', def: false}, native: {}}, {_id: 'info.Configurations.syncUpdate', type: 'state', common: {name: 'FUNCTION - sync update FHEM reading', type: 'boolean', read: true, write: true, role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.syncUpdateIOBin', type: 'state', common: {name: 'FUNCTION - sync update allowedIOBin', type: 'boolean', read: true, write: true, role: 'switch', def: true}, native: {}}, {_id: 'info.Configurations.logEventFHEMexclude', type: 'state', common: {name: 'SYNC - exclude logEventFHEM', type: 'string', read: true, write: true, role: 'state'}, native: {default: '.'}}, // info.Debug {_id: 'info.Debug.jsonlist2', type: 'state', common: {name: 'jsonlist2 of FHEM', type: 'string', read: true, write: true, role: 'json'}, native: {}}, {_id: 'info.Debug.meta', type: 'state', common: {name: 'Device NAME of FHEM', type: 'string', read: true, write: true, role: 'text'}, native: {}}, {_id: 'info.Debug.activate', type: 'state', common: {name: 'Debug Mode for Device(s) NAME', type: 'string', read: true, write: true, role: 'text'}, native: {}}, {_id: 'info.Debug.logDevelop', type: 'state', common: {name: 'More info debug', type: 'boolean', role: 'switch', def: false}, native: {}}, {_id: 'info.Debug.numberIn', type: 'state', common: {name: 'Number of events of FHEM last 5 min', type: 'number', read: true, write: false, role: 'value', def: 0}, native: {}}, {_id: 'info.Debug.timeIn', type: 'state', common: {name: 'Average time(ms) of events of FHEM last 5 min', type: 'number', read: true, write: false, role: 'value', def: 0}, native: {}}, {_id: 'info.Debug.numberOut', type: 'state', common: {name: 'Number of stateChanges(s) of ioBroker last 5 min', type: 'number', read: true, write: false, role: 'value', def: 0}, native: {}}, {_id: 'info.Debug.timeOut', type: 'state', common: {name: 'Average time(ms) stateChanges(s) of ioBroker last 5 min', type: 'number', read: true, write: false, role: 'value', def: 0}, native: {}}, {_id: 'info.Debug.fhemObjectsRead', type: 'state', common: {name: 'Device NAME of FHEM', type: 'string', read: true, write: true, role: 'text'}, native: {}}, // info.Info {_id: 'info.Info.buildDate', type: 'state', common: {name: 'Date of main.js', type: 'string', read: true, write: false, role: 'text'}, native: {}}, {_id: 'info.Info.roomioBroker', type: 'state', common: {name: 'room of fhem.x.info.Configurations.onlySyncRoom exist', type: 'boolean', read: true, write: false, role: 'indicator'}, native: {}}, {_id: 'info.Info.numberDevicesFHEM', type: 'state', common: {name: 'Number devices of FHEM (jsonlist2)', type: 'number', read: true, write: false, role: 'value'}, native: {}}, {_id: 'info.Info.numberDevicesFHEMsync', type: 'state', common: {name: 'Number devices of FHEM (synchronized)', type: 'number', read: true, write: false, role: 'value'}, native: {}}, {_id: 'info.Info.numberObjectsIOBout', type: 'state', common: {name: 'Number of objects IOB out (detected)', type: 'number', read: true, write: false, role: 'value'}, native: {}}, {_id: 'info.Info.numberObjectsIOBoutSub', type: 'state', common: {name: 'Number of objects IOB out (subscribe)', type: 'number', read: true, write: false, role: 'value'}, native: {}}, {_id: 'info.Info.numberObjectsIOBin', type: 'state', common: {name: 'Number of objects IOB in', type: 'number', read: true, write: false, role: 'value'}, native: {}}, {_id: 'info.Info.alive', type: 'state', common: {name: 'FHEM alive', type: 'boolean', read: true, write: false, role: 'indicator.connected'}, native: {}}, {_id: 'info.Info.numberDevicesFHEMignored', type: 'state', common: {name: 'Number devices of FHEM (ignored)', type: 'number', read: true, write: false, role: 'value'}, native: {}}, {_id: 'info.Info.lastWarn', type: 'state', common: {name: 'lastWarn', type: 'string', read: true, write: false, role: 'text'}, native: {}}, {_id: 'info.Info.lastError', type: 'state', common: {name: 'lastError', type: 'string', read: true, write: false, role: 'text'}, native: {}}, {_id: 'info.Info.lastInfo', type: 'state', common: {name: 'lastInfo', type: 'string', read: true, write: false, role: 'text'}, native: {}}, {_id: 'info.Info.lastSend2ioB', type: 'state', common: {name: 'lastSend2ioB', type: 'string', read: true, write: false, role: 'text'}, native: {}}, {_id: 'info.Info.lastIOBout', type: 'state', common: {name: 'lastIOBout', type: 'string', read: true, write: false, role: 'text'}, native: {}}, // info.Settings {_id: 'info.Settings.logCheckObject', type: 'state', common: {name: 'LOG "check channel ....." ', type: 'boolean', role: 'switch', def: false}, native: {}}, {_id: 'info.Settings.logCreateChannel', type: 'state', common: {name: 'LOG "Create channel ....." ', type: 'boolean', role: 'switch', def: false}, native: {}}, {_id: 'info.Settings.logDeleteChannel', type: 'state', common: {name: 'LOG "Delete channel ....." ', type: 'boolean', role: 'switch', def: false}, native: {}}, {_id: 'info.Settings.logEventFHEM', type: 'state', common: {name: 'LOG "event FHEM ....." all events from FHEM over telnet)', type: 'boolean', role: 'switch', def: false}, native: {}}, {_id: 'info.Settings.logEventFHEMglobal', type: 'state', common: {name: 'LOG "event FHEM(g) ....." events global from FHEM', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Settings.logEventFHEMreading', type: 'state', common: {name: 'LOG "event FHEM(r) ....." events readings from FHEM', type: 'boolean', role: 'switch', def: false}, native: {}}, {_id: 'info.Settings.logEventFHEMstate', type: 'state', common: {name: 'LOG "event FHEM(s) ....." events state from FHEM', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Settings.logEventIOB', type: 'state', common: {name: 'LOG "stateChange: ....." all events ioBroker to FHEM', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Settings.logUnhandledEventFHEM', type: 'state', common: {name: 'LOG "unhandled event FHEM ....." all events unhandled from FHEM', type: 'boolean', role: 'switch', def: true}, native: {}}, {_id: 'info.Settings.logUpdateChannel', type: 'state', common: {name: 'LOG "Update channel ....." ', type: 'boolean', role: 'switch', def: false}, native: {}}, {_id: 'info.Settings.logIgnoreConfigurations', type: 'state', common: {name: 'LOG "ignore FHEM device ....." ignored Devices from FHEM (info.Configurations) ', type: 'boolean', role: 'switch', def: false}, native: {}} ]; id = `${adapter.namespace}.info.resync`; infoObjects[id] = {id}; id = `${adapter.namespace}.info.connection`; infoObjects[id] = {id}; logInfo(fn, '> check new/update objects '); for (let i = 0; i < newPoints.length; i++) { adapter.getObject(newPoints[i]._id, newPoints[i], (e, obj) => { e && logError(fn, e); id = `${adapter.namespace}.${newPoints[i]._id}`; infoObjects[id] = {id}; if (!obj) { adapter.setObject(newPoints[i]._id, newPoints[i], e => { e && logError(fn, e); logInfo(fn, `> create ${adapter.namespace}.${newPoints[i]._id} - ${newPoints[i].common.name} (NEW)`, () => { if (i === newPoints.length - 1) { deleteMyObjects(ff, i, () => { logDebug(fn, '', 'end', 'D'); cb(); }); } }); }); } else { adapter.extendObject(newPoints[i]._id, newPoints[i], e => { e && logError(fn, e); logDebug(fn, '', `> update ${adapter.namespace}.${newPoints[i]._id} - ${newPoints[i].common.name}`, '', () => { if (i === newPoints.length - 1) { deleteMyObjects(ff, i, () => { logDebug(fn, '', 'end', 'D'); cb(); }); } }); }); } }); } } function deleteMyObjects(ff, i, cb) { let fn = `${ff}[deleteMyObjects] `; logDebug(fn, '', 'start', 'D'); logInfo(fn, '> check old objects and delete'); adapter.getStates(`${adapter.namespace}.info.*`, (e, states) => { if (e) { logError(fn, e); cb(); } else { for (const id in states) { if (!states.hasOwnProperty(id)) { continue; } if (!infoObjects[id]) { adapter.log.warn(`${id} ${JSON.stringify(states)}`); delObj.push({ command: 'delState', name: id }); delObj.push({ command: 'delObject', name: id }); } } logInfo(fn, `> ${i + 1} objects ${adapter.namespace}.info OK`); logDebug(fn, '', 'end', 'D'); cb(); } }); } //STEP 02 function getConfigurationsSYNC(ff, cb) { let fn = `${ff}[getConfigurationsSYNC] `; logDebug(fn, '', 'start', 'D'); if (!firstRun) logInfo(fn, `change Configurations of FUNCTION ===== check ${adapter.namespace}.info.Configurations (true or value) - select function of Adapter and Devices to sync`); allowedIOBin = allowedIOBinS.slice(); getConfig(fn, 'info.Configurations.allowedIOBin', allowedIOBin, () => { allowedIOBinExclude = allowedIOBinExcludeS.slice(); getConfig(fn, 'info.Configurations.allowedIOBinExclude', allowedIOBinExclude, () => { ignoreObjectsAttributesRoom = ignoreObjectsAttributesRoomS.slice(); getConfig(fn, 'info.Configurations.ignoreObjectsAttributesroom', ignoreObjectsAttributesRoom, () => { ignoreObjectsInternalsNAME = ignoreObjectsInternalsNAMES.slice(); getConfig(fn, 'info.Configurations.ignoreObjectsInternalsNAME', ignoreObjectsInternalsNAME, () => { ignoreObjectsInternalsTYPE = ignoreObjectsInternalsTYPES.slice(); getConfig(fn, 'info.Configurations.ignoreObjectsInternalsTYPE', ignoreObjectsInternalsTYPE, () => { allowedAttributes = allowedAttributesS.slice(); getConfig(fn, 'info.Configurations.allowedAttributes', allowedAttributes, () => { allowedInternals = allowedInternalsS.slice(); getConfig(fn, 'info.Configurations.allowedInternals', allowedInternals, () => { ignoreReadings = ignoreReadingsS.slice(); getConfig(fn, 'info.Configurations.ignoreReadings', ignoreReadings, () => { ignorePossibleSets = ignorePossibleSetsS.slice(); getConfig(fn, 'info.Configurations.ignorePossibleSets', ignorePossibleSets, () => { onlySyncNAME = []; getConfig(fn, 'info.Configurations.onlySyncNAME', onlySyncNAME, () => { onlySyncTYPE = []; getConfig(fn, 'info.Configurations.onlySyncTYPE', onlySyncTYPE, () => { onlySyncRoom = onlySyncRoomS.slice(); getConfig(fn, 'info.Configurations.onlySyncRoom', onlySyncRoom, () => { logEventFHEMexclude = logEventFHEMexcludeS.slice(); getConfig(fn, 'info.Configurations.logEventFHEMexclude', logEventFHEMexclude, () => { logDebug(fn, '', 'end', 'D'); cb && cb(); }); }); }); }); }); }); }); }); }); }); }); }); }); } //STEP 03 function getConfigurationsFUNCTION(ff, cb) { let fn = ff + '[getConfigurationsFUNCTION] '; logDebug(fn, '', 'start', 'D'); if (!firstRun) logInfo(fn, `change Configurations of FUNCTION ===== check ${adapter.namespace}.info.Configurations (value) - Devices to sync`); getSetting(fn, 'info.Configurations.autoRole', value => autoRole = value); getSetting(fn, 'info.Configurations.autoFunction', value => autoFunction = value); getSetting(fn, 'info.Configurations.autoRoom', value => autoRoom = value); getSetting(fn, 'info.Configurations.autoConfigFHEM', value => autoConfigFHEM = value); getSetting(fn, 'info.Configurations.autoSmartName', value => autoSmartName = value); getSetting(fn, 'info.Configurations.autoName', value => autoName = value); getSetting(fn, 'info.Configurations.autoType', value => autoType = value); getSetting(fn, 'info.Configurations.autoStates', value => autoStates = value); getSetting(fn, 'info.Configurations.autoRest', value => autoRest = value); getSetting(fn, 'info.Configurations.deleteUnusedObjects', value => deleteUnusedObjects = value); getSetting(fn, 'info.Configurations.advancedFunction', value => advancedFunction = value); getSetting(fn, 'info.Configurations.syncUpdate', value => syncUpdate = value); getSetting(fn, 'info.Configurations.syncUpdateIOBin', value => syncUpdateIOBin = value); getSetting(fn, 'info.Configurations.oldState', value => { oldState = value; adapter.setState('info.Info.buildDate', buildDate, true); let start = '----- start FHEM Adapter Instanz ' + adapter.namespace; let text; if (advancedFunction) { text = start; } else { text = '----- not in use - info.Configurations.advancedFunction(false)'; } adapter.setState('info.Info.lastWarn', text, true); adapter.setState('info.Info.lastError', text, true); adapter.setState('info.Info.lastInfo', text, true); adapter.setState('info.Info.lastSend2ioB', text, true); adapter.setState('info.Info.lastIOBout', text, true); adapter.setState('info.Commands.lastCommand', start, true); logDebug(fn, '', 'end', 'D'); cb && cb(); }); } //STEP 04 function getSettings(ff, cb) { let fn = ff + '[getSettings] '; logDebug(fn, '', 'start', 'D'); if (!firstRun) logInfo(fn, `change Settings ===== check ${adapter.namespace}.info.Settings (true) - select message ioBroker admin > LOG`); getSetting(fn, 'info.Settings.logCheckObject', value => logCheckObject = value); getSetting(fn, 'info.Settings.logUpdateChannel', value => logUpdateChannel = value); getSetting(fn, 'info.Settings.logCreateChannel', value => logCreateChannel = value); getSetting(fn, 'info.Settings.logDeleteChannel', value => logDeleteChannel = value); getSetting(fn, 'info.Settings.logEventIOB', value => logEventIOB = value); getSetting(fn, 'info.Settings.logEventFHEM', value => logEventFHEM = value); getSetting(fn, 'info.Settings.logEventFHEMglobal', value => logEventFHEMglobal = value); getSetting(fn, 'info.Settings.logEventFHEMreading', value => logEventFHEMreading = value); getSetting(fn, 'info.Settings.logEventFHEMstate', value => logEventFHEMstate = value); getSetting(fn, 'info.Settings.logUnhandledEventFHEM', value => logUnhandledEventFHEM = value); getSetting(fn, 'info.Settings.logIgnoreConfigurations', value => { logIgnoreConfigurations = value; logDebug(fn, '', 'end', 'D'); cb && cb(); }); } // more function getSetting(ff, id, cb) { let fn = `${ff}[getSetting] `; logDebug(fn, '', id, 'D'); adapter.getObject(id, (e, obj) => { e && logError(fn, e); if (obj) { adapter.getState(id, (e, state) => { e && logError(fn, e); if (state) { logDebug(fn, '', `${id} ${state.val}`, ''); state.val && logInfo(fn, `> ${obj.common.name} - ${id} (${state.val})`); adapter.setState(id, state.val, true); cb(state.val); } else { logDebug(fn, '', `${id} - no state found`, ''); cb(); } }); } else { logDebug(fn, '', `${id} - no object found`, ''); cb(); } }); } function getConfig(ff, id, config, cb) { let fn = ff + '[getConfig] '; adapter.log.debug(`${fn + id} (${config})`); adapter.getObject(id, (e, obj) => { e && logError(fn, e); if (obj) { adapter.getState(id, (e, state) => { e && logError(fn, e); adapter.log.debug(`${fn + id}: ${JSON.stringify(state)}`); if (state && state.val) { adapter.setState(id, state.val, true); const part = (state.val.toString()).split(','); if (part[0]) { for (const i in part) { config.push(part[i].trim()); } } config.length && logInfo(fn, `> ${obj.common.name} - ${id} (${config})`); cb && cb(); } else { cb && cb(); } }); } }); } //STEP 05 function getDebug(ff, cb) { let fn = `${ff}[getDebug] `; logDebug(fn, '', 'start', 'D'); if (!firstRun) { logInfo(fn, `CHANGE debug ===== check ${adapter.namespace}.info.Debug - Activate Debug-Mode for channel(s)`); } debugNAME = []; adapter.getState('info.Debug.activate', (e, state) => { e && logError(fn, e); if (state) { const part = (state.val || '').toString().split(','); if (part[0]) { for (const i in part) { debugNAME.push(part[i].trim()); } } if (debugNAME.length) { logInfo(fn, `> ${adapter.namespace}.info.Debug.activate = ${debugNAME}`); } else { logInfo(fn, `> no sync - ${adapter.namespace}.info.Debug.activate`); } logDebug(fn, '', `${fn}with obj - end`, 'D'); cb && cb(); } else { logDebug(fn, '', `${fn}end`, 'D'); cb && cb(); } }); } //STEP 06 function checkSubscribe(ff, cb) { let fn = `${ff}[checkSubscribe] `; logDebug(fn, '', 'start', 'D'); if (!allowedIOBin.length) { logInfo(fn, `> no sync - ${adapter.namespace}.info.Configurations.allowedIOBin`); adapter.setState('info.Info.numberObjectsIOBoutSub', 0, true); cb && cb(); return; } let end = 0; allowedIOBin.forEach(search => { adapter.getForeignStates(`${search}*`, (e, states) => { if (e) { logError(fn, `error: ${e}`); } else { logDebug(fn, '', `${fn} detected${JSON.stringify(states)}`, 'D'); logInfo(fn, `> detected ${Object.keys(states).length} state(s) of "${search}"`); for (const id in states) { if (!states.hasOwnProperty(id)) { continue; } let foundEx = false; let end1 = 0; if (!allowedIOBinExclude.length) allowedIOBinExclude = ['?']; allowedIOBinExclude.forEach(searchEx => { if (id.startsWith(searchEx)) { logInfo(fn, `>> excluded ${id}`); foundEx = true; } end1++; if (end1 === allowedIOBinExclude.length && !foundEx) { let idFHEM = convertNameIob(fn, id); let val; try { val = states[id].val; } catch (e) { val = '???'; } fhemINs[idFHEM] = { id: id, val: val }; fhemIgnore[idFHEM] = {id: id}; logDebug(fn, '', `${fn} found ${id}`, ''); foundEx = false; end1 = 0; } }); } end++; if (end === allowedIOBin.length) { adapter.setState('info.Info.numberObjectsIOBoutSub', Object.keys(fhemINs).length, true); logDebug(fn, '', 'end', 'D'); cb && cb(); } } }); }); } // STEP 07-09 function syncFHEM(ff, cb) { let fn = ff + '[syncFHEM] '; logDebug(fn, '', 'start', 'D'); let send = 'jsonlist2'; if (onlySyncNAME.length) { send = `${send} ${onlySyncNAME},${adapter.namespce}.send2ioB`; logInfo(fn, `> only jsonlist2 ${onlySyncNAME} - ${adapter.namespace}.info.Configurations.onlySyncNAME (${onlySyncNAME})`); } if (!connected) { logInfo(fn, `> Connected FHEM telnet ${adapter.config.host}:${adapter.config.port} - send telnet "${send}"`); connected = true; adapter.setState('info.connection', true, true); } telnetOut.send(send, (e, result) => { e && logError(fn, `telnetOut.send: ${e}`); if (result) { logInfo(fn, '> result of jsonlist2 OK'); let objects = null; try { objects = JSON.parse(result); } catch (e) { if (e.name === 'SyntaxError' && e.message.startsWith('Unexpected token')) { let stelle = Number(e.message.replace(/[^0-9]/g, '')); let stelleN = result.lastIndexOf('NAME', stelle); let stelleName = result.indexOf(',', stelleN); logError(fn, `> SyntaxError jsonlist2 of FHEM device ${result.substr(stelleN, stelleName - stelleN)} --> stop instance ${adapter.namespace}`); let stelleE = result.indexOf('Name', stelleN); adapter.log.debug(`${fn}SyntaxError: ${result.substr(stelleN, stelleE - stelleN)}`); } else { logError(fn, `Cannot parse answer for jsonlist2: ${e}`); } if (firstRun) adapter.setForeignState('system.adapter.fhem.1.alive', false, false); } if (objects) { logInfo(fn, `> get ${objects.Results.length} Device(s) of FHEM`); if (connected) { parseObjects(fn, objects.Results, () => { logDebug(fn, '', fn + 'end', 'D'); cb && cb(); }); } else { cb && cb(); } } else { logDebug(fn, '', `${fn} no objects - end`, 'D'); cb && cb(); } } else { logDebug(fn, '', `${fn} no result - end`, 'D'); cb && cb(); } }); } function parseObjects(ff, objs, cb) { let fn = `${ff}[parseObjects] `; logDebug(fn, '', 'start', 'D'); const rooms = {}; const objects = []; const states = []; let id; let obj; let alias; let suche = 'no'; let text; const debugShow = ''; if (firstRun) { for (let i = 0; i < objs.length; i++) { try { if (iobroker) { continue; } if (objs[i].Attributes.room) { suche = objs[i].Attributes.room.split(','); for (const r in suche) { if (onlySyncRoom.includes(suche[r])) { adapter.log.debug(`${fn}detected room ${onlySyncRoom} / ${i} > iobroker=true`); iobroker = true; } } } } catch (e) { logError(fn, `Cannot check room of object: ${JSON.stringify(objs[i])} ${e}`); } } adapter.setState('info.Info.numberDevicesFHEM', objs.length, true); firstRun && logInfo(fn, `STEP 08 ===== parse Objects - check ${objs.length} Device(s) of FHEM detected`); adapter.setState('info.Info.roomioBroker', iobroker, true); iobroker && logInfo(fn, `> only sync device(s) from room(s) = ${onlySyncRoom} - ${adapter.namespace}.info.Info.roomioBroker (${iobroker})`); onlySyncNAME.length && logInfo(fn, `> only sync device(s) = ${onlySyncNAME} - ${adapter.namespace}.info.Configurations.onlySyncNAME (${onlySyncNAME})`); ignoreObjectsAttributesRoom.length && logInfo(fn, `> no sync device(s) of room(s) = ${ignoreObjectsAttributesRoom} - ${adapter.namespace}.info.Configurations.ignoreObjectsAttributesroom (${ignoreObjectsAttributesRoom})`); ignoreObjectsInternalsNAME.length && logInfo(fn, `> no sync device(s) with Internals:NAME = ${ignoreObjectsInternalsNAME} - ${adapter.namespace}.info.Configurations.ignoreObjectsInternalsNAME (${ignoreObjectsInternalsNAME})`); ignoreObjectsInternalsTYPE.length && logInfo(fn, `> no sync device(s) with Internals:TYPE = ${ignoreObjectsInternalsTYPE} - ${adapter.namespace}.info.Configurations.ignoreObjectsInternalsTYPE (${ignoreObjectsInternalsTYPE})`); } for (let i = 0; i < objs.length; i++) { const device = objs[i].Name; const debugN = device + ' | '; if (!connected) { (cb); return; } try { // Auto-created by ioBroker ? if (objs[i].Attributes.comment && objs[i].Attributes.comment.startsWith('Auto-created by ioBroker fhem')) { // nicht eigene Instanz? if (!objs[i].Attributes.comment.includes(`Auto-created by ioBroker ${adapter.namespace}`)) { fhemIgnore[device] = {id: device}; logIgnoreConfig(fn, device, `comment: ${objs[i].Attributes.comment}`, i, objs.length); continue; } if (!fhemINs[device] && objs[i].Attributes.room.startsWith('ioB_IN')) { logIgnoreConfig(fn, device, `comment: ${objs[i].Attributes.comment}`, i, objs.length); sendFHEM(fn, `delete ${device}`); continue; } if (fhemINs[device] && objs[i].Attributes.room.startsWith('ioB_IN')) { fhemIN[device] = {id: device}; fhemIgnore[device] = {id: device}; logIgnoreConfig(fn, device, `comment: ${objs[i].Attributes.comment}`, i, objs.length); continue; } if (device.includes('alive')) { fhemIgnore[device] = {id: device}; logIgnoreConfig(fn, device, `comment: ${objs[i].Attributes.comment}`, i, objs.length); continue; } if (device.includes('send2ioB')) { fhemIgnore[device] = {id: device}; continue; } } if (objs[i].Attributes && iobroker) { if (!objs[i].Attributes.room) { logIgnoreConfig(fn, device, 'no room, iobroker=true', i, objs.length); continue; } else { let weiter = true; let searchRoom = objs[i].Attributes.room.split(','); for (const r in searchRoom) { if (onlySyncRoom.includes(searchRoom[r]) || searchRoom[r] === 'ioB_System') { logDebug(fn, device, `detected room ${searchRoom[r]}/${r} of FHEM device "${device}"`, 'D'); weiter = false; } } if (weiter && !synchro) { unusedObjects(fn, `${convertNameFHEM(fn, device)}.*`, cb); } if (weiter) { logIgnoreConfig(fn, device, `room <> ${onlySyncRoom}`, i, objs.length); continue; } } } if (onlySyncNAME.length && !onlySyncNAME.includes(objs[i].Internals.NAME)) { logIgnoreConfig(fn, device, `NAME <> ${onlySyncNAME}`, i, objs.length); continue; } if (onlySyncTYPE.length && !onlySyncTYPE.includes(objs[i].Internals.TYPE)) { logIgnoreConfig(fn, device, `TYPE <> ${onlySyncTYPE}`, i, objs.length); continue; } if (ignoreObjectsInternalsTYPE.includes(objs[i].Internals.TYPE)) { logIgnoreConfig(fn, device, `TYPE: ${ignoreObjectsInternalsTYPE}`, i, objs.length); continue; } if (ignoreObjectsInternalsNAME.includes(objs[i].Internals.NAME)) { logIgnoreConfig(fn, device, `NAME: ${ignoreObjectsInternalsNAME}`, i, objs.length); continue; } if (ignoreObjectsAttributesRoom.includes(objs[i].Attributes.room)) { logIgnoreConfig(fn, device, `room: ${ignoreObjectsAttributesRoom}`, i, objs.length); continue; } if (objs[i].Attributes && objs[i].Attributes.room === 'hidden') { logIgnoreConfig(fn, device, 'room: hidden', i, objs.length); continue; } logDebug(fn, device, `detected FHEM device "${device}" to sync`, 'D'); let isOn = false; let isOff = false; let setStates = {}; let Funktion = 'no'; let id; const nameIob = convertNameFHEM(fn, device); const channel = `${adapter.namespace}.${nameIob}`; //alias? if (objs[i].Attributes && objs[i].Attributes.alias) { alias = objs[i].Attributes.alias; } else { alias = device; } obj = { _id: channel, type: 'channel', common: { name: alias }, native: objs[i] }; if (objs[i].Internals.TYPE === 'HUEBridge') { if (!objs[i].Attributes.createGroupReadings) { sendFHEM(fn, `attr ${device} createGroupReadings 1`, 'TYPE:HUEBridge'); } } if (objs[i].Internals.TYPE === 'HUEDevice') { Funktion = 'light'; if (objs[i].Internals.type) { if (objs[i].Internals.type.includes('ZLL') || objs[i].Internals.type === 'MotionDetector') { Funktion = 'sensor'; } } } if (objs[i].Internals.TYPE === 'SONOSPLAYER') { Funktion = 'audio'; obj.common.role = 'media.music'; if (!objs[i].Attributes.generateVolumeEvent) { sendFHEM(fn, `attr ${device} generateVolumeEvent 1`, 'TYPE:SONOSPLAYER'); } } if (objs[i].Attributes.model === 'HM-CC-RT-DN') { Funktion = 'heating'; obj.common.role = 'thermostate'; } if (objs[i].Attributes.subType === 'thermostat') { Funktion = 'heating'; } if (objs[i].Attributes.subType === 'smokeDetector') { Funktion = 'security'; } if (objs[i].Attributes.subType === 'blindActuator') { Funktion = 'blind'; } // Functions if (Funktion !== 'no' && autoFunction && objs[i].Attributes.room) { setFunction(channel, Funktion); } objects.push(obj); text = `check channel ${channel} | name: ${alias} | room: ${objs[i].Attributes.room} | role: ${obj.common.role} | function: ${Funktion} | ${i + 1}/${objs.length}`; if (logCheckObject && !debugNAME.includes(device)) { logInfo(fn, text); } else { logDebug(fn, device, text, ''); } // Rooms if (objs[i].Attributes && objs[i].Attributes.room && autoRoom) { const rrr = objs[i].Attributes.room.split(','); for (let r = 0; r < rrr.length; r++) { rrr[r] = rrr[r].trim(); rooms[rrr[r]] = rooms[rrr[r]] || []; rooms[rrr[r]].push(channel); } } // Attributes if (objs[i].Attributes) { (debugNAME.includes(device) || debug) && adapter.log.info(`${debugN} > check Attributes`); if (!objs[i].Attributes.alias) { adapter.log.debug('check alias of ' + device + ' > not detected! set alias automatically in FHEM'); sendFHEM(fn, `attr ${device} alias ${device}`); } for (const attr in objs[i].Attributes) { // allowed Attributes? if (!allowedAttributes.includes(attr)) { (debugNAME.includes(device) || debug) && adapter.log.warn(`${debugN} >> ${attr} = ${objs[i].Attributes[attr]} > no sync - not included in ${adapter.namespace}.info.Config.allowedAttributes`); continue; } const val = objs[i].Attributes[attr]; obj = { _id: `${channel}.Attributes.${convertNameFHEM(fn, attr)}`, type: 'state', common: { name: `${alias} ${attr}`, type: 'string', role: 'text', read: true, write: true }, native: { Name: device, Attribute: attr, Attributes: true } }; (debugNAME.includes(device) || debug) && adapter.log.info(`${debugN} >> ${attr} = ${objs[i].Attributes[attr]} > ${obj._id} = ${val} | type: ${obj.common.type} | read: ${obj.common.read} | write: ${obj.common.write} | role: ${obj.common.role}`); objects.push(obj); states.push({ id: obj._id, val: val, ts: Date.now(), ack: true }); } } // Internals if (objs[i].Internals) { (debugNAME.includes(device) || debug) && adapter.log.info(`${debugN} > check Internals`); for (const attr in objs[i].Internals) { // allowed Internals?