518 lines
17 KiB
JavaScript
518 lines
17 KiB
JavaScript
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<l; i++) {
|
|
|
|
let a = this.streams[i];
|
|
if (a.name === register && a.unitId === unitId && a.section === section)
|
|
{
|
|
let tb_value = a.tb_value;
|
|
let value;
|
|
let l = response.length;
|
|
let temp_val = 0;
|
|
|
|
if (l === 2)
|
|
{
|
|
temp_val = (response[0]*(2**16) + response[1]);
|
|
|
|
if(temp_val >= (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 } } ] }
|
|
|