/* jshint -W097 */ /* jshint strict: false */ /* jslint node: true */ 'use strict'; const utils = require(__dirname + '/lib/utils'); let rsonoff = null; let fs; let serialport = null; let adapter = utils.Adapter({ name: 'rsonoff', unload: stop }); process.on('SIGINT', stop); adapter.on('ready', function () { try { serialport = require('serialport'); } catch (err) { adapter.log.warn('Serial is not available'); } adapter.setState('info.connection', adapter.config.params.slave ? 0 : false, true); main(); }); adapter.on('message', function (obj) { if (obj) { switch (obj.command) { case 'listUart': if (obj.callback) { adapter.log.warn('Module serialport is not available'); adapter.sendTo(obj.from, obj.command, [{comName: 'Not available'}], obj.callback); } break; } } }); function stop(callback) { if (rsonoff) { rsonoff.close(); rsonoff = null; } if (adapter && adapter.setState && adapter.config && adapter.config.params) { adapter.setState('info.connection', adapter.config.params.slave ? 0 : false, true); } if (typeof callback === 'function') callback(); setTimeout(function() { process.exit(); }, 5000); } let objects = {}; let enums = {}; let infoRegExp = new RegExp(adapter.namespace.replace('.', '\\.') + '\\.info\\.'); adapter.on('stateChange', (id, state) => { if (state && !state.ack && id && !infoRegExp.test(id)) { if (!rsonoff) { adapter.log.warn('No connection') } else { if (objects[id]) { rsonoff.write(id, state); } else { adapter.getObject(id, (err, data) => { if (!err) { objects[id] = data; rsonoff.write(id, state); } }); } } } }); function addToEnum(enumName, id, callback) { adapter.getForeignObject(enumName, function (err, obj) { if (!err && obj) { let pos = obj.common.members.indexOf(id); if (pos === -1) { obj.common.members.push(id); adapter.setForeignObject(obj._id, obj, function (err) { if (callback) callback(err); }); } else { if (callback) callback(err); } } else { if (callback) callback(err); } }); } function removeFromEnum(enumName, id, callback) { adapter.getForeignObject(enumName, function (err, obj) { if (!err && obj) { let pos = obj.common.members.indexOf(id); if (pos !== -1) { obj.common.members.splice(pos, 1); adapter.setForeignObject(obj._id, obj, function (err) { if (callback) callback(err); }); } else { if (callback) callback(err); } } else { if (callback) callback(err); } }); } function syncEnums(enumGroup, id, newEnumName, callback) { if (!enums[enumGroup]) { adapter.getEnum(enumGroup, function (err, _enums) { enums[enumGroup] = _enums; syncEnums(enumGroup, id, newEnumName, callback); }); return; } // try to find this id in enums let found = false; let count = 0; for (let e in enums[enumGroup]) { if (enums[enumGroup].hasOwnProperty(e) && enums[enumGroup][e].common && enums[enumGroup][e].common.members && enums[enumGroup][e].common.members.indexOf(id) !== -1) { if (enums[enumGroup][e]._id !== newEnumName) { count++; removeFromEnum(enums[enumGroup][e]._id, id, function () { if (!--count && typeof callback === 'function') callback(); }); } else { found = true; } } } if (!found && newEnumName) { count++; addToEnum(newEnumName, id, function () { if (!--count&& typeof callback === 'function') callback(); }); } if (!count && typeof callback === 'function') callback(); } const type_items_len = { 'uint8be': 1 }; const _rmap = { 0: 15, 1: 14, 2: 13, 3: 12, 4: 11, 5: 10, 6: 9, 7: 8, 8: 7, 9: 6, 10: 5, 11: 4, 12: 3, 13: 2, 14: 1, 15: 0 }; const _dmap = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15 }; function address2alias(id, address, isDirect, offset) { if (typeof address === 'string') address = parseInt(address, 10); if (id === 'disInputs' || id === 'coils') { address = Math.floor(address / 16) * 16 + (isDirect ? _dmap[address % 16] : _rmap[address % 16]); address += offset; return address; } else { return address + offset; } } function createExtendObject(id, objData, callback) { adapter.getObject(id, function (err, oldObj) { if (!err && oldObj) { adapter.extendObject(id, objData, callback); } else { adapter.setObjectNotExists(id, objData, callback); } }); } function processTasks(tasks, callback) { if (!tasks || !tasks.length) { if (typeof callback === 'function') callback(); return; } let task = tasks.shift(); if (task.name === 'add') { createExtendObject(task.id, task.obj, function () { setTimeout(processTasks, 0, tasks, callback); }); } else if (task.name === 'del') { adapter.delObject(task.id, function () { setTimeout(processTasks, 0, tasks, callback); }); } else if (task.name === 'syncEnums') { syncEnums('rooms', task.id, task.obj, function () { setTimeout(processTasks, 0, tasks, callback); }); } else { throw 'Unknown task'; } } function prepareConfig(config) { let params = config.params; params.slave = parseInt(params.slave, 10) || 0; // required in stop let options = { config: { type: params.type || 'tcp', slave: params.slave, alwaysUpdate: params.alwaysUpdate, round: parseInt(params.round, 10) || 0, timeout: parseInt(params.timeout, 10) || 5000, defaultDeviceId: (params.deviceId === undefined || params.deviceId === null) ? 1 : (parseInt(params.deviceId, 10) || 0), }, devices: {} }; options.config.round = Math.pow(10, options.config.round); if (!options.config.slave) { options.config.multiDeviceId = params.multiDeviceId === true || params.multiDeviceId === 'true'; } let deviceIds = []; checkDeviceIds(options, config.holdingRegs, deviceIds); deviceIds.sort(); // settings for master if (!options.config.slave) { options.config.poll = parseInt(params.poll, 10) || 1000; // default is 1 second options.config.recon = parseInt(params.recon, 10) || 60000; options.config.maxBlock = parseInt(params.maxBlock, 10) || 100; options.config.maxBoolBlock = parseInt(params.maxBoolBlock, 10) || 128; options.config.pulsetime = parseInt(params.pulsetime || 1000); } if (params.type === 'tcp' || params.type === 'tcprtu') { options.config.tcp = { port: parseInt(params.port, 10) || 502, bind: params.bind }; } else { options.config.serial = { comName: params.comName, baudRate: params.baudRate, dataBits: params.dataBits, stopBits: params.stopBits, parity: params.parity }; } for (let d = 0; d < deviceIds.length; d++) { let deviceId = deviceIds[d]; options.devices[deviceId] = {}; let device = options.devices[deviceId]; if (options.config.slave) { device.holdingRegs = { fullIds: [], changed: true, addressHigh: 0, addressLow: 0, values: [], mapping: {}, offset: parseInt(params.holdingRegsOffset, 10) }; } else { device.holdingRegs = { deviceId: deviceId, addressLow: 0, length: 0, config: [], blocks: [], cyclicWrite: [], // only holdingRegs and coils offset: parseInt(params.holdingRegsOffset, 10) }; } } options.objects = objects; return options; } function checkDeviceIds(options, config, deviceIds) { for (let i = config.length - 1; i >= 0; i--) { config[i].deviceId = !options.config.multiDeviceId ? options.config.defaultDeviceId : (config[i].deviceId !== undefined ? parseInt(config[i].deviceId, 10) : options.config.defaultDeviceId); if (isNaN(config[i].deviceId)) config[i].deviceId = options.config.defaultDeviceId; if (deviceIds.indexOf(config[i].deviceId) === -1) { deviceIds.push(config[i].deviceId); } } } function checkObjects(options, regType, regName, regFullName, tasks, newObjects) { let regs = options[regType]; for (let i = 0; regs.length > i; i++) { // const id = adapter.namespace + '.' + regs[i].id; const id = adapter.namespace + '.' + regType + '.' + regs[i]._address + '.' + regs[i].name; regs[i].id = id; adapter.log.warn('id=' + id); regs[i].fullId = id; objects[id] = { _id: regs[i].id, type: 'state', common: { name: regs[i].description, role: regs[i].role, type: regType === 'coils' || regType === 'disInputs' ? 'boolean' : ((regs[i].type === 'string' || regs[i].type === 'string') ? 'string' : 'number'), read: true, write: regType === 'coils' || regType === 'holdingRegs', def: regType === 'coils' || regType === 'disInputs' ? false : 0 }, native: { regType: regType, address: regs[i].address, deviceId: regs[i].deviceId } }; objects[id].common.unit = regs[i].unit || ''; objects[id].native.type = regs[i].type; objects[id].native.len = regs[i].len; objects[id].native.offset = regs[i].offset; objects[id].native.factor = regs[i].factor; objects[id].native.poll = regs[i].poll; tasks.push({ id: regs[i].id, name: 'add', obj: objects[id] }); tasks.push({ id: id, name: 'syncEnums', obj: regs[i].room }); newObjects.push(id); } if (regs.length) { tasks.push({ id: regName, name: 'add', obj: { type: 'channel', common: { name: regFullName }, native: {} } }); } } function parseConfig(callback) { let options = prepareConfig(adapter.config); const params = adapter.config.params; const localOptions = { multiDeviceId: options.config.multiDeviceId, showAliases: (params.showAliases === true || params.showAliases === 'true'), doNotRoundAddressToWord: (params.doNotRoundAddressToWord === true || params.doNotRoundAddressToWord === 'true'), directAddresses: (params.directAddresses === true || params.directAddresses === 'true'), maxBlock: options.config.maxBlock, maxBoolBlock: options.config.maxBoolBlock }; adapter.getForeignObjects(adapter.namespace + '.*', (err, list) => { let oldObjects = list; let newObjects = []; let tasks = []; // ------------- create states and objects ---------------------------- checkObjects(adapter.config, 'holdingRegs', 'holdingRegisters', 'Holding registers', tasks, newObjects); if (options.config.slave) { device.holdingRegs.fullIds = adapter.config.holdingRegs.filter(e => e.deviceId === deviceId).map(e => e.fullId); } tasks.push({ id: 'info', name: 'add', obj: { type: 'channel', common: { name: 'info' }, native: {} } }); // create/ update 'info.connection' object adapter.getObject('info.connection', function (err, obj) { if (!obj) { obj = { type: 'state', common: { name: 'Number of connected partners', role: 'indicator.connected', write: false, read: true, type: options.config.slave ? 'number' : 'boolean' }, native: {} }; adapter.setObjectNotExists('info.connection', obj); } else if (options.config.slave && obj.common.type !== 'number') { obj.common.type = 'number'; obj.common.name = 'Number of connected masters'; adapter.setObjectNotExists('info.connection', obj); } else if (!options.config.slave && obj.common.type !== 'boolean') { obj.common.type = 'boolean'; obj.common.name = 'If master connected'; adapter.setObjectNotExists('info.connection', obj); } }); newObjects.push(adapter.namespace + '.info.connection'); // clear unused states for (let id_ in oldObjects) { if (oldObjects.hasOwnProperty(id_) && newObjects.indexOf(id_) === -1) { tasks.push({ id: id_, name: 'del' }); } } processTasks(tasks, function () { oldObjects = []; newObjects = []; adapter.subscribeStates('*'); callback(options); }); }); } function main() { parseConfig(options => { let Modbus; Modbus = require(__dirname + '/lib/master'); rsonoff = new Modbus(options, adapter); rsonoff.start(); }); }