diff --git a/config b/config index 9472cb0..d247301 100644 --- a/config +++ b/config @@ -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 diff --git a/createNode.py b/createNode.py index 21406cc..a8cea91 100644 --- a/createNode.py +++ b/createNode.py @@ -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)) diff --git a/databases/settings.table b/databases/settings.table index 5abd2b1..070dcf5 100644 --- a/databases/settings.table +++ b/databases/settings.table @@ -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|........................................... diff --git a/flow/cloudmqttconnect.js b/flow/cloudmqttconnect.js index d619784..b03491f 100644 --- a/flow/cloudmqttconnect.js +++ b/flow/cloudmqttconnect.js @@ -63,36 +63,39 @@ 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, - port: o.port, - clientId: o.clientid, - username: o.username, - rejectUnauthorized: false, - resubscribe: false + host: o.host, + port: o.port, + clientId: o.clientid, + username: o.username, + rejectUnauthorized: false, + 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); @@ -121,30 +124,30 @@ exports.install = function(instance) { TRY(function() { 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}}); + 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)); } - 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) { - console.log('MQTT client error', err); - sendClientError = false; + 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; } clientReady = false; }); @@ -154,25 +157,21 @@ exports.install = function(instance) { instance.on('0', function(data) { - if(clientReady) - { - //do we have some data in backup file? if any, process data from database - if(saveTelemetryOnError) - { + if (clientReady) { + //do we have some data in backup file? if any, process data from database + 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}); - } - 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) - { + let stringifiedJson = JSON.stringify(data.data) + client.publish(`${o.topic}_forward`, stringifiedJson, { qos: 1 }); + } + 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 && o.topic) { //create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql makeBackupFromDbFile(); @@ -188,71 +187,62 @@ 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; } } } } - + } - - 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"); @@ -279,21 +268,20 @@ exports.install = function(instance) { //clear tbdata.nosql fs.writeFileSync(source, ""); fs.truncateSync(source, 0); - + } } 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,59 +295,54 @@ 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); //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); } } - + } - 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(); diff --git a/flow/cmd_manager.js b/flow/cmd_manager.js index c553e4c..661102b 100644 --- a/flow/cmd_manager.js +++ b/flow/cmd_manager.js @@ -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)) { - for (const [key, value] of Object.entries(dailyReport)) { + if (["name", "time", "dusk_and_dawn"].includes(key)) continue; - 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 { - } 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,36 +1752,70 @@ exports.install = function(instance) { 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 (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) { - if (rvo_is_on === true) { + let now = Date.now(); - if ("nodeIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].nodeIsOnButShouldBeOffTime; + if (rvo_is_on === true) { + + if ("dimmingIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].dimmingIsOnButShouldBeOffTime; + + if (values.dimming > 0) { + if ("dimmingIsZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingIsZeroTime; + dailyReport[node] = { ...dailyReport[node], initialTs: now }; + } else { + if (!("dimmingIsZeroTime" in dailyReport[node])) dailyReport[node] = { ...dailyReport[node], dimmingIsZeroTime: now, initialTs: now }; + else dailyReport[node] = { ...dailyReport[node], initialTs: now }; + } - 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 }; + + if ("dimmingIsZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingIsZeroTime; + + 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 ("dimmingIsOnButShouldBeOffTime" in dailyReport[node]) delete dailyReport[node].dimmingIsOnButShouldBeOffTime; + dailyReport[node] = { ...dailyReport[node], initialTs: now }; + } + } + } - } else { + if (register === 76) { - if ("dimmingAndPowerAreZeroTime" in dailyReport[node]) delete dailyReport[node].dimmingAndPowerAreZeroTime; + 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 }; + } - 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 }; - } + 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 }; + } + + } } } @@ -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 }); } } } diff --git a/flow/db_init.js b/flow/db_init.js index a759465..30e2a3c 100644 --- a/flow/db_init.js +++ b/flow/db_init.js @@ -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'); diff --git a/flow/designer.json b/flow/designer.json index d757f87..580f5a3 100644 --- a/flow/designer.json +++ b/flow/designer.json @@ -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 diff --git a/flow/nodesdb_changecheck.js b/flow/nodesdb_changecheck.js index be1e700..195623f 100644 --- a/flow/nodesdb_changecheck.js +++ b/flow/nodesdb_changecheck.js @@ -16,55 +16,55 @@ const nodesOriginalFile = path.join(__dirname, '../databases/nodes_original/', ' exports.install = function(instance) { - function compareArrays(array1, array2) { - let message = ""; - let areEqual = true; - let zmenene = [] + function compareArrays(array1, array2) { + let message = ""; + let areEqual = true; + let zmenene = [] - if (array1.length !== array2.length) { - message += "Nezhoda v pocte nodov. " + if (array1.length !== array2.length) { + message += "Nezhoda v pocte nodov. " + } + + const set1 = new Set(array1.map(obj => JSON.stringify(obj))); + const set2 = new Set(array2.map(obj => JSON.stringify(obj))); + + for (const objStr of set1) { + + if (!set2.has(objStr)) { + zmenene.push(objStr) + areEqual = false; + } else { + set2.delete(objStr); } + } - const set1 = new Set(array1.map(obj => JSON.stringify(obj))); - const set2 = new Set(array2.map(obj => JSON.stringify(obj))); + 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); + } + else console.log("Arrays are equal."); - for (const objStr of set1) { + console.log(message) + } - if (!set2.has(objStr)) { - zmenene.push(objStr) - areEqual = false; - } else { - set2.delete(objStr); - } - } - 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); - } - else console.log("Arrays are equal."); + instance.on("data", _ => { - console.log(message) + 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 })) + let nodes_original = fs.readFileSync(nodesOriginalFile, { encoding: 'utf8', flag: 'r' }); + + try { + nodes_original = JSON.parse(nodes_original); + } catch (e) { + console.log(e) } - instance.on("data", _ => { - - 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})) - let nodes_original = fs.readFileSync(nodesOriginalFile, { encoding: 'utf8', flag: 'r' }); - - try { - nodes_original = JSON.parse(nodes_original); - } catch(e) { - console.log(e) - } - - - setTimeout(() => compareArrays(nodes_actual, nodes_original),10000); - }) + setTimeout(() => compareArrays(nodes_actual, nodes_original), 10000); + }) } diff --git a/flow/slack_filter.js b/flow/slack_filter.js index 6b2d2a5..6618523 100644 --- a/flow/slack_filter.js +++ b/flow/slack_filter.js @@ -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(); + }) }; diff --git a/readme.md b/readme.md index 00a2146..1e8fac4 100644 --- a/readme.md +++ b/readme.md @@ -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.