grafia/flow/energomonitor_socomec.js
2025-08-14 22:59:29 +02:00

363 lines
10 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.31": {
3: "i-35",
2: "i-30",
11: "i-35",
"12A": "i-60A",
"12B": "i-60B",
"13A": "i-60A",
"13B": "i-60B",
"14A": "i-60A",
"14B": "i-60B",
"15A": "i-60A",
"15B": "i-60B",
16: "i-30",
21: "i-30",
"22A": "i-60A",
"22B": "i-60B",
"23A": "i-60A",
"23B": "i-60B",
"24A": "i-60A",
"24B": "i-60B",
// 76: "i-30"//vymyslene pre testovanie
}
};
exports.install = function(instance) {
const modbus = require('jsmodbus');
const net = require('net');
require('events').EventEmitter.defaultMaxListeners = 20;
const allModulesEnergy = {unipi_76: ""}; // 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.11": {
// "streams": [
// {
// "unitId": 1,
// "section": "",
// "name": 18488,
// "tb_value":"phase_1_power",
// "bytes": 2,
// "multiplier":1,
// },
// ]
// },
// "192.168.1.12": {
// "streams": [
// {
// "unitId": 18,
// "section": "",
// "name": 18488,
// "tb_value": "total_energy",
// "bytes": 2,
// "multiplier": 1,
// "month": 0
// }
// ]
// }
// }
const tbNames = {
"192.168.1.31": {
"3": "JX1ObgmqGZ54DMyYL7aJMlAEVdve38WKRzwjNrQ9",
"2": "RO8rjaBDy21qPQJzW7oKN17pK3xmNleVZg9Ed4Gw",
"11": "RvmwNz8QPblKp41GD7l4NY7JrLVYoBO92dMegn6W",
"12A": "K94XLav1glVRnyQ6r01VZ3kme3YJwBxM5oOzdP2j",
"12B": "3JjOWdylwgNLzxVab7NPpJ0Z2vG64rq8PEB5QmDo",
"13A": "Z5KyJe9nEg1QNbWlX0wmNP7oDjBLdqzR83VGv624",
"13B": "PjLblDgRBO6WQqnxmkJwpb7Jv3ewZN4p5a89yKdY",
"14A": "dz4ojlpP85JMgDLZWkQ1p3kaKYqQexEr62GXRV1y",
"14B": "d9x2V5LGYBzXp4mMRAOPpV0PloaqJwnQj6DgrNe3",
"15A": "1JMYvnx2RzKEo4aWQ7D9pXAL8yZV3m9NBePXbrdj",
"15B": "d5xjWYMwEJon6rLlK7ylNxkqgV4DaOeNB9ZX3Gzb",
"16": "gRoJEyXVx4qD9er287LwpEkwBzGldaPjLWQKm3Mv",
"21": "3JjOWdylwgNLzxVab7NPyn0Z2vG64rq8PEB5QmDo",
"22A": "Z5KyJe9nEg1QNbWlX0wmEB7oDjBLdqzR83VGv624",
"22B": "1JMYvnx2RzKEo4aWQ7D9y5AL8yZV3m9NBePXbrdj",
"23A": "PjLblDgRBO6WQqnxmkJwyr7Jv3ewZN4p5a89yKdY",
"23B": "dz4ojlpP85JMgDLZWkQ1GGkaKYqQexEr62GXRV1y",
"24A": "d5xjWYMwEJon6rLlK7ylZmkqgV4DaOeNB9ZX3Gzb",
"24B": "gRoJEyXVx4qD9er287LwyvkwBzGldaPjLWQKm3Mv"
// "76": "vymyslene pre testovanie"
}
};
class SocketWithClients {
constructor (ip, data) {
this.ip = ip;
this.data = data;
this.options = {
'host': this.ip,
'port': '502'
}
this.streams = data.streams; //pole
this.startSocket();
}
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 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);
});
// 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 - "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 - "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;
}
let dataToTB = {
[tbName]: [
{
"ts": date,
"values": values
}
]
};
instance.send(1, dataToTB);
break;
}
}
};
}
const newSocket = new SocketWithClients("192.168.1.31", conversionTable["192.168.1.31"]);
}