exports.id = 'modbus_reader'; exports.title = 'Modbus reader'; exports.version = '2.0.0'; exports.group = 'Worksys'; exports.color = '#2134B0'; exports.output = ["red", "white"]; 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 ErrorToServiceHandler = require('./helper/ErrorToServiceHandler'); const errorHandler = new ErrorToServiceHandler(); const { sendNotification } = require('./helper/notification_reporter'); const instanceSendTo = { debug: 0, dido_controller: 1, }; //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; // kedze potrebujeme ist stale dookola pre jednotlive zariadenia, potrebujeme ci uz index ako aj adresu zariadenia, a aj pocet registrov na vycitanie this.deviceAddress = null; // adresa zariadenia (1 ma EM340 a 2 ma twilight_sensor) this.indexInDeviceConfig = 0; // prvy item v 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); } 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; }) }; 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, 2000); } 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, {}, "", instanceSendTo.tb, instance); delete numberOfNotResponding[obj.device]; } obj.transformResponse(resp, register, obj.deviceAddress); obj.error = 0; obj.index++; if(obj.index < obj.lengthOfActualDeviceStream) setTimeout(obj.readRegisters, 0); else obj.setNewStream(); }).catch (function () { console.log("error pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute); obj.error++; if(obj.error == obj.lengthOfActualDeviceStream) { instance.send(instanceSendTo.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, {}, "", instanceSendTo.tb, instance); numberOfNotResponding[obj.device] = 1; } obj.error = 0; numberOfNotResponding[obj.device] += 1; } console.error(require('util').inspect(arguments, { depth: null })) obj.index++; if(obj.index < obj.lengthOfActualDeviceStream) setTimeout(obj.readRegisters, 0); else obj.setNewStream(); }) }; transformResponse = (response, register, deviceAddress) => { 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(deviceAddress, register, tbName, tbAttribute, response, a.multiplier, value); // if(tbName == undefined) return; if(this.index + 1 + this.errors < this.lengthOfActualDeviceStream) { this.allValues[tbAttribute] = value; return; } const values = { ...this.allValues, [tbAttribute]: value, }; this.checkNullVoltage(values); instance.send(instanceSendTo.dido_controller, {values: values}); this.allValues = {}; break; } } } setNewStream = () => { // console.log('------------',this.lengthOfActualDeviceStream, this.index); // console.log('------------',this.indexInDeviceConfig, deviceConfig.length); 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(instanceSendTo.tb, dataToTB); // const dataToDiDo = { // values: values // } // instance.send(instanceSendTo.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}, "", instanceSendTo.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}, "", instanceSendTo.tb, instance, "voltage" + phase); } } }) } // we use dataToTbHandler. Therefore we need to check, if objects we send to dido_controller are not empty isObjectEmpty = (objectName) => { return Object.keys(objectName).length === 0 && objectName.constructor === Object; } } setTimeout(() => { const newSocket = new SocketWithClients(); tbName = FLOW.OMS_rvo_tbname; }, 25000); }