iobroker.fhem
Version:
930 lines (928 loc) • 162 kB
JavaScript
/* 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?