Compare commits

...

2 commits

Author SHA1 Message Date
06b289d7a3 Initial commit - testing version 2025-08-08 16:53:33 +02:00
cf16481324 version 2025-08-08; refactoring 2025-08-08 16:29:39 +02:00
11 changed files with 6636 additions and 3818 deletions

View file

@ -27,8 +27,6 @@ exports.html = `<div class="padding">
</div>
</div>`;
const { promisifyBuilder } = require('./helper/db_helper');
const fs = require('fs');
const mqtt = require('mqtt');
@ -56,7 +54,6 @@ let lastRestoreTime = 0;
// if there is an error in client connection, flow logs to monitor.txt. Not to log messages every second, we use sendClientError variable
let sendClientError = true;
exports.install = function(instance) {
var client;
@ -65,14 +62,12 @@ exports.install = function(instance) {
let o = null; //options
function main()
{
function main() {
loadSettings();
}
//set opts according to db settings
function loadSettings()
{
function loadSettings() {
o = instance.options;
if (!o.topic) o.topic = FLOW.GLOBALS.settings.cloud_topic;
@ -91,8 +86,7 @@ exports.install = function(instance) {
connectToTbServer();
}
function connectToTbServer()
{
function connectToTbServer() {
var url = "mqtt://" + opts.host + ":" + opts.port;
console.log("MQTT URL: ", url);
@ -118,18 +112,18 @@ exports.install = function(instance) {
// message is type of buffer
message = message.toString();
if (message[0] === '{') {
TRY(function() {
try {
message = JSON.parse(message);
if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) {
client.publish(`${o.topic}_forward`, `{"device": "${message.device}", "id": ${message.data.id}, "data": {"success": true}}`, { qos: 1 });
instance.send(SEND_TO.rpcCall, { "device": message.device, "id": message.data.id, "RPC response": { "success": true } });
}
}, () => instance.debug('MQTT: Error parsing data', message));
}
} catch (e) { instance.debug('MQTT: Error parsing data', message) }
instance.send(SEND_TO.rpcCall, { "topic": o.topic, "content": message });
}
});
client.on('close', function() {
@ -154,11 +148,9 @@ exports.install = function(instance) {
instance.on('0', function(data) {
if(clientReady)
{
if (clientReady) {
//do we have some data in backup file? if any, process data from database
if(saveTelemetryOnError)
{
if (saveTelemetryOnError) {
//read telemetry data and send back to server
if (!processingData) processDataFromDatabase();
}
@ -166,13 +158,11 @@ exports.install = function(instance) {
let stringifiedJson = JSON.stringify(data.data)
client.publish(`${o.topic}_forward`, stringifiedJson, { qos: 1 });
}
else
{
else {
//logger.debug("Client unavailable. Data not sent !", JSON.stringify(data.data));
instance.send(SEND_TO.debug, { "message": "Client unavailable. Data not sent !", "data": data.data });
if(saveTelemetryOnError)
{
if (saveTelemetryOnError) {
//create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql
makeBackupFromDbFile();
@ -194,22 +184,18 @@ exports.install = function(instance) {
};
function getDbBackupFileCounter(type)
{
function getDbBackupFileCounter(type) {
var files = fs.readdirSync(__dirname + "/../databases");
let counter = 0;
for(var i = 0; i < files.length; i++)
{
for (var i = 0; i < files.length; i++) {
if (files[i] == "tbdatacloud.nosql") continue;
if(files[i].endsWith(".nosql"))
{
if (files[i].endsWith(".nosql")) {
let pos = files[i].indexOf(".");
if(pos > -1)
{
if (pos > -1) {
let fileCounter = counter;
let firstDigit = files[i].slice(0, pos);
@ -218,19 +204,15 @@ exports.install = function(instance) {
if (isNaN(fileCounter)) fileCounter = 0;
//console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type);
if(type == "max")
{
if(fileCounter > counter)
{
if (type == "max") {
if (fileCounter > counter) {
counter = fileCounter;
}
}
else if(type == "min")
{
else if (type == "min") {
if (counter == 0) counter = fileCounter;
if(fileCounter < counter)
{
if (fileCounter < counter) {
counter = fileCounter;
}
}
@ -251,8 +233,7 @@ exports.install = function(instance) {
//to avoid large file: tbdata.nosql
//init value is 0!
if(insertNoSqlCounter > 0)
{
if (insertNoSqlCounter > 0) {
--insertNoSqlCounter;
return;
}
@ -264,8 +245,7 @@ exports.install = function(instance) {
var stats = fs.statSync(source);
var fileSizeInBytes = stats.size;
if(fileSizeInBytes > noSqlFileSizeLimit)
{
if (fileSizeInBytes > noSqlFileSizeLimit) {
let counter = 1;
counter = getDbBackupFileCounter("max");
@ -292,8 +272,7 @@ exports.install = function(instance) {
let currentTime = now.getTime();
let diff = currentTime - lastRestoreTime;
if( (diff / 1000) < restore_backup_wait)
{
if ((diff / 1000) < restore_backup_wait) {
//console.log("*********restore_backup_wait", diff, restore_backup_wait);
return;
}
@ -315,15 +294,13 @@ exports.install = function(instance) {
//select all data - use limit restore_from_backup
let records = await promisifyBuilder(nosql.find().take(restore_from_backup));
for(let i = 0; i < records.length; i++)
{
for (let i = 0; i < records.length; i++) {
if (clientReady) {
let item = records[i];
let id = item.id;
if(id !== undefined)
{
if (id !== undefined) {
//console.log("------------processDataFromDatabase - remove", id, dataBase, i);
try {
@ -343,22 +320,19 @@ exports.install = function(instance) {
}
}
else
{
else {
processingData = false;
return;
}
}
if(records.length > 0)
{
if (records.length > 0) {
//clean backup file
if (counter > 0) nosql.clean();
}
//no data in db, remove
if(records.length == 0)
{
if (records.length == 0) {
if (counter > 0) nosql.drop();
}

View file

@ -30,7 +30,7 @@ exports.readme = `Manager for CMD calls`;
exports.install = function(instance) {
const SerialPort = require('serialport');
const { SerialPort } = require('serialport');
const { exec } = require('child_process');
const { crc16 } = require('easy-crc');
const { runSyncExec, writeData } = require('./helper/serialport_helper');
@ -104,29 +104,29 @@ exports.install = function(instance) {
let priorities = [];
let minutes = 1;
priorities["1"] = minutes; // dimming
priorities["76"] = minutes; // power
priorities["1"] = minutes;
priorities["76"] = minutes;
minutes = 5;
// minutes = 5;
priorities["75"] = minutes;//current
priorities["79"] = minutes;//energy
priorities["87"] = minutes;//aktualny cas
//priorities["84"] = minutes;
minutes = 10;
priorities["74"] = minutes; // voltage
priorities["77"] = minutes; // power factor
priorities["78"] = minutes; // frequency
// minutes = 10;
priorities["74"] = minutes;
priorities["77"] = minutes;
priorities["78"] = minutes;
minutes = 60;
priorities["0"] = minutes; // statecode
priorities["6"] = minutes; // dusk
priorities["7"] = minutes; // dawn
priorities["8"] = minutes; // profile
// minutes = 60;
priorities["0"] = minutes;
priorities["6"] = minutes;
priorities["7"] = minutes;
priorities["8"] = minutes;
minutes = 60 * 24;
priorities["89"] = minutes; // verzia fw
priorities["80"] = minutes; // lifetime
// minutes = 60 * 24;
priorities["89"] = minutes;
priorities["80"] = minutes;
//prikazy kt sa budu spustat na dany node - see config.js in terminal-oms.app. (1 - dimming)
let listOfCommands = [0, 1, 6, 7, 8, 74, 75, 76, 77, 78, 79, 80, 87, 89];
@ -168,7 +168,7 @@ exports.install = function(instance) {
tbHandler.setSender(exports.title);
let now = new Date();
console.log("Cmd-mngr installed", now.toLocaleString("sk-SK"));
console.log("CMD Manager installed", now.toLocaleString("sk-SK"));
sunCalcResult = calculateDuskDawn();
@ -267,7 +267,7 @@ exports.install = function(instance) {
try {
nodeProfile = JSON.parse(nodeProfile);
} catch (error) {
logger.debug("Cmd-mngr: Error parsing node profile", error);
logger.debug("Cmd_manager - Error parsing node profile", error);
}
}
@ -753,7 +753,7 @@ exports.install = function(instance) {
function reportOfflineNodeStatus(line) {
logger.info("Cmd-mngr: ------>reportOffLineNodeStatus for line ", line);
logger.debug("Cmd-mngr: ------>reportOfflineNodeStatus for line", line);
values = {};
values["dimming"] = 0;//brightness
@ -780,7 +780,7 @@ exports.install = function(instance) {
sendTelemetry({ ...values }, tbname, date);
}
});
})
}
@ -797,6 +797,7 @@ exports.install = function(instance) {
}
function detectIfResponseIsValid(bytes) {
//ak sa odpoved zacina 0 - je to v poriadku, inak je NOK
let type = "RESPONSE";
@ -1035,7 +1036,7 @@ exports.install = function(instance) {
} catch (error) {
if (profilestr !== "") {
//errLogger.error(profilestr, error);
console.log(`Cmd_mngr: Unable to process line profile ${line}. Error: `, error);
console.log(`Cmd_manager: Unable to process line profile ${line}. Error: `, error);
errorHandler.sendMessageToService(profilestr + "-" + error, 0, "js_error");
} else {
turnLine("off", line, "No line profile. Switching it off on startup");
@ -1300,13 +1301,11 @@ exports.install = function(instance) {
function nodeDbStatusModify(node, data) {
dbNodes.modify(data).where("node", node).make(function(builder) {
builder.callback(function(err, response) {
dbNodes.modify(data).where("node", node).callback(function(err, response) {
if (!err) {
nodesData[node] = { ...nodesData[node], ...data };
}
});
});
}
@ -1322,7 +1321,7 @@ exports.install = function(instance) {
if ((currentTimestamp - reportDuskDawn.dusk_time) < 60 * 1000) {
//reportovali sme?
if (reportDuskDawn.dusk_time_reported != sunCalcResult.dusk_time) {
//sendNotification("Cmd-mngr: calculated Time of dusk", SETTINGS.rvoTbName, "dusk_has_occured", { value: sunCalcResult["dusk"] }, "", SEND_TO.tb, instance);
//sendNotification("CMD Manager: calculated Time of dusk", SETTINGS.rvoTbName, "dusk_has_occured", { value: sunCalcResult["dusk"] }, "", SEND_TO.tb, instance);
reportDuskDawn.dusk_time_reported = sunCalcResult.dusk_time;
}
}
@ -1339,7 +1338,7 @@ exports.install = function(instance) {
if ((currentTimestamp - reportDuskDawn.dawn_time) < 60 * 1000) {
//reportovali sme?
if (reportDuskDawn.dawn_time_reported != sunCalcResult.dawn_time) {
//sendNotification(": calculated Time of dawn", SETTINGS.rvoTbName, "dawn_has_occured", { value: sunCalcResult["dawn"] }, "", SEND_TO.tb, instance);
//sendNotification("CMD Manager: calculated Time of dawn", SETTINGS.rvoTbName, "dawn_has_occured", { value: sunCalcResult["dawn"] }, "", SEND_TO.tb, instance);
reportDuskDawn.dawn_time_reported = sunCalcResult.dawn_time;
}
}
@ -1370,7 +1369,7 @@ exports.install = function(instance) {
if (!rsPort.isOpen) {
instance.send(SEND_TO.debug, "!rsPort.isOpen");
//await rsPort.open();
//console.log("Cmd-mngr: !rsPort.isOpen");
//console.log("Cmd_manager - !rsPort.isOpen");
}
let currentTask = tasks[0];
@ -1391,6 +1390,7 @@ exports.install = function(instance) {
let type = params.type;
let tbname = params.tbname;
let node = params.address;
let register = params.register;
let line = null;
let itIsNodeCommand;
@ -1539,6 +1539,7 @@ exports.install = function(instance) {
let dataBytes = data.slice(5, 9);
let result = detectIfResponseIsValid(data);
if(register === 79) console.log("node responsee: ", dataBytes);
//ak sa odpoved zacina 0 - je to v poriadku, inak je NOK
let message = result.message; // OK, NOK
let message_type = result.type;
@ -1561,22 +1562,21 @@ exports.install = function(instance) {
if (type == "set_node_profile") {
let result = cmdCounterResolve(node);
if (result == 0) {
dbNodes.modify({ processed: true }).where("node", node).make(function(builder) {
builder.callback(function(err, response) {
dbNodes.modify({ processed: true }).where("node", node).callback(function(err, response) {
sendNotification("Cmd-mngr: process cmd", SETTINGS.rvoTbName, "dimming_profile_was_successfully_received_by_node", { node: node }, "", SEND_TO.tb, instance);
sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "dimming_profile_was_successfully_received_by_node", { node: node }, "", SEND_TO.tb, instance);
logger.debug("--> profil úspešne odoslaný na node č. " + node);
nodesData[node].processed = true;
nodeProfileSendFail.delete(node);
});
});
}
}
//parse read response
if (params.rw == 0) {
values = processResponse(register, dataBytes); //read
console.log("command params: ", params.address, register);
}
if (itIsNodeCommand) {
@ -1587,7 +1587,7 @@ exports.install = function(instance) {
//master node
if (node == 0) {
sendNotification("Cmd-mngr: process cmd", SETTINGS.rvoTbName, "master_node_is_responding_again", {}, "", SEND_TO.tb, instance, "rvo_status");
sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "master_node_is_responding_again", {}, "", SEND_TO.tb, instance, "rvo_status");
SETTINGS.masterNodeIsResponding = true;
if (register == 4) values["edge_fw_version"] = SETTINGS.edge_fw_version;
}
@ -1607,6 +1607,7 @@ exports.install = function(instance) {
}
else {
terminalCommandResponse(params, "ERROR", data);
handleNokResponseOnRsPort("handleNOK else block", params, itIsNodeCommand, saveToTb);
@ -1650,15 +1651,6 @@ exports.install = function(instance) {
}
function repeatCommand(params) {
params.repeatCounter++;
if (params.repeatCounter < 4) {
params.timestamp = 0;
params.addMinutesToTimestamp = 0;
tasks.push(params);
}
}
function handleNokResponseOnRsPort(message, params, itIsNodeCommand, saveToTb) {
let node = params.address;
@ -1674,7 +1666,6 @@ exports.install = function(instance) {
if (itIsNodeCommand) {
values.comm_status = "NOK";
nodesData[node].readout.comm_status = "NOK";
repeatCommand(params);
}
if (updateStatus) {
@ -1686,7 +1677,7 @@ exports.install = function(instance) {
//master node
if (node == 0) {
sendNotification("Cmd-mngr: process cmd", SETTINGS.rvoTbName, "master_node_is_not_responding", {}, "", SEND_TO.tb, instance, "rvo_status");
sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "master_node_is_not_responding", {}, "", SEND_TO.tb, instance, "rvo_status");
logger.debug("master_node_is_not_responding", params);
SETTINGS.masterNodeIsResponding = false;
@ -1698,7 +1689,7 @@ exports.install = function(instance) {
logger.debug("profil nebol úspešne odoslaný na node č. ", params);
if (!nodeProfileSendFail.has(node)) {
sendNotification("Cmd-mngr: process cmd", tbName, "configuration_of_dimming_profile_to_node_failed", { node: node }, "", SEND_TO.tb, instance);
sendNotification("CMD Manager: process cmd", tbName, "configuration_of_dimming_profile_to_node_failed", { node: node }, "", SEND_TO.tb, instance);
nodeProfileSendFail.add(node);
}
}
@ -1710,7 +1701,6 @@ exports.install = function(instance) {
}
function sendNodesData() {
Object.keys(nodesData).forEach(node => {
if (nodesData[node]["status"] !== "OFFLINE") {
@ -1732,6 +1722,9 @@ exports.install = function(instance) {
//console.log("params.refFlowdataKey is undefined", params);
return;
}
else {
console.log("params.refFlowdataKey: ", params);
}
let message = null;
let type = null;
@ -1754,6 +1747,7 @@ exports.install = function(instance) {
}
logger.debug(message);
logger.debug(params);
//make http response
let responseObj = {}
@ -1812,23 +1806,22 @@ exports.install = function(instance) {
function handleRsPort() {
if (rsPort) {
rsPort.removeAllListeners();
rsPort = null;
}
console.log("cmd_man: handleRsPort called");
//! rsPort LM = "/dev/ttymxc4", rsPort UNIPI = "/dev/ttyUSB0"
// const rsPort = new SerialPort("/dev/ttymxc4", { autoOpen: false }); //LM
// const rsPort = new SerialPort("/dev/ttyUSB0", { autoOpen: false }); // UNIPI
if (SETTINGS.serial_port == "" || SETTINGS.serial_port == undefined || SETTINGS.serial_port.length === 1) SETTINGS.serial_port = "ttymxc4";
rsPort = new SerialPort(`/dev/${SETTINGS.serial_port}`, { autoOpen: false });
console.log('SETTINGS.serial_port', SETTINGS.serial_port);
//rsPort = new SerialPort({path: `/dev/${SETTINGS.serial_port}`, baudRate: 57600, autoOpen: false });
rsPort = new SerialPort({ path: "/dev/ttyUSB0", baudRate: 9600, autoOpen: false });
//(node:16372) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 13 data listeners added to [SerialPort]. Use emitter.setMaxListeners() to increase limit
//rsPort.setMaxListeners(0);
rsPort.on('open', async function() {
logger.debug("Cmd-mngr: rsPort opened success");
logger.debug("CMD manager - rsPort opened success");
console.log("CMD manager - rsPort opened success");
await runSyncExec(`stty -F /dev/${SETTINGS.serial_port} 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke`).then(function(status) {
instance.send(SEND_TO.debug, "RPC runSyncExec - Promise Resolved:" + status);
@ -1836,21 +1829,28 @@ exports.install = function(instance) {
logger.debug(0, "RPC runSyncExec - Promise Resolved:" + status);
}).catch(function(reason) {
instance.send(SEND_TO.debug, "Cmd-mngr: RPC runSyncExec - promise rejected:" + reason);
instance.send(SEND_TO.debug, "CMD manager - RPC runSyncExec - promise rejected:" + reason);
console.log("cmd_man: rsport error", reason);
});
});
rsPort.on('error', function(err) {
//TODO report to service!!!
//errLogger.error(exports.title, "unable to open port", SETTINGS.serial_port, err.message);
errorHandler.sendMessageToService([exports.title, "unable to open port", SETTINGS.serial_port, err.message], 0);
monitor.info("Cmd-mngr: Error on rsPort", err.message);
console.log("cmd_manager: unable to open rsport", SETTINGS.serial_port, err.message);
instance.send(SEND_TO.debug, err.message);
});
rsPort.on("close", () => {
monitor.info("Cmd-mngr: rsPort closed, reconnecting...");
setTimeout(handleRsPort, 1000);
setTimeout(() => rsPort.open(), 1000);
});
rsPort.open();
rsPort.open(function(err) {
if (err) console.log('rsport open error', err);
})
}
@ -1922,13 +1922,13 @@ exports.install = function(instance) {
if (cmd == "buildTasks") {
clearInterval(interval);
logger.debug("-->Cmd-mngr: BUILD TASKS");
logger.debug("-->CMD MANAGER - BUILD TASKS");
buildTasks();
//logger.debug("tasks:");
//logger.debug(tasks);
logger.debug("-->Cmd-mngr: RUN TASKS");
logger.debug("-->CMD MANAGER - RUN TASKS");
interval = setInterval(runTasks, 5000);
}
else if (cmd == "reload_relays") {
@ -2002,8 +2002,8 @@ exports.install = function(instance) {
if (relaysData.hasOwnProperty(line)) {
let tbname = relaysData[line].tbname;
if (value == "Off") sendNotification("Cmd-mngr: onData", tbname, "circuit_breaker_was_turned_off_line", { line: line }, "", SEND_TO.tb, instance, "circuit_breaker");
else sendNotification("Cmd-mngr: onData", tbname, "circuit_breaker_was_turned_on_line", { line: line }, "", SEND_TO.tb, instance, "circuit_breaker");
if (value == "Off") sendNotification("CMD Manager: onData", tbname, "circuit_breaker_was_turned_off_line", { line: line }, "", SEND_TO.tb, instance, "circuit_breaker");
else sendNotification("CMD Manager: onData", tbname, "circuit_breaker_was_turned_on_line", { line: line }, "", SEND_TO.tb, instance, "circuit_breaker");
//report status liniu
sendTelemetry({ status: status }, tbname)
@ -2193,22 +2193,19 @@ exports.install = function(instance) {
if (tbname == nodesData[node].tbname) {
if (profile != "") profile = JSON.stringify(profile);
dbNodes.modify({ processed: false, profile: profile }).where("node", node).make(function(builder) {
builder.callback(function(err, response) {
dbNodes.modify({ processed: false, profile: profile }).where("node", node).callback(function(err, response) {
logger.debug("worksys - update node profile done", profile);
if (profile === "") logger.debug("worksys - update node profile done - profile is empty");
//profil úspešne prijatý pre node č. xx
sendNotification("Cmd-mngr", tbname, "dimming_profile_was_processed_for_node", { node: node }, profile, SEND_TO.tb, instance);
sendNotification("CMD manager", tbname, "dimming_profile_was_processed_for_node", { node: node }, profile, SEND_TO.tb, instance);
nodesData[node].processed = false;
nodesData[node].profile = profile;
processNodeProfile(node);
});
});
}
}
}
@ -2240,9 +2237,7 @@ exports.install = function(instance) {
removeTask({ type: "relay", line: line });
if (profile != "") profile = JSON.stringify(profile);
dbRelays.modify({ profile: profile }).where("line", line).make(function(builder) {
builder.callback(function(err, response) {
dbRelays.modify({ profile: profile }).where("line", line).callback(function(err, response) {
//update profile
logger.debug("worksys - update relay profile done:", profile);
@ -2255,8 +2250,7 @@ exports.install = function(instance) {
buildTasks({ processLineProfiles: true, line: line });
sendNotification("Cmd-mngr: set profile from worksys", tbname, "switching_profile_was_processed_for_line", { line: line }, profile, SEND_TO.tb, instance);
});
sendNotification("CMD manager - set profile from worksys", tbname, "switching_profile_was_processed_for_line", { line: line }, profile, SEND_TO.tb, instance);
});
break;
}
@ -2298,7 +2292,7 @@ exports.install = function(instance) {
let params = flowdata.data.body;
if (params == undefined) {
//logger.debug("Cmd-mngr: flowdata.data.body is undefined");
//logger.debug("CMD manager flowdata.data.body is undefined");
return;
}
@ -2629,7 +2623,9 @@ exports.install = function(instance) {
//energia
else if (register == 79) {
let energy = bytesToInt(bytes);
values["energy"] = energy / 1000; //energia v kWh -> delit 1000
console.log("bytesToIng ",bytesToInt(bytes))
//Energiu treba reportovať v kWh -> delit 1000
values["energy"] = energy / 1000;
}
//doba života

View file

@ -6,6 +6,26 @@ exports.version = '1.0.2';
exports.icon = 'sign-out';
exports.output = 2;
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="host" data-jc-config="placeholder:test.mosquitto.org;required:false" class="m">Hostname or IP address (if not empty - setting will override db setting)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:1883;required:true" class="m">Port</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="clientid">@(Client id)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="username" class="m">@(Username)</div>
</div>
</div>
</div>`;
exports.readme = `
# DB initialization
`;
@ -13,7 +33,6 @@ exports.readme = `
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
const { initNotification } = require('./helper/notification_reporter');
const errorHandler = require('./helper/ErrorToServiceHandler');
const total_energy = require('../databases/total_energy');
const SEND_TO = {
db_init: 0,
@ -22,6 +41,7 @@ const SEND_TO = {
exports.install = async function(instance) {
const dbNodes = TABLE("nodes");
const dbRelays = TABLE("relays");
const dbSettings = TABLE("settings");
@ -50,7 +70,7 @@ exports.install = async function(instance) {
Object.keys(dbs.nodesData).forEach(node => dbs.nodesData[node].readout = {})
dbs.settings = {
edge_fw_version: "2025-07-08", //rok-mesiac-den
edge_fw_version: "2025-04-24", //rok-mesiac-den
language: responseSettings[0]["lang"],
rvo_name: responseSettings[0]["rvo_name"],
project_id: responseSettings[0]["project_id"],
@ -78,11 +98,6 @@ exports.install = async function(instance) {
maintenance_mode: false,
}
let rvo_number = responseSettings[0]["rvo_name"].match(/\D+(\d{1,2})_/)[1];
dbs.settings.energy_to_switch_lamps = total_energy[rvo_number];
if (dbs.settings.energy_to_switch_lamps === undefined) console.log('=============== db_init.js: energy_to_switch_lamps is undefined');
FLOW.dbLoaded = true;
errorHandler.setProjectId(dbs.settings.project_id);
initNotification();

File diff suppressed because it is too large Load diff

2775
flow/designer.json_orig Normal file

File diff suppressed because it is too large Load diff

View file

@ -364,7 +364,7 @@ exports.install = function(instance) {
data.map(item => {
let value = item['value'];
let pin = item["dev"] + item["circuit"]; // for example "relay1_03" or "input1_01"
let pin = item["dev"] + item["circuit"]; // for example "ro1_03" or "di1_01"
if (pin == undefined) return;
switchLogic(pin, value);
@ -516,9 +516,9 @@ exports.install = function(instance) {
}
else if (ws) {
//pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method
//pin = "ro1_03" or "di1_01" ... we must make just "1_01" with slice method
monitor.info(`Dido: turnLine ${onOrOff} - (line, pin, force)`, line, pin, force, info);
let cmd = { "cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": value };
let cmd = { "cmd": "set", "dev": "relay", "circuit": pin.slice(2), "value": value };
ws.send(JSON.stringify(cmd));
}
@ -754,9 +754,9 @@ exports.install = function(instance) {
pins = [4, 6];
}
} else if (controllerType === "unipi") {
pins = ["input1_01", "input1_04", "input1_05"];
pins = ["di1_01", "di1_04", "di1_05"];
if (hasMainSwitch === 1) {
pins = ["input1_01", "input1_04"];
pins = ["di1_01", "di1_04"];
}
}
@ -781,7 +781,7 @@ exports.install = function(instance) {
for (const pinIndex of pinIndexes) {
if (previousValues[pinIndex] === 0) {
if ((pinIndex === 6 || pinIndex === 'input1_01' || pinIndex === 'input1_05') && SETTINGS.maintenance_mode) continue;
if ((pinIndex === 6 || pinIndex === 'di1_01' || pinIndex === 'di1_05') && SETTINGS.maintenance_mode) continue;
status = "NOK";
break;
}
@ -798,7 +798,7 @@ exports.install = function(instance) {
// we pass array to function in case of rsPort ==> switchLogic([55,3,0,1]) ==> [[55,3,0,1]]
// we pass two values in case of websocket ==> switchLogic("relay1_03",1) ==> ["relay1_03",1]
// we pass two values in case of websocket ==> switchLogic("ro1_03",1) ==> ["ro1_03",1]
const switchLogic = (...args) => {
let values = {};
@ -849,18 +849,18 @@ exports.install = function(instance) {
else if (type == "rotary_switch_state") {
// combination of these two pins required to get result
let pin2, pin3;
if (pinIndex == 2 || pinIndex == "input1_02") {
if (pinIndex == 2 || pinIndex == "di1_02") {
pin2 = newPinValue;
pin3 = previousValues[3] || previousValues["input1_03"];
pin3 = previousValues[3] || previousValues["di1_03"];
if (pin3 == undefined) {
previousValues[pinIndex] = newPinValue;
return;
}
}
else if (pinIndex == 3 || pinIndex == "input1_03") {
else if (pinIndex == 3 || pinIndex == "di1_03") {
pin3 = newPinValue;
pin2 = previousValues[2] || previousValues["input1_02"];
pin2 = previousValues[2] || previousValues["di1_02"];
if (pin2 == undefined) {
previousValues[pinIndex] = newPinValue;
@ -913,7 +913,7 @@ exports.install = function(instance) {
}
//Dverovy kontakt - pin 6
//! Ak je rvo s dvoma dverovymi kontaktami, ked pride z evoku signal z input1_05, co bol predytm "state_of_main switch" handlujeme ho teraz ako 'door_condition'
//! Ak je rvo s dvoma dverovymi kontaktami, ked pride z evoku signal z di1_05, co bol predytm "state_of_main switch" handlujeme ho teraz ako 'door_condition'
else if (type == "door_condition" || type === "state_of_main_switch") {
newPinValue === 0 ? value = "open" : value = "closed";
@ -1369,60 +1369,60 @@ exports.install = function(instance) {
//! pins.table --> from UNIPI
// pin:string|type:string|line:number
// *|input1_01|state_of_main_switch|0|...........
// *|input1_02|rotary_switch_state|0|...........
// *|input1_03|rotary_switch_state|0|...........
// *|di1_01|state_of_main_switch|0|...........
// *|di1_02|rotary_switch_state|0|...........
// *|di1_03|rotary_switch_state|0|...........
// *|intut1_04|power_supply|0|...........
// *|input1_05|door_condition|0|...........
// *|input1_06|state_of_breaker|1|...........
// *|input1_07|state_of_breaker|2|...........
// *|input1_08|state_of_breaker|3|...........
// *|relay1_02|state_of_contactor|1|...........
// *|relay1_03|state_of_contactor|2|...........
// *|relay1_04|state_of_contactor|3|...........
// *|di1_05|door_condition|0|...........
// *|di1_06|state_of_breaker|1|...........
// *|di1_07|state_of_breaker|2|...........
// *|di1_08|state_of_breaker|3|...........
// *|ro1_02|state_of_contactor|1|...........
// *|ro1_03|state_of_contactor|2|...........
// *|ro1_04|state_of_contactor|3|...........
// *|287D8776E0013CE9|temperature|0|...........
//! pins_data --> from UNIPI
// {
// input1_01: {
// pin: 'input1_01',
// di1_01: {
// pin: 'di1_01',
// type: 'door_condition',
// line: 0,
// tbname: 'PLBJzmK1r3Gynd6OW0gGYz0e5wV4vx9bDEqNgYR8'
// },
// input1_02: {
// pin: 'input1_02',
// di1_02: {
// pin: 'di1_02',
// type: 'rotary_switch_state',
// line: 0,
// tbname: 'PLBJzmK1r3Gynd6OW0gGYz0e5wV4vx9bDEqNgYR8'
// },
// input1_03: {
// pin: 'input1_03',
// di1_03: {
// pin: 'di1_03',
// type: 'rotary_switch_state',
// line: 0,
// tbname: 'PLBJzmK1r3Gynd6OW0gGYz0e5wV4vx9bDEqNgYR8'
// },
// input1_04: {
// pin: 'input1_04',
// di1_04: {
// pin: 'di1_04',
// type: 'power_supply',
// line: 0,
// tbname: 'PLBJzmK1r3Gynd6OW0gGYz0e5wV4vx9bDEqNgYR8'
// },
// input1_05: {
// pin: 'input1_05',
// di1_05: {
// pin: 'di1_05',
// type: 'state_of_main_switch',
// line: 0,
// tbname: 'PLBJzmK1r3Gynd6OW0gGYz0e5wV4vx9bDEqNgYR8'
// },
// input1_06: {
// pin: 'input1_06',
// di1_06: {
// pin: 'di1_06',
// type: 'state_of_breaker',
// line: 1,
// tbname: '52dD6ZlV1QaOpRBmbAqK8bkKnGzWMLj4eJq38Pgo'
// },
// relay1_02: {
// pin: 'relay1_02',
// ro1_02: {
// pin: 'ro1_02',
// type: 'state_of_contactor',
// line: 1,
// tbname: '52dD6ZlV1QaOpRBmbAqK8bkKnGzWMLj4eJq38Pgo'

View file

@ -1,4 +1,36 @@
function bytesToInt_orig(bytes, numberOfBytes) {
let buffer = [];
if (Array.isArray(bytes)) {
buffer = bytes.slice(0);
if (numberOfBytes != undefined) {
buffer = bytes.slice(bytes.length - numberOfBytes);
}
}
else buffer.push(bytes);
//var decimal = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
let l = (buffer.length - 1) * 8;
let decimal = 0;
for (let i = 0; i < buffer.length; i++) {
var s = buffer[i] << l;
if (l < 8) s = buffer[i]
decimal = decimal + s;
l = l - 8;
}
// console.log("decimal utils.js: ", decimal);
let decimal1 = 0n;
for (let i = 0; i < buffer.length; i++) {
decimal1 += BigInt(buffer[i]) * (2n ** BigInt((buffer.length - 1 - i) * 8));
}
// console.log("decimal biging utils.js: ", decimal1);
return decimal;
}
//bytestouintBE
function bytesToInt(bytes, numberOfBytes) {
let buffer = [];
if (Array.isArray(bytes)) {
buffer = bytes.slice(0);
@ -8,13 +40,39 @@ function bytesToInt(bytes, numberOfBytes) {
}
else buffer.push(bytes);
console.log(bytes, buffer);
let result = 0;
for (let i = 0; i < buffer.length; i++) {
result = (result << 8) | buffer[i];
result = (result << 8) | bytes[i];
}
// console.log("decimal biging utils.js: ", decimal1);
console.log("originall: ", bytesToInt_orig(buffer));
console.log("uint little endian: ", bytesToUintLE(buffer));
console.log('neww: ', result >>> 0);
return result >>> 0;
}
return result >>> 0; //ensure it's an unsigned 32-bit number
function bytesToUintLE(bytes, numberOfBytes) {
let buffer = [];
if (Array.isArray(bytes)) {
buffer = bytes.slice(0);
if (numberOfBytes != undefined) {
buffer = bytes.slice(bytes.length - numberOfBytes);
}
}
else buffer.push(bytes);
//var decimal = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
let result = 0;
for (let i = buffer.length - 1; i <= 0; i--) {
result = (result << 8) | bytes[i];
}
return result >>> 0;
}
function resizeArray(arr, newSize, defaultValue) {
while (newSize > arr.length)

View file

@ -16,7 +16,7 @@ exports.readme = `
`;
const modbus = require('jsmodbus');
const SerialPort = require('serialport');
const {SerialPort} = require('serialport');
const { timeoutInterval, deviceConfig } = require("../databases/modbus_config");
const { sendNotification } = require('./helper/notification_reporter');
@ -36,7 +36,6 @@ let mainSocket;
let phases;
//phases where voltage is 0 (set)
let noVoltage;
let energyToSwitchLamps;
exports.install = function(instance) {
@ -77,13 +76,8 @@ exports.install = function(instance) {
let obj = this;
if (this.socket) {
this.socket.removeAllListeners();
this.socket = null;
}
this.socket = new SerialPort("/dev/ttymxc0", {
baudRate: 9600,
this.socket = new SerialPort({path: "/dev/ttymxc0",
baudRate: 9600
})
// we create a client for every deviceAddress ( = address) in list and push them into dictionary
@ -92,11 +86,15 @@ exports.install = function(instance) {
}
this.socket.on('error', function(e) {
console.log('Modbus_reader: Socket connection error', e); //'ECONNREFUSED' or 'ECONNRESET' ??
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('Modbus_reader: Socket connection closed - Waiting 10 seconds before connecting again');
console.log('Socket connection closed ' + exports.title + ' Waiting 10 seconds before trying to connect again');
setTimeout(obj.startSocket, 10000);
});
@ -117,8 +115,7 @@ exports.install = function(instance) {
this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number
this.device = dev.device; //em340, twilight_sensor
//if we just start to loop devices from the beginning, or there is just 1 device in config, we wait whole timeoutInterval
if (this.indexInDeviceConfig == 0 || deviceConfig.length === 1) setTimeout(this.readRegisters, this.timeoutInterval);
if (this.indexInDeviceConfig == 0) setTimeout(this.readRegisters, this.timeoutInterval);
else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES);
}
@ -307,12 +304,15 @@ exports.install = function(instance) {
const actualTotalPower = values.total_power;
if (actualTotalPower > energyToSwitchLamps && this.onNotificationSent == false) {
const numberOfNodes = Object.keys(FLOW.GLOBALS.nodesData).length;
if (numberOfNodes == 0) numberOfNodes = 20; // to make sure, we send notification if totalPower is more than 300
if (actualTotalPower > numberOfNodes * 15 && this.onNotificationSent == false) {
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_on", {}, "", SEND_TO.tb, instance);
this.onNotificationSent = true;
this.offNotificationSent = false;
}
else if (actualTotalPower <= energyToSwitchLamps && this.offNotificationSent == false) {
else if (actualTotalPower <= numberOfNodes * 15 && this.offNotificationSent == false) {
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_off", {}, "", SEND_TO.tb, instance);
this.onNotificationSent = false;
this.offNotificationSent = true;
@ -330,9 +330,9 @@ exports.install = function(instance) {
phases = FLOW.GLOBALS.settings.phases;
tbName = FLOW.GLOBALS.settings.rvoTbName;
noVoltage = FLOW.GLOBALS.settings.no_voltage;
energyToSwitchLamps = FLOW.GLOBALS.settings.energy_to_switch_lamps / 2.5; //half value is enought to show if lamps are turned on or off
if (deviceConfig.length) mainSocket = new SocketWithClients();
else console.log("Modbus_reader: no modbus device in configuration");
mainSocket = new SocketWithClients();
console.log("novoltage: ", noVoltage, typeof noVoltage);
// this notification is to show, that flow (unipi) has been restarted
sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance);

0
flow/variables.txt Normal file
View file

View file

@ -155,7 +155,7 @@ exports.install = function(instance) {
clientId: mqtt_clientid,
username: mqtt_username,
rejectUnauthorized: false,
resubscribe: false
resubscribe: false,
};
wsmqttName = getWsmqttName(mqtt_host);
@ -188,15 +188,17 @@ exports.install = function(instance) {
// message is type of buffer
message = message.toString();
if (message[0] === '{') {
TRY(function() {
try {
message = JSON.parse(message);
if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) {
client.publish(topic, `{"device": ${message.device}, "id": ${message.data.id}, "data": {"success": true}}`, { qos: 1 });
instance.send(SEND_TO.rpcCall, { "device": message.device, "id": message.data.id, "RPC response": { "success": true } });
}
}, () => instance.debug('MQTT: Error parsing data', message));
} catch (e) {
console.log('MQTT: Error parsing data', e);
}
}
instance.send(SEND_TO.rpcCall, { "topic": topic, "content": message });