From 524596ac25527a3fdef0ef7f3211ab4cda37e1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=9F=E8=BF=9B?= Date: Wed, 16 Jan 2019 04:10:27 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20main.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 2038 ++++++++++++++++++++++--------------------------------- 1 file changed, 827 insertions(+), 1211 deletions(-) diff --git a/main.js b/main.js index fd25473..62addaf 100644 --- a/main.js +++ b/main.js @@ -1,1211 +1,827 @@ -/* jshint -W097 */ -/* jshint strict: false */ -/* jslint node: true */ -'use strict'; - -const utils = require(__dirname + '/lib/utils'); -let modbus = null; -let fs; - -let serialport = null; - -let adapter = utils.Adapter({ - name: 'modbus', - 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) { - if (serialport) { - // read all found serial ports - serialport.list(function (err, ports) { - listSerial(ports); - adapter.log.info('List of port: ' + JSON.stringify(ports)); - adapter.sendTo(obj.from, obj.command, ports, obj.callback); - }); - } else { - adapter.log.warn('Module serialport is not available'); - adapter.sendTo(obj.from, obj.command, [{comName: 'Not available'}], obj.callback); - } - } - break; - } - } -}); - -function stop(callback) { - if (modbus) { - modbus.close(); - modbus = 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 (!modbus) { - adapter.log.warn('No connection') - } else { - if (objects[id]) { - modbus.write(id, state); - } else { - adapter.getObject(id, (err, data) => { - if (!err) { - objects[id] = data; - modbus.write(id, state); - } - }); - } - } - } -}); - -function filterSerialPorts(path) { - fs = fs || require('fs'); - // get only serial port names - if (!(/(tty(S|ACM|USB|AMA|MFD)|rfcomm)/).test(path)) return false; - - return fs - .statSync(path) - .isCharacterDevice(); -} - -function listSerial(ports) { - ports = ports || []; - const path = require('path'); - fs = fs || require('fs'); - - // Filter out the devices that aren't serial ports - let devDirName = '/dev'; - - let result; - try { - result = fs - .readdirSync(devDirName) - .map(function (file) { - return path.join(devDirName, file); - }) - .filter(filterSerialPorts) - .map(function (port) { - let found = false; - for (let v = 0; v < ports.length; v++) { - if (ports[v].comName === port) { - found = true; - break; - } - } - if (!found) ports.push({comName: port}); - return {comName: port}; - }); - } catch (e) { - if (require('os').platform() !== 'win32') { - adapter.log.error('Cannot read "' + devDirName + '": ' + e); - } - result = []; - } - return result; -} - -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, - 'uint8le': 1, - 'int8be': 1, - 'int8le': 1, - 'uint16be': 1, - 'uint16le': 1, - 'int16be': 1, - 'int16le': 1, - 'int16be1': 1, - 'int16le1': 1, - 'uint32be': 2, - 'uint32le': 2, - 'uint32sw': 2, - 'uint32sb': 2, - 'int32be': 2, - 'int32le': 2, - 'int32sw': 2, - 'int32sb': 2, - 'uint64be': 4, - 'uint64le': 4, - 'int64be': 4, - 'int64le': 4, - 'floatbe': 2, - 'floatle': 2, - 'floatsw': 2, - 'floatsb': 2, - 'doublebe': 4, - 'doublele': 4, - 'string': 0 -}; - -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.disInputs, deviceIds); - checkDeviceIds(options, config.coils, deviceIds); - checkDeviceIds(options, config.inputRegs, 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.disInputs = { - fullIds: [], - changed: true, - addressHigh: 0, - addressLow: 0, - values: [], - mapping: {}, - offset: parseInt(params.disInputsOffset, 10) - }; - - device.coils = { - fullIds: [], - changed: true, - addressHigh: 0, - addressLow: 0, - values: [], - mapping: {}, - offset: parseInt(params.coilsOffset, 10) - }; - - device.inputRegs = { - fullIds: [], - changed: true, - addressHigh: 0, - addressLow: 0, - values: [], - mapping: {}, - offset: parseInt(params.inputRegsOffset, 10) - }; - - device.holdingRegs = { - fullIds: [], - changed: true, - addressHigh: 0, - addressLow: 0, - values: [], - mapping: {}, - offset: parseInt(params.holdingRegsOffset, 10) - }; - } else { - device.disInputs = { - deviceId: deviceId, - addressLow: 0, - length: 0, - config: [], - blocks: [], - offset: parseInt(params.disInputsOffset, 10) - }; - - device.coils = { - deviceId: deviceId, - addressLow: 0, - length: 0, - config: [], - blocks: [], - cyclicWrite: [], // only holdingRegs and coils - offset: parseInt(params.coilsOffset, 10) - }; - - device.inputRegs = { - deviceId: deviceId, - addressLow: 0, - length: 0, - config: [], - blocks: [], - offset: parseInt(params.inputRegsOffset, 10) - }; - - 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; - 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 - } - }; - if (regType === 'coils') { - objects[id].native.poll = regs[i].poll; - objects[id].native.wp = regs[i].wp; - } else - if (regType === 'inputRegs' || regType === 'holdingRegs') { - 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; - if (regType === 'holdingRegs') { - 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: {} - } - }); - } -} - -// localOptions = { -// multiDeviceId -// showAliases -// doNotRoundAddressToWord -// directAddresses -// isSlave -// maxBlock -// maxBoolBlock -// }; -function iterateAddresses(isBools, deviceId, result, regName, regType, localOptions) { - const config = result.config; - if (config && config.length) { - result.addressLow = 0xFFFFFFFF; - result.addressHigh = 0; - - for (let i = config.length - 1; i >= 0; i--) { - if (config[i].deviceId !== deviceId) continue; - const address = config[i].address = parseInt(config[i].address, 10); - - if (address < 0) { - adapter.log.error(`Invalid ${regName} address: ${address}`); - config.splice(i, 1); - continue; - } - - if (!isBools) { - config[i].type = config[i].type || 'uint16be'; - config[i].offset = parseFloat(config[i].offset) || 0; - config[i].factor = parseFloat(config[i].factor) || 1; - if (config[i].type === 'string') { - config[i].len = parseInt(config[i].len, 10) || 1; - } else { - config[i].len = type_items_len[config[i].type]; - } - config[i].len = config[i].len || 1; - } else { - config[i].len = 1; - } - - if (localOptions.multiDeviceId) { - config[i].id = regName + '.' + deviceId + '.'; - } else { - config[i].id = regName + '.'; - } - - if (localOptions.showAliases) { - config[i].id += address2alias(regType, address, localOptions.directAddresses, result.offset); - } else { - config[i].id += address; - } - config[i].id += (config[i].name ? '_' + (config[i].name.replace('.', '_').replace(' ', '_')) : ''); - - // collect cyclic write registers - if (config[i].cw) { - result.cyclicWrite.push(adapter.namespace + '.' + config[i].id); - } - - if (address < result.addressLow) result.addressLow = address; - if (address + config[i].len > result.addressHigh) { - result.addressHigh = address + (config[i].len || 1); - } - } - - const maxBlock = isBools ? localOptions.maxBoolBlock : localOptions.maxBlock; - let lastAddress = null; - let startIndex = 0; - let blockStart = 0; - let i; - for (i = 0; i < config.length; i++) { - if (config[i].deviceId !== deviceId) continue; - - if (lastAddress === null) { - startIndex = i; - blockStart = config[i].address; - lastAddress = blockStart + config[i].len; - } - - // try to detect next block - if (result.blocks) { - if ((config[i].address - lastAddress > 10 && config[i].len < 10) || (lastAddress - blockStart >= maxBlock)) { - if (result.blocks.map(obj => obj.start).indexOf(blockStart) === -1) { - result.blocks.push({start: blockStart, count: lastAddress - blockStart, startIndex: startIndex, endIndex: i}); - } - blockStart = config[i].address; - startIndex = i; - } - } - lastAddress = config[i].address + config[i].len; - } - if (lastAddress && lastAddress - blockStart && result.blocks && result.blocks.map(obj => obj.start).indexOf(blockStart) === -1) { - result.blocks.push({start: blockStart, count: lastAddress - blockStart, startIndex: startIndex, endIndex: i}); - } - - if (config.length) { - result.length = result.addressHigh - result.addressLow; - if (isBools && !localOptions.doNotRoundAddressToWord) { - result.addressLow = Math.floor(result.addressLow / 16) * 16; - - if (result.length % 16) { - result.length = (Math.floor(result.length / 16) + 1) * 16; - } - if (result.blocks) { - for (let b = 0; b < result.blocks.length; b++) { - result.blocks[b].start = Math.floor(result.blocks[b].start / 16) * 16; - - if (result.blocks[b].count % 16) { - result.blocks[b].count = (Math.floor(result.blocks[b].count / 16) + 1) * 16; - } - } - } - } - } else { - result.length = 0; - } - - if (result.mapping) { - for (let i = 0; i < config.length; i++) { - result.mapping[config[i].address - result.addressLow] = adapter.namespace + '.' + config[i].id; - } - } - } -} - -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 = []; - - adapter.config.disInputs.sort(sortByAddress); - adapter.config.coils.sort(sortByAddress); - adapter.config.inputRegs.sort(sortByAddress); - adapter.config.holdingRegs.sort(sortByAddress); - - let tasks = []; - - for (let _deviceId in options.devices) { - if (!options.devices.hasOwnProperty(_deviceId)) continue; - let device = options.devices[_deviceId]; - let deviceId = parseInt(_deviceId, 10); - - device.disInputs.config = adapter.config.disInputs. filter(e => e.deviceId === deviceId); - device.coils.config = adapter.config.coils. filter(e => e.poll && e.deviceId === deviceId); - device.inputRegs.config = adapter.config.inputRegs. filter(e => e.deviceId === deviceId); - device.holdingRegs.config = adapter.config.holdingRegs.filter(e => e.poll && e.deviceId === deviceId); - - // ----------- remember poll values -------------------------- - if (!options.config.slave) { - tasks.push({ - id: 'info.pollTime', - name: 'add', - obj: { - type: 'state', - common: { - name: 'Poll time', - type: 'number', - role: '', - write: false, - read: true, - def: 0, - unit: 'ms' - }, - native: {} - } - }); - newObjects.push(adapter.namespace + '.info.pollTime'); - } - - // Discrete inputs - iterateAddresses(true, deviceId, device.disInputs, 'discreteInputs', 'disInputs', localOptions); - iterateAddresses(true, deviceId, device.coils, 'coils', 'coils', localOptions); - iterateAddresses(false, deviceId, device.inputRegs, 'inputRegisters', 'inputRegs', localOptions); - iterateAddresses(false, deviceId, device.holdingRegs, 'holdingRegisters', 'holdingRegs', localOptions); - /*let regs = adapter.config.disInputs; - let res = device.disInputs; - if (regs && regs.length) { - res.addressLow = 0xFFFFFFFF; - res.addressHigh = 0; - for (i = regs.length - 1; i >= 0; i--) { - address = parseInt(regs[i].address, 10); - - if (address < 0) { - adapter.log.error('Invalid discrete inputs address: ' + address); - regs.splice(i, 1); - continue; - } - - regs[i].id = 'discreteInputs.'; - if (showAliases) { - regs[i].id += address2alias('disInputs', address, directAddresses, res.offset, options.multiDeviceId, regs[i].deviceId); - } else { - regs[i].id += address; - } - regs[i].id += (regs[i].name ? '_' + (regs[i].name.replace('.', '_').replace(' ', '_')) : ''); - } - - if (regs.length) { - regs.sort(sortByAddress); - if (!doNotRoundAddressToWord) { - res.addressLow = Math.floor(regs[0].address / 16) * 16; - } - res.addressHigh = regs[regs.length - 1].address; - res.length = res.addressHigh - res.addressLow + 1; - if (!doNotRoundAddressToWord && (res.length % 16)) { - res.length = (Math.floor(res.length / 16) + 1) * 16; - } - } else { - res.length = 0; - } - } - - // Coils - regs = adapter.config.coils; - res = device.coils; - if (regs && regs.length) { - res.addressLow = 0xFFFFFFFF; - res.addressHigh = 0; - for (i = regs.length - 1; i >= 0; i--) { - address = parseInt(regs[i].address, 10); - - if (address < 0) { - adapter.log.error('Invalid coils address: ' + address); - regs.splice(i, 1); - continue; - } - - regs[i].id = 'coils.'; - if (showAliases) { - regs[i].id += address2alias('coils', address, directAddresses, res.offset, options.multiDeviceId, regs[i].deviceId); - } else { - regs[i].id += address; - } - regs[i].id += (regs[i].name ? '_' + (regs[i].name.replace('.', '_').replace(' ', '_')) : ''); - - if (options.config.slave || regs[i].poll) { - if (address < res.addressLow) res.addressLow = address; - if (address > res.addressHigh) res.addressHigh = address; - } - } - if (regs.length) { - regs.sort(sortByAddress); - if (!doNotRoundAddressToWord) { - res.addressLow = Math.floor(res.addressLow / 16) * 16; - } - - res.length = res.addressHigh - res.addressLow + 1; - if (!doNotRoundAddressToWord && (res.length % 16)) { - res.length = (Math.floor(res.length / 16) + 1) * 16; - } - } else { - regs.length = 0; - } - if (regs.mapping) { - for (i = 0; i < regs.length; i++) { - regs.mapping[regs[i].address - res.addressLow] = adapter.namespace + '.' + regs[i].id; - } - } - } - - // Input registers - regs = adapter.config.inputRegs; - res = device.inputRegs; - if (regs.length) { - for (i = regs.length - 1; i >= 0; i--) { - address = parseInt(regs[i].address, 10); - if (address < 0) { - adapter.log.error('Invalid input register address: ' + address); - regs.splice(i, 1); - continue; - } - - regs[i].type = regs[i].type || 'uint16be'; - regs[i].offset = parseFloat(regs[i].offset) || 0; - regs[i].factor = parseFloat(regs[i].factor) || 1; - if (regs[i].type === 'string') { - regs[i].len = parseInt(regs[i].len) || 1; - } else { - regs[i].len = type_items_len[regs[i].type]; - } - regs[i].len = regs[i].len || 1; - - if (!regs[i].len) regs[i].len = parseInt(regs[i].len) || 1; - - regs[i].id = 'inputRegisters.'; - if (showAliases) { - regs[i].id += address2alias('inputRegs', address, directAddresses, res.offset, options.multiDeviceId, regs[i].deviceId); - } else { - regs[i].id += address; - } - - regs[i].id += (regs[i].name ? '_' + (regs[i].name.replace('.', '_').replace(' ', '_')) : ''); - } - - lastAddress = null; - startIndex = 0; - for (i = 0; i < regs.length; i++) { - address = parseInt(regs[i].address, 10); - if (address < 0) continue; - if (lastAddress === null) { - startIndex = i; - blockStart = address; - lastAddress = address + regs[i].len; - } - - // try to detect next block - if (res.blocks) { - if ((address - lastAddress > 10 && regs[i].len < 10) || (lastAddress - blockStart >= options.config.maxBlock)) { - if (res.blocks.map(obj => obj.start).indexOf(blockStart) === -1) { - res.blocks.push({start: blockStart, count: lastAddress - blockStart, startIndex: startIndex, endIndex: i}); - } - blockStart = address; - startIndex = i; - } - } - lastAddress = address + regs[i].len; - } - if (res.blocks && res.blocks.map(obj => obj.start).indexOf(blockStart) === -1) { - res.blocks.push({start: blockStart, count: lastAddress - blockStart, startIndex: startIndex, endIndex: i}); - } - if (regs.length) { - res.addressLow = regs[0].address; - res.addressHigh = regs[regs.length - 1].address + regs[regs.length - 1].len; - res.length = res.addressHigh - res.addressLow; - } else { - regs.length = 0; - } - } - - // Holding registers - regs = adapter.config.holdingRegs; - res = device.holdingRegs; - if (regs.length) { - res.addressLow = 0xFFFFFFFF; - res.addressHigh = 0; - - for (i = regs.length - 1; i >= 0; i--) { - address = parseInt(regs[i].address, 10); - - if (address < 0) { - adapter.log.error('Invalid holding register address: ' + address); - regs.splice(i, 1); - continue; - } - - regs[i].type = regs[i].type || 'uint16be'; - regs[i].offset = parseFloat(regs[i].offset) || 0; - regs[i].factor = parseFloat(regs[i].factor) || 1; - if (regs[i].type === 'string') { - regs[i].len = parseInt(regs[i].len) || 1; - } else { - regs[i].len = type_items_len[regs[i].type]; - } - regs[i].len = regs[i].len || 1; - - regs[i].id = 'holdingRegisters.'; - if (showAliases) { - regs[i].id += address2alias('holdingRegs', address, directAddresses, res.offset, options.multiDeviceId, regs[i].deviceId); - } else { - regs[i].id += address; - } - regs[i].id += (regs[i].name ? '_' + (regs[i].name.replace('.', '_').replace(' ', '_')) : ''); - - // collect cyclic write registers - if (regs[i].cw) { - res.cyclicWrite.push(adapter.namespace + '.' + regs[i].id); - } - - if (options.config.slave || regs[i].poll) { - if (address < res.addressLow) res.addressLow = address; - if (address + regs[i].len > res.addressHigh) res.addressHigh = address + regs[i].len; - } - } - - lastAddress = null; - startIndex = 0; - for (i = 0; i < regs.length; i++) { - address = parseInt(regs[i].address, 10); - if (address < 0) continue; - if (lastAddress === null) { - startIndex = i; - blockStart = address; - lastAddress = address + regs[i].len; - } - // try to detect next block - if (res.blocks) { - if ((address - lastAddress > 10 && regs[i].len < 10) || (lastAddress - blockStart >= options.config.maxBlock)) { - if (res.blocks.map(obj => obj.start).indexOf(blockStart) === -1) { - res.blocks.push({start: blockStart, count: lastAddress - blockStart, startIndex: startIndex, endIndex: i}); - } - blockStart = address; - startIndex = i; - } - } - lastAddress = address + regs[i].len; - } - if (res.blocks && res.blocks.map(obj => obj.start).indexOf(blockStart) === -1) { - res.blocks.push({start: blockStart, count: lastAddress - blockStart, startIndex: startIndex, endIndex: i}); - } - - if (regs.length) { - res.length = res.addressHigh - res.addressLow; - } else { - res.length = 0; - } - - lastAddress = null; - - if (regs.mapping) { - for (i = 0; i < regs.length; i++) { - res.mapping[regs[i].address - res.addressLow] = adapter.namespace + '.' + regs[i].id; - } - } - }*/ - - // ------------- create states and objects ---------------------------- - checkObjects(adapter.config, 'disInputs', 'discreteInputs', 'Discrete inputs', tasks, newObjects); - checkObjects(adapter.config, 'coils', 'coils', 'Coils', tasks, newObjects); - checkObjects(adapter.config, 'inputRegs', 'inputRegisters', 'Input registers', tasks, newObjects); - checkObjects(adapter.config, 'holdingRegs', 'holdingRegisters', 'Holding registers', tasks, newObjects); - - if (options.config.slave) { - device.disInputs.fullIds = adapter.config.disInputs .filter(e => e.deviceId === deviceId).map(e => e.fullId); - device.coils.fullIds = adapter.config.coils .filter(e => e.deviceId === deviceId).map(e => e.fullId); - device.inputRegs.fullIds = adapter.config.inputRegs .filter(e => e.deviceId === deviceId).map(e => e.fullId); - device.holdingRegs.fullIds = adapter.config.holdingRegs.filter(e => e.deviceId === deviceId).map(e => e.fullId); - } - - /*for (i = 0; regs.length > i; i++) { - id = adapter.namespace + '.' + regs[i].id; - regs[i].fullId = id; - objects[id] = { - _id: regs[i].id, - type: 'state', - common: { - name: regs[i].description, - role: regs[i].role, - type: 'boolean', - read: true, - write: false, - def: false - }, - native: { - regType: 'disInputs', - address: regs[i].address - } - }; - tasks.push({ - id: regs[i].id, - name: 'add', - obj: objects[id] - }); - tasks.push({ - id: id, - name: 'syncEnums', - obj: regs[i].room - }); - newObjects.push(id); - } - - regs = adapter.config.coils; - for (i = 0; regs.length > i; i++) { - id = adapter.namespace + '.' + regs[i].id; - regs[i].fullId = id; - objects[id] = { - _id: regs[i].id, - type: 'state', - common: { - name: regs[i].description, - role: regs[i].role, - type: 'boolean', - read: true, - write: true, - def: false - }, - native: { - regType: 'coils', - address: regs[i].address, - - poll: regs[i].poll, - wp: regs[i].wp - } - }; - - tasks.push({ - id: regs[i].id, - name: 'add', - obj: objects[id] - }); - tasks.push({ - id: id, - name: 'syncEnums', - obj: regs[i].room - }); - newObjects.push(id); - } - - regs = adapter.config.inputRegs; - for (i = 0; regs.length > i; i++) { - id = adapter.namespace + '.' + regs[i].id; - regs[i].fullId = id; - objects[id] = { - _id: regs[i].id, - type: 'state', - common: { - name: regs[i].description, - role: regs[i].role, - type: 'number', - read: true, - write: false, - def: 0, - unit: regs[i].unit || '' - }, - native: { - regType: 'inputRegs', - address: regs[i].address, - type: regs[i].type, - len: regs[i].len, - offset: regs[i].offset, - factor: regs[i].factor - } - }; - tasks.push({ - id: regs[i].id, - name: 'add', - obj: objects[id] - }); - tasks.push({ - id: id, - name: 'syncEnums', - obj: regs[i].room - }); - newObjects.push(id); - } - - regs = adapter.config.holdingRegs; - for (i = 0; regs.length > i; i++) { - id = adapter.namespace + '.' + regs[i].id; - regs[i].fullId = id; - objects[id] = { - _id: regs[i].id, - type: 'state', - common: { - name: regs[i].description, - role: regs[i].role, - type: 'number', - read: true, - write: true, - def: 0, - unit: regs[i].unit || '' - }, - native: { - regType: 'holdingRegs', - address: regs[i].address, - poll: regs[i].poll, - // wp: adapter.config.coils[i].wp - type: regs[i].type, - len: regs[i].len, - offset: regs[i].offset, - factor: regs[i].factor - } - }; - 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 (!options.config.multiDeviceId) { - break; - } - } - - 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; - if (options.config.slave) { - Modbus = require(__dirname + '/lib/slave'); - } else { - Modbus = require(__dirname + '/lib/master'); - } - modbus = new Modbus(options, adapter); - modbus.start(); - }); -} - -function sortByAddress(a, b) { - let ad = parseFloat(a.address); - let bd = parseFloat(b.address); - return ((ad < bd) ? -1 : ((ad > bd) ? 1 : 0)); -} +/* jshint -W097 */ +/* jshint strict: false */ +/* jslint node: true */ +'use strict'; + +const utils = require(__dirname + '/lib/utils'); +let modbus = null; +let fs; + +let serialport = null; + +let adapter = utils.Adapter({ + name: 'modbus', + 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) { + if (serialport) { + // read all found serial ports + serialport.list(function (err, ports) { + listSerial(ports); + adapter.log.info('List of port: ' + JSON.stringify(ports)); + adapter.sendTo(obj.from, obj.command, ports, obj.callback); + }); + } else { + adapter.log.warn('Module serialport is not available'); + adapter.sendTo(obj.from, obj.command, [{comName: 'Not available'}], obj.callback); + } + } + break; + } + } +}); + +function stop(callback) { + if (modbus) { + modbus.close(); + modbus = 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 (!modbus) { + adapter.log.warn('No connection') + } else { + if (objects[id]) { + modbus.write(id, state); + } else { + adapter.getObject(id, (err, data) => { + if (!err) { + objects[id] = data; + modbus.write(id, state); + } + }); + } + } + } +}); + +function filterSerialPorts(path) { + fs = fs || require('fs'); + // get only serial port names + if (!(/(tty(S|ACM|USB|AMA|MFD)|rfcomm)/).test(path)) return false; + + return fs + .statSync(path) + .isCharacterDevice(); +} + +function listSerial(ports) { + ports = ports || []; + const path = require('path'); + fs = fs || require('fs'); + + // Filter out the devices that aren't serial ports + let devDirName = '/dev'; + + let result; + try { + result = fs + .readdirSync(devDirName) + .map(function (file) { + return path.join(devDirName, file); + }) + .filter(filterSerialPorts) + .map(function (port) { + let found = false; + for (let v = 0; v < ports.length; v++) { + if (ports[v].comName === port) { + found = true; + break; + } + } + if (!found) ports.push({comName: port}); + return {comName: port}; + }); + } catch (e) { + if (require('os').platform() !== 'win32') { + adapter.log.error('Cannot read "' + devDirName + '": ' + e); + } + result = []; + } + return result; +} + +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, + 'uint8le': 1, + 'int8be': 1, + 'int8le': 1, + 'uint16be': 1, + 'uint16le': 1, + 'int16be': 1, + 'int16le': 1, + 'int16be1': 1, + 'int16le1': 1, + 'uint32be': 2, + 'uint32le': 2, + 'uint32sw': 2, + 'uint32sb': 2, + 'int32be': 2, + 'int32le': 2, + 'int32sw': 2, + 'int32sb': 2, + 'uint64be': 4, + 'uint64le': 4, + 'int64be': 4, + 'int64le': 4, + 'floatbe': 2, + 'floatle': 2, + 'floatsw': 2, + 'floatsb': 2, + 'doublebe': 4, + 'doublele': 4, + 'string': 0 +}; + +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.disInputs, deviceIds); + checkDeviceIds(options, config.coils, deviceIds); + checkDeviceIds(options, config.inputRegs, 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.disInputs = { + fullIds: [], + changed: true, + addressHigh: 0, + addressLow: 0, + values: [], + mapping: {}, + offset: parseInt(params.disInputsOffset, 10) + }; + + device.coils = { + fullIds: [], + changed: true, + addressHigh: 0, + addressLow: 0, + values: [], + mapping: {}, + offset: parseInt(params.coilsOffset, 10) + }; + + device.inputRegs = { + fullIds: [], + changed: true, + addressHigh: 0, + addressLow: 0, + values: [], + mapping: {}, + offset: parseInt(params.inputRegsOffset, 10) + }; + + device.holdingRegs = { + fullIds: [], + changed: true, + addressHigh: 0, + addressLow: 0, + values: [], + mapping: {}, + offset: parseInt(params.holdingRegsOffset, 10) + }; + } else { + device.disInputs = { + deviceId: deviceId, + addressLow: 0, + length: 0, + config: [], + blocks: [], + offset: parseInt(params.disInputsOffset, 10) + }; + + device.coils = { + deviceId: deviceId, + addressLow: 0, + length: 0, + config: [], + blocks: [], + cyclicWrite: [], // only holdingRegs and coils + offset: parseInt(params.coilsOffset, 10) + }; + + device.inputRegs = { + deviceId: deviceId, + addressLow: 0, + length: 0, + config: [], + blocks: [], + offset: parseInt(params.inputRegsOffset, 10) + }; + + 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; + 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 + } + }; + if (regType === 'coils') { + objects[id].native.poll = regs[i].poll; + objects[id].native.wp = regs[i].wp; + } else + if (regType === 'inputRegs' || regType === 'holdingRegs') { + 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; + if (regType === 'holdingRegs') { + 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: {} + } + }); + } +} + +// localOptions = { +// multiDeviceId +// showAliases +// doNotRoundAddressToWord +// directAddresses +// isSlave +// maxBlock +// maxBoolBlock +// }; +function iterateAddresses(isBools, deviceId, result, regName, regType, localOptions) { + const config = result.config; + if (config && config.length) { + result.addressLow = 0xFFFFFFFF; + result.addressHigh = 0; + + for (let i = config.length - 1; i >= 0; i--) { + if (config[i].deviceId !== deviceId) continue; + const address = config[i].address = parseInt(config[i].address, 10); + + if (address < 0) { + adapter.log.error(`Invalid ${regName} address: ${address}`); + config.splice(i, 1); + continue; + } + + if (!isBools) { + config[i].type = config[i].type || 'uint16be'; + config[i].offset = parseFloat(config[i].offset) || 0; + config[i].factor = parseFloat(config[i].factor) || 1; + if (config[i].type === 'string') { + config[i].len = parseInt(config[i].len, 10) || 1; + } else { + config[i].len = type_items_len[config[i].type]; + } + config[i].len = config[i].len || 1; + } else { + config[i].len = 1; + } + + if (localOptions.multiDeviceId) { + config[i].id = regName + '.' + deviceId + '.'; + } else { + config[i].id = regName + '.'; + } + + if (localOptions.showAliases) { + config[i].id += address2alias(regType, address, localOptions.directAddresses, result.offset); + } else { + config[i].id += address; + } + config[i].id += (config[i].name ? '_' + (config[i].name.replace('.', '_').replace(' ', '_')) : ''); + + // collect cyclic write registers + if (config[i].cw) { + result.cyclicWrite.push(adapter.namespace + '.' + config[i].id); + } + + if (address < result.addressLow) result.addressLow = address; + if (address + config[i].len > result.addressHigh) { + result.addressHigh = address + (config[i].len || 1); + } + } + + const maxBlock = isBools ? localOptions.maxBoolBlock : localOptions.maxBlock; + let lastAddress = null; + let startIndex = 0; + let blockStart = 0; + let i; + for (i = 0; i < config.length; i++) { + if (config[i].deviceId !== deviceId) continue; + + if (lastAddress === null) { + startIndex = i; + blockStart = config[i].address; + lastAddress = blockStart + config[i].len; + } + + // try to detect next block + if (result.blocks) { + if ((config[i].address - lastAddress > 10 && config[i].len < 10) || (lastAddress - blockStart >= maxBlock)) { + if (result.blocks.map(obj => obj.start).indexOf(blockStart) === -1) { + result.blocks.push({start: blockStart, count: lastAddress - blockStart, startIndex: startIndex, endIndex: i}); + } + blockStart = config[i].address; + startIndex = i; + } + } + lastAddress = config[i].address + config[i].len; + } + if (lastAddress && lastAddress - blockStart && result.blocks && result.blocks.map(obj => obj.start).indexOf(blockStart) === -1) { + result.blocks.push({start: blockStart, count: lastAddress - blockStart, startIndex: startIndex, endIndex: i}); + } + + if (config.length) { + result.length = result.addressHigh - result.addressLow; + if (isBools && !localOptions.doNotRoundAddressToWord) { + result.addressLow = Math.floor(result.addressLow / 16) * 16; + + if (result.length % 16) { + result.length = (Math.floor(result.length / 16) + 1) * 16; + } + if (result.blocks) { + for (let b = 0; b < result.blocks.length; b++) { + result.blocks[b].start = Math.floor(result.blocks[b].start / 16) * 16; + + if (result.blocks[b].count % 16) { + result.blocks[b].count = (Math.floor(result.blocks[b].count / 16) + 1) * 16; + } + } + } + } + } else { + result.length = 0; + } + + if (result.mapping) { + for (let i = 0; i < config.length; i++) { + result.mapping[config[i].address - result.addressLow] = adapter.namespace + '.' + config[i].id; + } + } + } +} + +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 = []; + + adapter.config.disInputs.sort(sortByAddress); + adapter.config.coils.sort(sortByAddress); + adapter.config.inputRegs.sort(sortByAddress); + adapter.config.holdingRegs.sort(sortByAddress); + + let tasks = []; + + for (let _deviceId in options.devices) { + if (!options.devices.hasOwnProperty(_deviceId)) continue; + let device = options.devices[_deviceId]; + let deviceId = parseInt(_deviceId, 10); + + device.disInputs.config = adapter.config.disInputs. filter(e => e.deviceId === deviceId); + device.coils.config = adapter.config.coils. filter(e => e.poll && e.deviceId === deviceId); + device.inputRegs.config = adapter.config.inputRegs. filter(e => e.deviceId === deviceId); + device.holdingRegs.config = adapter.config.holdingRegs.filter(e => e.poll && e.deviceId === deviceId); + + // ----------- remember poll values -------------------------- + if (!options.config.slave) { + tasks.push({ + id: 'info.pollTime', + name: 'add', + obj: { + type: 'state', + common: { + name: 'Poll time', + type: 'number', + role: '', + write: false, + read: true, + def: 0, + unit: 'ms' + }, + native: {} + } + }); + newObjects.push(adapter.namespace + '.info.pollTime'); + } + + // Discrete inputs + iterateAddresses(true, deviceId, device.disInputs, 'discreteInputs', 'disInputs', localOptions); + iterateAddresses(true, deviceId, device.coils, 'coils', 'coils', localOptions); + iterateAddresses(false, deviceId, device.inputRegs, 'inputRegisters', 'inputRegs', localOptions); + iterateAddresses(false, deviceId, device.holdingRegs, 'holdingRegisters', 'holdingRegs', localOptions); + + // ------------- create states and objects ---------------------------- + checkObjects(adapter.config, 'disInputs', 'discreteInputs', 'Discrete inputs', tasks, newObjects); + checkObjects(adapter.config, 'coils', 'coils', 'Coils', tasks, newObjects); + checkObjects(adapter.config, 'inputRegs', 'inputRegisters', 'Input registers', tasks, newObjects); + checkObjects(adapter.config, 'holdingRegs', 'holdingRegisters', 'Holding registers', tasks, newObjects); + + if (options.config.slave) { + device.disInputs.fullIds = adapter.config.disInputs .filter(e => e.deviceId === deviceId).map(e => e.fullId); + device.coils.fullIds = adapter.config.coils .filter(e => e.deviceId === deviceId).map(e => e.fullId); + device.inputRegs.fullIds = adapter.config.inputRegs .filter(e => e.deviceId === deviceId).map(e => e.fullId); + device.holdingRegs.fullIds = adapter.config.holdingRegs.filter(e => e.deviceId === deviceId).map(e => e.fullId); + } + + + if (!options.config.multiDeviceId) { + break; + } + } + + 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; + /* + if (options.config.slave) { + Modbus = require(__dirname + '/lib/slave'); + } else { + Modbus = require(__dirname + '/lib/master'); + } + modbus = new Modbus(options, adapter); + modbus.start(); + */ + }); +} + +function sortByAddress(a, b) { + let ad = parseFloat(a.address); + let bd = parseFloat(b.address); + return ((ad < bd) ? -1 : ((ad > bd) ? 1 : 0)); +} From a89d232562d462d88b96d17d83f6d960b693f92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=9F=E8=BF=9B?= Date: Wed, 16 Jan 2019 04:25:58 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=E5=88=A0=E9=99=A4=20slave.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/slave.js | 406 --------------------------------------------------- 1 file changed, 406 deletions(-) delete mode 100644 lib/slave.js diff --git a/lib/slave.js b/lib/slave.js deleted file mode 100644 index e517d5d..0000000 --- a/lib/slave.js +++ /dev/null @@ -1,406 +0,0 @@ -'use strict'; -const common = require(__dirname + '/common.js'); -const Modbus = require('./jsmodbus'); - -// expected -// let options = { -// config: { -// round: 1, -// tcp: { -// port: 502 -// } -// }, -// objects: { -// } -// coils: { -// config: ... -// changed: true, -// addressHigh: 0, -// addressLow: 0, -// values: [], -// mapping: {} -// }, -// inputRegs: ..., -// disInputs: ..., -// holdingRegs: ... -// }; - -function Slave(options, adapter) { - let objects = options.objects; - let device = options.devices[Object.keys(options.devices)[0]]; - let delayStart = true; - let modbusServer; - - function getListOfClients(clients) { - let list = []; - for(let c in clients) { - if (clients.hasOwnProperty(c)) { - let address = clients[c].address().address; - if (address) list.push(address); - } - } - return list.join(','); - } - - this.write = (id, state) => { - if (!objects[id] || !objects[id].native) { - adapter.log.error('Can not set state ' + id + ': unknown object'); - return; - } - - if (objects[id].native.float === undefined) { - objects[id].native.float = - objects[id].native.type === 'floatle' || objects[id].native.type === 'floatbe' || objects[id].native.type === 'floatsw' || - objects[id].native.type === 'doublele' || objects[id].native.type === 'doublebe' || objects[id].native.type === 'floatsb'; - } - let val; - let buffer; - let b; - - let t = typeof state.val; - let type = objects[id].native.regType; - let regs = device[type]; - if (!regs) { - adapter.log.error('Invalid type ' + type); - return; - } - regs.changed = true; - - if (type === 'disInputs' || type === 'coils') { - if (t === 'boolean' || t === 'number') { - regs.values[objects[id].native.address - regs.addressLow] = state.val ? 1 : 0; - } else { - regs.values[objects[id].native.address - regs.addressLow] = parseInt(state.val, 10) ? 1 : 0; - } - } else if (type === 'inputRegs' || type === 'holdingRegs') { - if (objects[id].native.type !== 'string') { - if (t === 'boolean') { - val = state.val ? 1 : 0; - } else if (t === 'number') { - val = state.val; - } else { - val = parseFloat(state.val); - } - val = (val - objects[id].native.offset) / objects[id].native.factor; - if (!objects[id].native.float) val = Math.round(val); - } else { - val = state.val; - } - buffer = common.writeValue(objects[id].native.type, val, objects[id].native.len); - for (b = 0; b < buffer.length; b++) { - regs.values[(objects[id].native.address - regs.addressLow) * 2 + b] = buffer[b]; - } - } else { - adapter.log.error('Unknown state "' + id + '" type: ' + objects[id].native.regType); - } - }; - - this.start = () => { - if (!delayStart && !modbusServer) { - modbusServer = Modbus('server', 'tcp')({ - options: { - log: adapter.log, - tcp: { - port: parseInt(options.config.tcp.port, 10) || 502, - hostname: options.config.tcp.bind || '127.0.0.1', - } - }, - responseDelay: 100, - coils: new Buffer((device.coils.addressHigh >> 3) + ((device.coils.addressHigh - 1) % 8 ? 1 : 0)), - discrete: new Buffer((device.disInputs.addressHigh >> 3) + ((device.disInputs.addressHigh - 1) % 8 ? 1 : 0)), - input: new Buffer(device.inputRegs.addressHigh * 2), - holding: new Buffer(device.holdingRegs.addressHigh * 2) - }); - - modbusServer.on('readCoilsRequest', function (start, quantity) { - let regs = device.coils; - if (regs.changed || (regs.lastStart > start || regs.lastEnd < start + quantity)) { - regs.lastStart = start; - regs.lastEnd = start + quantity; - regs.changed = false; - let resp = new Array(Math.ceil(quantity / 16) * 2); - let i = 0; - let data = this.getCoils(); - let j; - for (j = 0; j < resp.length && start + j < data.byteLength; j++) { - resp[j] = data.readUInt8(start + j); - } - for (; j < resp.length; j++) { - resp[j] = 0; - } - - while (i < quantity && i + start < regs.addressHigh) { - if (regs.values[i + start - regs.addressLow]) { - resp[Math.floor(i / 8)] |= 1 << (i % 8); - } else { - resp[Math.floor(i / 8)] &= ~(1 << (i % 8)); - } - i++; - } - let len = data.length; - for (i = 0; i < resp.length; i++) { - if (start + i >= len) break; - data.writeUInt8(resp[i], start + i); - } - } - }); - - modbusServer.on('readDiscreteInputsRequest', function (start, quantity) { - let regs = device.disInputs; - if (regs.changed || (regs.lastStart > start || regs.lastEnd < start + quantity)) { - regs.lastStart = start; - regs.lastEnd = start + quantity; - regs.changed = false; - let resp = new Array(Math.ceil(quantity / 16) * 2); - let i = 0; - let data = this.getDiscrete(); - let j; - for (j = 0; j < resp.length && start + j < data.byteLength; j++) { - resp[j] = data.readUInt8(start + j); - } - for (; j < resp.length; j++) { - resp[j] = 0; - } - while (i < quantity && i + start < regs.addressHigh) { - if (regs.values[i + start - regs.addressLow]) { - resp[Math.floor(i / 8)] |= 1 << (i % 8); - } else { - resp[Math.floor(i / 8)] &= ~(1 << (i % 8)); - } - i++; - } - let len = data.length; - for (i = 0; i < resp.length; i++) { - if (start + i >= len) break; - data.writeUInt8(resp[i], start + i); - } - } - }); - - modbusServer.on('readInputRegistersRequest', function (start, quantity) { - let regs = device.inputRegs; - if (regs.changed || (regs.lastStart > start || regs.lastEnd < start + quantity)) { - regs.lastStart = start; - regs.lastEnd = start + quantity; - regs.changed = false; - let data = this.getInput(); - const end = start + quantity * 2; - const low = regs.addressLow * 2; - const high = regs.addressHigh * 2; - for (let i = start; i < end; i++) { - if (i >= data.length) break; - if (i >= low && i < high) { - data.writeUInt8(regs.values[i - low], i); - } else { - data.writeUInt8(0, i); - } - } - } - }); - - modbusServer.on('readHoldingRegistersRequest', function (start, quantity) { - let regs = device.holdingRegs; - if (regs.changed || (regs.lastStart > start || regs.lastEnd < start + quantity)) { - regs.lastStart = start; - regs.lastEnd = start + quantity; - regs.changed = false; - let data = this.getHolding(); - const end = start + quantity * 2; - const low = regs.addressLow * 2; - const high = regs.addressHigh * 2; - for (let i = start; i < end; i++) { - if (i >= data.length) break; - if (i >= low && i < high) { - data.writeUInt8(regs.values[i - low], i); - } else { - data.writeUInt8(0, i); - } - } - } - }); - - modbusServer.on('postWriteSingleCoilRequest', function (start, value) { - let regs = device.coils; - let a = start - regs.addressLow; - - if (a >= 0 && regs.mapping[a]) { - adapter.setState(regs.mapping[a], value, true, function (err) { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state: ' + err); - }); - regs.values[a] = value; - } - }); - - const mPow2 = [ - 0x01, - 0x02, - 0x04, - 0x08, - 0x10, - 0x20, - 0x40, - 0x80 - ]; - - modbusServer.on('postWriteMultipleCoilsRequest', function (start, length /* , byteLength*/) { - let regs = device.coils; - let i = 0; - let data = this.getCoils(); - if (start < regs.addressLow) { - start = regs.addressLow; - } - - while (i < length && i + start < regs.addressHigh) { - let a = i + start - regs.addressLow; - if (a >= 0 && regs.mapping[a]) { - let value = data.readUInt8((i + start) >> 3); - value = value & mPow2[(i + start) % 8]; - adapter.setState(regs.mapping[a], !!value, true, function (err) { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state: ' + err); - }); - regs.values[a] = !!value; - } - i++; - } - }); - - modbusServer.on('postWriteSingleRegisterRequest', function (start, value) { - let regs = device.holdingRegs; - start = start >> 1; - let a = start - regs.addressLow; - - if (a >= 0 && regs.mapping[a]) { - let native = options.objects[regs.mapping[a]].native; - let buf = new Buffer(2); - buf.writeUInt16BE(value); - let val = common.extractValue(native.type, native.len, buf, 0); - - if (native.type !== 'string') { - val = (val - native.offset) / native.factor; - val = Math.round(val * options.config.round) / options.config.round; - } - - adapter.setState(regs.mapping[a], val, true, function (err) { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state: ' + err); - }); - - regs.values[a] = buf[0]; - regs.values[a + 1] = buf[1]; - } - }); - - modbusServer.on('postWriteMultipleRegistersRequest', function (start, length /* , byteLength*/) { - let regs = device.holdingRegs; - let data = this.getHolding(); - let i = 0; - start = start >> 1; - - if (start < regs.addressLow) { - start = regs.addressLow; - } - - while (i < length && i + start < regs.addressHigh) { - let a = i + start - regs.addressLow; - if (a >= 0 && regs.mapping[a]) { - let native = options.objects[regs.mapping[a]].native; - - let val = common.extractValue(native.type, native.len, data, i + start); - if (native.type !== 'string') { - val = val * native.factor + native.offset; - val = Math.round(val * options.config.round) / options.config.round; - } - adapter.setState(regs.mapping[a], val, true, function (err) { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state: ' + err); - }); - for (let k = 0; k < native.len * 2; k++) { - regs.values[a * 2 + k] = data.readUInt8(start * 2 + k); - } - i += native.len; - } else { - i++; - } - } - }); - - modbusServer.on('connection', client => { - let list = getListOfClients(modbusServer.getClients()); - adapter.log.debug('+ Clients connected: ' + list); - adapter.setState('info.connection', list, true); - }).on('close', client => { - let list = getListOfClients(modbusServer.getClients()); - adapter.log.debug('- Client connected: ' + list); - adapter.setState('info.connection', list, true); - }).on('error', err => { - let list = getListOfClients(modbusServer.getClients()); - adapter.log.info('- Clients connected: ' + list); - adapter.setState('info.connection', list, true); - adapter.log.warn('Error on connection: ' + JSON.stringify(err)); - }); - } - }; - - this.close = () => { - if (modbusServer) { - try { - modbusServer.close(); - } catch (e) { - - } - modbusServer = null; - } - }; - - this._initValues = (states, regs) => { - // build ready arrays - for (let i = 0; regs.fullIds.length > i; i++) { - let id = regs.fullIds[i]; - if (states[id] && states[id].val !== undefined) { - this.write(id, states[id]); - } else { - adapter.setState(id, 0, true, err => { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state ' + id + ': ' + err); - }); - } - } - // fill with 0 empty values - for (let i = 0; i < regs.values.length; i++) { - if (regs.values[i] === undefined || regs.values[i] === null) { - regs.values[i] = 0; - } else if (typeof regs.values[i] === 'boolean') { - regs.values[i] = regs.values[i] ? 1 : 0; - } else if (typeof regs.values[i] !== 'number') { - regs.values[i] = parseInt(regs.values[i], 10) ? 1 : 0; - } - } - }; - - this.initValues = callback => { - // read all states - adapter.getStates('*', (err, states) => { - this._initValues(states, device.disInputs); - this._initValues(states, device.coils); - this._initValues(states, device.inputRegs); - this._initValues(states, device.holdingRegs); - callback(); - }); - }; - - (function _constructor() { - adapter.setState('info.connection', 0, true); - - this.initValues(() => { - delayStart = false; - adapter.log.debug('Slave ready to start'); - this.start(); - }); - }.bind(this))(); - - return this; -} - -module.exports = Slave; \ No newline at end of file From 3f6658ff1433f97b0739133b1917c5172076ad4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=9F=E8=BF=9B?= Date: Wed, 16 Jan 2019 04:26:38 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20main.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/main.js b/main.js index 62addaf..435456f 100644 --- a/main.js +++ b/main.js @@ -808,15 +808,9 @@ function parseConfig(callback) { function main() { parseConfig(options => { let Modbus; - /* - if (options.config.slave) { - Modbus = require(__dirname + '/lib/slave'); - } else { - Modbus = require(__dirname + '/lib/master'); - } + Modbus = require(__dirname + '/lib/master'); modbus = new Modbus(options, adapter); modbus.start(); - */ }); } From eb5c14d5d4b18ea8e69586e3879bf054ea224262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=9F=E8=BF=9B?= Date: Wed, 16 Jan 2019 04:32:11 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20main.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/main.js b/main.js index 435456f..9db78d3 100644 --- a/main.js +++ b/main.js @@ -4,13 +4,13 @@ 'use strict'; const utils = require(__dirname + '/lib/utils'); -let modbus = null; +let rsonoff = null; let fs; let serialport = null; let adapter = utils.Adapter({ - name: 'modbus', + name: 'rsonoff', unload: stop }); @@ -50,9 +50,9 @@ adapter.on('message', function (obj) { }); function stop(callback) { - if (modbus) { - modbus.close(); - modbus = null; + if (rsonoff) { + rsonoff.close(); + rsonoff = null; } if (adapter && adapter.setState && adapter.config && adapter.config.params) { @@ -72,16 +72,16 @@ let infoRegExp = new RegExp(adapter.namespace.replace('.', '\\.') + '\\.info\\.' adapter.on('stateChange', (id, state) => { if (state && !state.ack && id && !infoRegExp.test(id)) { - if (!modbus) { + if (!rsonoff) { adapter.log.warn('No connection') } else { if (objects[id]) { - modbus.write(id, state); + rsonoff.write(id, state); } else { adapter.getObject(id, (err, data) => { if (!err) { objects[id] = data; - modbus.write(id, state); + rsonoff.write(id, state); } }); } @@ -809,8 +809,8 @@ function main() { parseConfig(options => { let Modbus; Modbus = require(__dirname + '/lib/master'); - modbus = new Modbus(options, adapter); - modbus.start(); + rsonoff = new Modbus(options, adapter); + rsonoff.start(); }); } From d139465cb59aaba511f4a6aa6fd6efdeef5ef6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=92=9F=E8=BF=9B?= Date: Wed, 16 Jan 2019 04:35:11 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20master.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/master.js | 1265 ++++++++++++++++++++++++------------------------- 1 file changed, 615 insertions(+), 650 deletions(-) diff --git a/lib/master.js b/lib/master.js index d2b254f..0ae8354 100644 --- a/lib/master.js +++ b/lib/master.js @@ -1,650 +1,615 @@ -'use strict'; -const common = require(__dirname + '/common.js'); -const Modbus = require('./jsmodbus'); -// expected -// let options = { -// config: { -// type: 'tcp', -// recon: -// timeout: -// pulsetime: -// poll: -// defaultDeviceId: 0, -// tcp: { // only if type="tcp" or type="tcprtu" -// bind: '0.0.0.0', -// port: 502, -// }, -// serial: { // only if type="serial" -// comName: 'tty', -// parity: -// dataBits: -// stopBits -// baudRate -// }, -// maxBlock -// }, -// objects: { -// } -// coils: { -// addressLow: 0, -// length: 1000, -// config: [] -// blocks: {}, -// cyclicWrite // only holdingRegs -// }, -// inputRegs: ..., -// disInputs: ..., -// holdingRegs: ... -// }; - - -function Master(options, adapter) { - let modbusClient; - let connected; - let connectTimer; - let nextPoll; - let pollTime; - let errorCount = 0; - let ackObjects = {}; - let objects = options.objects; - let isStop = false; - let pulseList = {}; - let sendBuffer = {}; - let devices = options.devices; - let deviceIds = Object.keys(devices); - let reconnectTimeout = null; - - function reconnect(isImmediately) { - if (reconnectTimeout) { - clearTimeout(reconnectTimeout); - reconnectTimeout = null; - } - if (nextPoll) { - clearTimeout(nextPoll); - nextPoll = null; - } - - try { - if (modbusClient) { - modbusClient.close(); - } - } catch (e) { - adapter.log.error('Cannot close master: ' + e); - } - - if (connected) { - if (options.config.tcp) { - adapter.log.info('Disconnected from slave ' + options.config.tcp.bind); - } else { - adapter.log.info('Disconnected from slave'); - } - - connected = false; - adapter.setState('info.connection', false, true); - } - - if (!connectTimer) { - connectTimer = setTimeout(function () { - connectTimer = null; - modbusClient.connect(); - }, isImmediately ? 1000 : options.config.recon); - } - } - - function pollBinariesBlock(device, regType, func, block, callback) { - let regs = device[regType]; - if (block >= regs.blocks.length) { - return callback(); - } - - const regBlock = regs.blocks[block]; - - if (regBlock.startIndex === regBlock.endIndex) { - regBlock.endIndex++; - } - - adapter.log.debug(`Poll ${regType} DevID(${regs.deviceId}) address ${regBlock.start} - ${regBlock.count} bits`); - if (modbusClient) { - modbusClient[func](regs.deviceId, regBlock.start, regBlock.count).then(response => { - if (response.data && response.data.length) { - for (let n = regBlock.startIndex; n < regBlock.endIndex; n++) { - let id = regs.config[n].id; - let val = response.data[regs.config[n].address - regBlock.start]; - - if (options.config.alwaysUpdate || ackObjects[id] === undefined || ackObjects[id].val !== val) { - ackObjects[id] = {val: val}; - adapter.setState(id, !!val, true, err => { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state ' + id + ': ' + err); - }); - } - } - } else { - adapter.log.warn(`Null buffer length ${func} for ${regType} ${regBlock.start}`); - } - setImmediate(() => pollBinariesBlock(device, regType, func, block + 1, callback)); - }).catch(err => { - callback(err); - }); - } else { - adapter.log.debug(`Poll canceled, because no connection`); - callback('No connection'); - } - } - - function pollBinariesBlocks(device, regType, func, callback) { - let regs = device[regType]; - if (regs.length) { - pollBinariesBlock(device, regType, func, 0, err => { - callback(err); - }); - } else { - callback(); - } - } - - function pollFloatBlock(device, regType, func, block, callback) { - let regs = device[regType]; - - if (block >= regs.blocks.length) { - return callback(); - } - - const regBlock = regs.blocks[block]; - - if (regBlock.startIndex === regBlock.endIndex) { - regBlock.endIndex++; - } - - adapter.log.debug(`Poll ${regType} DevID(${regs.deviceId}) address ${regBlock.start} - ${regBlock.count} bytes`); - if (modbusClient) { - modbusClient[func](regs.deviceId, regBlock.start, regBlock.count).then(response => { - if (response.payload && response.payload.length) { - for (let n = regBlock.startIndex; n < regBlock.endIndex; n++) { - let id = regs.config[n].id; - let val = common.extractValue(regs.config[n].type, regs.config[n].len, response.payload, regs.config[n].address - regBlock.start); - if (regs.config[n].type !== 'string') { - val = val * regs.config[n].factor + regs.config[n].offset; - val = Math.round(val * options.config.round) / options.config.round; - } - if (options.config.alwaysUpdate || ackObjects[id] === undefined || ackObjects[id].val !== val) { - ackObjects[id] = {val: val}; - adapter.setState(id, val, true, err => { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error(`Can not set state ${id}: ${err}`); - }); - } - } - } else { - adapter.log.warn(`Null buffer length ${func} for ${regType} ${regBlock.start}`); - } - // special case - if (options.config.maxBlock < 2 && regs.config[regBlock.startIndex].cw) { - // write immediately the current value - writeFloatsReg(device, regType, objects[regs.config[regBlock.startIndex].fullId], () => { - pollFloatBlock(device, regType, func, block + 1, callback); - }); - } else { - setImmediate(() => pollFloatBlock(device, regType, func, block + 1, callback)); - } - }).catch(err => { - callback(err); - }); - } else { - adapter.log.debug(`Poll canceled, because no connection`); - callback('No connection'); - } - } - - function pollFloatsBlocks(device, regType, func, callback) { - let regs = device[regType]; - if (regs.length) { - pollFloatBlock(device, regType, func, 0, err => { - if (!err && regs.cyclicWrite && regs.cyclicWrite.length && options.config.maxBlock >= 2) { - writeFloatsRegs(device, regType, 0, callback); - } else { - callback(err); - } - }); - } else { - callback(); - } - } - - function writeFloatsReg(device, regType, obj, callback) { - let regs = device[regType]; - if (obj.native.len > 1) { - let buffer = new Buffer(obj.native.len * 2); - for (let b = 0; b < buffer.length; b++) { - buffer[b] = regs.config[(obj.native.address - regs.addressLow) * 2 + b]; - } - modbusClient.writeMultipleRegisters(regs.deviceId, obj.native.address, buffer) - .then(response => callback()) - .catch(err => { - adapter.log.error('Cannot write: ' + JSON.stringify(err)); - callback(err); - }); - } else { - callback(); - } - } - - function writeFloatsRegs(device, regType, i, callback) { - let regs = device[regType]; - - if (i >= regs.cyclicWrite.length) { - return callback(); - } - - let id = regs.cyclicWrite[i]; - - writeFloatsReg(device, regType, objects[id], () => { - writeFloatsRegs(device, regType, i + 1, callback); - }); - } - - function pollResult(startTime, err, cb) { - if (err) { - errorCount++; - - adapter.log.warn(`Poll error count: ${errorCount} code: ${JSON.stringify(err)}`); - adapter.setState('info.connection', false, true); - - if (errorCount > 12 * deviceIds.length) { // 2 reconnects did not help, restart adapter - throw new Error('Reconnect did not help, restart adapter'); - } else if (errorCount < 6 * deviceIds.length && connected) { - cb && cb(); - } else { - cb && cb('disconnect'); - } - } else { - let currentPollTime = (new Date()).valueOf() - startTime; - if (pollTime !== null && pollTime !== undefined) { - if (Math.abs(pollTime - currentPollTime) > 100) { - pollTime = currentPollTime; - adapter.setState('info.pollTime', currentPollTime, true); - } - } else { - pollTime = currentPollTime; - adapter.setState('info.pollTime', currentPollTime, true); - } - - if (errorCount > 0) { - adapter.setState('info.connection', true, true); - errorCount = 0; - } - cb && cb(); - } - } - - function pollDevice(device, callback) { - adapter.log.debug(`Poll device ${device.coils.deviceId}`); - let startTime = new Date().valueOf(); - let requestTimer = setTimeout(() => { - requestTimer = null; - if (connected && !isStop) { - pollResult(startTime, 'App Timeout', callback); - } - }, options.config.timeout + 200); - - // TODO: use promises here - pollBinariesBlocks(device, 'disInputs', 'readDiscreteInputs', err => { - if (err) { - if (requestTimer) { - clearTimeout(requestTimer); - requestTimer = null; - if (connected && !isStop) { - pollResult(startTime, err, callback); - } - } - } else { - pollBinariesBlocks(device, 'coils', 'readCoils', err => { - if (err) { - if (requestTimer) { - clearTimeout(requestTimer); - requestTimer = null; - if (connected && !isStop) { - pollResult(startTime, err, callback); - } - } - } else { - pollFloatsBlocks(device, 'inputRegs', 'readInputRegisters', err => { - if (err) { - if (requestTimer) { - clearTimeout(requestTimer); - requestTimer = null; - if (connected && !isStop) { - pollResult(startTime, err, callback); - } - } - } else { - pollFloatsBlocks(device, 'holdingRegs', 'readHoldingRegisters', err => { - if (requestTimer) { - clearTimeout(requestTimer); - requestTimer = null; - if (connected && !isStop) { - pollResult(startTime, err, callback); - } - } - }); - } - }); - } - }); - } - }); - } - - function poll(i, cb) { - if (typeof i === 'function') { - cb = i; - i = 0; - } - i = i || 0; - if (i >= deviceIds.length) { - if (deviceIds.find(id => !devices[id].err)) { - nextPoll = setTimeout(function () { - nextPoll = null; - poll(); - }, options.config.poll); - } else { - !reconnectTimeout && reconnect(); - } - cb && cb(); - } else { - pollDevice(devices[deviceIds[i]], err => { - devices[deviceIds[i]].err = err; - setImmediate(poll, i + 1, cb); - }); - } - } - - function send() { - if (!modbusClient) { - adapter.log.error('Client not connected'); - return; - } - - let id = Object.keys(sendBuffer)[0]; - - let type = objects[id].native.regType; - let val = sendBuffer[id]; - - if (type === 'coils') { - if (val === 'true' || val === true) val = 1; - if (val === 'false' || val === false) val = 0; - val = parseFloat(val); - - modbusClient.writeSingleCoil(objects[id].native.deviceId, objects[id].native.address, !!val).then(response => { - adapter.log.debug('Write successfully [' + objects[id].native.address + ']: ' + val); - }).catch(err => { - adapter.log.error('Cannot write [' + objects[id].native.address + ']: ' + JSON.stringify(err)); - // still keep on communication - if (!isStop) { - !reconnectTimeout && reconnect(true); - } - }); - } else if (type === 'holdingRegs') { - if (objects[id].native.float === undefined) { - objects[id].native.float = - objects[id].native.type === 'floatle' || objects[id].native.type === 'floatbe' || objects[id].native.type === 'floatsw' || - objects[id].native.type === 'doublele' || objects[id].native.type === 'doublebe' || objects[id].native.type === 'floatsb'; - } - - if (objects[id].native.type !== 'string') { - val = parseFloat(val); - val = (val - objects[id].native.offset) / objects[id].native.factor; - if (!objects[id].native.float) val = Math.round(val); - } - if (objects[id].native.len > 1) { - let hrBuffer = common.writeValue(objects[id].native.type, val, objects[id].native.len); - - modbusClient.writeMultipleRegisters(objects[id].native.deviceId, objects[id].native.address, hrBuffer, function (err, response) { - adapter.log.debug('Write successfully [' + objects[id].native.address + ']: ' + val); - }).catch(err => { - adapter.log.error('Cannot write [' + objects[id].native.address + ']: ' + JSON.stringify(err)); - // still keep on communication - if (!isStop) { - !reconnectTimeout && reconnect(true); - } - }); - } else { - if (!modbusClient) { - adapter.log.error('Client not connected'); - return; - } - let buffer = common.writeValue(objects[id].native.type, val, objects[id].native.len); - - modbusClient.writeSingleRegister(objects[id].native.deviceId, objects[id].native.address, buffer).then(function (response) { - adapter.log.debug('Write successfully [' + objects[id].native.address + ': ' + val); - }).catch(err => { - adapter.log.error('Cannot write [' + objects[id].native.address + ']: ' + JSON.stringify(err)); - // still keep on communication - if (!isStop) { - !reconnectTimeout && reconnect(true); - } - }); - } - } - - delete(sendBuffer[id]); - if (Object.keys(sendBuffer).length) { - setTimeout(send, 0); - } - } - - function writeHelper(id, state) { - sendBuffer[id] = state.val; - - if (Object.keys(sendBuffer).length === 1) { - send(); - } - } - - this.write = (id, state) => { - if (!objects[id] || !objects[id].native) { - adapter.log.error('Can not set state ' + id + ': unknown object'); - return; - } - - if (objects[id].native.regType === 'coils' || objects[id].native.regType === 'holdingRegs') { - if (!objects[id].native.wp) { - writeHelper(id, state); - - setTimeout(function () { - let _id = id.substring(adapter.namespace.length + 1); - adapter.setState(id, ackObjects[_id] ? ackObjects[_id].val : null, true, function (err) { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state ' + id + ': ' + err); - }); - }, options.config.poll * 1.5); - - } else { - if (pulseList[id] === undefined) { - let _id = id.substring(adapter.namespace.length + 1); - pulseList[id] = ackObjects[_id] ? ackObjects[_id].val : !state.val; - - setTimeout(function () { - writeHelper(id, {val: pulseList[id]}); - - setTimeout(function () { - if (ackObjects[_id]) { - adapter.setState(id, ackObjects[_id].val, true, function (err) { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state ' + id + ': ' + err); - }); - } - delete pulseList[id]; - }, options.config.poll * 1.5); - - }, options.config.pulsetime); - - writeHelper(id, state); - } - } - } else { - setImmediate(() => { - let _id = id.substring(adapter.namespace.length + 1); - adapter.setState(id, ackObjects[_id] ? ackObjects[_id].val : null, true, function (err) { - // analyse if the state could be set (because of permissions) - if (err) adapter.log.error('Can not set state ' + id + ': ' + err); - }); - }); - } - }; - - this.start = () => { - if (modbusClient && typeof modbusClient.connect === 'function') { - modbusClient.connect(); - } - }; - - this.close = () => { - isStop = true; - if (nextPoll) { - clearTimeout(nextPoll); - nextPoll = null; - } - if (modbusClient) { - try { - modbusClient.close(); - } catch (e) { - - } - modbusClient = null; - } - }; - - (function _constructor () { - adapter.setState('info.connection', false, true); - - if (options.config.type === 'tcp') { - const tcp = options.config.tcp; - if (!tcp || !tcp.bind || tcp.bind === '0.0.0.0') { - adapter.log.error('IP address is not defined'); - return; - } - try { - modbusClient = Modbus('client', 'tcp')({ - options: { - tcp: { - host: tcp.bind, - port: parseInt(tcp.port, 10) || 502, - autoReconnect: false, - }, - log: adapter.log, - timeout: options.config.timeout, - unitId: options.config.defaultDeviceId - } - }); - } catch (e) { - adapter.log.error('Cannot connect to "' + tcp.bind + ':' + (parseInt(tcp.port, 10) || 502) + '": ' + e); - } - } else if (options.config.type === 'tcprtu') { - const tcp = options.config.tcp; - if (!tcp || !tcp.bind || tcp.bind === '0.0.0.0') { - adapter.log.error('IP address is not defined'); - return; - } - try { - modbusClient = Modbus('client', 'tcp-rtu')({ - options: { - tcp: { - host: tcp.bind, - port: parseInt(tcp.port, 10) || 502, - autoReconnect: false, - }, - log: adapter.log, - timeout: options.config.timeout, - unitId: options.config.defaultDeviceId - } - }); - } catch (e) { - adapter.log.error('Cannot connect to "' + tcp.bind + ':' + (parseInt(tcp.port, 10) || 502) + '": ' + e); - } - } else if (options.config.type === 'serial') { - const serial = options.config.serial; - if (!serial || !serial.comName) { - adapter.log.error('Serial devicename is not defined'); - return; - } - - try { - modbusClient = Modbus('client', 'serial')({ - options: { - serial: { - portName: serial.comName, - baudRate: parseInt(serial.baudRate, 10) || 9600, - dataBits: parseInt(serial.dataBits, 10) || 8, - stopBits: parseInt(serial.stopBits, 10) || 1, - parity: serial.parity || 'none', - }, - log: adapter.log, - timeout: options.config.timeout, - unitId: options.config.defaultDeviceId - } - }); - } catch (e) { - adapter.log.error('Cannot open port "' + serial.comName + '" [' + (parseInt(serial.baudRate, 10) || 9600) + ']: ' + e); - } - } else { - adapter.log.error(`Unsupported type ${options.config.type}"`); - return; - } - - if (!modbusClient) { - adapter.log.error('Cannot create modbus master!'); - return; - } - modbusClient.on('connect', function () { - if (!connected) { - if (options.config.type === 'tcp') { - adapter.log.info('Connected to slave ' + options.config.tcp.bind); - } else { - adapter.log.info('Connected to slave'); - } - connected = true; - adapter.setState('info.connection', true, true); - } - - if (nextPoll) { - clearTimeout(nextPoll); - nextPoll = null; - } - - poll(); - }).on('disconnect', () => { - if (isStop) return; - if (!reconnectTimeout) { - reconnectTimeout = setTimeout(reconnect, 1000); - } - }); - - modbusClient.on('close', () => { - if (isStop) return; - if (!reconnectTimeout) { - reconnectTimeout = setTimeout(reconnect, 1000); - } - }); - - modbusClient.on('error', err => { - if (isStop) return; - adapter.log.warn('On error: ' + JSON.stringify(err)); - if (!reconnectTimeout) { - reconnectTimeout = setTimeout(reconnect, 1000); - } - }); - - modbusClient.on('trashCurrentRequest', err => { - if (isStop) return; - adapter.log.warn('Error: ' + JSON.stringify(err)); - if (!reconnectTimeout) { - reconnectTimeout = setTimeout(reconnect, 1000); - } - }); - })(); - - return this; -} - -module.exports = Master; +'use strict'; +const common = require(__dirname + '/common.js'); +const Modbus = require('./jsmodbus'); + + +function Master(options, adapter) { + let modbusClient; + let connected; + let connectTimer; + let nextPoll; + let pollTime; + let errorCount = 0; + let ackObjects = {}; + let objects = options.objects; + let isStop = false; + let pulseList = {}; + let sendBuffer = {}; + let devices = options.devices; + let deviceIds = Object.keys(devices); + let reconnectTimeout = null; + + function reconnect(isImmediately) { + if (reconnectTimeout) { + clearTimeout(reconnectTimeout); + reconnectTimeout = null; + } + if (nextPoll) { + clearTimeout(nextPoll); + nextPoll = null; + } + + try { + if (modbusClient) { + modbusClient.close(); + } + } catch (e) { + adapter.log.error('Cannot close master: ' + e); + } + + if (connected) { + if (options.config.tcp) { + adapter.log.info('Disconnected from slave ' + options.config.tcp.bind); + } else { + adapter.log.info('Disconnected from slave'); + } + + connected = false; + adapter.setState('info.connection', false, true); + } + + if (!connectTimer) { + connectTimer = setTimeout(function () { + connectTimer = null; + modbusClient.connect(); + }, isImmediately ? 1000 : options.config.recon); + } + } + + function pollBinariesBlock(device, regType, func, block, callback) { + let regs = device[regType]; + if (block >= regs.blocks.length) { + return callback(); + } + + const regBlock = regs.blocks[block]; + + if (regBlock.startIndex === regBlock.endIndex) { + regBlock.endIndex++; + } + + adapter.log.debug(`Poll ${regType} DevID(${regs.deviceId}) address ${regBlock.start} - ${regBlock.count} bits`); + if (modbusClient) { + modbusClient[func](regs.deviceId, regBlock.start, regBlock.count).then(response => { + if (response.data && response.data.length) { + for (let n = regBlock.startIndex; n < regBlock.endIndex; n++) { + let id = regs.config[n].id; + let val = response.data[regs.config[n].address - regBlock.start]; + + if (options.config.alwaysUpdate || ackObjects[id] === undefined || ackObjects[id].val !== val) { + ackObjects[id] = {val: val}; + adapter.setState(id, !!val, true, err => { + // analyse if the state could be set (because of permissions) + if (err) adapter.log.error('Can not set state ' + id + ': ' + err); + }); + } + } + } else { + adapter.log.warn(`Null buffer length ${func} for ${regType} ${regBlock.start}`); + } + setImmediate(() => pollBinariesBlock(device, regType, func, block + 1, callback)); + }).catch(err => { + callback(err); + }); + } else { + adapter.log.debug(`Poll canceled, because no connection`); + callback('No connection'); + } + } + + function pollBinariesBlocks(device, regType, func, callback) { + let regs = device[regType]; + if (regs.length) { + pollBinariesBlock(device, regType, func, 0, err => { + callback(err); + }); + } else { + callback(); + } + } + + function pollFloatBlock(device, regType, func, block, callback) { + let regs = device[regType]; + + if (block >= regs.blocks.length) { + return callback(); + } + + const regBlock = regs.blocks[block]; + + if (regBlock.startIndex === regBlock.endIndex) { + regBlock.endIndex++; + } + + adapter.log.debug(`Poll ${regType} DevID(${regs.deviceId}) address ${regBlock.start} - ${regBlock.count} bytes`); + if (modbusClient) { + modbusClient[func](regs.deviceId, regBlock.start, regBlock.count).then(response => { + if (response.payload && response.payload.length) { + for (let n = regBlock.startIndex; n < regBlock.endIndex; n++) { + let id = regs.config[n].id; + let val = common.extractValue(regs.config[n].type, regs.config[n].len, response.payload, regs.config[n].address - regBlock.start); + if (regs.config[n].type !== 'string') { + val = val * regs.config[n].factor + regs.config[n].offset; + val = Math.round(val * options.config.round) / options.config.round; + } + if (options.config.alwaysUpdate || ackObjects[id] === undefined || ackObjects[id].val !== val) { + ackObjects[id] = {val: val}; + adapter.setState(id, val, true, err => { + // analyse if the state could be set (because of permissions) + if (err) adapter.log.error(`Can not set state ${id}: ${err}`); + }); + } + } + } else { + adapter.log.warn(`Null buffer length ${func} for ${regType} ${regBlock.start}`); + } + // special case + if (options.config.maxBlock < 2 && regs.config[regBlock.startIndex].cw) { + // write immediately the current value + writeFloatsReg(device, regType, objects[regs.config[regBlock.startIndex].fullId], () => { + pollFloatBlock(device, regType, func, block + 1, callback); + }); + } else { + setImmediate(() => pollFloatBlock(device, regType, func, block + 1, callback)); + } + }).catch(err => { + callback(err); + }); + } else { + adapter.log.debug(`Poll canceled, because no connection`); + callback('No connection'); + } + } + + function pollFloatsBlocks(device, regType, func, callback) { + let regs = device[regType]; + if (regs.length) { + pollFloatBlock(device, regType, func, 0, err => { + if (!err && regs.cyclicWrite && regs.cyclicWrite.length && options.config.maxBlock >= 2) { + writeFloatsRegs(device, regType, 0, callback); + } else { + callback(err); + } + }); + } else { + callback(); + } + } + + function writeFloatsReg(device, regType, obj, callback) { + let regs = device[regType]; + if (obj.native.len > 1) { + let buffer = new Buffer(obj.native.len * 2); + for (let b = 0; b < buffer.length; b++) { + buffer[b] = regs.config[(obj.native.address - regs.addressLow) * 2 + b]; + } + modbusClient.writeMultipleRegisters(regs.deviceId, obj.native.address, buffer) + .then(response => callback()) + .catch(err => { + adapter.log.error('Cannot write: ' + JSON.stringify(err)); + callback(err); + }); + } else { + callback(); + } + } + + function writeFloatsRegs(device, regType, i, callback) { + let regs = device[regType]; + + if (i >= regs.cyclicWrite.length) { + return callback(); + } + + let id = regs.cyclicWrite[i]; + + writeFloatsReg(device, regType, objects[id], () => { + writeFloatsRegs(device, regType, i + 1, callback); + }); + } + + function pollResult(startTime, err, cb) { + if (err) { + errorCount++; + + adapter.log.warn(`Poll error count: ${errorCount} code: ${JSON.stringify(err)}`); + adapter.setState('info.connection', false, true); + + if (errorCount > 12 * deviceIds.length) { // 2 reconnects did not help, restart adapter + throw new Error('Reconnect did not help, restart adapter'); + } else if (errorCount < 6 * deviceIds.length && connected) { + cb && cb(); + } else { + cb && cb('disconnect'); + } + } else { + let currentPollTime = (new Date()).valueOf() - startTime; + if (pollTime !== null && pollTime !== undefined) { + if (Math.abs(pollTime - currentPollTime) > 100) { + pollTime = currentPollTime; + adapter.setState('info.pollTime', currentPollTime, true); + } + } else { + pollTime = currentPollTime; + adapter.setState('info.pollTime', currentPollTime, true); + } + + if (errorCount > 0) { + adapter.setState('info.connection', true, true); + errorCount = 0; + } + cb && cb(); + } + } + + function pollDevice(device, callback) { + adapter.log.debug(`Poll device ${device.coils.deviceId}`); + let startTime = new Date().valueOf(); + let requestTimer = setTimeout(() => { + requestTimer = null; + if (connected && !isStop) { + pollResult(startTime, 'App Timeout', callback); + } + }, options.config.timeout + 200); + + // TODO: use promises here + pollBinariesBlocks(device, 'disInputs', 'readDiscreteInputs', err => { + if (err) { + if (requestTimer) { + clearTimeout(requestTimer); + requestTimer = null; + if (connected && !isStop) { + pollResult(startTime, err, callback); + } + } + } else { + pollBinariesBlocks(device, 'coils', 'readCoils', err => { + if (err) { + if (requestTimer) { + clearTimeout(requestTimer); + requestTimer = null; + if (connected && !isStop) { + pollResult(startTime, err, callback); + } + } + } else { + pollFloatsBlocks(device, 'inputRegs', 'readInputRegisters', err => { + if (err) { + if (requestTimer) { + clearTimeout(requestTimer); + requestTimer = null; + if (connected && !isStop) { + pollResult(startTime, err, callback); + } + } + } else { + pollFloatsBlocks(device, 'holdingRegs', 'readHoldingRegisters', err => { + if (requestTimer) { + clearTimeout(requestTimer); + requestTimer = null; + if (connected && !isStop) { + pollResult(startTime, err, callback); + } + } + }); + } + }); + } + }); + } + }); + } + + function poll(i, cb) { + if (typeof i === 'function') { + cb = i; + i = 0; + } + i = i || 0; + if (i >= deviceIds.length) { + if (deviceIds.find(id => !devices[id].err)) { + nextPoll = setTimeout(function () { + nextPoll = null; + poll(); + }, options.config.poll); + } else { + !reconnectTimeout && reconnect(); + } + cb && cb(); + } else { + pollDevice(devices[deviceIds[i]], err => { + devices[deviceIds[i]].err = err; + setImmediate(poll, i + 1, cb); + }); + } + } + + function send() { + if (!modbusClient) { + adapter.log.error('Client not connected'); + return; + } + + let id = Object.keys(sendBuffer)[0]; + + let type = objects[id].native.regType; + let val = sendBuffer[id]; + + if (type === 'coils') { + if (val === 'true' || val === true) val = 1; + if (val === 'false' || val === false) val = 0; + val = parseFloat(val); + + modbusClient.writeSingleCoil(objects[id].native.deviceId, objects[id].native.address, !!val).then(response => { + adapter.log.debug('Write successfully [' + objects[id].native.address + ']: ' + val); + }).catch(err => { + adapter.log.error('Cannot write [' + objects[id].native.address + ']: ' + JSON.stringify(err)); + // still keep on communication + if (!isStop) { + !reconnectTimeout && reconnect(true); + } + }); + } else if (type === 'holdingRegs') { + if (objects[id].native.float === undefined) { + objects[id].native.float = + objects[id].native.type === 'floatle' || objects[id].native.type === 'floatbe' || objects[id].native.type === 'floatsw' || + objects[id].native.type === 'doublele' || objects[id].native.type === 'doublebe' || objects[id].native.type === 'floatsb'; + } + + if (objects[id].native.type !== 'string') { + val = parseFloat(val); + val = (val - objects[id].native.offset) / objects[id].native.factor; + if (!objects[id].native.float) val = Math.round(val); + } + if (objects[id].native.len > 1) { + let hrBuffer = common.writeValue(objects[id].native.type, val, objects[id].native.len); + + modbusClient.writeMultipleRegisters(objects[id].native.deviceId, objects[id].native.address, hrBuffer, function (err, response) { + adapter.log.debug('Write successfully [' + objects[id].native.address + ']: ' + val); + }).catch(err => { + adapter.log.error('Cannot write [' + objects[id].native.address + ']: ' + JSON.stringify(err)); + // still keep on communication + if (!isStop) { + !reconnectTimeout && reconnect(true); + } + }); + } else { + if (!modbusClient) { + adapter.log.error('Client not connected'); + return; + } + let buffer = common.writeValue(objects[id].native.type, val, objects[id].native.len); + + modbusClient.writeSingleRegister(objects[id].native.deviceId, objects[id].native.address, buffer).then(function (response) { + adapter.log.debug('Write successfully [' + objects[id].native.address + ': ' + val); + }).catch(err => { + adapter.log.error('Cannot write [' + objects[id].native.address + ']: ' + JSON.stringify(err)); + // still keep on communication + if (!isStop) { + !reconnectTimeout && reconnect(true); + } + }); + } + } + + delete(sendBuffer[id]); + if (Object.keys(sendBuffer).length) { + setTimeout(send, 0); + } + } + + function writeHelper(id, state) { + sendBuffer[id] = state.val; + + if (Object.keys(sendBuffer).length === 1) { + send(); + } + } + + this.write = (id, state) => { + if (!objects[id] || !objects[id].native) { + adapter.log.error('Can not set state ' + id + ': unknown object'); + return; + } + + if (objects[id].native.regType === 'coils' || objects[id].native.regType === 'holdingRegs') { + if (!objects[id].native.wp) { + writeHelper(id, state); + + setTimeout(function () { + let _id = id.substring(adapter.namespace.length + 1); + adapter.setState(id, ackObjects[_id] ? ackObjects[_id].val : null, true, function (err) { + // analyse if the state could be set (because of permissions) + if (err) adapter.log.error('Can not set state ' + id + ': ' + err); + }); + }, options.config.poll * 1.5); + + } else { + if (pulseList[id] === undefined) { + let _id = id.substring(adapter.namespace.length + 1); + pulseList[id] = ackObjects[_id] ? ackObjects[_id].val : !state.val; + + setTimeout(function () { + writeHelper(id, {val: pulseList[id]}); + + setTimeout(function () { + if (ackObjects[_id]) { + adapter.setState(id, ackObjects[_id].val, true, function (err) { + // analyse if the state could be set (because of permissions) + if (err) adapter.log.error('Can not set state ' + id + ': ' + err); + }); + } + delete pulseList[id]; + }, options.config.poll * 1.5); + + }, options.config.pulsetime); + + writeHelper(id, state); + } + } + } else { + setImmediate(() => { + let _id = id.substring(adapter.namespace.length + 1); + adapter.setState(id, ackObjects[_id] ? ackObjects[_id].val : null, true, function (err) { + // analyse if the state could be set (because of permissions) + if (err) adapter.log.error('Can not set state ' + id + ': ' + err); + }); + }); + } + }; + + this.start = () => { + if (modbusClient && typeof modbusClient.connect === 'function') { + modbusClient.connect(); + } + }; + + this.close = () => { + isStop = true; + if (nextPoll) { + clearTimeout(nextPoll); + nextPoll = null; + } + if (modbusClient) { + try { + modbusClient.close(); + } catch (e) { + + } + modbusClient = null; + } + }; + + (function _constructor () { + adapter.setState('info.connection', false, true); + + if (options.config.type === 'tcp') { + const tcp = options.config.tcp; + if (!tcp || !tcp.bind || tcp.bind === '0.0.0.0') { + adapter.log.error('IP address is not defined'); + return; + } + try { + modbusClient = Modbus('client', 'tcp')({ + options: { + tcp: { + host: tcp.bind, + port: parseInt(tcp.port, 10) || 502, + autoReconnect: false, + }, + log: adapter.log, + timeout: options.config.timeout, + unitId: options.config.defaultDeviceId + } + }); + } catch (e) { + adapter.log.error('Cannot connect to "' + tcp.bind + ':' + (parseInt(tcp.port, 10) || 502) + '": ' + e); + } + } else if (options.config.type === 'tcprtu') { + const tcp = options.config.tcp; + if (!tcp || !tcp.bind || tcp.bind === '0.0.0.0') { + adapter.log.error('IP address is not defined'); + return; + } + try { + modbusClient = Modbus('client', 'tcp-rtu')({ + options: { + tcp: { + host: tcp.bind, + port: parseInt(tcp.port, 10) || 502, + autoReconnect: false, + }, + log: adapter.log, + timeout: options.config.timeout, + unitId: options.config.defaultDeviceId + } + }); + } catch (e) { + adapter.log.error('Cannot connect to "' + tcp.bind + ':' + (parseInt(tcp.port, 10) || 502) + '": ' + e); + } + } else if (options.config.type === 'serial') { + const serial = options.config.serial; + if (!serial || !serial.comName) { + adapter.log.error('Serial devicename is not defined'); + return; + } + + try { + modbusClient = Modbus('client', 'serial')({ + options: { + serial: { + portName: serial.comName, + baudRate: parseInt(serial.baudRate, 10) || 9600, + dataBits: parseInt(serial.dataBits, 10) || 8, + stopBits: parseInt(serial.stopBits, 10) || 1, + parity: serial.parity || 'none', + }, + log: adapter.log, + timeout: options.config.timeout, + unitId: options.config.defaultDeviceId + } + }); + } catch (e) { + adapter.log.error('Cannot open port "' + serial.comName + '" [' + (parseInt(serial.baudRate, 10) || 9600) + ']: ' + e); + } + } else { + adapter.log.error(`Unsupported type ${options.config.type}"`); + return; + } + + if (!modbusClient) { + adapter.log.error('Cannot create modbus master!'); + return; + } + modbusClient.on('connect', function () { + if (!connected) { + if (options.config.type === 'tcp') { + adapter.log.info('Connected to slave ' + options.config.tcp.bind); + } else { + adapter.log.info('Connected to slave'); + } + connected = true; + adapter.setState('info.connection', true, true); + } + + if (nextPoll) { + clearTimeout(nextPoll); + nextPoll = null; + } + + poll(); + }).on('disconnect', () => { + if (isStop) return; + if (!reconnectTimeout) { + reconnectTimeout = setTimeout(reconnect, 1000); + } + }); + + modbusClient.on('close', () => { + if (isStop) return; + if (!reconnectTimeout) { + reconnectTimeout = setTimeout(reconnect, 1000); + } + }); + + modbusClient.on('error', err => { + if (isStop) return; + adapter.log.warn('On error: ' + JSON.stringify(err)); + if (!reconnectTimeout) { + reconnectTimeout = setTimeout(reconnect, 1000); + } + }); + + modbusClient.on('trashCurrentRequest', err => { + if (isStop) return; + adapter.log.warn('Error: ' + JSON.stringify(err)); + if (!reconnectTimeout) { + reconnectTimeout = setTimeout(reconnect, 1000); + } + }); + })(); + + return this; +} + +module.exports = Master;