Handle contactors separately in daily_report

This commit is contained in:
rasta5man 2025-10-19 19:47:04 +02:00
parent 7093d765ec
commit c5c5b21f47
10 changed files with 427 additions and 242 deletions

4
config
View file

@ -6,7 +6,7 @@ package#flow (Object) : { url: '/' }
table.relays : line:number|tbname:string|contactor:number|profile:string
table.nodes : node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number
table.settings : rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string|has_main_switch:boolean
table.nodes : node:number|pole_number:string|node_type:string|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number
table.settings : rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string|has_main_switch:boolean|daily_report:boolean
table.pins : pin:string|type:string|line:number
table.notifications : key:string|weight:string|sk:string|en:string

View file

@ -14,7 +14,7 @@ with open("/home/unipi/flowserver/databases/nodes.table", 'r') as file:
if counter != 1:
i = [m.start() for m in re.finditer(re.escape(search_str), line)]
node = line[ i[0] + 1 : i[1] ]
tbname = line[ i[1] + 1 : i[2] ]
tbname = line[ i[3] + 1 : i[4] ]
final.append({node:tbname})
counter += 1
print(json.dumps(final))

View file

@ -1,2 +1,2 @@
rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string|has_main_switch:boolean
+|rvo_senica_22_ip10.0.0.109|en|28.F46E9D0E0000|48.70826502|17.28455203|192.168.252.1|rvo_senica_22_ip10.0.0.109|9excvr7yBcF3gl3kYZGY|1883|0|48|unipi|ttyUSB0|1|20|5|6|3|u109|0|...........................................
rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string|has_main_switch:boolean|daily_report:boolean
+|rvo_senica_22_ip10.0.0.109|en|28.F46E9D0E0000|48.70826502|17.28455203|192.168.252.1|rvo_senica_22_ip10.0.0.109|9excvr7yBcF3gl3kYZGY|1883|0|48|unipi|ttyUSB0|1|20|5|6|3|u109|0|1|...........................................

View file

@ -63,19 +63,17 @@ exports.install = function(instance) {
var opts;
var clientReady = false;
let o = null; //options
let o = {}; //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;
if (!o.topic) o.topic = FLOW.GLOBALS.settings.cloud_topic;
opts = {
host: o.host,
@ -86,13 +84,18 @@ exports.install = function(instance) {
resubscribe: false
};
console.log("wsmqttpublich -> loadSettings from instance.options",o);
console.log("wsmqttpublich -> loadSettings from instance.options", o);
if (!o.topic) {
instance.status("Not configured", "white");
console.log("Cloud mqtt connect: no topic selected");
return;
}
connectToTbServer();
}
function connectToTbServer()
{
function connectToTbServer() {
var url = "mqtt://" + opts.host + ":" + opts.port;
console.log("MQTT URL: ", url);
@ -122,27 +125,27 @@ exports.install = function(instance) {
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}});
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));
}
instance.send(SEND_TO.rpcCall, {"topic":o.topic, "content":message });
instance.send(SEND_TO.rpcCall, { "topic": o.topic, "content": message });
});
client.on('close', function() {
clientReady = false;
instance.status("Disconnected", "red");
instance.send(SEND_TO.debug, {"message":"Client CLOSE signal received !"});
instance.send(SEND_TO.debug, { "message": "Client CLOSE signal received !" });
});
client.on('error', function(err) {
instance.status("Err: "+ err.code, "red");
instance.send(SEND_TO.debug, {"message":"Client ERROR signal received !", "error":err, "opt":opts });
if(sendClientError) {
instance.status("Err: " + err.code, "red");
instance.send(SEND_TO.debug, { "message": "Client ERROR signal received !", "error": err, "opt": opts });
if (sendClientError) {
console.log('MQTT client error', err);
sendClientError = false;
}
@ -154,25 +157,21 @@ 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();
if (!processingData) processDataFromDatabase();
}
let stringifiedJson = JSON.stringify(data.data)
client.publish(`${o.topic}_forward`, stringifiedJson, {qos: 1});
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 });
instance.send(SEND_TO.debug, { "message": "Client unavailable. Data not sent !", "data": data.data });
if(saveTelemetryOnError)
{
if (saveTelemetryOnError && o.topic) {
//create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql
makeBackupFromDbFile();
@ -188,49 +187,41 @@ exports.install = function(instance) {
})
instance.close = function(done) {
if(clientReady){
if (clientReady) {
client.end();
}
};
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] == "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);
fileCounter = parseInt(firstDigit);
if(isNaN(fileCounter)) fileCounter = 0;
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")
{
if(counter == 0) counter = fileCounter;
else if (type == "min") {
if (counter == 0) counter = fileCounter;
if(fileCounter < counter)
{
if (fileCounter < counter) {
counter = fileCounter;
}
}
@ -239,20 +230,19 @@ exports.install = function(instance) {
}
if(type == "max") counter++;
if (type == "max") counter++;
return counter;
}
const makeBackupFromDbFile = async () => {
if(!saveTelemetryOnError) return;
if (!saveTelemetryOnError) return;
//to avoid large file: tbdata.nosql
//init value is 0!
if(insertNoSqlCounter > 0)
{
if (insertNoSqlCounter > 0) {
--insertNoSqlCounter;
return;
}
@ -264,8 +254,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");
@ -285,15 +274,14 @@ exports.install = function(instance) {
const processDataFromDatabase = async () => {
if(restore_from_backup <= 0) return;
if (restore_from_backup <= 0) return;
//calculate diff
const now = new Date();
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;
}
@ -307,7 +295,7 @@ exports.install = function(instance) {
let dataBase = 'tbdata';
var nosql;
if(counter == 0) dataBase = 'tbdatacloud';
if (counter == 0) dataBase = 'tbdatacloud';
else dataBase = counter + "." + 'tbdatacloud';
nosql = NOSQL(dataBase);
@ -315,27 +303,25 @@ 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++)
{
if(clientReady) {
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 {
let message = JSON.parse(JSON.stringify(item));
delete message.id;
client.publish(`${o.topic}_forward`, JSON.stringify(message), {qos:1});
client.publish(`${o.topic}_forward`, JSON.stringify(message), { qos: 1 });
//remove from database
await promisifyBuilder(nosql.remove().where("id", id));
} catch(error) {
} catch (error) {
//process error
console.log("processDataFromDatabase", error);
}
@ -343,23 +329,20 @@ 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();
if (counter > 0) nosql.clean();
}
//no data in db, remove
if(records.length == 0)
{
if(counter > 0) nosql.drop();
if (records.length == 0) {
if (counter > 0) nosql.drop();
}
const d = new Date();

View file

@ -112,7 +112,7 @@ exports.install = function(instance) {
priorities["75"] = minutes; // current
priorities["79"] = minutes; // energy
priorities["87"] = minutes; // aktualny cas
//priorities["84"] = minutes;
//priorities["84"] = minutes; //accelerometer
minutes = 10;
priorities["74"] = minutes; // voltage
@ -195,14 +195,17 @@ exports.install = function(instance) {
customTasksInterval = setInterval(reportEdgeDateTimeAndNumberOfLuminaires, 120_000);
setTimeout(reportEdgeDateTimeAndNumberOfLuminaires, 4000);
//dailyReport related
//NOTE: dailyReport related
emptyReportToSend();
// we handle "contactor" key separately in reportToSend
reportToSend["contactor"] = { off: [], on: [] };
breakerCounter = Object.keys(relaysData).length - 1; // we get number of lines (breakers) except of line 0
rvoPeriod = setInterval(setRvoPeriod, SET_RVO_PERIOD_TIME);
setTimeout(setRvoPeriod, 3000);
setInterval(setSunCalcResult, SET_SUNCALC_RESULT_TIME);
handleDailyReport = setInterval(dailyReportHandler, DAILY_REPORT_HANDLER_TIME);
//if dawn, we empty "contactor" key in reportToSend
setInterval(emptyContactorDataInReport, 60000);
setCorrectTime = setInterval(setCorrectPlcTimeOnceADay, 60000 * 60); // 1 hour
setCorrectPlcTimeOnceADay();
@ -1342,51 +1345,69 @@ exports.install = function(instance) {
const now = Date.now();
if (rvo_is_on) {
for (const [key, value] of Object.entries(dailyReport)) {
if (["name", "time", "dusk_and_dawn"].includes(key)) continue;
let poleNumber = nodesData[key]["pole_number"];
let line = dailyReport[key].line;
let nodeType = nodesData[key]["node_type"];
let fullNodeName = key // key == nodeNumber
if (poleNumber && nodeType) {
fullNodeName = SETTINGS.rvo_number + "/" + poleNumber + "_" + line + "L_" + key + "_" + nodeType;
} else {
fullNodeName = key;
}
if (rvo_is_on) {
if (value.initialTs) {
if (value.initialTs + ADD_NODE_TO_REPORT_TIME < now) {
addToArrayIfUnique(reportToSend["night_no_data"], key);
console.log('report nedostava ziadne data uz hodinu', key);
addToArrayIfUnique(reportToSend["night_no_data"], fullNodeName);
console.log('report nedostava ziadne data uz hodinu', fullNodeName);
value.initialTs = now;
}
}
if (value.dimmingAndPowerAreZeroTime) {
if (value.dimmingAndPowerAreZeroTime + ADD_NODE_TO_REPORT_TIME < now) {
addToArrayIfUnique(reportToSend["night_dimming=0"], key);
console.log("report node dimming je 0", key);
value.dimmingAndPowerAreZeroTime = now;
if (value.dimmingIsZeroTime) {
if (value.dimmingIsZeroTime + ADD_NODE_TO_REPORT_TIME < now) {
addToArrayIfUnique(reportToSend["night_dimming=0"], fullNodeName);
console.log("report node dimming je 0 ale ma svietit", fullNodeName);
value.dimmingIsZeroTime = now;
}
}
if (value.powerIsZeroTime) {
if (value.powerIsZeroTime + ADD_NODE_TO_REPORT_TIME < now) {
addToArrayIfUnique(reportToSend["night_power=0"], fullNodeName);
console.log("report node power je 0 ale ma svietit", fullNodeName);
value.powerIsZeroTime = now;
}
}
} else {
for (const [key, value] of Object.entries(dailyReport)) {
if (["name", "time", "dusk_and_dawn"].includes(key)) continue;
let nodeIsOnLine = dailyReport[key].line;
let contactorStatus = relaysData[nodeIsOnLine].contactor;
let contactorStatus = relaysData[line].contactor;
if (contactorStatus === 1) {
if (value.initialTs) {
if (value.initialTs + ADD_NODE_TO_REPORT_TIME < now) {
addToArrayIfUnique(reportToSend["day_24/7_no_data"], key);
console.log('node je na 24/7 ale nedostava ziadne data uz hodinu', key);
addToArrayIfUnique(reportToSend["day_24/7_no_data"], fullNodeName);
console.log('node je na 24/7 ale nedostava ziadne data uz hodinu', fullNodeName);
value.initialTs = now;
}
}
if (value.nodeIsOnButShouldBeOffTime) {
if (value.nodeIsOnButShouldBeOffTime + ADD_NODE_TO_REPORT_TIME < now) {
addToArrayIfUnique(reportToSend["day_24/7_dimming>0"], key);
console.log("report nema svietit ale svieti viac ako hodinu", key);
value.nodeIsOnButShouldBeOffTime = now;
if (value.dimmingIsOnButShouldBeOffTime) {
if (value.dimmingIsOnButShouldBeOffTime + ADD_NODE_TO_REPORT_TIME < now) {
addToArrayIfUnique(reportToSend["day_24/7_dimming>0"], fullNodeName);
console.log("report dimming je > 0 ale nema svietit", fullNodeName);
value.dimmingIsOnButShouldBeOffTime = now;
}
}
if (value.powerIsOnButShouldBeOffTime) {
if (value.powerIsOnButShouldBeOffTime + ADD_NODE_TO_REPORT_TIME < now) {
addToArrayIfUnique(reportToSend["day_24/7_power>0"], fullNodeName);
console.log("report power je > 0 ale nema svietit", fullNodeName);
value.powerIsOnButShouldBeOffTime = now;
}
}
@ -1408,6 +1429,8 @@ exports.install = function(instance) {
function dailyReportHandler() {
if (!SETTINGS.daily_report) return;
// after dawn we empty reportToSend and start to get data for a new day
const date = new Date();
const hour = date.getHours();
@ -1432,6 +1455,10 @@ exports.install = function(instance) {
initialReportStatus = true;
// ak sa funkcia spusti o hodinu alebo 2 neskor, ako je usvit (moze sa to stat, kedze kazde zapnutie/vypnutie linii odznova spusti casovac.
// TODO: usvit bol 7:11, posledna "reportHandler" bola 6:55. Spustila sa podmienka5. Dalsi "reportHandler mal byt 7:55, ale 7:41 sa vypli linie (teda "reportHandler" sa pusti az o hodinu 8:41),
// ale kedze uzivatel zapol linie o 8:30, posunulo sa "reportHandlovanie" na 9:30. Kedze v tento moment sa reportToSend vymazava (podmienka 3),
// tak zapnutie linii pouzivatelom o 8:30 sa do reportu nikdy nedostane
// preco nevymazavam reportToSend hned po usvite ??? (asi preto ze sa moze stat ze rvo ma stale zapnute linie a stane sa ze do reportu na dalsi den sa dostanu nody, ktore tam nemaju co robit!!!
} else if (hour === sunCalcResult.dawn_hours + 1 || hour === sunCalcResult.dawn_hours + 2) {
if (!initialReportStatus) {
emptyReportToSend();
@ -1453,17 +1480,30 @@ exports.install = function(instance) {
}
//We empty reportToSend object, but we want to keep contactor data for a current day
function emptyReportToSend() {
emptyJsObject(reportToSend);
reportToSend["contactor"] = { off: [], on: [] };
Object.keys(reportToSend).forEach(key => { if (key !== "contactor") delete jsObject[key] });
reportToSend["night_no_data"] = [];
reportToSend["night_dimming=0"] = [];
reportToSend["night_power=0"] = [];
reportToSend["day_24/7_no_data"] = [];
reportToSend["day_24/7_dimming>0"] = [];
//console.log(`je ${sunCalcResult.dawn_hours}:${sunCalcResult.dawn_minutes}, resetuje sa reportToSend: `, reportToSend);
reportToSend["day_24/7_power>0"] = [];
console.log(`resetuje sa reportToSend`);
}
// to have proper contactor data in report for a current day, we empty it when dawn is reached:
function emptyContactorDataInReport() {
const d = new Date();
if (sunCalcResult.dawn_hours === d.getHours() && sunCalcResult.dawn_minutes === d.getMinutes()) {
delete reportToSend.contactor;
reportToSend["contactor"] = { off: [], on: [] };
}
}
//NOTE: ked je initialTs stale rovnaky a zistime ze linie su vypnute (relaysData.line.contactor) - zistim, ci je node na tej linii. Vieme, ze ak je linia vypnuta je to v poriadku lebo nie je na 24/7 linii
//ak je na 24/7 linii, reportujeme, ze neprijima data
function emptyDailyReport() {
@ -1500,12 +1540,6 @@ exports.install = function(instance) {
return;
}
if (!rsPort.isOpen) {
instance.send(SEND_TO.debug, "!rsPort.isOpen");
//await rsPort.open();
//console.log("Cmd-mngr: !rsPort.isOpen");
}
let currentTask = tasks[0];
if (currentTask.debug) {
@ -1560,7 +1594,7 @@ exports.install = function(instance) {
date.setDate(date.getDate() + 1);//next day
let sunCalcResult;
if (timePointName) { sunCalcResult = calculateDuskDawn(date, params.line); console.log("typee-rellay: ", sunCalcResult.dawn_time, sunCalcResult.dusk_time); }
if (timePointName) sunCalcResult = calculateDuskDawn(date, params.line);
if (timePointName == "dawn") {
@ -1615,6 +1649,7 @@ exports.install = function(instance) {
// state_of_braker: disconnected = true?
if (!rsPort.isOpen) {
console.log("Cmd-mngr: rsPort is not opened in runTasks", params);
interval = setInterval(runTasks, LONG_INTERVAL);
return;
}
@ -1717,39 +1752,73 @@ exports.install = function(instance) {
values.status = "OK";
nodesData[node].readout = { ...nodesData[node].readout, ...values };
if (SETTINGS.daily_report) {
//TODO: co ak nedostavame odpovede z nodu ? Musime vyreportovat!!
//v dailyReport teda musi byt kompletny zoznam nodov a neustale kontrolovat, ci maju data, ak nie report node - neodpoveda
if (register === 1 || register === 76) {
if (register === 1) {
let now = Date.now();
if (rvo_is_on === true) {
if ("nodeIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].nodeIsOnButShouldBeOffTime;
if ("dimmingIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].dimmingIsOnButShouldBeOffTime;
if (values.dimming > 0 || values.power > 0) {
if ("dimmingAndPowerAreZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingAndPowerAreZeroTime;
if (values.dimming > 0) {
if ("dimmingIsZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingIsZeroTime;
dailyReport[node] = { ...dailyReport[node], initialTs: now };
} else {
if (!("dimmingAndPowerAreZeroTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], dimmingAndPowerAreZeroTime: now, initialTs: now };
if (!("dimmingIsZeroTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], dimmingIsZeroTime: now, initialTs: now };
else dailyReport[node] = { ...dailyReport[node], initialTs: now };
}
} else {
if ("dimmingAndPowerAreZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingAndPowerAreZeroTime;
if ("dimmingIsZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingIsZeroTime;
if (values.dimming > 0 || values.power > 0) {
if (!("nodeIsOnButShouldBeOffTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], nodeIsOnButShouldBeOffTime: now, initialTs: now };
if (values.dimming > 0) {
if (!("dimmingIsOnButShouldBeOffTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], dimmingIsOnButShouldBeOffTime: now, initialTs: now };
else dailyReport[node] = { ...dailyReport[node], initialTs: now };
} else {
if ("nodeIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].nodeIsOnButShouldBeOffTime;
if ("dimmingIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].dimmingIsOnButShouldBeOffTime;
dailyReport[node] = { ...dailyReport[node], initialTs: now };
}
}
}
if (register === 76) {
let now = Date.now();
if (rvo_is_on === true) {
if ("powerIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].powerIsOnButShouldBeOffTime;
if (values.power > 1) {
if ("powerIsZeroTime" in dailyReport[node]) delete dailyReport[node].powerIsZeroTime;
dailyReport[node] = { ...dailyReport[node], initialTs: now };
} else {
if (!("powerIsZeroTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], powerIsZeroTime: now, initialTs: now };
else dailyReport[node] = { ...dailyReport[node], initialTs: now };
}
} else {
if ("powerIsZeroTime" in dailyReport[node]) delete dailyReport[node].powerIsZeroTime;
if (values.power > 1) {
if (!("powerIsOnButShouldBeOffTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], powerIsOnButShouldBeOffTime: now, initialTs: now };
else dailyReport[node] = { ...dailyReport[node], initialTs: now };
} else {
if ("powerIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].powerIsOnButShouldBeOffTime;
dailyReport[node] = { ...dailyReport[node], initialTs: now };
}
}
}
}
}
//master node
@ -1989,6 +2058,8 @@ exports.install = function(instance) {
function setRvoPeriod() {
if (!SETTINGS.daily_report) return;
const ts = Date.now();
previous_rvo_is_on_value = rvo_is_on;
@ -2489,11 +2560,11 @@ exports.install = function(instance) {
// v relaysData je contactor bud 0 alebo 1, ale z platformy prichadza true, false;
if (value == false) {
turnLine("off", line, "command received from platform");
reportToSend["contactor"]["off"].push({ [line]: Date.now(), maintenance_mode: SETTINGS.maintenance_mode });
if (line != 0) reportToSend["contactor"]["off"].push({ [line]: Date.now(), maintenance_mode: SETTINGS.maintenance_mode });
}
else {
turnLine("on", line, "command received from platform");
reportToSend["contactor"]["on"].push({ [line]: Date.now(), maintenance_mode: SETTINGS.maintenance_mode });
if (line != 0) reportToSend["contactor"]["on"].push({ [line]: Date.now(), maintenance_mode: SETTINGS.maintenance_mode });
}
}
}

View file

@ -49,8 +49,10 @@ exports.install = async function(instance) {
if (dbs.nodesData.hasOwnProperty("0")) delete dbs.nodesData["0"];
Object.keys(dbs.nodesData).forEach(node => dbs.nodesData[node].readout = {})
let rvo_number = responseSettings[0]["rvo_name"].match(/\D+(\d{1,2})_/)[1];
dbs.settings = {
edge_fw_version: "2025-08-08", //rok-mesiac-den
edge_fw_version: "2025-10-08", //rok-mesiac-den
language: responseSettings[0]["lang"],
rvo_name: responseSettings[0]["rvo_name"],
project_id: responseSettings[0]["project_id"],
@ -72,6 +74,8 @@ exports.install = async function(instance) {
phases: responseSettings[0]["phases"],
cloud_topic: responseSettings[0]["cloud_topic"],
has_main_switch: responseSettings[0]["has_main_switch"],
daily_report: responseSettings[0]["daily_report"],
rvo_number: rvo_number,
//dynamic values
masterNodeIsResponding: true, //cmd_manager
@ -79,7 +83,6 @@ exports.install = async function(instance) {
}
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');

View file

@ -449,8 +449,8 @@
"color": "gray"
},
"options": {
"data": "{line: 3, command: \"turnOff\", force: true}",
"datatype": "object"
"datatype": "object",
"data": "{line: 3, command: \"turnOff\", force: true}"
},
"color": "#F6BB42",
"notes": ""
@ -501,8 +501,8 @@
"color": "gray"
},
"options": {
"datatype": "string",
"data": "profile_nodes"
"data": "profile_nodes",
"datatype": "string"
},
"color": "#F6BB42",
"notes": ""
@ -712,8 +712,8 @@
"color": "gray"
},
"options": {
"datatype": "object",
"data": "{line: 1, command: \"turnOn\", force: true}"
"data": "{line: 1, command: \"turnOn\", force: true}",
"datatype": "object"
},
"color": "#F6BB42",
"notes": ""
@ -852,8 +852,8 @@
"color": "gray"
},
"options": {
"data": "{command: \"turnOnAlarm\"}",
"datatype": "object"
"datatype": "object",
"data": "{command: \"turnOnAlarm\"}"
},
"color": "#F6BB42",
"notes": ""
@ -882,8 +882,8 @@
"color": "gray"
},
"options": {
"data": "{command: \"turnOffAlarm\"}",
"datatype": "object"
"datatype": "object",
"data": "{command: \"turnOffAlarm\"}"
},
"color": "#F6BB42",
"notes": ""
@ -1019,7 +1019,7 @@
"output": []
},
"state": {
"text": "858.06 MB / 985.68 MB",
"text": "858.88 MB / 985.68 MB",
"color": "gray"
},
"options": {
@ -1049,7 +1049,7 @@
"output": []
},
"state": {
"text": "5.69 GB / 7.26 GB",
"text": "5.78 GB / 7.26 GB",
"color": "gray"
},
"options": {
@ -1162,9 +1162,9 @@
"color": "gray"
},
"options": {
"stringify": "json",
"url": "http://192.168.252.2:8004/sentmessage",
"method": "POST",
"url": "http://192.168.252.2:8004/sentmessage"
"stringify": "json"
},
"color": "#5D9CEC",
"notes": ""
@ -1505,7 +1505,7 @@
"output": []
},
"state": {
"text": "13% / 74.17 MB",
"text": "49.2% / 69.91 MB",
"color": "gray"
},
"options": {
@ -1972,8 +1972,8 @@
"component": "virtualwirein",
"tab": "1612772287426",
"name": "tb-push",
"x": 77.75,
"y": 1630,
"x": 88.75,
"y": 1685,
"connections": {
"0": [
{
@ -2028,7 +2028,7 @@
"tag_on_include": "[{\"user_id\":\"U072JE5JUQG\", \"includes\":[\"Electrometer\", \"Twilight sensor\"]}]",
"message_includes": "[\"is responding again\", \"Flow has been restarted\", \"Node db has changed\"]",
"types": "[\"emergency\", \"critical\", \"error\", \"alert\"]",
"name": "rvo_senica_15_ip114"
"name": ""
},
"color": "#30E193",
"notes": ""
@ -2057,9 +2057,9 @@
"color": "gray"
},
"options": {
"stringify": "json",
"url": "http://192.168.252.2:8004/slack",
"method": "POST",
"url": "http://192.168.252.2:8004/slack"
"stringify": "json"
},
"color": "#5D9CEC",
"notes": ""
@ -2095,8 +2095,8 @@
"component": "trigger",
"tab": "1612772287426",
"name": "Trigger",
"x": 79,
"y": 1723,
"x": 87,
"y": 1591,
"connections": {
"0": [
{
@ -2114,8 +2114,8 @@
"color": "gray"
},
"options": {
"datatype": "object",
"data": "{ \"g9OxBZ5KRwNznlY6pAppqEAWXvjdEL4eGQobMDy2\": [ { \"ts\": 1716289039281, \"values\": { \"_event\": { \"type\": \"alert\", \"status\": \"new\", \"source\": { \"func\": \"CMD Manager: process cmd\", \"component\": \"1619515097737\", \"component_name\": \"CMD Manager\", \"edge\": \"g9OxBZ5KRwNznlY6pAppqEAWXvjdEL4eGQobMDy2\" }, \"message\": \"NOW CONNECTED TO SLACK !\", \"message_data\": \"\" } } } ] }"
"data": "{ \"g9OxBZ5KRwNznlY6pAppqEAWXvjdEL4eGQobMDy2\": [ { \"ts\": 1716289039281, \"values\": { \"_event\": { \"type\": \"alert\", \"status\": \"new\", \"source\": { \"func\": \"CMD Manager: process cmd\", \"component\": \"1619515097737\", \"component_name\": \"CMD Manager\", \"edge\": \"g9OxBZ5KRwNznlY6pAppqEAWXvjdEL4eGQobMDy2\" }, \"message\": \"NOW CONNECTED TO SLACK !\", \"message_data\": \"\" } } } ] }",
"datatype": "object"
},
"color": "#F6BB42",
"notes": ""
@ -2677,8 +2677,8 @@
"component": "nodesdb_change_check",
"tab": "1612772287426",
"name": "Nodes DB change check",
"x": 263.8833312988281,
"y": 1993.2333984375,
"x": 266.8833312988281,
"y": 1980.2333984375,
"connections": {
"0": [
{
@ -2689,6 +2689,16 @@
"index": "0",
"id": "1732700642917"
}
],
"1": [
{
"index": "0",
"id": "1759958914348"
},
{
"index": "0",
"id": "1759959003114"
}
]
},
"disabledio": {
@ -2708,8 +2718,8 @@
"component": "virtualwirein",
"tab": "1612772287426",
"name": "db-init",
"x": 84.75,
"y": 1994,
"x": 76.75,
"y": 1988,
"connections": {
"0": [
{
@ -2737,8 +2747,8 @@
"component": "debug",
"tab": "1612772287426",
"name": "nodesChange",
"x": 561.8833312988281,
"y": 2055.2333984375,
"x": 571.8833312988281,
"y": 2018.2333984375,
"connections": {},
"disabledio": {
"input": [],
@ -2761,8 +2771,8 @@
"component": "virtualwireout",
"tab": "1612772287426",
"name": "tb-push",
"x": 557.8833312988281,
"y": 1949,
"x": 567.8833312988281,
"y": 1933,
"connections": {},
"disabledio": {
"input": [],
@ -2924,9 +2934,9 @@
"color": "gray"
},
"options": {
"url": "http://192.168.252.2:8015/daily_report",
"stringify": "json",
"method": "POST",
"stringify": "json"
"url": "http://192.168.252.2:8015/daily_report"
},
"color": "#5D9CEC",
"notes": ""
@ -2978,6 +2988,114 @@
},
"color": "#967ADC",
"notes": ""
},
{
"id": "1759958914348",
"component": "httprequest",
"tab": "1612772287426",
"name": "http://192.168.252.2:8015/node_numbers",
"x": 571,
"y": 2111,
"connections": {
"0": [
{
"index": "0",
"id": "1759958927094"
}
]
},
"disabledio": {
"input": [],
"output": []
},
"state": {
"text": "",
"color": "gray"
},
"options": {
"stringify": "json",
"method": "POST",
"url": "http://192.168.252.2:8015/node_numbers"
},
"color": "#5D9CEC",
"notes": ""
},
{
"id": "1759958927094",
"component": "debug",
"tab": "1612772287426",
"name": "nodesChangeCloud",
"x": 951.8833312988281,
"y": 2109.2333984375,
"connections": {},
"disabledio": {
"input": [],
"output": []
},
"state": {
"text": "Enabled",
"color": "gray"
},
"options": {
"type": "data",
"repository": false,
"enabled": true
},
"color": "#967ADC",
"notes": ""
},
{
"id": "1759959003114",
"component": "debug",
"tab": "1612772287426",
"name": "nodeChangeCloud1",
"x": 574,
"y": 2218,
"connections": {},
"disabledio": {
"input": [],
"output": []
},
"state": {
"text": "Enabled",
"color": "gray"
},
"options": {
"type": "data",
"repository": false,
"enabled": true
},
"color": "#967ADC",
"notes": ""
},
{
"id": "1760020290879",
"component": "virtualwirein",
"tab": "1612772287426",
"name": "db-init",
"x": 84.75,
"y": 1787,
"connections": {
"0": [
{
"index": "1",
"id": "1718016052341"
}
]
},
"disabledio": {
"input": [],
"output": []
},
"state": {
"text": "db-init",
"color": "gray"
},
"options": {
"wirename": "db-init"
},
"color": "#303E4D",
"notes": ""
}
],
"version": 615

View file

@ -38,7 +38,7 @@ exports.install = function(instance) {
}
}
if(!areEqual) {
if (!areEqual) {
message += `Aktualne nody: ${zmenene.toString()}. Zmenene proti originalu: ${Array.from(set2).join(' ')}`;
sendNotification("Nodesdb_changecheck", FLOW.GLOBALS.settings.rvoTbName, "nodes_db_changed", "", message, 0, instance);
}
@ -53,17 +53,17 @@ exports.install = function(instance) {
let nodesData = FLOW.GLOBALS.nodesData;
// we check if nodes.table has changed compared to nodes_original.table (we have array of nodes e.g. [{node:255, tbname: "agruhuwhgursuhgo34hgsdiguhrr"}]
const nodes_actual = Object.keys(nodesData).map(node => ({[node]: nodesData[node].tbname}))
const nodes_actual = Object.keys(nodesData).map(node => ({ [node]: nodesData[node].tbname }))
let nodes_original = fs.readFileSync(nodesOriginalFile, { encoding: 'utf8', flag: 'r' });
try {
nodes_original = JSON.parse(nodes_original);
} catch(e) {
} catch (e) {
console.log(e)
}
setTimeout(() => compareArrays(nodes_actual, nodes_original),10000);
setTimeout(() => compareArrays(nodes_actual, nodes_original), 10000);
})
}

View file

@ -2,7 +2,7 @@ exports.id = 'slack_filter';
exports.title = 'Slack Filter';
exports.group = 'Citysys';
exports.color = '#30E193';
exports.input = 1;
exports.input = 2;
exports.output = 1;
exports.author = 'Jakub Klena';
exports.icon = 'plug';
@ -33,7 +33,7 @@ exports.install = function(instance) {
instance["savedSlackMessages"] = [];
var timer = null;
instance.on('data', function(response) {
instance.on('0', function(response) {
if (!running) return;
let value = response.data;
if (typeof value !== 'object') return;
@ -181,6 +181,8 @@ exports.install = function(instance) {
};
instance.on('options', instance.reconfigure);
setTimeout(instance.reconfigure, 10000);
instance.on("1", _ => {
instance.reconfigure();
})
};

View file

@ -1,10 +1,18 @@
# Total.js version 4 - Flow
This is a version of citysys-flowserver, running on Debian12 OS.
It has the latest LTS version of nodejs and latest npm modules installed.
# Total.js version 3 - Flow
- open terminal / command-line
- open app directory
- run `$ npm install`
- run `$ node index.js`
- open browser `http://0.0.0.0:12345`
# Settings configuration:
- set in databases directory
- chosen options:
- restore_from_backup => number of records, that are restored from db after thingsboard reconnected
- restore_backup_wait => number of seconds between bulk of records are sent
- node_status_nok_time => number of hours after which the node sends a message to thingsboard that its status is NOK
- phases => number of phases in RVO
- cloud_topic => we need to set cloud_topic, if we want to send telemetry to RADO's thingsboard
- daily_report => if we want to create and send daily reports by email, set this to 1
- send_changed_node_numbers => if we are sending daily reports, we need to keep a track of changed node numbers. They are processed on the cloud. Set this to 1 if yes.