Daily reports - dimming and power combined results

This commit is contained in:
rasta5man 2025-09-27 23:53:39 +02:00
parent cf16481324
commit 7093d765ec
5 changed files with 433 additions and 86 deletions

View file

@ -3,7 +3,7 @@ exports.title = 'CMD Manager';
exports.group = 'Worksys';
exports.color = '#5D9CEC';
exports.version = '0.0.3';
exports.output = ['red', 'blue', 'yellow', 'blue', 'white'];
exports.output = ['red', 'blue', 'yellow', 'blue', 'white', 'green'];
exports.input = 3;
exports.icon = 'cloud-upload';
@ -34,7 +34,7 @@ exports.install = function(instance) {
const { exec } = require('child_process');
const { crc16 } = require('easy-crc');
const { runSyncExec, writeData } = require('./helper/serialport_helper');
const { bytesToInt, longToByteArray, addZeroBefore } = require('./helper/utils');
const { bytesToInt, longToByteArray, addZeroBefore, isEmptyObject, emptyJsObject, writeToFile, addToArrayIfUnique } = require('./helper/utils');
const bitwise = require('bitwise');
var SunCalc = require('./helper/suncalc');
@ -65,7 +65,8 @@ exports.install = function(instance) {
tb: 1,
http_response: 2,
dido_controller: 3,
infoSender: 4
infoSender: 4,
dailyReport: 5
}
const PRIORITY_TYPES = {
@ -139,12 +140,13 @@ exports.install = function(instance) {
let relaysData;
let nodesData;
// holds dusk, dawn (and more) values
let sunCalcResult;
let reportDuskDawn;
//helper container for counting resolved group of commands (commands related to set profile)
let cmdCounter = {};//key is node, value is counter
//TODO: is this set working?? According to previous checks, sets do not work
//if sending of profile to node fails, we send notification and push node into set, so we do not send notification twice
const nodeProfileSendFail = new Set();
@ -152,6 +154,22 @@ exports.install = function(instance) {
let temperatureInSenica = null;
let accelerometerInterval = null;
//NOTE: daily reports senica:
//rvo_is_on status, we monitor all nodes - if true, power and dimming should be not 0, if false, 24/7 nodes should be 0, other rvo should not respond
let rvo_is_on;
let previous_rvo_is_on_value;
let handleDailyReport; //setInterval for dailyReportHandler function
let dailyReport = {}; //data needed for daily report
let reportToSend = {}; //final report which we send to cloud
let breakerCounter = null; // status of breakers after flow starts is not in report
let rvoPeriod; // interval of checking dusk dawn and if rvo_is_on
let initialReportStatus = false;
const DAILY_REPORT_HANDLER_TIME = 3_600_000;
const SET_SUNCALC_RESULT_TIME = 3_600_000;
const SET_RVO_PERIOD_TIME = 300_000;
const ADD_NODE_TO_REPORT_TIME = 3_600_000;
//END OF VARIABLE SETTINGS
//--------------------------------
@ -172,24 +190,24 @@ exports.install = function(instance) {
sunCalcResult = calculateDuskDawn();
reportDuskDawn = {
dusk_time: sunCalcResult.dusk_time,
dawn_time: sunCalcResult.dawn_time,
dusk_time_reported: undefined,
dawn_time_reported: undefined
};
handleRsPort();
customTasksInterval = setInterval(function() {
reportEdgeDateTimeAndNumberOfLuminaires();
}, 120000);
reportEdgeDateTimeAndNumberOfLuminaires();
customTasksInterval = setInterval(reportEdgeDateTimeAndNumberOfLuminaires, 120_000);
setTimeout(reportEdgeDateTimeAndNumberOfLuminaires, 4000);
//dailyReport related
emptyReportToSend();
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);
setCorrectTime = setInterval(setCorrectPlcTimeOnceADay, 60000 * 60); // 1 hour
setCorrectPlcTimeOnceADay();
sendNodeReadout = setInterval(sendNodesData, 150000);
sendNodeReadout = setInterval(sendNodesData, 150_000);
accelerometerInterval = setInterval(accelerometerData, 60000 * 30); //30 min
}
@ -792,6 +810,10 @@ exports.install = function(instance) {
info: info
};
//to assure, handleDailyReportFunction do not run before rvo_is_on variable changes. So after every line switch we reset the timer
clearInterval(handleDailyReport);
handleDailyReport = setInterval(dailyReportHandler, DAILY_REPORT_HANDLER_TIME);
//logger.debug("linia", line, obj);
instance.send(SEND_TO.dido_controller, obj);
}
@ -863,11 +885,10 @@ exports.install = function(instance) {
logger.debug("-->buildTasks clear tasks");
}
else {
processLineProfiles = false;
processLineProfiles = params.processLineProfiles;
processBroadcast = false;
processNodes = false;
processLineProfiles = params.processLineProfiles;
processLine = params.line;
}
@ -920,6 +941,13 @@ exports.install = function(instance) {
// if astro clock true, we remove all regular profile points
time_points = [];
//TODO: when astro clock == true, is offset calculated ???
//let duskAstroClockOffset = profile.dusk_astro_clock_offset;
//let dawnAstroClockOffset = profile.dawn_astro_clock_offset;
//console.log("line astro clock offsets dusk a dawn: ", duskAstroClockOffset, dawnAstroClockOffset);
// maybe add dusk dawn offset to calculateDuskDawn ?? what dusk dawn will be if dawn_lux_sensor == true??
//let sunCalcResult = calculateDuskDawn(new Date(), line, duskAstroClockOffset, dawnAstroClockOffset);
let sunCalcResult = calculateDuskDawn(new Date(), line);
// adding dusk dawn to timpoints
@ -1053,7 +1081,7 @@ exports.install = function(instance) {
let d = new Date();
let time = d.getTime();
let sunCalcResult = calculateDuskDawn();
//let sunCalcResult = calculateDuskDawn();
{
let params = getParams();
@ -1310,49 +1338,154 @@ exports.install = function(instance) {
}
function processDailyReport() {
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;
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);
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;
}
}
}
} 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;
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);
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;
}
}
}
}
}
let report = {};
report["name"] = SETTINGS.rvo_name;
report["time"] = new Date();
report["report"] = reportToSend;
writeToFile(F.path.root("report_data.log"), report, true);
instance.send(SEND_TO.dailyReport, report);
}
function dailyReportHandler() {
// after dawn we empty reportToSend and start to get data for a new day
const date = new Date();
const hour = date.getHours();
const minute = date.getMinutes();
console.log("now: ", hour, minute, "suncalc: ", sunCalcResult.dawn_hours, sunCalcResult.dawn_minutes);
if (hour === sunCalcResult.dawn_hours) {
//NOTE: ak je este pred usvitom, spracujeme report a posleme na cloud, ak uz je po usvite, resetujeme report plnime data noveho dna
if (minute >= sunCalcResult.dawn_minutes - 2) {
emptyReportToSend();
emptyDailyReport();
console.log("Podmienka 1 v dailyReport handler");
} else {
processDailyReport();
emptyReportToSend();
emptyDailyReport();
console.log("Podmienka 2 v dailyReport handler");
}
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.
} else if (hour === sunCalcResult.dawn_hours + 1 || hour === sunCalcResult.dawn_hours + 2) {
if (!initialReportStatus) {
emptyReportToSend();
emptyDailyReport();
initialReportStatus = true;
console.log("Podmienka 3 v dailyReport handler");
} else {
processDailyReport();
initialReportStatus = true;
console.log("Podmienka 4 v dailyReport handler");
}
} else {
processDailyReport();
initialReportStatus = false;
console.log("Podmienka 5 v dailyReport handler");
}
console.log("initialReportStatus: ", initialReportStatus);
}
function emptyReportToSend() {
emptyJsObject(reportToSend);
reportToSend["contactor"] = { off: [], on: [] };
reportToSend["night_no_data"] = [];
reportToSend["night_dimming=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);
console.log(`resetuje sa reportToSend`);
}
//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() {
const ts = Date.now();
emptyJsObject(dailyReport);
//NOTE: do dailyReport dame vsetky nody a pravidelne kontrolujeme ci maju data
//ked nastane sumrak/usvit a svetla maju zacat/prestat svietit, dailyReport spravime nanovo:
for (const [key, value] of Object.entries(nodesData)) {
dailyReport[value.node] = { line: value.line, initialTs: ts };
}
console.log("dailyReport: ", dailyReport);
}
async function runTasks() {
clearInterval(interval);
let currentTimestamp = Date.now();
//report dusk, dawn---------------------------------
if (reportDuskDawn.dusk_time < currentTimestamp) {
//vyreportuj iba ak nie je velky rozdiel napr. 60 sekund
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);
reportDuskDawn.dusk_time_reported = sunCalcResult.dusk_time;
}
}
var nextDay = new Date();
nextDay.setDate(nextDay.getDate() + 1);
sunCalcResult = calculateDuskDawn(nextDay);
reportDuskDawn.dusk_time = sunCalcResult.dusk_time;
}
if (reportDuskDawn.dawn_time < currentTimestamp) {
//vyreportuj iba ak nie je velky rozdiel napr. 60 sekund
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);
reportDuskDawn.dawn_time_reported = sunCalcResult.dawn_time;
}
}
var nextDay = new Date();
nextDay.setDate(nextDay.getDate() + 1);
sunCalcResult = calculateDuskDawn(nextDay);
reportDuskDawn.dawn_time = sunCalcResult.dawn_time;
}
//--------------------------------------------------------
//sort tasks based on timestamp
tasks.sort(function(a, b) {
if (a.timestamp <= currentTimestamp && b.timestamp <= currentTimestamp) {
@ -1409,6 +1542,7 @@ exports.install = function(instance) {
}
//kontrola nespracovanych profilov nodov
//TODO: co ked niektore nody neprijimaju profily? kazdu hodinu zahltuju "tasks" array
if (type == "process_profiles") {
//na vsetky zapnutych liniach sa spracuju nespracovane profily nodov
loadRelaysData();
@ -1426,7 +1560,8 @@ exports.install = function(instance) {
date.setDate(date.getDate() + 1);//next day
let sunCalcResult;
if (timePointName) sunCalcResult = calculateDuskDawn(date, params.line);
if (timePointName) { sunCalcResult = calculateDuskDawn(date, params.line); console.log("typee-rellay: ", sunCalcResult.dawn_time, sunCalcResult.dusk_time); }
if (timePointName == "dawn") {
tasks[0].timestamp = sunCalcResult.dawn_time;
@ -1497,7 +1632,6 @@ exports.install = function(instance) {
if (register == 6 && params.recipient === 2) {
if (type != "cmd-terminal") {
let sunCalcResult = calculateDuskDawn();
params.byte1 = sunCalcResult["dusk_hours"];//h
params.byte2 = sunCalcResult["dusk_minutes"];//m
}
@ -1506,7 +1640,6 @@ exports.install = function(instance) {
//Time of dawn
if (register == 7 && params.recipient === 2) {
if (type != "cmd-terminal") {
let sunCalcResult = calculateDuskDawn();
params.byte1 = sunCalcResult["dawn_hours"];//h
params.byte2 = sunCalcResult["dawn_minutes"];//m
}
@ -1583,6 +1716,40 @@ exports.install = function(instance) {
values.comm_status = "OK";
values.status = "OK";
nodesData[node].readout = { ...nodesData[node].readout, ...values };
//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) {
let now = Date.now();
if (rvo_is_on === true) {
if ("nodeIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].nodeIsOnButShouldBeOffTime;
if (values.dimming > 0 || values.power > 0) {
if ("dimmingAndPowerAreZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingAndPowerAreZeroTime;
dailyReport[node] = { ...dailyReport[node], initialTs: now };
} else {
if (!("dimmingAndPowerAreZeroTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], dimmingAndPowerAreZeroTime: now, initialTs: now };
else dailyReport[node] = { ...dailyReport[node], initialTs: now };
}
} else {
if ("dimmingAndPowerAreZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingAndPowerAreZeroTime;
if (values.dimming > 0 || values.power > 0) {
if (!("nodeIsOnButShouldBeOffTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], nodeIsOnButShouldBeOffTime: now, initialTs: now };
else dailyReport[node] = { ...dailyReport[node], initialTs: now };
} else {
if ("nodeIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].nodeIsOnButShouldBeOffTime;
dailyReport[node] = { ...dailyReport[node], initialTs: now };
}
}
}
}
//master node
@ -1811,6 +1978,36 @@ exports.install = function(instance) {
}
function setSunCalcResult() {
//if next day, we get new dusk and dawn. To make sure, in local timezone 2:00 means 00:00 in utc (it means next day)
if (new Date().getHours() === 2) {
sunCalcResult = calculateDuskDawn();
console.log("Novy suncalc: ", sunCalcResult);
}
}
function setRvoPeriod() {
const ts = Date.now();
previous_rvo_is_on_value = rvo_is_on;
if (ts < sunCalcResult.dawn_time - 1_800_000) { // nodes should be on (00:00 -> 06:00 ( - pol hodina, lebo rvo vypina skor)
rvo_is_on = true;
} else if (sunCalcResult.dawn_time - 1_800_000 < ts && ts < sunCalcResult.dusk_time + 1_800_000) { // nodes should be off 06:00 -> 21:00
rvo_is_on = false;
} else if (sunCalcResult.dusk_time + 1_800_000 < ts) { // nodes should be on 21:00 -> 00:00
rvo_is_on = true;
}
if (previous_rvo_is_on_value !== rvo_is_on) {
emptyDailyReport();
console.log("rvo_is: ", rvo_is_on, previous_rvo_is_on_value);
}
}
function handleRsPort() {
if (rsPort) {
@ -1868,6 +2065,11 @@ exports.install = function(instance) {
main();
})
instance.on("2", _ => {
console.log("dailyReport, reportToSend, rvo_is_on", dailyReport, reportToSend, rvo_is_on, previous_rvo_is_on_value);
})
instance.on("1", async function(flowdata) {
//instance.send(SEND_TO.debug, "on Data");
@ -1987,6 +2189,9 @@ exports.install = function(instance) {
}
else if (cmd == "state_of_breaker") {
//istic linie
breakerCounter--;
let value = flowdata.data.value;
let line = parseInt(flowdata.data.line);
@ -2003,8 +2208,14 @@ 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-mngr: onData", tbname, "circuit_breaker_was_turned_off_line", { line: line }, "", SEND_TO.tb, instance, "circuit_breaker");
if (breakerCounter < 0) reportToSend["contactor"]["off"].push({ [line]: Date.now(), maintenance_mode: SETTINGS.maintenance_mode });
}
else {
sendNotification("Cmd-mngr: onData", tbname, "circuit_breaker_was_turned_on_line", { line: line }, "", SEND_TO.tb, instance, "circuit_breaker");
if (breakerCounter < 0) reportToSend["contactor"]["on"].push({ [line]: Date.now(), maintenance_mode: SETTINGS.maintenance_mode });
}
//report status liniu
sendTelemetry({ status: status }, tbname)
@ -2276,8 +2487,14 @@ exports.install = function(instance) {
if (isObject(relayObject)) line = relayObject.line;
// v relaysData je contactor bud 0 alebo 1, ale z platformy prichadza true, false;
if (value == false) turnLine("off", line, "command received from platform");
else turnLine("on", line, "command received from platform");
if (value == false) {
turnLine("off", line, "command received from platform");
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 });
}
}
}
else {
@ -2472,13 +2689,11 @@ exports.install = function(instance) {
tbHandler.sendToTb(dataToTb, instance);
}
function calculateDuskDawn(date, line, duskOffset = 0, dawnOffset = 0) {
if (date === undefined) date = new Date();
//if(duskOffset === undefined) duskOffset = 0;
//if(dawnOffset === undefined) dawnOffset = 0;
//let line = keys[i];
let profilestr = "";
if (relaysData[line] != undefined) profilestr = relaysData[line].profile;
@ -2492,7 +2707,6 @@ exports.install = function(instance) {
//http://suncalc.net/#/48.5598,18.169,11/2021.04.07/11:06
//https://mapa.zoznam.sk/zisti-gps-suradnice-m6
let dusk_astro_clock_offset = duskOffset;//minutes
let dawn_astro_clock_offset = dawnOffset;//minutes
@ -2520,9 +2734,6 @@ exports.install = function(instance) {
}
//dusk - súmrak
//down, sunrise - svitanie
} catch (error) {
if (profilestr != "") {
logger.debug(profilestr);
@ -2741,10 +2952,12 @@ exports.install = function(instance) {
}
function getObjectByTbValue(object, tbname) {
return object[Object.keys(object).find(key => object[key].tbname === tbname)];
}
function isObject(item) {
return (typeof item === "object" && !Array.isArray(item) && item !== null);
}