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