exports.id = 'socomec'; exports.title = 'Energomonitor socomec'; exports.version = '1.0.3'; 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 energomonitor socomec suitcase`; const streamBuilder = require("./helper/energo_streambuilder"); const structure = { "192.168.1.21": { 16: "i-35", "2A": "i-60A", "2B": "i-60B", "3A": "i-60A", "3B": "i-60B", "4A": "i-60A", "4B": "i-60B", "5A": "i-60A", "5B": "i-60B", "6A": "i-60A", "6B": "i-60B", "7A": "i-60A", "7B": "i-60B", "8A": "i-60A", "8B": "i-60B", "9A": "i-60A", "9B": "i-60B", "10A": "i-60A", "10B": "i-60B", "11A": "i-60A", "11B": "i-60B", "12A": "i-60A", "12B": "i-60B", "13A": "i-60A", "13B": "i-60B", "14A": "i-60A", "14B": "i-60B", }, "192.168.1.22": { 10: "i-35", "2A": "i-60A", "2B": "i-60B", "3A": "i-60A", "3B": "i-60B", "4A": "i-60A", "4B": "i-60B", "5A": "i-60A", "5B": "i-60B", "6A": "i-60A", "6B": "i-60B", "7A": "i-60A", "7B": "i-60B", "8A": "i-60A", "8B": "i-60B", "9A": "i-60A", "9B": "i-60B" } }; exports.install = function(instance) { const modbus = require('jsmodbus'); const net = require('net'); require('events').EventEmitter.defaultMaxListeners = 20; const allModulesEnergy = {unipi_75: ""}; // key to identify source data const date = new Date(); let hour = date.getHours(); let sendAllModulesEnergy = setInterval(() => { const d = new Date(); const h = d.getHours(); if(h != hour) { instance.send(2, allModulesEnergy); hour = h; } }, 180000); instance.on('close', function(){ clearInterval(sendAllModulesEnergy); }); const conversionTable = streamBuilder.makeStreamsTable(structure); // const conversionTable = // { // "192.168.1.21": { // "streams": [ // { // "unitId": 1, // "section": "", // "name": 18488, // "tb_value":"total_energy", // "bytes": 2, // "multiplier":1, // "previousEnergy": null, // "month": 2 // }, // ] // } // } const tbNames = { "192.168.1.21": { "16": "aw4eELG2DlPMdn1JW0Bz2Z0qQXOZRN3xB5yp8VKr", "2A": "d9x2V5LGYBzXp4mMRAOPV10PloaqJwnQj6DgrNe3", "2B": "eod9aRWLVl34Gx1Dn7VYbDk2rz6qjgmpEXwQJN5Z", "3A": "3a5oqJN1bgnx4Ol9dk8BnqAByE6jQ8mKDWMpGrLV", "3B": "EjgWGnXaLy9opPOz20ngyQk86BlYM3w1deVQvbKr", "4A": "rDbQ84xzwgdqEoPm3kbPKDA9anOZY1RXyBv2LVM6", "4B": "wvKJdZML6mXP4DzWBAXOj87jxNloa5g23Ve9Y1ry", "5A": "E6Kg9oDnLWyzPRMva7vWy9AJxp4VG58qO2w1lZYe", "5B": "roKgWqY95V3mXMRzyAjrynAbLjexpJPvaGDBw826", "6A": "Nzp2OoJlqn6r1ZgvdA3RoE7abBwP5G4eE3RQmyxD", "6B": "rDbQ84xzwgdqEoPm3kbPKWA9anOZY1RXyBv2LVM6", "7A": "E6Kg9oDnLWyzPRMva7vWyyAJxp4VG58qO2w1lZYe", "7B": "roKgWqY95V3mXMRzyAjry6AbLjexpJPvaGDBw826", "8A": "nJL5lPMwBx23YpqRe0rqy4AdamXvWVbOrD4gNzy8", "8B": "ZmRwd93QL4gaezxEbAx1yb01prn2XjlPvGyqJ6BO", "9A": "eod9aRWLVl34Gx1Dn7VYb9k2rz6qjgmpEXwQJN5Z", "9B": "Nzp2OoJlqn6r1ZgvdA3Rov7abBwP5G4eE3RQmyxD", "10A": "3a5oqJN1bgnx4Ol9dk8Bn1AByE6jQ8mKDWMpGrLV", "10B": "EjgWGnXaLy9opPOz20ngydk86BlYM3w1deVQvbKr", "11A": "PLBJzmK1r3Gynd6OW0g2yqAe5wV4vx9bDEqNgYR8", "11B": "wvKJdZML6mXP4DzWBAXOjV7jxNloa5g23Ve9Y1ry", "12A": "nJL5lPMwBx23YpqRe0rqybAdamXvWVbOrD4gNzy8", "12B": "52dD6ZlV1QaOpRBmbAqvyN0KnGzWMLj4eJq38Pgo", "13A": "XMBbew5z4ELrZa2mRAdZ4xk8vPN6gy3DdVYlpKjq", "13B": "PLBJzmK1r3Gynd6OW0g2yzAe5wV4vx9bDEqNgYR8", "14A": "52dD6ZlV1QaOpRBmbAqvyb0KnGzWMLj4eJq38Pgo", "14B": "gYbDLqlyZVoRerQpB72Gp4AWJnwM5z24POKa8Exj", }, "192.168.1.22": { "10": "ZmRwd93QL4gaezxEbAx1yw01prn2XjlPvGyqJ6BO", "2A": "B5EoxeMVp4zwr8nqW0GeGG7RjvD1PNamOGbLg63Z", "2B": "WlVJBygjDZMeKX3vnAMW6Pk8NqdmG2x1Y69LQ4P5", "3A": "zrR51V2ajQ9ZLygPKkEPz10YDq38xOJolENBXGnv", "3B": "BaY3Xpy1EbKGjLq2O7m9N97rx8owgQz9P4dDJRmN", "4A": "JzwxZXOvDj1bVrN4nkWe3zA8qdyBl3MRKLpGPgaQ", "4B": "o9vbeQlLMVg8j5dq4kedZK0NxZpEmnXzwYKO1ar2", "5A": "gP1eOZVj3Q9lv5aDEk4M4a7rdpqW8yLm2BbKzJxM", "5B": "5dBNwRp9graYJxZn409RBvklVov1b2QLPDqGm6XK", "6A": "g9OxBZ5KRwNznlY6pApbNOkWXvjdEL4eGQobMDy2", "6B": "pE5X8NQPaow6vlOZxk6Y6z0q42ezGBMyWgDVjR3L", "7A": "2O14VBzl8aDmWdNw3A535GkGyZ5qLJoEMpj6R9ng", "7B": "DbQY6zyveZRwK5drV0ZlB87joE4XJM83N9xl2nWq", "8A": "6lQGaY9RDywdVzObj0PZpb7Pg4NBn3exEK51LWZq", "8B": "m6EYyZoJ4gWexdjVPARaxV7RDOq9wv2N5XzKGplr", "9A": "apKVJBwOyrP35m2lv7KEpz0YXbeWNd64En9GxRqg", "9B": "OzNMgZ9n43qPbjXmy7zONeA2DKdYvW5e6pxGRrVa" } }; // static class attribute ??? const valuesToRecount = ['total_energy', 'phase_1_current', 'phase_2_current', 'phase_3_current', 'neutral_wire_current', 'total_active_power', 'total_reactive_power', 'total_apparent_power']; class SocketWithClients { constructor (ip, data) { this.ip = ip; this.data = data; this.options = { 'host': this.ip, 'port': '502' } this.streams = data.streams; //pole // distribute ==> we are sending attributes from 'valuesToRecount' with value divided by 2 to these 4 tbNames. To ensure, we send 'energy_last_month' and 'energy_update' attribute as well, we must store 'month' key and previousEnergy // can be static class attribute ?? this.distribute = { "6lQGaY9RDywdVzObj0PZpb7Pg4NBn3exEK51LWZq": {month: this.getCurrentMonth(), previousEnergy: null, tbNames: ["nJL5lPMwBx23YpqRe0rq4bAdamXvWVbOrD4gNzy8", "ZmRwd93QL4gaezxEbAx1oK01prn2XjlPvGyqJ6BO"]}, "PLBJzmK1r3Gynd6OW0g2yqAe5wV4vx9bDEqNgYR8": {month: this.getCurrentMonth(), previousEnergy: null, tbNames: ["E6Kg9oDnLWyzPRMva7vWR9AJxp4VG58qO2w1lZYe","roKgWqY95V3mXMRzyAjrYnAbLjexpJPvaGDBw826"]} } this.startSocket(); } getCurrentMonth = () => { const date = new Date(); return date.getMonth(); }; startSocket = () => { let obj = this; this.index = 0; this.clients = {}; this.socket = new net.Socket(); this.socket.connect(this.options, function() { console.log('Connected to socket server'); }); this.socket.on('error', function(e) { console.log('socket connection error', e); if(e.code == 'ECONNREFUSED' || e.code == 'ECONNRESET') { console.log(exports.title + ' Waiting 1 minute before trying to connect again'); setTimeout(obj.startSocket, 60000); } }); this.socket.on('close', function() { console.log('Socket connection closed ' + exports.title + ' Waiting 1 minute before trying to connect again'); setTimeout(obj.startSocket, 60000); }); // we create client for all modules (unitIds) and push them into dictionary for( let i = 0; i < obj.data.streams.length; i++) { if(!this.clients.hasOwnProperty(obj.data.streams[i].unitId)) { this.clients[obj.data.streams[i].unitId] = new modbus.client.TCP(this.socket, obj.data.streams[i].unitId); } } this.socket.on('connect', function () { console.log("socket connected"); setTimeout(obj.readRegisters, 10000); }); }; readRegisters = () => { const lenghtOfStreams = this.streams.length; if(this.index >= lenghtOfStreams) { this.index = 0; setTimeout(this.readRegisters, 300000); return; } let unitId = this.streams[this.index].unitId; let section = this.streams[this.index].section; let register = this.streams[this.index].name; let bytes = this.streams[this.index].bytes; let date = Date.now(); let tbval = this.streams[this.index].tb_value; // console.log("citam tieto hodnoty",unitId, register, tbval); let obj = this; this.clients[unitId].readHoldingRegisters(register, bytes) .then( function (resp) { resp = resp.response._body.valuesAsArray; // console.log(unitId, register, tbval, resp); obj.sendData(resp, register, date, unitId, section); obj.index++; setTimeout(obj.readRegisters, 0); }).catch (function () { console.log("error pri citani z grafie", register, unitId, section, tbNames[obj.ip][unitId + section], tbval); //! IMPLEMENTOVAT POSIELANIE CHYB PODLA POSLEDNHO REGISTRA V MODULE === "total_power_factor" if(tbval === "total_power_factor") { obj.sendNokStatus(tbNames[obj.ip][unitId + section], date); if(arguments["0"].err == "Offline") { obj.socket.emit("close"); return; } } //! POSIELANIE NOK STATUSU - posle sa az pri poslednom registri z daneho unitu, nie pri kazdej chybnej hlaske // if(obj.index + 1 == lenghtOfStreams) // { // obj.sendNokStatus(tbNames[obj.ip][unitId + section], date); // } // else if(obj.streams[obj.index + 1].unitId != unitId || obj.streams[obj.index + 1].section != section) // { // obj.sendNokStatus(tbNames[obj.ip][unitId + section], date); // } //console.error(require('util').inspect(arguments, { // depth: null //})) obj.index++; setTimeout(obj.readRegisters, 0); }) }; sendNokStatus = (tbName, date) => { let dataToTB = { [tbName]: [ { "ts": date, "values": { "status": "NOK", } } ] }; instance.send(1, dataToTB); // console.log("poslane do tb po chybe", dataToTB[tbName][0], dataToTB); }; sendData = (response, register, date, unitId, section) => { let l = this.streams.length; for (let i=0; i= (2**31)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (temp_val & 0x80000000), ak vieš robiť logický súčin { //temp_val = temp_val - 2**31; // odstránim MSB bit, eventuálne sa dá použiť aj (temp_val & 0x7FFFFFFF), ak vieš robiť logický súčin //temp_val = temp_val * (-1); // spravím z toho zápornú hodnotu temp_val = temp_val - "0xFFFFFFFF" + 1; } } else if (l === 1) { temp_val = response[0]; if(temp_val >= (2**15)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (temp_val & 0x8000), ak vieš robiť logický súčin { // temp_val = temp_val - 2**15; // odstránim MSB bit, eventuálne sa dá použiť aj (temp_val & 0x7FFF), ak vieš robiť logický súčin // temp_val = temp_val * (-1); // spravím z toho zápornú hodnotu temp_val = temp_val - "0xFFFF" + 1; } } value = temp_val; value = value / a.multiplier; let tbName = tbNames[this.ip][unitId.toString() + section]; // console.log(unitId, register, tbName, tb_value, response, a.multiplier, value, section); // console.log(unitId, register, tb_value, value); if(tbName == undefined) return; const values = { "status": "OK", [tb_value]: value }; // we send "energy_last_month" value, that is equal to "total_energy" value, on first day of new month ==> it means when month changes if(tb_value == "total_energy") { const previousEnergy = a.previousEnergy; a.previousEnergy = value; if(previousEnergy != null) { values["energy_update"] = value - previousEnergy; } const d = new Date(date); const currentMonth = d.getMonth(); const month = a.month; if(month != currentMonth) { values["energy_last_month"] = value; a.month = currentMonth; } allModulesEnergy[this.ip + '@' + unitId + section] = value; if(this.ip + '@' + unitId + section == "192.168.1.22@8A") { allModulesEnergy[this.ip + '@' + unitId + section + '_48'] = value / 2; allModulesEnergy[this.ip + '@' + unitId + section + '_64'] = value / 2; } } let dataToTB = { [tbName]: [ { "ts": date, "values": values } ] }; instance.send(1, dataToTB); // new code - ensures, we send values to another 4 devices, and "energy_last_month" value at the start of new month. if(this.distribute.hasOwnProperty(tbName)) { if(valuesToRecount.includes(tb_value)) { value = value/2; const values = { "status": "OK", [tb_value]: value }; if(tb_value == "total_energy") { const previousEnergy = this.distribute[tbName].previousEnergy; this.distribute[tbName].previousEnergy = value; if(previousEnergy != null) { values["energy_update"] = value - previousEnergy; } let d = new Date(date); let month = d.getMonth(); if(month != this.distribute[tbName].month) { values["energy_last_month"] = value; this.distribute[tbName].month = month; } } this.distribute[tbName].tbNames.map(sendTo => { dataToTB = { [sendTo]: [ { "ts": date, "values": values } ] }; instance.send(1, dataToTB); }) } } break; } } }; } const newSocket = new SocketWithClients("192.168.1.21", conversionTable["192.168.1.21"]); const newSocket2 = new SocketWithClients("192.168.1.22", conversionTable["192.168.1.22"]); } //! VYPOCTY "energy_last_month_delta" pre virtualne devices za februar 2022 - zmazat neskor // "192.168.1.22@8A":{"value":76772,"zone":"lithoman_48_64","tbname":"6lQGaY9RDywdVzObj0PZpb7Pg4NBn3exEK51LWZq"} // "192.168.1.22@8A":{"value":50857,"zone":"lithoman_48_64","tbname":"6lQGaY9RDywdVzObj0PZpb7Pg4NBn3exEK51LWZq"} // "energy_last_month_delta" = 25915 // -------- // { "nJL5lPMwBx23YpqRe0rq4bAdamXvWVbOrD4gNzy8": [ { "ts": 1643673891157, "values": { "energy_last_month_delta": 12958 } } ] } // "nJL5lPMwBx23YpqRe0rq4bAdamXvWVbOrD4gNzy8", "ZmRwd93QL4gaezxEbAx1oK01prn2XjlPvGyqJ6BO" // "192.168.1.22@8A_48":{"value":38386,"zone":"lithoman_48"},"192.168.1.22@8A_64":{"value":38386,"zone":"lithoman_64"} // "192.168.1.22@8A_48":{"value":25428,"zone":"lithoman_48"},"192.168.1.22@8A_64":{"value":25428,"zone":"lithoman_64"} // -------- // "192.168.1.21@11A":{"value":11830,"zone":"sitma_cmc","tbname":"PLBJzmK1r3Gynd6OW0g2yqAe5wV4vx9bDEqNgYR8"} // "192.168.1.21@11A":{"value":7530,"zone":"sitma_cmc","tbname":"PLBJzmK1r3Gynd6OW0g2yqAe5wV4vx9bDEqNgYR8"} // "energy_last_month_delta" = 4300 // ------- // 5915 // 3765 // = 2150 // this.distribute = { // "6lQGaY9RDywdVzObj0PZpb7Pg4NBn3exEK51LWZq": {month: this.getCurrentMonth(), previousEnergy: null, tbNames: ["nJL5lPMwBx23YpqRe0rq4bAdamXvWVbOrD4gNzy8", "ZmRwd93QL4gaezxEbAx1oK01prn2XjlPvGyqJ6BO"]}, // "PLBJzmK1r3Gynd6OW0g2yqAe5wV4vx9bDEqNgYR8": {month: this.getCurrentMonth(), previousEnergy: null, tbNames: ["E6Kg9oDnLWyzPRMva7vWR9AJxp4VG58qO2w1lZYe","roKgWqY95V3mXMRzyAjrYnAbLjexpJPvaGDBw826"]} // } // { "E6Kg9oDnLWyzPRMva7vWR9AJxp4VG58qO2w1lZYe": [ { "ts": 1643673891157, "values": { "energy_last_month_delta": 2150 } } ] }