exports.id = 'modbus_reader'; exports.title = 'Modbus reader'; exports.version = '2.0.0'; exports.group = 'Worksys'; exports.color = '#2134B0'; exports.output = ["red", "white", "yellow"]; exports.click = false; exports.author = 'Rastislav Kovac'; exports.icon = 'bolt'; exports.readme = ` Modbus requests to modbus devices (electromer, twilight sensor, thermometer. Component keeps running arround deviceConfig array in "timeoutInterval" intervals. Array items are objects with single modbus devices. Everything is sent to dido_controller. If requests to device fail (all registers must fail to send NOK status) , we send "NOK-'device'" status to dido_controller. This device needs to be configured in dido_controller!!! Double check if it is. In dido_controller we calculate final status and all values with status are pushed to tb. `; const modbus = require('jsmodbus') const SerialPort = require('serialport') const { timeoutInterval, deviceConfig } = require("../databases/modbus_config"); const { sendNotification } = require('./helper/notification_reporter'); const DELAY_BETWEEN_DEVICES = 10000; const SEND_TO = { debug: 0, dido_controller: 1, tb: 2 }; //to handle NOK and OK sendNotifications s const numberOfNotResponding = {}; let tbName = null; exports.install = function(instance) { class SocketWithClients { constructor () { this.stream = null; this.socket = null; this.clients = {}; this.allValues = {}; this.errors = 0; this.index = 0; this.timeoutInterval = 5000; // we need to go always around for all devices. So we need index value, device address, as well as number of registers for single device this.deviceAddress = null; // device address (1 - EM340 and 2 for twilight_sensor) this.indexInDeviceConfig = 0; // first item in deviceConfig this.lengthOfActualDeviceStream = null; this.device = null; this.startSocket(); } startSocket = () => { let obj = this; this.socket = new SerialPort("/dev/ttymxc0", { baudRate: 9600, }) // we create a client for every deviceAddress ( = address) in list and push them into dictionary for( let i = 0; i < deviceConfig.length; i++) { this.clients[deviceConfig[i].deviceAddress] = new modbus.client.RTU(this.socket, deviceConfig[i].deviceAddress, 2000); // 2000 is timeout in register request, default is 5000, which is too long } this.socket.on('error', function(e) { console.log('socket connection error', e); if(e.code == 'ECONNREFUSED' || e.code == 'ECONNRESET') { console.log(exports.title + ' Waiting 10 seconds before trying to connect again'); setTimeout(obj.startSocket, 10000); } }); this.socket.on('close', function() { console.log('Socket connection closed ' + exports.title + ' Waiting 10 seconds before trying to connect again'); setTimeout(obj.startSocket, 10000); }); this.socket.on('open', function () { console.log("socket connected"); obj.getActualStreamAndDevice(); obj.timeoutInterval = timeoutInterval - DELAY_BETWEEN_DEVICES; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES }) }; getActualStreamAndDevice = () => { const dev = deviceConfig[this.indexInDeviceConfig]; this.index = 0; this.errors = 0; this.stream = dev.stream; this.lengthOfActualDeviceStream = dev.stream.length; this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number this.device = dev.device; //em340, twilight_sensor if(this.indexInDeviceConfig == 0) setTimeout(this.readRegisters, this.timeoutInterval); else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES); } readRegisters = () => { const str = this.stream[this.index]; const register = str.register; const size = str.size; const tbAttribute = str.tbAttribute; let obj = this; this.clients[this.deviceAddress].readHoldingRegisters(register, size) .then( function (resp) { resp = resp.response._body.valuesAsArray; //resp is array of length 1 or 2, f.e. [2360,0] // console.log(deviceAddress, register, tbAttribute, resp); //device is responding again after NOK status if(numberOfNotResponding.hasOwnProperty(obj.device)) { let message = ""; if(obj.device == "em340") { message = "electrometer_ok"; } else if(obj.device == "twilight_sensor") { message = "twilight_sensor_ok"; } message && sendNotification("modbus_reader: readRegisters", tbName, message, {}, "", SEND_TO.tb, instance); delete numberOfNotResponding[obj.device]; } obj.transformResponse(resp, register); //obj.errors = 0; obj.index++; obj.readAnotherRegister(); }).catch (function () { console.log("errors pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute); obj.errors++; if(obj.errors == obj.lengthOfActualDeviceStream) { instance.send(SEND_TO.dido_controller, {status: "NOK-" + obj.device}); // NOK-em340, NOK-em111, NOK-twilight_sensor, NOK-thermometer //todo - neposlalo notification, ked sme vypojili twilight a neposle to do tb, ale do dido ?? if(!numberOfNotResponding.hasOwnProperty(obj.device)) { let message = ""; if(obj.device == "twilight_sensor") { message = "twilight_sensor_nok"; } else if(obj.device == "em340") { message = "electrometer_nok"; } message && sendNotification("modbus_reader: readingTimeouted", tbName, message, {}, "", SEND_TO.tb, instance); numberOfNotResponding[obj.device] = 1; } obj.errors = 0; numberOfNotResponding[obj.device] += 1; } console.error(require('util').inspect(arguments, { depth: null })) // if reading out of device's last register returns error, we send accumulated allValues to dido_controller (if allValues are not an empty object) if(obj.index + 1 >= obj.lengthOfActualDeviceStream) { if(!isObjectEmpty(obj.allValues)) instance.send(SEND_TO.dido_controller, {values: obj.allValues}); obj.allValues = {}; } obj.index++; obj.readAnotherRegister(); }) }; readAnotherRegister = () => { if(this.index < this.lengthOfActualDeviceStream) setTimeout(this.readRegisters, 0); else this.setNewStream(); } transformResponse = (response, register) => { for (let i = 0; i < this.lengthOfActualDeviceStream; i++) { let a = this.stream[i]; if (a.register === register) { let tbAttribute = a.tbAttribute; let multiplier = a.multiplier; let value = this.calculateValue(response, multiplier); // console.log(register, tbName, tbAttribute, response, a.multiplier, value); // if(tbName == undefined) return; if(this.index + 1 < this.lengthOfActualDeviceStream) { this.allValues[tbAttribute] = value; return; } const values = { ...this.allValues, [tbAttribute]: value, }; this.checkNullVoltage(values); instance.send(SEND_TO.dido_controller, {values: values}); this.allValues = {}; break; } } } setNewStream = () => { if(this.lengthOfActualDeviceStream == this.index) { if(this.indexInDeviceConfig + 1 == deviceConfig.length) { this.indexInDeviceConfig = 0; } else { this.indexInDeviceConfig += 1; } this.getActualStreamAndDevice(); } } // sendFinalObjects = (values) => // { // const date = Date.now(); // // values["status"] = "OK"; // const dataToTB = { // [tbName]: [ // { // "ts": date, // "values": values // } // ] // }; // instance.send(SEND_TO.tb, dataToTB); // const dataToDiDo = { // values: values // } // instance.send(SEND_TO.dido_controller, dataToDiDo); // } calculateValue = (response, multiplier) => { let value = 0; let l = response.length; if (l === 2) { value = (response[1]*(2**16) + response[0]); if(value >= (2**31)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x80000000), ak vieš robiť logický súčin { value = value - "0xFFFFFFFF" + 1; } } else if (l === 1) { value = response[0]; if(value >= (2**15)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x8000), ak vieš robiť logický súčin { value = value - "0xFFFF" + 1; } } return Math.round(value * multiplier * 10) / 10; } checkNullVoltage = (values) => { if(!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return; Object.keys(values).map(singleValue => { if (["Phase_1_voltage", "Phase_2_voltage", "Phase_3_voltage"].includes(singleValue)) { let l = singleValue.split("_"); let phase = parseInt(l[1]); if(FLOW.OMS_no_voltage == undefined) FLOW.OMS_no_voltage = new Set(); // console.log(values[singleValue], tbName); if(values[singleValue] == 0) { FLOW.OMS_no_voltage.add(phase); sendNotification("modbus_citys: checkNullVoltage", tbName, "no_voltage_on_phase", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase ); // console.log('no voltage') } else { FLOW.OMS_no_voltage.delete(phase); // console.log('voltage detected') sendNotification("modbus_citys: checkNullVoltage", tbName, "voltage_on_phase_restored", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase); } } }) } } const isObjectEmpty = (objectName) => { return Object.keys(objectName).length === 0 && objectName.constructor === Object; } setTimeout(() => { const newSocket = new SocketWithClients(); tbName = FLOW.OMS_rvo_tbname; }, 25000); }