diff --git a/databases/notifications.table b/databases/notifications.table index a0590d3..77f9e7a 100644 --- a/databases/notifications.table +++ b/databases/notifications.table @@ -20,9 +20,9 @@ key:string|weight:string|sk:string|en:string +|power_supply_works_correctly|NOTICE|Napájací zdroj pracuje správne|Power supply works correctly|............... +|battery_level_is_low|ERROR|Batéria má nízku úroveň napätia|Battery level is low|............... +|battery_level_is_ok|NOTICE|Batéria má správnu úroveň napätia|Battery level is OK|............... -+|door_has_been_open|NOTICE|Dvere boli otvorené|Door has been open|............... -+|door_has_been_closed|NOTICE|Dvere boli zatvorené|Door has been closed|............... -+|door_has_been_open_without_permision_alarm_is_on|WARNING|Dvere boli otvorené bez povolania - zapnutá siréna|Door has been open without permision - alarm is on|............... ++|door_opened|NOTICE|Dvere boli otvorené|Door has been opeed|............... ++|door_closed|NOTICE|Dvere boli zatvorené|Door has been closed|............... ++|door_opened_without_permission|WARNING|Dvere boli otvorené bez povoeania - zapnutá siréna|Door has been oed without permision - alarm is on|............... +|state_of_contactor_for_line|INFORMATIONAL|Stav stýkača pre líniu č. ${line} je ${value}|State of contactor for line no. ${line} is ${value}|............... +|local_database_is_corrupted|CRITICAL|||............... +|electrometer_nok|ERROR|Elektromer neodpovedá|Electrometer is not responding|............... @@ -34,4 +34,4 @@ key:string|weight:string|sk:string|en:string +|twilight_sensor_ok|NOTICE|Sensor súmraku znovu odpovedá|Twilight sensor is responding again|............... +|lamps_have_turned_on|NOTICE|Lampy sa zapli|Lamps have turned on|............... +|lamps_have_turned_off|NOTICE|Lampy sa vypli|Lamps have turned off|............... -+|flow_restart|NOTICE|Restart flowu|Flow has been restarted|............... ++|flow_restart|NOTICE|Restart flowu|Flow has been restarted|............... \ No newline at end of file diff --git a/databases/settings.table b/databases/settings.table index a182df5..98ae003 100644 --- a/databases/settings.table +++ b/databases/settings.table @@ -1,2 +1,2 @@ -rvo_name:string|lang:string|temperature_adress:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|projects_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number -+|rvo_senica_39_10.0.0.132|en|28.427B45920702|48.70826502|17.28455203|192.168.252.1|rvo_senica_39_10.0.0.132|qzSNuCNrLP4OL1v47YEe|1883|0|68|unipi|ttyUSB0|1|20|5|........................................... +rvo_name:string|lang:string|temperature_adress: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 ++|rvo_senica_42_10.0.0.118|en|28.427B45920702|48.70826502|17.28455203|192.168.252.1|rvo_senica_42_10.0.0.118|QMvF7etEvbYMMr8Q6baP|1883|0|59|unipi|ttyUSB0|1|20|5|6|3|.............. diff --git a/databases/tbdatacloud.nosql b/databases/tbdatacloud.nosql new file mode 100644 index 0000000..e69de29 diff --git a/flow/cloudmqttconnect.js b/flow/cloudmqttconnect.js new file mode 100644 index 0000000..79bc04e --- /dev/null +++ b/flow/cloudmqttconnect.js @@ -0,0 +1,404 @@ +exports.id = 'cloudmqttconnect'; +exports.title = 'Cloud connect mqtt'; +exports.group = 'MQTT'; +exports.color = '#888600'; +exports.version = '1.0.2'; +exports.icon = 'sign-out'; +exports.input = 1; +exports.output = 2; +exports.options = { host: 'tb-stage.worksys.io', port: 1883, clientid: "", username: "" }; + +exports.html = `
+
+
+
Hostname or IP address (if not empty - setting will override db setting)
+
+
+
Port
+
+
+
+
+
@(Client id)
+
+
+
topic
+
+
+
`; + + + +const { promisifyBuilder } = require('./helper/db_helper'); +const fs = require('fs'); +const mqtt = require('mqtt'); + +const SEND_TO = { + debug: 0, + rpcCall: 1, +} + +//CONFIG +let saveTelemetryOnError = true;//backup_on_failure overrides this value +//------------------------ + +const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304 +let insertNoSqlCounter = 0; +let insertBackupNoSqlCounter = 0; +let processingData = false; + +let backup_on_failure = true;//== saveTelemetryOnError - create backup client send failure +let restore_from_backup = 50; //how many rows process at once? +let restore_backup_wait = 3;//wait seconds +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; + + +const nosql = NOSQL('tbdatacloud'); + + +exports.install = function(instance) { + + var client; + var opts; + var clientReady = false; + + let o = null; //options + + function main() + { + loadSettings(); + } + + //set opts according to db settings + function loadSettings() + { + + o = instance.options; + opts = { + host: o.host, + port: o.port, + clientId: o.clientid, + username: o.username, + rejectUnauthorized: false, + resubscribe: false + }; + + + console.log("wsmqttpublich -> loadSettings from instance.options", instance.options); + + connectToTbServer(); + } + + function connectToTbServer() + { + var url = "mqtt://" + opts.host + ":" + opts.port; + console.log("MQTT URL: ", url); + + client = mqtt.connect(url, opts); + + client.on('connect', function() { + client.subscribe(`${o.topic}_backward`, (err) => { + if (!err) { + console.log("MQTT subscribed"); + } + }); + instance.status("Connected", "green"); + clientReady = true; + sendClientError = true; + }); + + client.on('reconnect', function() { + client.subscribe(`${o.topic}_backward`, (err) => { + if (!err) { + console.log("MQTT subscribed"); + } + }); + instance.status("Reconnecting", "yellow"); + clientReady = false; + }); + + client.on('message', function(topic, message) { + // message is type of buffer + message = message.toString(); + if (message[0] === '{') { + 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}}); + } + + }, () => instance.debug('MQTT: Error parsing data', message)); + } + + instance.send(SEND_TO.rpcCall, {"topic":o.topic, "content":message }); + }); + + client.on('close', function(err) { + clientReady = false; + + if (err && err.toString().indexOf('Error')) { + instance.status("Err: "+err.code, "red"); + instance.send(SEND_TO.debug, {"message":"Client CLOSE signal received !", "error":err, "opt":opts }); + } else { + instance.status("Disconnected", "red"); + instance.send(SEND_TO.debug, {"message":"Client CLOSE signal received !", "error":err, "opt":opts }); + } + + client.reconnect(); + }); + + 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; + } + clientReady = false; + }); + + } + + + instance.on('0', function(data) { + + 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(clientReady) + { + client.publish(`${o.topic}_forward`, data.data, {qos: 1}); + //console.log("ondata..",data.data) + + + // Pokad ten error na 38 chápem tak tento parameter MUSÍ byt string... + // Tak to musim dekódovat na strane Cloudu zas + + } + 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) + { + try { + let d = JSON.parse(data.data); + //create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql + makeBackupFromDbFile(); + + //write to tb + d.id = UID(); + nosql.insert(d); + } catch(e) { + console.log("cloudconnect - unable to parse data from wsmqtt"); + } + } + + } + }); + + + instance.close = function(done) { + if(clientReady){ + client.end(); + } + }; + + + function getDbBackupFileCounter(type) + { + var files = fs.readdirSync(__dirname + "/../databases"); + + let counter = 0; + for(var i = 0; i < files.length; i++) + { + + if(files[i] == "tbdatacloud.nosql") continue; + + if(files[i].endsWith(".nosql")) + { + + let pos = files[i].indexOf("."); + if(pos > -1) + { + + let fileCounter = counter; + let firstDigit = files[i].slice(0, pos); + + fileCounter = parseInt(firstDigit); + if(isNaN(fileCounter)) fileCounter = 0; + //console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type); + + if(type == "max") + { + if(fileCounter > counter) + { + counter = fileCounter; + } + } + else if(type == "min") + { + if(counter == 0) counter = fileCounter; + + if(fileCounter < counter) + { + counter = fileCounter; + } + } + } + } + + } + + if(type == "max") counter++; + + return counter; + } + + const makeBackupFromDbFile = async () => { + + if(!saveTelemetryOnError) return; + + //to avoid large file: tbdata.nosql + + //init value is 0! + if(insertNoSqlCounter > 0) + { + --insertNoSqlCounter; + return; + } + + insertNoSqlCounter = 100; + + let source = __dirname + "/../databases/tbdatacloud.nosql"; + + var stats = fs.statSync(source); + var fileSizeInBytes = stats.size; + + if(fileSizeInBytes > noSqlFileSizeLimit) + { + + let counter = 1; + counter = getDbBackupFileCounter("max"); + + let destination = __dirname + "/../databases/" + counter + "." + "tbdatacloud.nosql"; + + //make backup file + fs.copyFileSync(source, destination); + //fs.renameSync(p, p + "." + counter); + + //clear tbdata.nosql + fs.writeFileSync(source, ""); + fs.truncateSync(source, 0); + + } + } + + const processDataFromDatabase = async () => { + + 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) + { + //console.log("*********restore_backup_wait", diff, restore_backup_wait); + return; + } + + processingData = true; + + //get filename to process + let counter = getDbBackupFileCounter("min"); + + //we have some backup files + let dataBase = 'tbdata'; + + var nosql; + 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) { + + let item = records[i]; + let id = item.id; + + if(id !== undefined) + { + //console.log("------------processDataFromDatabase - remove", id, dataBase, i); + + try { + + let o = JSON.parse(JSON.stringify(item)); + delete o.id; + let message = JSON.stringify(o); + + client.publish(`${o.topic}_forward`, message, {qos:1}); + //console.log("db...", message) + //remove from database + await promisifyBuilder(nosql.remove().where("id", id)); + + } catch(error) { + //process error + console.log("processDataFromDatabase", error); + } + + + } + + } + else + { + processingData = false; + return; + } + } + + if(records.length > 0) + { + //clean backup file + if(counter > 0) nosql.clean(); + } + + //no data in db, remove + if(records.length == 0) + { + if(counter > 0) nosql.drop(); + } + + const d = new Date(); + lastRestoreTime = d.getTime(); + + processingData = false; + + } + + instance.on('options', main); + main(); + +}; diff --git a/flow/cmd_manager.js b/flow/cmd_manager.js index 63c1a1f..d84daf3 100644 --- a/flow/cmd_manager.js +++ b/flow/cmd_manager.js @@ -3,13 +3,10 @@ exports.title = 'CMD Manager'; exports.group = 'Worksys'; exports.color = '#5D9CEC'; exports.version = '0.0.3'; -exports.output = ['red', 'blue', 'yellow', 'blue', 'white']; - //blue - send message to relays - -exports.input = 2; +exports.output = ['red', 'blue', 'yellow', 'blue', 'white']; +exports.input = 3; exports.icon = 'cloud-upload'; -//exports.npm = ['serialport' , 'child_process']; exports.html = `
@@ -29,669 +26,637 @@ exports.html = `
`; - exports.readme = `Manager for CMD calls`; -const SerialPort = require('serialport'); -const { exec } = require('child_process'); -const { crc8, crc16, crc32 } = require('easy-crc'); -const { openPort, runSyncExec, writeData } = require('./helper/serialport_helper.js'); -const { bytesToInt, longToByteArray, addZeroBefore, isEmptyObject, convertUTCDateToLocalDate } = require('./helper/utils'); -const bitwise = require('bitwise'); - -var SunCalc = require('./helper/suncalc.js'); -const DataToTbHandler = require('./helper/DataToTbHandler.js'); -const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler.js'); -const { promisifyBuilder, makeMapFromDbResult} = require('./helper/db_helper.js'); -const { sendNotification, initNotifications, ERRWEIGHT } = require('./helper/notification_reporter.js'); - -const dbNodes = TABLE("nodes"); -const dbRelays = TABLE("relays"); -const dbSettings = TABLE("settings"); - -//https://github.com/log4js-node/log4js-node/blob/master/examples/example.js -//file: { type: 'file', filename: path.join(__dirname, 'log/file.log') } -var path = require('path'); -var log4js = require("log4js"); -const process = require('process'); - -//TODO - to remove? -// runTasks intervals -const SHORT_INTERVAL = 30; -const LONG_INTERVAL = 300; - -//send data to following instances: -const SEND_TO = { - debug: 0, - tb: 1, - http_response: 2, - dido_controller: 3, - infoSender: 4 -} - -const PRIORITY_TYPES = { - terminal: 0, - fw_detection: 1,//reserved only for FW detection - FLOW.OMS_masterNodeIsResponding - high_priority: 2,//reserverd only for: read dimming / brightness (after set dimming from platform) - relay_profile: 3, - node_broadcast: 4, - node_profile: 5, - node_cmd: 6 -} - -const TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION = 600000; // 10 minutes - -//list of command calls to process. Processing in runTasks function -let tasks = []; - -let interval = null;//timeout for procesing tasks -let customTasksInterval = null; -let refFlowdata = null;//holds reference to httprequest flowdata -let refFlowdataObj = {}; - -//load from settings -let latitude = 48.70826502;//48.682255758; -let longitude = 17.28455203;//17.278910807; - -const gmtOffset = 0; - -//ak nie je nastaveny -//https://www.tecmint.com/set-time-timezone-and-synchronize-time-using-timedatectl-command/ -//https://stackoverflow.com/questions/16086962/how-to-get-a-time-zone-from-a-location-using-latitude-and-longitude-coordinates - -//priorities for registers -let priorities = []; - -let minutes = 1; -priorities["0"] = minutes; -priorities["1"] = minutes; - -minutes = 5; -priorities["74"] = minutes; -priorities["75"] = minutes; -priorities["76"] = minutes; -priorities["77"] = minutes; -priorities["78"] = minutes; -priorities["79"] = minutes; -priorities["84"] = minutes; - -minutes = 10; -priorities["87"] = minutes; -priorities["6"] = minutes; -priorities["7"] = minutes; -priorities["80"] = minutes; -priorities["8"] = minutes; -priorities["3"] = minutes; -priorities["89"] = minutes; - -//prikazy kt sa budu spustat na dany node - see config.js in terminal-oms.app. (1 - dimming) -let listOfCommands = [0,1,3,6,7,8,74,75,76,77,78,79,80,84,87,89]; - -const errorHandler = new ErrorToServiceHandler(); - -let rotary_switch_state = "Off"; -let lux_sensor; -let state_of_breaker = {};//key is line, value is On/Off -let disconnectedReport = {};//key is tbname, value true/false - -let relaysData = {};//key is line, value is data from db -let nodesData = {};//key is node, value data from db - -//helper container for counting resolved group of commands (commands related to set profile) -let cmdCounter = {};//key is node, value is counter - -//END OF VARIABLE SETTINGS -//-------------------------------- - - -log4js.configure({ - appenders: { - errLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'err.txt') }, - monitorLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'monitor.txt') }, - console: { type: 'console' } - }, - categories: { - errLogs: { appenders: ['console', 'errLogs'], level: 'error' }, - monitorLogs: { appenders: ['console', 'monitorLogs'], level: 'trace' }, - //another: { appenders: ['console'], level: 'trace' }, - default: { appenders: ['console'], level: 'trace' } - } -}); - -const errLogger = log4js.getLogger("errLogs"); -const logger = log4js.getLogger(); -const monitor = log4js.getLogger("monitorLogs"); - -//USAGE -//logger.debug("text") -//monitor.info('info'); -//errLogger.error("some error"); - - -function cmdCounterResolve(address) -{ - if(cmdCounter.hasOwnProperty(address)) - { - cmdCounter[address] = cmdCounter[address] - 1; - - let result = cmdCounter[address]; - if(result == 0) delete cmdCounter[address]; - return result; - } - return -1; -} - - -function getParams(priority) -{ - let params = {}; - - //core rpc values - params.address = 0;//if(recipient === 0) address = 0; - params.byte1 = 0;//msb, podla dokumentacie data3 - params.byte2 = 0;//podla dokumentacie data2 - params.byte3 = 0;//podla dokumentacie data1 - params.byte4 = 0;//lsb, podla dokumentacie data0 - params.recipient = 0;//0: Master, 1: Slave, 2: Broadcast - params.register = -1;//register number - params.rw = 0;//0: read, 1: write - - //other values - //params.type = "cmd"; "relay" "cmd-terminal" "set_node_profile" "process_profiles" - //params.tbname = tbname; - params.priority = PRIORITY_TYPES.node_cmd; //default priority - if more tasks with the same timestamp, we sort them based on priority - params.timestamp = 0; //execution time - if timestamp < Date.now(), the task is processed - if(priority != undefined ) - { - params.timestamp = priority; - params.priority = priority; - } - - params.addMinutesToTimestamp = 0;//repeat task if value is > 0, - - //params.timePointName = "luxOff" // "luxOn", "dusk", "dawn", "profileTimepoint" - //params.info = ""; - - return params; -} - - -async function loadSettings() -{ - let responseSettings = await promisifyBuilder(dbSettings.find()); - - latitude = responseSettings[0]["latitude"]; - longitude = responseSettings[0]["longitude"]; - - //globals - FLOW.OMS_language = responseSettings[0]["lang"]; - FLOW.OMS_rvo_name = responseSettings[0]["rvo_name"]; - FLOW.OMS_projects_id = responseSettings[0]["projects_id"]; - //FLOW.OMS_rvo_tbname = responseSettings[0]["tbname"]; - FLOW.OMS_temperature_adress = responseSettings[0]["temperature_adress"]; - FLOW.OMS_controller_type = responseSettings[0]["controller_type"]; - FLOW.OMS_serial_port = responseSettings[0]["serial_port"]; - FLOW.OMS_node_status_nok_time = responseSettings[0]["node_status_nok_time"] * 60 * 60 * 1000; // hour * minutes * seconds - //logger.debug('settings', responseSettings[0]); - - initNotifications(); -} - -loadSettings(); - - -async function loadNodes() -{ - const responseNodes = await promisifyBuilder(dbNodes.find()); - nodesData = makeMapFromDbResult(responseNodes, "node"); -} - -loadNodes(); - - -//nastav profil nodu -function processNodeProfile(node) -{ - if(rotary_switch_state != "Automatic") - { - logger.debug("unable to process profile for node", node, "rotary_switch_state != Automatic"); - return; - } - - let nodeObj = nodesData[node]; - let line = nodeObj.line; - - if(relaysData[line].contactor == 0) - { - logger.debug("line line is off", line, node); - return; - } - - if(nodeObj.processed == 1) - { - logger.debug("node was already processed", node); - return; - } - - let nodeProfile = nodeObj.profile; - logger.debug("processNodeProfile: start - set profile for ", node, nodeProfile); - if(nodeProfile) { - - try { - nodeProfile = JSON.parse(nodeProfile); - } catch (error) { - logger.debug("Cmd_manager - Error parsing node profile", error); - } - - } - - logger.debug("processNodeProfile", node, line, nodeObj, nodeProfile); - - let timestamp = PRIORITY_TYPES.node_cmd; - - removeTask({type: "set_node_profile", address: node}); - - if(nodeProfile === "") - { - //vypneme profil nodu, posleme cmd - //Pokiaľ je hodnota rovná 1 – Profil sa zapne, ostatné bity sa nezmenia. - //Pokiaľ sa hodnota rovná 2 – profil sa vypne, ostatné bity sa nezmenia - - logger.debug("turn off profile"); - - let params = getParams(PRIORITY_TYPES.node_cmd); - params.type = "set_node_profile"; - params.address = node; - params.byte1 = 0; - params.byte2 = 0; - params.byte3 = 0; - params.byte4 = 96; - params.recipient = 1; - params.register = 8; - params.rw = 1;//write - params.timestamp = timestamp; - params.addMinutesToTimestamp = 0; - params.info = 'turn off/reset node profile'; - - cmdCounter[node] = 1; - - tasks.push(params); - } - else - { - let tasksProfile = []; - - //vypneme profil - Zapísať hodnotu 32 do registra Time Schedule Settings – reset profilu - let params = getParams(PRIORITY_TYPES.node_cmd); - params.type = "set_node_profile"; - params.address = node; - params.byte1 = 0; - params.byte2 = 0; - params.byte3 = 0; - params.byte4 = 96; - params.recipient = 1; - params.register = 8; - params.rw = 1;//write - params.timestamp = timestamp; - params.addMinutesToTimestamp = 0; - params.info = 'turn off node profile'; - - tasksProfile.push(params); - - timestamp++; - - logger.debug("processNodeProfile: TS1 Time point a TS1 Time Point Levels ", node); - - //TS1 Time point a TS1 Time Point Levels - let register = 9; - for(let i = 0; i < nodeProfile.intervals.length; i++) - { - let obj = nodeProfile.intervals[i]; - //let timePoint = obj.time_point; - let dim_value = obj.value; - - - //Reg 9 až Reg 40 - - /* - Samotný profil sa zapisuje do max. 16 párov – časový bod a úroveň. - Prázdny profil je vtedy keď časový bod obsahuje hodnotu 0xFFFFFFFF (táto hodnota sa zapíše do registrov keď sa aktivuje reset profilu do registru 8). - Páry sa prechádzajú časovo zoradené takže teoreticky je jedno v akom poradí sa zapisujú ale je lepšie ich zapisovať v chronologickom poradí od 13:00. - Časový bod má formát: - Byte 3: hodiny Byte 2: minúty Byte 1: sekundy Byte 0 – rezervované - Register úrovne má rovnaký formát ako dimming register (Reg 1). - */ - - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - //params.byte1 = 0;//msb, podla dokumentacie data3 - //params.byte2 = 0;//podla dokumentacie data2 - //params.byte3 = 0;//podla dokumentacie data1 - //params.byte4 = 0;//lsb, podla dokumentacie data0 - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - let start_time = obj.start_time; - let t = start_time.split(":"); - //if(timePoint != undefined) t = timePoint.split(":"); - //else t = [0,0]; - - logger.debug("processNodeProfile: TS1 Time point ", (i + 1), node); - - params = getParams(PRIORITY_TYPES.node_cmd); - params.type = "set_node_profile"; - params.address = node; - params.byte1 = parseInt(t[0]);//hh - params.byte2 = parseInt(t[1]);//mm - params.byte3 = 0;//ss - params.byte4 = 0;// - params.recipient = 1; - params.register = register; - params.rw = 1;//write - params.timestamp = timestamp; - params.addMinutesToTimestamp = 0; - params.info = 'TS1 Time point ' + (i + 1); - - tasksProfile.push(params); - - register++; - timestamp++; - - params = getParams(PRIORITY_TYPES.node_cmd); - params.type = "set_node_profile"; - params.address = node; - params.byte1 = 0; - params.byte2 = 0; - params.byte3 = 0;//ss - params.byte4 = parseInt(dim_value) + 128;// - params.recipient = 1; - params.register = register; - params.rw = 1;//write - params.timestamp = timestamp; - params.addMinutesToTimestamp = 0; - params.info = 'TS1 Time point Levels ' + (i + 1); - - tasksProfile.push(params); - - register++; - timestamp++; - } - - //Threshold lux level for DUSK/DAWN - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - //params.byte1 = 0;//msb, podla dokumentacie data3 - //params.byte2 = 0;//podla dokumentacie data2 - //params.byte3 = 0;//podla dokumentacie data1 - //params.byte4 = 0;//lsb, podla dokumentacie data0 - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - - //Time schedule settings na koniec - //if(nodeProfile.dusk_lux_sensor || nodeProfile.dawn_lux_sensor) - { - - logger.debug("processNodeProfile: Threshold lux level for DUSK/DAWN", node); - - let params = getParams(PRIORITY_TYPES.node_cmd); - params.type = "set_node_profile"; - params.address = node; - params.register = 96; - params.recipient = 1; - params.rw = 1;//write - params.timestamp = timestamp; - params.addMinutesToTimestamp = 0; - params.info = "Threshold lux level for DUSK/DAWN"; - - if(nodeProfile.dusk_lux_sensor) - { - let v = nodeProfile.dusk_lux_sensor_value; - let ba = longToByteArray(v); - - params.byte1 = ba[1];//msb - params.byte2 = ba[0]; - } - - if(nodeProfile.dawn_lux_sensor) - { - let v = nodeProfile.dawn_lux_sensor_value; - let ba = longToByteArray(v); - - params.byte3 = ba[1];//msb - params.byte4 = ba[0]; - } - - tasksProfile.push(params); - timestamp++; - - } - - //DUSK/DAWN max. adjust period - { - - logger.debug("processNodeProfile: DUSK/DAWN max. adjust period", node); - - let params = getParams(PRIORITY_TYPES.node_cmd); - params.type = "set_node_profile"; - params.address = node; - params.register = 97; - params.recipient = 1; - params.rw = 1;//write - params.timestamp = timestamp; - params.addMinutesToTimestamp = 0; - params.info = "DUSK/DAWN max. adjust period"; - - if(nodeProfile.astro_clock) - { - let v = nodeProfile.dusk_lux_sensor_time_window; - let ba = longToByteArray(v); - - params.byte1 = ba[1];//msb - params.byte2 = ba[0]; - } - - if(nodeProfile.astro_clock) - { - let v = nodeProfile.dawn_lux_sensor_time_window; - let ba = longToByteArray(v); - - params.byte3 = ba[1];//msb - params.byte4 = ba[0]; - } - - tasksProfile.push(params); - timestamp++; - - } - - //Static offset - { - - //Statický offset pre časy úsvitu a súmraku. Byte 1 je pre DUSK, Byte 0 je pre DAWN. Formát: - //Bity 0 – 6: hodnota v minútach - //Bit 7: znamienko (1 – mínus) - - logger.debug("processNodeProfile: Static offset", node); - - let params = getParams(PRIORITY_TYPES.node_cmd); - params.type = "set_node_profile"; - params.address = node; - params.register = 98; - params.recipient = 1; - params.rw = 1;//write - params.timestamp = timestamp; - params.addMinutesToTimestamp = 0; - params.info = "Static offset"; - - if(nodeProfile.astro_clock) - { - let dusk_astro_clock_offset = parseInt(nodeProfile.dusk_astro_clock_offset); - let dawn_astro_clock_offset = parseInt(nodeProfile.dawn_astro_clock_offset); - - if(dusk_astro_clock_offset < 0) - { - params.byte3 = (dusk_astro_clock_offset * -1) + 128; - } - else - { - params.byte3 = dusk_astro_clock_offset; - } - - if(dawn_astro_clock_offset < 0) - { - params.byte4 = (dawn_astro_clock_offset * -1) + 128; - } - else - { - params.byte4 = dawn_astro_clock_offset; - } - } - - tasksProfile.push(params); - timestamp++; - } - - logger.debug("Time schedule settings - turn on", node); - - params = getParams(PRIORITY_TYPES.node_cmd); - params.type = "set_node_profile"; - params.address = node; - params.register = 8; - params.recipient = 1; - params.rw = 1;//write - - //Time schedule settings - let bits = []; - - //Byte 0 (LSB): - //Bit 0 (LSB) – zapnutie/vypnutie profilov ako takých (1 – zapnuté). - bits.push(1); - //Bit 1 – 3 - zatiaľ nepoužité (zapisovať 0) - bits.push(0); - bits.push(0); - bits.push(0); - if(nodeProfile.astro_clock == true) - { - //Bit 4 – ak je nastavený profil sa riadi podľa astrohodín, a je 0 tak profil je jednoduchý - bits.push(1); - } - else bits.push(0); - - //Bit 5 – zápis 1 spôsobí reset nastavení profilu (nastavenie prázdneho profilu) - bits.push(0); - - //Bity 6-7 - zatiaľ nepoužité - bits.push(0); - bits.push(0); - - params.byte4 = bitwise.byte.write(bits.reverse()); - - //Byte 2 – nastavenie pre lux senzor: - bits = []; - - //Bit 0 (LSB) – riadenie súmraku podľa lux senzoru (1 – zapnuté). Súmrak sa môže posúvať v rámci času v registri 97 podľa intenzity osvetlenia - if(nodeProfile.dusk_lux_sensor == true)//sumrak - { - bits.push(1); - } - else bits.push(0); - - //Bit 1 - riadenie úsvitu podľa lux senzoru (1 – zapnuté). Úsvit sa môže posúvať v rámci času v registri 97 podľa intenzity osvetlenia - if(nodeProfile.dawn_lux_sensor == true)//usvit - { - bits.push(1); - } - else bits.push(0); - - //Bit 2 – zdroj pre hodnotu luxov – 0 – RVO posiela hodnoty zo svojho luxmetra, 1 – node má pripojený svoj vlastný lux meter. - bits.push(0);//zatial neimplementovane - - //Bit 3 – 7 - nepoužité - bits.push(0); - bits.push(0); - bits.push(0); - bits.push(0); - bits.push(0); - - params.byte2 = bitwise.byte.write(bits.reverse()); - params.timestamp = timestamp; - params.info = "Time schedule settings - turn on"; - - tasksProfile.push(params); - - //zaver - cmdCounter[node] = tasksProfile.length; - - //tasks.push(tasksProfile); - tasks = tasks.concat(tasksProfile); - - } - - logger.debug("finished set profile for ", node); - -} - - -function cleanUpRefFlowdataObj() -{ - let now = new Date(); - let timestamp = now.getTime(); - - //clear old refFlowdata references - let keys = Object.keys(refFlowdataObj); - for(let i = 0; i < keys.length; i++) - { - let timestampKey = keys[i]; - - if((timestamp - timestampKey) > 60*1000 ) - { - console.log("cleanUpRefFlowdataObj delete", timestampKey); - delete refFlowdataObj[ timestampKey ]; - } - } -} - - -function removeTask(obj) -{ - let keys = Object.keys(obj); - tasks = tasks.filter((task) => { - - let counter = 0; - for(let i = 0; i < keys.length; i++) - { - let key = keys[i]; - if(task.hasOwnProperty(key) && obj.hasOwnProperty(key)) - { - if(task[key] == obj[key]) counter++; - } - } - - if(counter == keys.length) return false; - return true; - }); -} - exports.install = function(instance) { - let now = new Date(); - console.log("CMD Manager installed", now.toLocaleString("sk-SK")); - - const tbHandler = new DataToTbHandler(SEND_TO.tb); - tbHandler.setSender(exports.title); + const SerialPort = require('serialport'); + 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 bitwise = require('bitwise'); - //FLOW.OMS_projects_id, name: FLOW.OMS_rvo_name - //const errorHandler = new ErrorToServiceHandler(instance, SEND_TO.infoSender); - errorHandler.setProjectsId(FLOW.OMS_projects_id); - //const errorHandler = new ErrorToServiceHandler(instance); - //errorHandler.sendMessageToService("ahoj", 0); + var SunCalc = require('./helper/suncalc'); + const DataToTbHandler = require('./helper/DataToTbHandler'); + const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler'); + const { sendNotification } = require('./helper/notification_reporter'); + const process = require('process'); + const { errLogger, logger, monitor } = require('./helper/logger'); - let sunCalcResult = calculateDuskDawn(); + const dbNodes = TABLE("nodes"); + const dbRelays = TABLE("relays"); - let reportDuskDawn = { - dusk_time: sunCalcResult.dusk_time, - dawn_time: sunCalcResult.dawn_time, - dusk_time_reported: undefined, - dawn_time_reported: undefined - }; + let GLOBALS; + let SETTINGS; + let rsPort; + let tbHandler; + + // runTasks intervals + const SHORT_INTERVAL = 30; + const LONG_INTERVAL = 300; + + //send data to following instances: + const SEND_TO = { + debug: 0, + tb: 1, + http_response: 2, + dido_controller: 3, + infoSender: 4 + } + + const PRIORITY_TYPES = { + terminal: 0, + fw_detection: 1,//reserved only for FW detection - SETTINGS.masterNodeIsResponding + high_priority: 2,//reserverd only for: read dimming / brightness (after set dimming from platform) + relay_profile: 3, + node_broadcast: 4, + node_profile: 5, + node_cmd: 6 + } + + const TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION = 600000; // 10 minutes + + //list of command calls to process. Processing in runTasks function + let tasks = []; + + let interval = null;//timeout for procesing tasks + let customTasksInterval = null; // interval for reportEdgeDateTimeAndNumberOfLuminaires(); + let setCorrectTime = null; // interval for setting a correct edgeTime + + let refFlowdataObj = {}; + + //load from settings + let latitude = 48.70826502;//48.682255758; + let longitude = 17.28455203;//17.278910807; + + const gmtOffset = 0; + + //ak nie je nastaveny + //https://www.tecmint.com/set-time-timezone-and-synchronize-time-using-timedatectl-command/ + //https://stackoverflow.com/questions/16086962/how-to-get-a-time-zone-from-a-location-using-latitude-and-longitude-coordinates + + //priorities for registers + let priorities = []; + + let minutes = 1; + priorities["0"] = minutes; + priorities["1"] = minutes; + + minutes = 5; + priorities["74"] = minutes; + priorities["75"] = minutes; + priorities["76"] = minutes; + priorities["77"] = minutes; + priorities["78"] = minutes; + priorities["79"] = minutes; + priorities["84"] = minutes; + + minutes = 10; + priorities["87"] = minutes; + priorities["6"] = minutes; + priorities["7"] = minutes; + priorities["80"] = minutes; + priorities["8"] = minutes; + priorities["3"] = minutes; + priorities["89"] = minutes; + + //prikazy kt sa budu spustat na dany node - see config.js in terminal-oms.app. (1 - dimming) + let listOfCommands = [0,1,3,6,7,8,74,75,76,77,78,79,80,84,87,89]; + + const errorHandler = new ErrorToServiceHandler(); + + let rotary_switch_state; + let lux_sensor; + let state_of_breaker = {};//key is line, value is On/Off + let disconnectedReport = {};//key is tbname, value true/false + + let relaysData; + let nodesData; + let rvoTbName; + + 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 + + //END OF VARIABLE SETTINGS + //-------------------------------- + + + function main() + { + GLOBALS = FLOW.GLOBALS; + SETTINGS = FLOW.GLOBALS.settings; + relaysData = GLOBALS.relaysData; + nodesData = GLOBALS.nodesData; + latitude = GLOBALS.settings.latitude; + longitude = GLOBALS.settings.longitude; + + tbHandler = new DataToTbHandler(SEND_TO.tb); + tbHandler.setSender(exports.title); + + //SETTINGS.project_id, name: SETTINGS.rvo_name; + //const errorHandler = new ErrorToServiceHandler(instance, SEND_TO.infoSender); + errorHandler.setProjectsId(SETTINGS.project_id); + //const errorHandler = new ErrorToServiceHandler(instance); + //errorHandler.sendMessageToService("ahoj", 0); + + let now = new Date(); + console.log("CMD Manager installed", now.toLocaleString("sk-SK")); + + sunCalcResult = calculateDuskDawn(); + + reportDuskDawn = { + dusk_time: sunCalcResult.dusk_time, + dawn_time: sunCalcResult.dawn_time, + dusk_time_reported: undefined, + dawn_time_reported: undefined + }; + + handleRsPort(); + + //to ensure, edgeDateTime will be send to tb at full minute + customTasksInterval = setInterval(function() { + if(new Date().getSeconds() === 0) reportEdgeDateTimeAndNumberOfLuminaires(); + }, 1000); + + setCorrectTime = setInterval(setCorrectPlcTimeOnceADay, 60000 * 60); // 1 hour + setCorrectPlcTimeOnceADay(); + } + + + function cmdCounterResolve(address) + { + if(cmdCounter.hasOwnProperty(address)) + { + cmdCounter[address] = cmdCounter[address] - 1; + + let result = cmdCounter[address]; + if(result == 0) delete cmdCounter[address]; + return result; + } + return -1; + } + + + function getParams(priority) + { + let params = {}; + + //core rpc values + params.address = 0;//if(recipient === 0) address = 0; + params.byte1 = 0;//msb, podla dokumentacie data3 + params.byte2 = 0;//podla dokumentacie data2 + params.byte3 = 0;//podla dokumentacie data1 + params.byte4 = 0;//lsb, podla dokumentacie data0 + params.recipient = 0;//0: Master, 1: Slave, 2: Broadcast + params.register = -1;//register number + params.rw = 0;//0: read, 1: write + + //other values + //params.type = "cmd"; "relay" "cmd-terminal" "set_node_profile" "process_profiles" + //params.tbname = tbname; + params.priority = PRIORITY_TYPES.node_cmd; //default priority - if more tasks with the same timestamp, we sort them based on priority + params.timestamp = 0; //execution time - if timestamp < Date.now(), the task is processed + if(priority != undefined ) + { + params.timestamp = priority; + params.priority = priority; + } + + params.addMinutesToTimestamp = 0;//repeat task if value is > 0, + + //params.timePointName = "luxOff" // "luxOn", "dusk", "dawn", "profileTimepoint" + //params.info = ""; + //params.debug = true; // will console.log params in writeData response + + return params; + } + + + //nastav profil nodu + function processNodeProfile(node) + { + if(rotary_switch_state != "Automatic") + { + logger.debug("unable to process profile for node", node, "rotary_switch_state != Automatic"); + return; + } + + let nodeObj = nodesData[node]; + let line = nodeObj.line; + + if(relaysData[line].contactor == 0) + { + logger.debug("line line is off", line, node); + return; + } + + if(nodeObj.processed == 1) + { + //logger.debug("node was already processed", node); + return; + } + + let nodeProfile = nodeObj.profile; + logger.debug("processNodeProfile: start - set profile for ", node, nodeProfile); + if(nodeProfile) { + + try { + nodeProfile = JSON.parse(nodeProfile); + } catch (error) { + logger.debug("Cmd_manager - Error parsing node profile", error); + } + + } + + logger.debug("processNodeProfile", node, line, nodeObj, nodeProfile); + + let timestamp = PRIORITY_TYPES.node_cmd; + + removeTask({type: "set_node_profile", address: node}); + + if(nodeProfile === "") + { + //vypneme profil nodu, posleme cmd + //Pokiaľ je hodnota rovná 1 – Profil sa zapne, ostatné bity sa nezmenia. + //Pokiaľ sa hodnota rovná 2 – profil sa vypne, ostatné bity sa nezmenia + + logger.debug("turn off profile"); + + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 96; + params.recipient = 1; + params.register = 8; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'turn off/reset node profile'; + + cmdCounter[node] = 1; + + tasks.push(params); + } + else + { + let tasksProfile = []; + + //vypneme profil - Zapísať hodnotu 32 do registra Time Schedule Settings – reset profilu + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 96; + params.recipient = 1; + params.register = 8; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'turn off node profile'; + + tasksProfile.push(params); + + timestamp++; + + logger.debug("processNodeProfile: TS1 Time point a TS1 Time Point Levels ", node); + + //TS1 Time point a TS1 Time Point Levels + let register = 9; + for(let i = 0; i < nodeProfile.intervals.length; i++) + { + let obj = nodeProfile.intervals[i]; + //let timePoint = obj.time_point; + let dim_value = obj.value; + + + //Reg 9 až Reg 40 + + /* + Samotný profil sa zapisuje do max. 16 párov – časový bod a úroveň. + Prázdny profil je vtedy keď časový bod obsahuje hodnotu 0xFFFFFFFF (táto hodnota sa zapíše do registrov keď sa aktivuje reset profilu do registru 8). + Páry sa prechádzajú časovo zoradené takže teoreticky je jedno v akom poradí sa zapisujú ale je lepšie ich zapisovať v chronologickom poradí od 13:00. + Časový bod má formát: + Byte 3: hodiny Byte 2: minúty Byte 1: sekundy Byte 0 – rezervované + Register úrovne má rovnaký formát ako dimming register (Reg 1). + */ + + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + //params.byte1 = 0;//msb, podla dokumentacie data3 + //params.byte2 = 0;//podla dokumentacie data2 + //params.byte3 = 0;//podla dokumentacie data1 + //params.byte4 = 0;//lsb, podla dokumentacie data0 + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + let start_time = obj.start_time; + let t = start_time.split(":"); + //if(timePoint != undefined) t = timePoint.split(":"); + //else t = [0,0]; + + logger.debug("processNodeProfile: TS1 Time point ", (i + 1), node); + + params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = parseInt(t[0]);//hh + params.byte2 = parseInt(t[1]);//mm + params.byte3 = 0;//ss + params.byte4 = 0;// + params.recipient = 1; + params.register = register; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'TS1 Time point ' + (i + 1); + + tasksProfile.push(params); + + register++; + timestamp++; + + params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0;//ss + params.byte4 = parseInt(dim_value) + 128;// + params.recipient = 1; + params.register = register; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'TS1 Time point Levels ' + (i + 1); + + tasksProfile.push(params); + + register++; + timestamp++; + } + + //Threshold lux level for DUSK/DAWN + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + //params.byte1 = 0;//msb, podla dokumentacie data3 + //params.byte2 = 0;//podla dokumentacie data2 + //params.byte3 = 0;//podla dokumentacie data1 + //params.byte4 = 0;//lsb, podla dokumentacie data0 + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + + //Time schedule settings na koniec + //if(nodeProfile.dusk_lux_sensor || nodeProfile.dawn_lux_sensor) + { + + logger.debug("processNodeProfile: Threshold lux level for DUSK/DAWN", node); + + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 96; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "Threshold lux level for DUSK/DAWN"; + + if(nodeProfile.dusk_lux_sensor) + { + let v = nodeProfile.dusk_lux_sensor_value; + let ba = longToByteArray(v); + + params.byte1 = ba[1];//msb + params.byte2 = ba[0]; + } + + if(nodeProfile.dawn_lux_sensor) + { + let v = nodeProfile.dawn_lux_sensor_value; + let ba = longToByteArray(v); + + params.byte3 = ba[1];//msb + params.byte4 = ba[0]; + } + + tasksProfile.push(params); + timestamp++; + + } + + //DUSK/DAWN max. adjust period + { + + logger.debug("processNodeProfile: DUSK/DAWN max. adjust period", node); + + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 97; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "DUSK/DAWN max. adjust period"; + + if(nodeProfile.astro_clock) + { + let v = nodeProfile.dusk_lux_sensor_time_window; + let ba = longToByteArray(v); + + params.byte1 = ba[1];//msb + params.byte2 = ba[0]; + } + + if(nodeProfile.astro_clock) + { + let v = nodeProfile.dawn_lux_sensor_time_window; + let ba = longToByteArray(v); + + params.byte3 = ba[1];//msb + params.byte4 = ba[0]; + } + + tasksProfile.push(params); + timestamp++; + + } + + //Static offset + { + + //Statický offset pre časy úsvitu a súmraku. Byte 1 je pre DUSK, Byte 0 je pre DAWN. Formát: + //Bity 0 – 6: hodnota v minútach + //Bit 7: znamienko (1 – mínus) + + logger.debug("processNodeProfile: Static offset", node); + + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 98; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "Static offset"; + + if(nodeProfile.astro_clock) + { + let dusk_astro_clock_offset = parseInt(nodeProfile.dusk_astro_clock_offset); + let dawn_astro_clock_offset = parseInt(nodeProfile.dawn_astro_clock_offset); + + if(dusk_astro_clock_offset < 0) + { + params.byte3 = (dusk_astro_clock_offset * -1) + 128; + } + else + { + params.byte3 = dusk_astro_clock_offset; + } + + if(dawn_astro_clock_offset < 0) + { + params.byte4 = (dawn_astro_clock_offset * -1) + 128; + } + else + { + params.byte4 = dawn_astro_clock_offset; + } + } + + tasksProfile.push(params); + timestamp++; + } + + logger.debug("Time schedule settings - turn on", node); + + params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 8; + params.recipient = 1; + params.rw = 1;//write + + //Time schedule settings + let bits = []; + + //Byte 0 (LSB): + //Bit 0 (LSB) – zapnutie/vypnutie profilov ako takých (1 – zapnuté). + bits.push(1); + //Bit 1 – 3 - zatiaľ nepoužité (zapisovať 0) + bits.push(0); + bits.push(0); + bits.push(0); + if(nodeProfile.astro_clock == true) + { + //Bit 4 – ak je nastavený profil sa riadi podľa astrohodín, a je 0 tak profil je jednoduchý + bits.push(1); + } + else bits.push(0); + + //Bit 5 – zápis 1 spôsobí reset nastavení profilu (nastavenie prázdneho profilu) + bits.push(0); + + //Bity 6-7 - zatiaľ nepoužité + bits.push(0); + bits.push(0); + + params.byte4 = bitwise.byte.write(bits.reverse()); + + //Byte 2 – nastavenie pre lux senzor: + bits = []; + + //Bit 0 (LSB) – riadenie súmraku podľa lux senzoru (1 – zapnuté). Súmrak sa môže posúvať v rámci času v registri 97 podľa intenzity osvetlenia + if(nodeProfile.dusk_lux_sensor == true)//sumrak + { + bits.push(1); + } + else bits.push(0); + + //Bit 1 - riadenie úsvitu podľa lux senzoru (1 – zapnuté). Úsvit sa môže posúvať v rámci času v registri 97 podľa intenzity osvetlenia + if(nodeProfile.dawn_lux_sensor == true)//usvit + { + bits.push(1); + } + else bits.push(0); + + //Bit 2 – zdroj pre hodnotu luxov – 0 – RVO posiela hodnoty zo svojho luxmetra, 1 – node má pripojený svoj vlastný lux meter. + bits.push(0);//zatial neimplementovane + + //Bit 3 – 7 - nepoužité + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + + params.byte2 = bitwise.byte.write(bits.reverse()); + params.timestamp = timestamp; + params.info = "Time schedule settings - turn on"; + + tasksProfile.push(params); + + //zaver + cmdCounter[node] = tasksProfile.length; + + //tasks.push(tasksProfile); + tasks = tasks.concat(tasksProfile); + + } + + logger.debug("finished set profile for ", node); + + console.log("proces profile finished *********************") + } + + + function cleanUpRefFlowdataObj() + { + let now = new Date(); + let timestamp = now.getTime(); + + //clear old refFlowdata references + let keys = Object.keys(refFlowdataObj); + for(let i = 0; i < keys.length; i++) + { + let timestampKey = keys[i]; + + if((timestamp - timestampKey) > 60*1000 ) + { + console.log("cleanUpRefFlowdataObj delete", timestampKey); + delete refFlowdataObj[ timestampKey ]; + } + } + } + + + function removeTask(obj) + { + let keys = Object.keys(obj); + tasks = tasks.filter((task) => { + + let counter = 0; + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + if(task.hasOwnProperty(key) && obj.hasOwnProperty(key)) + { + if(task[key] == obj[key]) counter++; + } + } + + if(counter == keys.length) return false; + return true; + }); + } process.on('uncaughtException', function (err) { @@ -709,46 +674,32 @@ exports.install = function(instance) { function processAllNodeProfilesOnLine(line) { - for (let k in nodesData) { - //node:number|tbname:string|line:number|profile:string|processed:boolean - if(line == nodesData[k].line) { let node = nodesData[k].node; let processed = nodesData[k].processed; - if(!processed) - { - processNodeProfile(node); - } - else - { - logger.debug( `Node ${node} profile for line ${nodesData[k].line} was already processed`); - } + if(!processed) processNodeProfile(node); + //else logger.debug( `Node ${node} profile for line ${nodesData[k].line} was already processed`); } } } - async function loadRelaysData(line) { - - relaysData = await promisifyBuilder(dbRelays.find()); - relaysData = makeMapFromDbResult(relaysData, "line"); - + function loadRelaysData(line) + { for (const [key, value] of Object.entries(relaysData)) { if(key == "0") continue; if(line != undefined) { //ak sa jedna o update profilu linie - pozor dido_controller posiela command pre loadRelaysData - if(line != value.line ) continue; + if(line != value.line) continue; } if(value.contactor == 1) processAllNodeProfilesOnLine(value.line); } - -// console.log('.........', relaysData); } @@ -809,18 +760,7 @@ exports.install = function(instance) { nodesData[k].time_of_last_communication = time; } - let dataToTb = { - [tbname]: [ - { - ts: time, - values: { - status: status - } - } - ] - } - - tbHandler.sendToTb(dataToTb, instance); + sendTelemetry({status: status}, tbname, time); //prud, vykon - current, input power pre liniu pre vsetky nody @@ -890,6 +830,8 @@ exports.install = function(instance) { values["current"] = 0;//prúd values["status"] = "OFFLINE"; + const date = Date.now(); + // it happens, that some data did not get to tb after sending // we setTimeout to make more time for db to process telemetry (eg 150 messages at once) Object.keys(nodesData).forEach((node, index) => { @@ -900,18 +842,7 @@ exports.install = function(instance) { if(line == nodesData[node].line || line == undefined) { let tbname = nodesData[node].tbname; - - let dataToTb = { - [tbname]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - - //instance.send(SEND_TO.tb, dataToTb); - tbHandler.sendToTb(dataToTb, instance); + sendTelemetry(values, tbname, date) } },(index+1) * 300); @@ -949,17 +880,20 @@ exports.install = function(instance) { { //ak sa odpoved zacina 0 - je to v poriadku, inak je NOK let type = "RESPONSE"; - if(bytes[4] == 0) type = "RESPONSE"; + if(bytes.length == 1) type = "BROADCAST"; // odpoved z rsPortu na broadcast command: ["broadcast"] + else if(bytes[4] == 0) type = "RESPONSE"; else if(bytes[4] == 1) type = "ERROR"; else if(bytes[4] == 2) type = "EVENT"; else type = "UNKNOWN"; + let message = "OK"; + let error = ""; + if(type == "BROADCAST") return {message, type, error}; + let crc = crc16('ARC', bytes.slice(0, 9)); let c1 = (crc >> 8) & 0xFF; let c2 = crc & 0xFF; - let message = "OK"; - let error = ""; if(c1 != bytes[9]) { //CRC_ERROR @@ -996,10 +930,11 @@ exports.install = function(instance) { //BUILD TASKS// function buildTasks(params) { - //report FLOW.OMS_edge_fw_version as fw_version + //report SETTINGS.edge_fw_version as fw_version //report date as startdate //return; + console.log("buidTAaasks start ****************", params); monitor.info("buildTasks - params", params); @@ -1056,8 +991,7 @@ exports.install = function(instance) { monitor.info("buildTasks: profile for line", line); monitor.info("profile:", profile); - let time_points = profile.time_points; - if(time_points == undefined) time_points = profile.intervals; + let time_points= profile.intervals; // add name to regular profile timepoint and delete unused end_time key: time_points.forEach(point => { @@ -1158,7 +1092,7 @@ exports.install = function(instance) { //timepoint is in past, we add 24 hours start_time.setDate(start_time.getDate() + 1); } - + let params = getParams(PRIORITY_TYPES.relay_profile); params.type = "relay"; params.line = parseInt(line); @@ -1230,10 +1164,7 @@ exports.install = function(instance) { //PROCESS DEFAULT BROADCASTS - - //RPC pre nody / broadcast - //Time of dusk, Time of dawn - //Actual Time + //Time of dusk, Time of dawn, Actual Time if(processBroadcast) { @@ -1270,7 +1201,6 @@ exports.install = function(instance) { { //run broadcast Time of dawn - // addMinutesToTimestamp = 60*5; addMinutesToTimestamp = 60 * 3; //kazde 3 hodiny zisti novy dawn let params = getParams(PRIORITY_TYPES.node_broadcast); @@ -1298,7 +1228,7 @@ exports.install = function(instance) { } { - //run broadcast //Actual time + //run broadcast Actual time addMinutesToTimestamp = 5; let params = getParams(PRIORITY_TYPES.node_broadcast); @@ -1371,15 +1301,6 @@ exports.install = function(instance) { params.addMinutesToTimestamp = addMinutesToTimestamp; params.info = "generated cmd - buildTasks (node)"; - //monitor last node && last command - /* - if(register == listOfCommands[ listOfCommands.length - 1 ]) - { - //if(k == 632) params.debug = true; - if(k == 698) params.debug = true; - } - */ - tasks.push(params); } @@ -1402,11 +1323,11 @@ exports.install = function(instance) { params.address = 0; params.timestamp = Date.now() + 60000; params.addMinutesToTimestamp = 5; - params.tbname = FLOW.OMS_edgeName; + params.tbname = rvoTbName; params.info = "Master node FW verzia"; //params.debug = true; - //this will set FLOW.OMS_masterNodeIsResponding + //this will set SETTINGS.masterNodeIsResponding tasks.push(params); } @@ -1515,7 +1436,7 @@ exports.install = function(instance) { * function updates status and time_of_last_communication of node in the dbNodes * it only updates if conditions are met * it only updates time_of_last_communication of node, if the last written time was more than 10 minutes ago (600000 miliseconds) - * if newStatus of node is always receiving false, and it is already for more than FLOW.OMS_node_status_nok_time value, we update status to "NOK" in tb + * if newStatus of node is always receiving false, and it is already for more than SETTINGS.node_status_nok_time value, we update status to "NOK" in tb * function returns true, if status of node needs to be updated in TB (newStatus attribute is false in this case). */ function updateNodeStatus(node, newStatus) @@ -1529,65 +1450,43 @@ exports.install = function(instance) { let nodeCurrentStatus = nodeObj.status; const now = Date.now(); - if(newStatus == false && nodeCurrentStatus == false) { - if(node == 638 || node == 637) console.log("false, false, return", node, now) - return true; - } + let data = null; - if(newStatus == true && nodeCurrentStatus == true && nodeObj.time_of_last_communication > now - TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION){ - - if(node == 638 || node == 637) console.log("true true, return", node, now); + if(newStatus == true && nodeCurrentStatus == true && nodeObj.time_of_last_communication > now - TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION) return; + else if(newStatus == true && nodeCurrentStatus == true && nodeObj.time_of_last_communication < now - TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION) + { + data = {time_of_last_communication: now}; + nodeDbStatusModify(node, data); return; } - - if(newStatus == true && nodeCurrentStatus == true && nodeObj.time_of_last_communication < now - TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION) + else if(newStatus == false && nodeCurrentStatus == false) return true; + else if(newStatus == false && nodeCurrentStatus == true) { - dbNodes.modify({ time_of_last_communication: now}).where("node", node).make(function(builder) { - builder.callback(function(err, response) { - if(!err) { - nodeObj.time_of_last_communication = now; - - if(node == 638 || node == 637) console.log('zapisane do db => status true & true', node, now) - } - }); - }); - return; - } - - if(newStatus == false && nodeCurrentStatus == true) - { - if(nodeObj.time_of_last_communication + FLOW.OMS_node_status_nok_time > now) { - if(node == 638 || node == 637) console.log('false true, return', node, now); - return; - } + if(nodeObj.time_of_last_communication + SETTINGS.node_status_nok_time > now) return; else { - dbNodes.modify({ status: newStatus}).where("node", node).make(function(builder) { - builder.callback(function(err, response) { - if(!err) { - nodeObj.status = newStatus; - - if(node == 638 || node == 637) console.log('zapisane do db => status false & true', node, now) - } - }); - }); + data = {status: newStatus}; + nodeDbStatusModify(node, data); return true; } } - - if(newStatus == true && nodeCurrentStatus == false) + else if(newStatus == true && nodeCurrentStatus == false) { - dbNodes.modify({ status: newStatus, time_of_last_communication: now}).where("node", node).make(function(builder) { - builder.callback(function(err, response) { - if(!err) { - nodeObj.status = newStatus; - nodeObj.time_of_last_communication = now; - - if(node == 638 || node == 637) console.log('zapisane do db => status false & true', node, now) - } - }); - }); + data = {status: newStatus, time_of_last_communication: now}; + nodeDbStatusModify(node, data); return; } + + } + + + function nodeDbStatusModify(node, data){ + dbNodes.modify(data).where("node", node).make(function(builder) { + builder.callback(function(err, response) { + if(!err) { + nodesData[node] = {...nodesData[node], ...data}; + } + }); + }); } @@ -1606,7 +1505,7 @@ exports.install = function(instance) { //reportovali sme? if(reportDuskDawn.dusk_time_reported != sunCalcResult.dusk_time) { - sendNotification("CMD Manager: calculated Time of dusk", FLOW.OMS_edgeName, "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; } } @@ -1626,7 +1525,7 @@ exports.install = function(instance) { //reportovali sme? if(reportDuskDawn.dawn_time_reported != sunCalcResult.dawn_time) { - sendNotification("CMD Manager: calculated Time of dawn", FLOW.OMS_edgeName, "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; } } @@ -1660,6 +1559,7 @@ exports.install = function(instance) { { instance.send(SEND_TO.debug, "!rsPort.isOpen"); //await rsPort.open(); + console.log("Cmd_manager - !rsPort.isOpen"); } let currentTask = tasks[0]; @@ -1674,7 +1574,7 @@ exports.install = function(instance) { let params = {...tasks[0]}; //allow terminal commands - if(FLOW.OMS_maintenance_mode && params.type !== "cmd-terminal") + if(SETTINGS.maintenance_mode && params.type !== "cmd-terminal") { interval = setInterval(runTasks, LONG_INTERVAL); return; @@ -1718,7 +1618,6 @@ exports.install = function(instance) { return; } - //relay if(type == "relay") { @@ -1766,7 +1665,7 @@ exports.install = function(instance) { message = "off"; } - sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "switching_profile_point_applied_to_line", {line: params.line, value: message}, "", SEND_TO.tb, instance ); + sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "switching_profile_point_applied_to_line", {line: params.line, value: message}, "", SEND_TO.tb, instance ); interval = setInterval(runTasks, SHORT_INTERVAL); return; } @@ -1790,22 +1689,12 @@ exports.install = function(instance) { logger.debug("rotary_switch_state", rotary_switch_state); logger.debug("state_of_breaker", state_of_breaker[line]); - let dataToTb = { - [tbname]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - //report only once! if(!disconnectedReport.hasOwnProperty(tbname)) disconnectedReport[tbname] = false; if(!disconnectedReport[tbname]) { - //instance.send(SEND_TO.tb, dataToTb); - tbHandler.sendToTb(dataToTb, instance); + sendTelemetry(values, tbname) } interval = setInterval(runTasks, SHORT_INTERVAL); @@ -1818,7 +1707,7 @@ exports.install = function(instance) { const register = params.register; //high_priority - if(!FLOW.OMS_masterNodeIsResponding) + if(!SETTINGS.masterNodeIsResponding) { //ak neodpoveda, nebudeme vykonavat ziadne commands, okrem cmd-terminal, a fw version errorHandler.sendMessageToService("Master node is not responding"); @@ -1840,27 +1729,17 @@ exports.install = function(instance) { relayStatus = relaysData[line].contactor; } + if(line == 0) relayStatus = 0; if(type == "cmd-terminal") relayStatus = 1; //check if rotary_switch_state == "Off" - if(relayStatus == 0) { //console.log("------------------------------------relayStatus", relayStatus, line); let values = {"status": "OFFLINE"}; - - let dataToTb = { - [tbname]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - //instance.send(SEND_TO.tb, dataToTb); - tbHandler.sendToTb(dataToTb, instance); + sendTelemetry(values, tbname) interval = setInterval(runTasks, SHORT_INTERVAL); return; @@ -1948,12 +1827,9 @@ exports.install = function(instance) { endTime = new Date(); var timeDiff = endTime - startTime; - //--1-4 adresa, 5 status ak je status 0 - ok, nasleduju 4 byty data - //let bytes = data.slice(0); - let bytes = data; + //data je array z 11 bytov: 1-4 adresa, 5 status ak je status 0 - ok, nasleduju 4 byty data a 2 byty CRC let dataBytes = data.slice(5,9); - - let result = detectIfResponseIsValid(bytes); + let result = detectIfResponseIsValid(data); //ak sa odpoved zacina 0 - je to v poriadku, inak je NOK let message = result.message; // OK, NOK @@ -1995,7 +1871,7 @@ exports.install = function(instance) { dbNodes.modify({ processed: true }).where("node", nodeAddress).make(function(builder) { builder.callback(function(err, response) { - sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "dimming_profile_was_successfully_received_by_node", {node: nodeAddress}, "", SEND_TO.tb, instance ); + sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "dimming_profile_was_successfully_received_by_node", {node: nodeAddress}, "", SEND_TO.tb, instance ); logger.debug( "--> profil úspešne odoslaný na node č. " + nodeAddress); nodesData[nodeAddress].processed = true; @@ -2019,15 +1895,15 @@ exports.install = function(instance) { //master node if(nodeAddress == 0) { - sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "master_node_is_responding_again", {}, "", SEND_TO.tb, instance, "rvo_status" ); - FLOW.OMS_masterNodeIsResponding = true; - if(register == 4) values["edge_fw_version"] = FLOW.OMS_edge_fw_version; + 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; } //odoslanie príkazu z terminálu - dáta if(type == "cmd-terminal") { - sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "command_was_sent_from_terminal_interface", {}, params, SEND_TO.tb, instance ); + sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "command_was_sent_from_terminal_interface", {}, params, SEND_TO.tb, instance ); } if(params.debug) @@ -2037,17 +1913,7 @@ exports.install = function(instance) { if(saveToTb) { - let dataToTb = { - [tbname]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - - //instance.send(SEND_TO.tb, dataToTb); - tbHandler.sendToTb(dataToTb, instance); + sendTelemetry(values, tbname) } else { @@ -2127,9 +1993,9 @@ exports.install = function(instance) { //master node if(node == 0) { - sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "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); - FLOW.OMS_masterNodeIsResponding = false; + SETTINGS.masterNodeIsResponding = false; if(register == 4) values["master_node_version"] = "NOK"; } @@ -2154,17 +2020,7 @@ exports.install = function(instance) { // console.log("------",node, register, type, itIsNodeCommand, updateStatus, saveToTb, values); if(saveToTb && Object.keys(values).length > 0) { - - let dataToTb = { - [tbName]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - - tbHandler.sendToTb(dataToTb, instance); + sendTelemetry(values, tbName) } } @@ -2217,7 +2073,7 @@ exports.install = function(instance) { if(responseType == "FAILURE") responseObj["message"] = "ERROR WRITE FAILED: " + reason; else responseObj["bytes"] = data; - let refFlowdata = refFlowdataObj[params.refFlowdataKey]; + let refFlowdata = refFlowdataObj[params.refFlowdataKey]; //holds reference to httprequest flowdata if(refFlowdata) { refFlowdata.data = responseObj; @@ -2231,8 +2087,6 @@ exports.install = function(instance) { */ function reportEdgeDateTimeAndNumberOfLuminaires(){ - if(!FLOW.OMS_edgeName) return; - //Number of ok and nok nodes on platform does not equals to total number of nodes. //possible error is, that nodesData object is changing all the time. To make a proper calculation of ok,nok luminaires, we make a copy of it: let nodesData_clone = JSON.parse(JSON.stringify(nodesData)); @@ -2261,83 +2115,77 @@ exports.install = function(instance) { "edge_date_time": ts }; - let dataToTb = { - [FLOW.OMS_edgeName]: [ - { - "ts": ts, - "values": values - } - ] - } - - tbHandler.sendToTb(dataToTb, instance); - //instance.send(SEND_TO.tb, dataToTb); + sendTelemetry(values, SETTINGS.rvoTbName, ts); } - //to ensure, edgeDateTime will be send to tb at full minute - customTasksInterval = setInterval(function() { - if(new Date().getSeconds() === 0) reportEdgeDateTimeAndNumberOfLuminaires(); - }, 1000); + function handleRsPort() { - //! 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 + //! 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(FLOW.OMS_serial_port == "" || FLOW.OMS_serial_port == undefined || FLOW.OMS_serial_port.length === 1) FLOW.OMS_serial_port = "ttymxc4"; - const rsPort = new SerialPort(`/dev/${FLOW.OMS_serial_port}`, { 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); + 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 }); + //(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() { + rsPort.on('open', async function() { - logger.debug("CMD manager - rsPort opened sucess"); + logger.debug("CMD manager - rsPort opened success"); - //loadRelaysData(); + //loadRelaysData(); - await runSyncExec(`stty -F /dev/${FLOW.OMS_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); + 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); - logger.debug(0, "RPC runSyncExec - Promise Resolved:" + status); + logger.debug(0, "RPC runSyncExec - Promise Resolved:" + status); - //APP START - let dataToInfoSender = {id: FLOW.OMS_projects_id, name: FLOW.OMS_rvo_name}; - dataToInfoSender.fw_version = FLOW.OMS_edge_fw_version; - dataToInfoSender.startdate = new Date().toISOString().slice(0, 19).replace('T', ' '); - dataToInfoSender.__force__ = true; - - instance.send(SEND_TO.infoSender, dataToInfoSender); + //APP START + let dataToInfoSender = {id: SETTINGS.project_id, name: SETTINGS.rvo_name}; + dataToInfoSender.fw_version = SETTINGS.edge_fw_version; + dataToInfoSender.startdate = new Date().toISOString().slice(0, 19).replace('T', ' '); + dataToInfoSender.__force__ = true; + + instance.send(SEND_TO.infoSender, dataToInfoSender); - logger.debug(0, "---------------------------->START message send to service", dataToInfoSender); + logger.debug(0, "---------------------------->START message send to service", dataToInfoSender); - }).catch(function (reason) { - instance.send(SEND_TO.debug, "CMD manager - RPC runSyncExec - promise rejected:" + reason); + }).catch(function (reason) { + instance.send(SEND_TO.debug, "CMD manager - RPC runSyncExec - promise rejected:" + reason); + }); }); - }); - rsPort.on('error', function(err) { - - //TODO report to service!!! - //errLogger.error(exports.title, "unable to open port", FLOW.OMS_serial_port, err.message); - errorHandler.sendMessageToService([exports.title, "unable to open port", FLOW.OMS_serial_port, err.message], 0); + 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); - instance.send(SEND_TO.debug, err.message); - }); + instance.send(SEND_TO.debug, err.message); + }); - rsPort.on("close", () => { - rsPort.close(); - }); + rsPort.on("close", () => { + setTimeout(() => rsPort.open(), 1000); + }); - rsPort.open(); + rsPort.open(); + } + instance.on("close", () => { clearInterval(interval); clearInterval(customTasksInterval); + clearInterval(setCorrectTime); rsPort.close(); }); - instance.on("data", async function(flowdata) { + instance.on("0", flowdata => { + main(); + }) + + instance.on("1", async function(flowdata) { //instance.send(SEND_TO.debug, "on Data"); //instance.send(SEND_TO.debug, flowdata); @@ -2434,19 +2282,19 @@ exports.install = function(instance) { } else if(cmd == "rotary_switch_state") { + let value = flowdata.data.value; + //state was changed - if(rotary_switch_state != flowdata.data.value) + if(rotary_switch_state != value) { - if(rotary_switch_state == "Off") + if(value == "Off") { //vyreportovat vsetky svietdla reportOfflineNodeStatus(); } - else reportOnlineNodeStatus(); + rotary_switch_state = value; } - - rotary_switch_state = flowdata.data.value; } else if(cmd == "lux_sensor") { @@ -2501,25 +2349,10 @@ exports.install = function(instance) { else sendNotification("CMD Manager: onData", tbname, "circuit_breaker_was_turned_on_line", {line: line}, "", SEND_TO.tb, instance, "circuit_breaker"); //report status liniu - let values = { - "status": status - }; - - let dataToTb = { - [tbname]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - - //instance.send(SEND_TO.tb, dataToTb); - tbHandler.sendToTb(dataToTb, instance); + sendTelemetry({status: status}, tbname) //current value if(value == "Off") reportOfflineNodeStatus(line); //vyreportovat vsetky svietidla na linii - else reportOnlineNodeStatus(line); } } @@ -2537,7 +2370,11 @@ exports.install = function(instance) { if(flowdata.data.hasOwnProperty("topic")) { - let data = flowdata.data.content.data; + let data = getNested(flowdata.data, "content", "data"); + if(data == undefined) { + console.log("Invalid rpc command came from platform"); + return; + } let command = data.params.command; let method = data.method; @@ -2572,109 +2409,109 @@ exports.install = function(instance) { let node = keys[i]; //logger.debug( node, nodesData[node], tbname); - if(tbname == nodesData[node].tbname.trim()) + if(tbname == nodesData[node].tbname) { - let params = getParams(PRIORITY_TYPES.high_priority); + let params = getParams(PRIORITY_TYPES.high_priority); - value = parseInt(value); - if(value > 0) value = value + 128; + value = parseInt(value); + if(value > 0) value = value + 128; - //set dimming - LUM1_13 - 647 je node linie 1 kt. dobre vidime - params.type = "cmd"; - params.tbname = tbname; - params.address = node; - params.register = 1;//dimming - params.recipient = 1;//slave - params.byte4 = value; - params.rw = 1;//write - params.timestamp = PRIORITY_TYPES.high_priority; - params.info = 'set dimming from platform'; - //params.debug = true; + //set dimming - LUM1_13 - 647 je node linie 1 kt. dobre vidime + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 1;//dimming + params.recipient = 1;//slave + params.byte4 = value; + params.rw = 1;//write + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'set dimming from platform'; + //params.debug = true; - //ak linia je + //ak linia je - //debug(params); - logger.debug("dimming", params); + //debug(params); + logger.debug("dimming", params); - tasks.push(params); + tasks.push(params); - setTimeout(function(){ + setTimeout(function(){ - //spustime o 4 sekundy neskor, s prioritou PRIORITY_TYPES.high_priority - //a pridame aj vyreportovanie dimmingu - { - let params = getParams(PRIORITY_TYPES.high_priority); + //spustime o 4 sekundy neskor, s prioritou PRIORITY_TYPES.high_priority + //a pridame aj vyreportovanie dimmingu + { + let params = getParams(PRIORITY_TYPES.high_priority); - params.type = "cmd"; - params.tbname = tbname; - params.address = node; - params.register = 1;//dimming - params.recipient = 1;//slave - params.rw = 0;//read - params.timestamp = PRIORITY_TYPES.high_priority; - params.info = 'read dimming (after set dimming from platform)'; - params.debug = true; + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 1;//dimming + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read dimming (after set dimming from platform)'; + params.debug = true; - tasks.push(params); - } + tasks.push(params); + } - //pridame aj vyreportovanie - vykon - { - let params = getParams(PRIORITY_TYPES.high_priority); + //pridame aj vyreportovanie - vykon + { + let params = getParams(PRIORITY_TYPES.high_priority); - params.type = "cmd"; - params.tbname = tbname; - params.address = node; - params.register = 76; - params.recipient = 1;//slave - params.rw = 0;//read - params.timestamp = PRIORITY_TYPES.high_priority; - params.info = 'read Input Power (after set dimming from platform)'; - params.debug = true; + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 76; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read Input Power (after set dimming from platform)'; + params.debug = true; - tasks.push(params); - } + tasks.push(params); + } - //pridame aj vyreportovanie - prud svietidla - { - let params = getParams(PRIORITY_TYPES.high_priority); + //pridame aj vyreportovanie - prud svietidla + { + let params = getParams(PRIORITY_TYPES.high_priority); - params.type = "cmd"; - params.tbname = tbname; - params.address = node; - params.register = 75; - params.recipient = 1;//slave - params.rw = 0;//read - params.timestamp = PRIORITY_TYPES.high_priority; - params.info = 'read Input Current (after set dimming from platform)'; - params.debug = true; + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 75; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read Input Current (after set dimming from platform)'; + params.debug = true; - tasks.push(params); - } + tasks.push(params); + } - //pridame aj vyreportovanie - power faktor - ucinnik - { - let params = getParams(PRIORITY_TYPES.high_priority); + //pridame aj vyreportovanie - power faktor - ucinnik + { + let params = getParams(PRIORITY_TYPES.high_priority); - params.type = "cmd"; - params.tbname = tbname; - params.address = node; - params.register = 77; - params.recipient = 1;//slave - params.rw = 0;//read - params.timestamp = PRIORITY_TYPES.high_priority; - params.info = 'read power factor - Cos phi (after set dimming from platform)'; - params.debug = true; + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 77; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read power factor - Cos phi (after set dimming from platform)'; + params.debug = true; - tasks.push(params); - } + tasks.push(params); + } - },4000); - + },4000); + - nodeWasFound = true; + nodeWasFound = true; - break; + break; } } @@ -2702,7 +2539,7 @@ exports.install = function(instance) { for(let i = 0; i < keys.length; i++) { let node = keys[i]; - if(tbname == nodesData[node].tbname.trim()) + if(tbname == nodesData[node].tbname) { if(profile != "") profile = JSON.stringify(profile); @@ -2719,11 +2556,9 @@ exports.install = function(instance) { nodesData[node].processed = false; nodesData[node].profile = profile; - let line = nodesData[node].line; processNodeProfile(node); - - }); }); + }); } } } @@ -2768,10 +2603,16 @@ exports.install = function(instance) { logger.debug("worksys - update relay profile done:", profile); instance.send(SEND_TO.debug, "worksys - update relay profile done"); - loadRelaysData(line).then(function (data) { - logger.debug("loadRelaysData DONE for line", line); - buildTasks({processLineProfiles: true, line: line}); - }); + relaysData[line].profile = profile; + + loadRelaysData(line) + + //TODO build tasks by mala bezat az ked je vsetko loadRelaysData + //spracovane, pravdepodobne treba spravit promisy + logger.debug("loadRelaysData DONE for line", line); + console.log("zacina buildTasks po loadRelaysData.........") + + buildTasks({processLineProfiles: true, line: line}); sendNotification("CMD manager - set profile from worksys", tbname, "switching_profile_was_processed_for_line", {line: line}, profile, SEND_TO.tb, instance ); @@ -2788,16 +2629,16 @@ exports.install = function(instance) { if(command === "switch") { - // if we receive rpc from platform, to switch maintenance mode, we set OMS_maintenance_mode flow variable to value; - if(entity_type === "edb" || entity_type === "edb_ver4_se") FLOW.variables.OMS_maintenance_mode = value; - - let responseRelays = await promisifyBuilder(dbRelays.find().where("tbname", tbname)); + // if we receive rpc from platform, to switch maintenance mode, we set SETTINGS.maintenance_mode flow variable to value; + if(entity_type === "edb" || entity_type === "edb_ver4_se") SETTINGS.maintenance_mode = value; + const relayObject = getObjectByTbValue(relaysData, tbname); let line = 0; - if(responseRelays.length == 1) line = responseRelays[0].line; + if(isObject(relayObject)) line = relayObject.line; - if(value == false) turnOffLine(line, "command received form platform"); - else turnOnLine(line, "command received form platform"); + // v relaysData je contactor bud 0 alebo 1, ale z platformy prichadza true, false; + if(value == false) turnOffLine(line, "command received from platform"); + else turnOnLine(line, "command received from platform"); } } else @@ -2859,7 +2700,11 @@ exports.install = function(instance) { } }) -} // end of instance.export + +//function gets value of a nested property in an object and returns undefined if it does not exists: +function getNested(obj, ...args) { + return args.reduce((obj, level) => obj && obj[level], obj) +} /** @@ -2977,10 +2822,6 @@ function detectReadOnlyFilesystem() }); } -let setCorrectTime = setInterval(setCorrectPlcTimeOnceADay, 60000 * 60); // 1 hour -setCorrectPlcTimeOnceADay(); - - @@ -2989,6 +2830,19 @@ setCorrectPlcTimeOnceADay(); ///helper functions +function sendTelemetry(values, tbname, date=Date.now()) +{ + const dataToTb = { + [tbname]: [ + { + "ts": date, + "values": values + } + ] + } + + tbHandler.sendToTb(dataToTb, instance); +} function calculateDuskDawn(date, line, duskOffset = 0, dawnOffset = 0) { @@ -3268,7 +3122,8 @@ function processResponse(register, bytes) var d = new Date(); d.setHours(h); d.setMinutes(m); - d.setSeconds(s); + d.setSeconds(0); + d.setMilliseconds(0); timestamp = d.getTime(); } @@ -3381,223 +3236,13 @@ function com_generic(adresa, rec, rw, register, name, byte1, byte2, byte3, byte4 } - - - - - -// SAMPLE DATA - -const relaysDataExample = -{ - '0': { - line: 0, - tbname: 'jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV', - contactor: 1, - profile: '' - }, - '1': { - line: 1, - tbname: 'MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O', - contactor: 1, - profile: '{"intervals":[{"value":1,"end_time":"13:00","start_time":"13:00"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' - }, - '2': { - line: 2, - tbname: 'jBL12pg63eX4N9P7zy0lJLyEJKmlbkGwZMx0avQV', - contactor: 1, - profile: '{"intervals":[{"value":1,"end_time":"13:00","start_time":"13:00"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' - }, - '3': { - line: 3, - tbname: 'aAOzENGrvpbe0VoK7D6E1a819PZmdg3nl24JLQMk', - contactor: 1, - profile: '{"intervals":[{"value":1,"end_time":"13:00","start_time":"13:00"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' + function getObjectByTbValue(object, tbname) { + return object[Object.keys(object).find(key => object[key].tbname === tbname)]; } -} - -const rpcSwitchOffLine = -{ - "topic": "v1/gateway/rpc", - "content": { - "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", - "data": { - "id": 8, - "method": "set_command", - "params": { - "entities": [ - { - "entity_type": "edb_line", - "tb_name": "MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O" - } - ], - "command": "switch", - "payload": { - "value": false - } - } - } + function isObject (item) { + return (typeof item === "object" && !Array.isArray(item) && item !== null); } -} -const rpcSetNodeDimming = -{ - "topic": "v1/gateway/rpc", - "content": { - "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", - "data": { - "id": 10, - "method": "set_command", - "params": { - "entities": [ - { - "entity_type": "street_luminaire", - "tb_name": "jbN4q7JPZmexgdnz2yKbWdDYAWwO0Q3BMX6ERLoV" - } - ], - "command": "dimming", - "payload": { - "value": 5 - } - } - } - } -} +} // end of instance.export -const rpcLineProfile = -{ - "topic": "v1/gateway/rpc", - "content": { - "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", - "data": { - "id": 9, - "method": "set_profile", - "params": { - "entities": [ - { - "entity_type": "edb_line", - "tb_name": "MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O" - } - ], - "payload": { - "intervals": [ - { - "value": 0, - "end_time": "20:00", - "start_time": "13:00" - }, - { - "value": 1, - "end_time": "05:30", - "start_time": "20:00" - }, - { - "value": 0, - "end_time": "13:00", - "start_time": "05:30" - } - ], - "astro_clock": true, - "dawn_lux_sensor": false, - "dusk_lux_sensor": false, - "dawn_lux_sensor_value": 5, - "dusk_lux_sensor_value": 5, - "dawn_astro_clock_offset": 0, - "dusk_astro_clock_offset": 0, - "dawn_lux_sensor_time_window": 30, - "dusk_lux_sensor_time_window": 30, - "dawn_astro_clock_time_window": 60, - "dusk_astro_clock_time_window": 60 - } - } - } - } -} - - -const rpcNodeProfile = -{ - "topic": "v1/gateway/rpc", - "content": { - "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", - "data": { - "id": 11, - "method": "set_profile", - "params": { - "entities": [ - { - "entity_type": "street_luminaire", - "tb_name": "jbN4q7JPZmexgdnz2yKbWdDYAWwO0Q3BMX6ERLoV" - } - ], - "payload": { - "intervals": [ - { - "cct": 3000, - "value": 0, - "end_time": "17:50", - "start_time": "13:00" - }, - { - "cct": 3000, - "value": 100, - "end_time": "21:30", - "start_time": "17:50" - }, - { - "cct": 3000, - "value": 0, - "end_time": "13:00", - "start_time": "07:10" - }, - { - "cct": 3000, - "value": 50, - "end_time": "00:00", - "start_time": "21:30" - }, - { - "cct": 3000, - "value": 10, - "end_time": "04:30", - "start_time": "00:00" - }, - { - "cct": 3000, - "value": 100, - "end_time": "07:10", - "start_time": "04:30" - } - ], - "astro_clock": true, - "dawn_lux_sensor": false, - "dusk_lux_sensor": false, - "dawn_lux_sensor_value": 5, - "dusk_lux_sensor_value": 5, - "dawn_astro_clock_offset": 30, - "dusk_astro_clock_offset": 20, - "dawn_lux_sensor_time_window": 30, - "dusk_lux_sensor_time_window": 30, - "dawn_astro_clock_time_window": 60, - "dusk_astro_clock_time_window": 60 - } - } - } - } -} - - const sunCalcExample = { - dusk_no_offset: '20:18', - dawn_no_offset: '05:19', - dusk: '20:18', - dusk_hours: 20, - dusk_minutes: 18, - dawn: '05:19', - dawn_hours: 5, - dawn_minutes: 19, - dusk_time: 1715278688962, - dawn_time: 1715224744357, - dusk_astro_clock_offset: 0, - dawn_astro_clock_offset: 0 -} diff --git a/flow/cmd_manager131.js b/flow/cmd_manager131.js new file mode 100644 index 0000000..63c1a1f --- /dev/null +++ b/flow/cmd_manager131.js @@ -0,0 +1,3603 @@ +exports.id = 'cmd_manager'; +exports.title = 'CMD Manager'; +exports.group = 'Worksys'; +exports.color = '#5D9CEC'; +exports.version = '0.0.3'; +exports.output = ['red', 'blue', 'yellow', 'blue', 'white']; + +//blue - send message to relays + +exports.input = 2; +exports.icon = 'cloud-upload'; +//exports.npm = ['serialport' , 'child_process']; + +exports.html = ` +
+
+
+
RPC - run RPC calls

+
+
+
@(User)
+
+
+
@(Password)
+
+
+
@(My edge)
+
+
+
+`; + +exports.readme = `Manager for CMD calls`; + +const SerialPort = require('serialport'); +const { exec } = require('child_process'); +const { crc8, crc16, crc32 } = require('easy-crc'); +const { openPort, runSyncExec, writeData } = require('./helper/serialport_helper.js'); +const { bytesToInt, longToByteArray, addZeroBefore, isEmptyObject, convertUTCDateToLocalDate } = require('./helper/utils'); +const bitwise = require('bitwise'); + +var SunCalc = require('./helper/suncalc.js'); +const DataToTbHandler = require('./helper/DataToTbHandler.js'); +const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler.js'); +const { promisifyBuilder, makeMapFromDbResult} = require('./helper/db_helper.js'); +const { sendNotification, initNotifications, ERRWEIGHT } = require('./helper/notification_reporter.js'); + +const dbNodes = TABLE("nodes"); +const dbRelays = TABLE("relays"); +const dbSettings = TABLE("settings"); + +//https://github.com/log4js-node/log4js-node/blob/master/examples/example.js +//file: { type: 'file', filename: path.join(__dirname, 'log/file.log') } +var path = require('path'); +var log4js = require("log4js"); +const process = require('process'); + +//TODO - to remove? +// runTasks intervals +const SHORT_INTERVAL = 30; +const LONG_INTERVAL = 300; + +//send data to following instances: +const SEND_TO = { + debug: 0, + tb: 1, + http_response: 2, + dido_controller: 3, + infoSender: 4 +} + +const PRIORITY_TYPES = { + terminal: 0, + fw_detection: 1,//reserved only for FW detection - FLOW.OMS_masterNodeIsResponding + high_priority: 2,//reserverd only for: read dimming / brightness (after set dimming from platform) + relay_profile: 3, + node_broadcast: 4, + node_profile: 5, + node_cmd: 6 +} + +const TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION = 600000; // 10 minutes + +//list of command calls to process. Processing in runTasks function +let tasks = []; + +let interval = null;//timeout for procesing tasks +let customTasksInterval = null; +let refFlowdata = null;//holds reference to httprequest flowdata +let refFlowdataObj = {}; + +//load from settings +let latitude = 48.70826502;//48.682255758; +let longitude = 17.28455203;//17.278910807; + +const gmtOffset = 0; + +//ak nie je nastaveny +//https://www.tecmint.com/set-time-timezone-and-synchronize-time-using-timedatectl-command/ +//https://stackoverflow.com/questions/16086962/how-to-get-a-time-zone-from-a-location-using-latitude-and-longitude-coordinates + +//priorities for registers +let priorities = []; + +let minutes = 1; +priorities["0"] = minutes; +priorities["1"] = minutes; + +minutes = 5; +priorities["74"] = minutes; +priorities["75"] = minutes; +priorities["76"] = minutes; +priorities["77"] = minutes; +priorities["78"] = minutes; +priorities["79"] = minutes; +priorities["84"] = minutes; + +minutes = 10; +priorities["87"] = minutes; +priorities["6"] = minutes; +priorities["7"] = minutes; +priorities["80"] = minutes; +priorities["8"] = minutes; +priorities["3"] = minutes; +priorities["89"] = minutes; + +//prikazy kt sa budu spustat na dany node - see config.js in terminal-oms.app. (1 - dimming) +let listOfCommands = [0,1,3,6,7,8,74,75,76,77,78,79,80,84,87,89]; + +const errorHandler = new ErrorToServiceHandler(); + +let rotary_switch_state = "Off"; +let lux_sensor; +let state_of_breaker = {};//key is line, value is On/Off +let disconnectedReport = {};//key is tbname, value true/false + +let relaysData = {};//key is line, value is data from db +let nodesData = {};//key is node, value data from db + +//helper container for counting resolved group of commands (commands related to set profile) +let cmdCounter = {};//key is node, value is counter + +//END OF VARIABLE SETTINGS +//-------------------------------- + + +log4js.configure({ + appenders: { + errLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'err.txt') }, + monitorLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'monitor.txt') }, + console: { type: 'console' } + }, + categories: { + errLogs: { appenders: ['console', 'errLogs'], level: 'error' }, + monitorLogs: { appenders: ['console', 'monitorLogs'], level: 'trace' }, + //another: { appenders: ['console'], level: 'trace' }, + default: { appenders: ['console'], level: 'trace' } + } +}); + +const errLogger = log4js.getLogger("errLogs"); +const logger = log4js.getLogger(); +const monitor = log4js.getLogger("monitorLogs"); + +//USAGE +//logger.debug("text") +//monitor.info('info'); +//errLogger.error("some error"); + + +function cmdCounterResolve(address) +{ + if(cmdCounter.hasOwnProperty(address)) + { + cmdCounter[address] = cmdCounter[address] - 1; + + let result = cmdCounter[address]; + if(result == 0) delete cmdCounter[address]; + return result; + } + return -1; +} + + +function getParams(priority) +{ + let params = {}; + + //core rpc values + params.address = 0;//if(recipient === 0) address = 0; + params.byte1 = 0;//msb, podla dokumentacie data3 + params.byte2 = 0;//podla dokumentacie data2 + params.byte3 = 0;//podla dokumentacie data1 + params.byte4 = 0;//lsb, podla dokumentacie data0 + params.recipient = 0;//0: Master, 1: Slave, 2: Broadcast + params.register = -1;//register number + params.rw = 0;//0: read, 1: write + + //other values + //params.type = "cmd"; "relay" "cmd-terminal" "set_node_profile" "process_profiles" + //params.tbname = tbname; + params.priority = PRIORITY_TYPES.node_cmd; //default priority - if more tasks with the same timestamp, we sort them based on priority + params.timestamp = 0; //execution time - if timestamp < Date.now(), the task is processed + if(priority != undefined ) + { + params.timestamp = priority; + params.priority = priority; + } + + params.addMinutesToTimestamp = 0;//repeat task if value is > 0, + + //params.timePointName = "luxOff" // "luxOn", "dusk", "dawn", "profileTimepoint" + //params.info = ""; + + return params; +} + + +async function loadSettings() +{ + let responseSettings = await promisifyBuilder(dbSettings.find()); + + latitude = responseSettings[0]["latitude"]; + longitude = responseSettings[0]["longitude"]; + + //globals + FLOW.OMS_language = responseSettings[0]["lang"]; + FLOW.OMS_rvo_name = responseSettings[0]["rvo_name"]; + FLOW.OMS_projects_id = responseSettings[0]["projects_id"]; + //FLOW.OMS_rvo_tbname = responseSettings[0]["tbname"]; + FLOW.OMS_temperature_adress = responseSettings[0]["temperature_adress"]; + FLOW.OMS_controller_type = responseSettings[0]["controller_type"]; + FLOW.OMS_serial_port = responseSettings[0]["serial_port"]; + FLOW.OMS_node_status_nok_time = responseSettings[0]["node_status_nok_time"] * 60 * 60 * 1000; // hour * minutes * seconds + //logger.debug('settings', responseSettings[0]); + + initNotifications(); +} + +loadSettings(); + + +async function loadNodes() +{ + const responseNodes = await promisifyBuilder(dbNodes.find()); + nodesData = makeMapFromDbResult(responseNodes, "node"); +} + +loadNodes(); + + +//nastav profil nodu +function processNodeProfile(node) +{ + if(rotary_switch_state != "Automatic") + { + logger.debug("unable to process profile for node", node, "rotary_switch_state != Automatic"); + return; + } + + let nodeObj = nodesData[node]; + let line = nodeObj.line; + + if(relaysData[line].contactor == 0) + { + logger.debug("line line is off", line, node); + return; + } + + if(nodeObj.processed == 1) + { + logger.debug("node was already processed", node); + return; + } + + let nodeProfile = nodeObj.profile; + logger.debug("processNodeProfile: start - set profile for ", node, nodeProfile); + if(nodeProfile) { + + try { + nodeProfile = JSON.parse(nodeProfile); + } catch (error) { + logger.debug("Cmd_manager - Error parsing node profile", error); + } + + } + + logger.debug("processNodeProfile", node, line, nodeObj, nodeProfile); + + let timestamp = PRIORITY_TYPES.node_cmd; + + removeTask({type: "set_node_profile", address: node}); + + if(nodeProfile === "") + { + //vypneme profil nodu, posleme cmd + //Pokiaľ je hodnota rovná 1 – Profil sa zapne, ostatné bity sa nezmenia. + //Pokiaľ sa hodnota rovná 2 – profil sa vypne, ostatné bity sa nezmenia + + logger.debug("turn off profile"); + + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 96; + params.recipient = 1; + params.register = 8; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'turn off/reset node profile'; + + cmdCounter[node] = 1; + + tasks.push(params); + } + else + { + let tasksProfile = []; + + //vypneme profil - Zapísať hodnotu 32 do registra Time Schedule Settings – reset profilu + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 96; + params.recipient = 1; + params.register = 8; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'turn off node profile'; + + tasksProfile.push(params); + + timestamp++; + + logger.debug("processNodeProfile: TS1 Time point a TS1 Time Point Levels ", node); + + //TS1 Time point a TS1 Time Point Levels + let register = 9; + for(let i = 0; i < nodeProfile.intervals.length; i++) + { + let obj = nodeProfile.intervals[i]; + //let timePoint = obj.time_point; + let dim_value = obj.value; + + + //Reg 9 až Reg 40 + + /* + Samotný profil sa zapisuje do max. 16 párov – časový bod a úroveň. + Prázdny profil je vtedy keď časový bod obsahuje hodnotu 0xFFFFFFFF (táto hodnota sa zapíše do registrov keď sa aktivuje reset profilu do registru 8). + Páry sa prechádzajú časovo zoradené takže teoreticky je jedno v akom poradí sa zapisujú ale je lepšie ich zapisovať v chronologickom poradí od 13:00. + Časový bod má formát: + Byte 3: hodiny Byte 2: minúty Byte 1: sekundy Byte 0 – rezervované + Register úrovne má rovnaký formát ako dimming register (Reg 1). + */ + + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + //params.byte1 = 0;//msb, podla dokumentacie data3 + //params.byte2 = 0;//podla dokumentacie data2 + //params.byte3 = 0;//podla dokumentacie data1 + //params.byte4 = 0;//lsb, podla dokumentacie data0 + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + let start_time = obj.start_time; + let t = start_time.split(":"); + //if(timePoint != undefined) t = timePoint.split(":"); + //else t = [0,0]; + + logger.debug("processNodeProfile: TS1 Time point ", (i + 1), node); + + params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = parseInt(t[0]);//hh + params.byte2 = parseInt(t[1]);//mm + params.byte3 = 0;//ss + params.byte4 = 0;// + params.recipient = 1; + params.register = register; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'TS1 Time point ' + (i + 1); + + tasksProfile.push(params); + + register++; + timestamp++; + + params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0;//ss + params.byte4 = parseInt(dim_value) + 128;// + params.recipient = 1; + params.register = register; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'TS1 Time point Levels ' + (i + 1); + + tasksProfile.push(params); + + register++; + timestamp++; + } + + //Threshold lux level for DUSK/DAWN + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + //params.byte1 = 0;//msb, podla dokumentacie data3 + //params.byte2 = 0;//podla dokumentacie data2 + //params.byte3 = 0;//podla dokumentacie data1 + //params.byte4 = 0;//lsb, podla dokumentacie data0 + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + + //Time schedule settings na koniec + //if(nodeProfile.dusk_lux_sensor || nodeProfile.dawn_lux_sensor) + { + + logger.debug("processNodeProfile: Threshold lux level for DUSK/DAWN", node); + + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 96; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "Threshold lux level for DUSK/DAWN"; + + if(nodeProfile.dusk_lux_sensor) + { + let v = nodeProfile.dusk_lux_sensor_value; + let ba = longToByteArray(v); + + params.byte1 = ba[1];//msb + params.byte2 = ba[0]; + } + + if(nodeProfile.dawn_lux_sensor) + { + let v = nodeProfile.dawn_lux_sensor_value; + let ba = longToByteArray(v); + + params.byte3 = ba[1];//msb + params.byte4 = ba[0]; + } + + tasksProfile.push(params); + timestamp++; + + } + + //DUSK/DAWN max. adjust period + { + + logger.debug("processNodeProfile: DUSK/DAWN max. adjust period", node); + + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 97; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "DUSK/DAWN max. adjust period"; + + if(nodeProfile.astro_clock) + { + let v = nodeProfile.dusk_lux_sensor_time_window; + let ba = longToByteArray(v); + + params.byte1 = ba[1];//msb + params.byte2 = ba[0]; + } + + if(nodeProfile.astro_clock) + { + let v = nodeProfile.dawn_lux_sensor_time_window; + let ba = longToByteArray(v); + + params.byte3 = ba[1];//msb + params.byte4 = ba[0]; + } + + tasksProfile.push(params); + timestamp++; + + } + + //Static offset + { + + //Statický offset pre časy úsvitu a súmraku. Byte 1 je pre DUSK, Byte 0 je pre DAWN. Formát: + //Bity 0 – 6: hodnota v minútach + //Bit 7: znamienko (1 – mínus) + + logger.debug("processNodeProfile: Static offset", node); + + let params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 98; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "Static offset"; + + if(nodeProfile.astro_clock) + { + let dusk_astro_clock_offset = parseInt(nodeProfile.dusk_astro_clock_offset); + let dawn_astro_clock_offset = parseInt(nodeProfile.dawn_astro_clock_offset); + + if(dusk_astro_clock_offset < 0) + { + params.byte3 = (dusk_astro_clock_offset * -1) + 128; + } + else + { + params.byte3 = dusk_astro_clock_offset; + } + + if(dawn_astro_clock_offset < 0) + { + params.byte4 = (dawn_astro_clock_offset * -1) + 128; + } + else + { + params.byte4 = dawn_astro_clock_offset; + } + } + + tasksProfile.push(params); + timestamp++; + } + + logger.debug("Time schedule settings - turn on", node); + + params = getParams(PRIORITY_TYPES.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 8; + params.recipient = 1; + params.rw = 1;//write + + //Time schedule settings + let bits = []; + + //Byte 0 (LSB): + //Bit 0 (LSB) – zapnutie/vypnutie profilov ako takých (1 – zapnuté). + bits.push(1); + //Bit 1 – 3 - zatiaľ nepoužité (zapisovať 0) + bits.push(0); + bits.push(0); + bits.push(0); + if(nodeProfile.astro_clock == true) + { + //Bit 4 – ak je nastavený profil sa riadi podľa astrohodín, a je 0 tak profil je jednoduchý + bits.push(1); + } + else bits.push(0); + + //Bit 5 – zápis 1 spôsobí reset nastavení profilu (nastavenie prázdneho profilu) + bits.push(0); + + //Bity 6-7 - zatiaľ nepoužité + bits.push(0); + bits.push(0); + + params.byte4 = bitwise.byte.write(bits.reverse()); + + //Byte 2 – nastavenie pre lux senzor: + bits = []; + + //Bit 0 (LSB) – riadenie súmraku podľa lux senzoru (1 – zapnuté). Súmrak sa môže posúvať v rámci času v registri 97 podľa intenzity osvetlenia + if(nodeProfile.dusk_lux_sensor == true)//sumrak + { + bits.push(1); + } + else bits.push(0); + + //Bit 1 - riadenie úsvitu podľa lux senzoru (1 – zapnuté). Úsvit sa môže posúvať v rámci času v registri 97 podľa intenzity osvetlenia + if(nodeProfile.dawn_lux_sensor == true)//usvit + { + bits.push(1); + } + else bits.push(0); + + //Bit 2 – zdroj pre hodnotu luxov – 0 – RVO posiela hodnoty zo svojho luxmetra, 1 – node má pripojený svoj vlastný lux meter. + bits.push(0);//zatial neimplementovane + + //Bit 3 – 7 - nepoužité + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + + params.byte2 = bitwise.byte.write(bits.reverse()); + params.timestamp = timestamp; + params.info = "Time schedule settings - turn on"; + + tasksProfile.push(params); + + //zaver + cmdCounter[node] = tasksProfile.length; + + //tasks.push(tasksProfile); + tasks = tasks.concat(tasksProfile); + + } + + logger.debug("finished set profile for ", node); + +} + + +function cleanUpRefFlowdataObj() +{ + let now = new Date(); + let timestamp = now.getTime(); + + //clear old refFlowdata references + let keys = Object.keys(refFlowdataObj); + for(let i = 0; i < keys.length; i++) + { + let timestampKey = keys[i]; + + if((timestamp - timestampKey) > 60*1000 ) + { + console.log("cleanUpRefFlowdataObj delete", timestampKey); + delete refFlowdataObj[ timestampKey ]; + } + } +} + + +function removeTask(obj) +{ + let keys = Object.keys(obj); + tasks = tasks.filter((task) => { + + let counter = 0; + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + if(task.hasOwnProperty(key) && obj.hasOwnProperty(key)) + { + if(task[key] == obj[key]) counter++; + } + } + + if(counter == keys.length) return false; + return true; + }); +} + + +exports.install = function(instance) { + + let now = new Date(); + console.log("CMD Manager installed", now.toLocaleString("sk-SK")); + + const tbHandler = new DataToTbHandler(SEND_TO.tb); + tbHandler.setSender(exports.title); + + //FLOW.OMS_projects_id, name: FLOW.OMS_rvo_name + //const errorHandler = new ErrorToServiceHandler(instance, SEND_TO.infoSender); + errorHandler.setProjectsId(FLOW.OMS_projects_id); + //const errorHandler = new ErrorToServiceHandler(instance); + //errorHandler.sendMessageToService("ahoj", 0); + + let sunCalcResult = calculateDuskDawn(); + + let reportDuskDawn = { + dusk_time: sunCalcResult.dusk_time, + dawn_time: sunCalcResult.dawn_time, + dusk_time_reported: undefined, + dawn_time_reported: undefined + }; + + + process.on('uncaughtException', function (err) { + //TODO send to service + + errLogger.error('uncaughtException:', err.message) + errLogger.error(err.stack); + + errorHandler.sendMessageToService(err.message + "\n" + err.stack, 0, "js_error"); + //process.exit(1); + }) + + //te();//force error + + + function processAllNodeProfilesOnLine(line) + { + + for (let k in nodesData) { + //node:number|tbname:string|line:number|profile:string|processed:boolean + + if(line == nodesData[k].line) + { + let node = nodesData[k].node; + let processed = nodesData[k].processed; + + if(!processed) + { + processNodeProfile(node); + } + else + { + logger.debug( `Node ${node} profile for line ${nodesData[k].line} was already processed`); + } + } + } + } + + + async function loadRelaysData(line) { + + relaysData = await promisifyBuilder(dbRelays.find()); + relaysData = makeMapFromDbResult(relaysData, "line"); + + for (const [key, value] of Object.entries(relaysData)) + { + if(key == "0") continue; + if(line != undefined) + { + //ak sa jedna o update profilu linie - pozor dido_controller posiela command pre loadRelaysData + if(line != value.line ) continue; + } + + if(value.contactor == 1) processAllNodeProfilesOnLine(value.line); + } + +// console.log('.........', relaysData); + } + + + function reportOnlineNodeStatus(line) + { + //broadcast cas, o 3 sek neskor - status, brightness + //Po zapnutí línie broadcastovo aktualizovať predtým čas. + + logger.debug("--->reportOnlineNodeStatus for line", line); + + //return; + + //run broadcast //Actual time + addMinutesToTimestamp = 0; + + let params = {}; + + var d = new Date(); + let hours = d.getHours(); + let minutes = d.getMinutes(); + let seconds = d.getSeconds(); + + let time = d.getTime(); // time in ms + + params.address = 0xffffffff;//Broadcast + params.byte1 = hours;//h + params.byte2 = minutes;//m + params.byte3 = seconds;//s + params.byte4 = 0; + params.recipient = 2;//2 broadcast, address = 0 + params.register = 87;//Actual time + params.rw = 1;//write + + //other values + params.type = "cmd"; + params.timestamp = Date.now() + 60000; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "run broadcast: Actual time"; + + tasks.push(params); + + let sec = 3; + setTimeout(function(){ + //Po zapnutí línie - spraviť hromadný refresh stavu práve zapnutých svietidiel + + for (let k in nodesData) { + + //potrebujem nody k danej linii + if(line == nodesData[k].line || line == undefined) + { + let tbname = nodesData[k].tbname; + let node = nodesData[k].node; + let status = "NOK"; + + // if status of node was "OK" before switching it off, we set the node's time_of_last_communication on time, it was switched on again and send OK status to tb. + if(nodesData[k].status) { + status = "OK"; + nodesData[k].time_of_last_communication = time; + } + + let dataToTb = { + [tbname]: [ + { + ts: time, + values: { + status: status + } + } + ] + } + + tbHandler.sendToTb(dataToTb, instance); + + //prud, vykon - current, input power pre liniu pre vsetky nody + + //a pridame aj vyreportovanie dimmingu + { + let params = getParams(PRIORITY_TYPES.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 1;//dimming + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read dimming'; + //params.debug = true; + + tasks.push(params); + } + + //Prúd + { + let params = getParams(PRIORITY_TYPES.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 75;//prud + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read current'; + //params.debug = true; + + tasks.push(params); + } + + //výkon + { + let params = getParams(PRIORITY_TYPES.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 76;//výkon + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read power'; + //params.debug = true; + + tasks.push(params); + } + } + } + },sec*1000); + } + + + function reportOfflineNodeStatus(line) + { + logger.debug("--->reportOfflineNodeStatus for line", line); + + values = {}; + values["dimming"] = 0;//brightness + values["power"] = 0;//výkon + values["current"] = 0;//prúd + values["status"] = "OFFLINE"; + + // it happens, that some data did not get to tb after sending + // we setTimeout to make more time for db to process telemetry (eg 150 messages at once) + Object.keys(nodesData).forEach((node, index) => { + + setTimeout(function() { + + //potrebujem nody k danej linii + if(line == nodesData[node].line || line == undefined) + { + let tbname = nodesData[node].tbname; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(SEND_TO.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + } + + },(index+1) * 300); + }) + + } + + + function turnOnLine(line, info) + { + let obj = { + line: line, + command: "turnOn", + info: info + }; + + logger.debug("linia", line, obj); + instance.send(SEND_TO.dido_controller, obj); + } + + function turnOffLine(line, info) + { + let obj = { + line: line, + command: "turnOff", + info: info + }; + + logger.debug("linia", line, obj); + instance.send(SEND_TO.dido_controller, obj); + } + + + function detectIfResponseIsValid(bytes) + { + //ak sa odpoved zacina 0 - je to v poriadku, inak je NOK + let type = "RESPONSE"; + if(bytes[4] == 0) type = "RESPONSE"; + else if(bytes[4] == 1) type = "ERROR"; + else if(bytes[4] == 2) type = "EVENT"; + else type = "UNKNOWN"; + + let crc = crc16('ARC', bytes.slice(0, 9)); + let c1 = (crc >> 8) & 0xFF; + let c2 = crc & 0xFF; + + let message = "OK"; + let error = ""; + if(c1 != bytes[9]) + { + //CRC_ERROR + message = "NOK"; + error = "CRC_ERROR c1"; + instance.send(SEND_TO.debug, "CRC_ERROR c1"); + } + + if(c2 != bytes[10]) + { + //CRC_ERROR + message = "NOK"; + error = "CRC_ERROR c2"; + instance.send(SEND_TO.debug, "CRC_ERROR c2"); + } + + //crc error + if(type != "RESPONSE") + { + instance.send(SEND_TO.debug, bytes); + instance.send(SEND_TO.debug, "RESPONSE " + type + " - " + bytes[4]); + + //logger.debug(SEND_TO.debug, "RESPONSE " + type + " - " + bytes[4], bytes); + + error = "type is: " + type; + + message = "NOK"; + } + + return {message, type, error}; + } + + + //BUILD TASKS// + function buildTasks(params) + { + //report FLOW.OMS_edge_fw_version as fw_version + //report date as startdate + + //return; + + monitor.info("buildTasks - params", params); + + let processLine; //defined line + let init = false; + let processLineProfiles = true; + let processBroadcast = true; + let processNodes = true; + + if(params == undefined) + { + init = true; + tasks = []; + logger.debug("-->buildTasks clear tasks"); + } + else + { + processLineProfiles = false; + processBroadcast = false; + processNodes = false; + + processLineProfiles = params.processLineProfiles; + processLine = params.line; + } + + //load profiles pre linie + //relaysData[ record["line"] ] + + let now = new Date(); + + if(processLineProfiles) + { + //process line profiles + let keys = Object.keys(relaysData); + for(let i = 0; i < keys.length; i++) + { + let line = parseInt(keys[i]); //line is turned off by default + let profilestr = relaysData[line].profile; + + if(processLine != undefined) + { + if(processLine != line) continue; + } + + try { + + /** + * we process line profiles: timepoints, astro clock, lux_sensor, offsets ... + */ + if(profilestr === "") throw ("Profile is not defined"); + let profile = JSON.parse(profilestr); + if(Object.keys(profile).length === 0) throw ("Profile is empty"); + + monitor.info("buildTasks: profile for line", line); + monitor.info("profile:", profile); + + let time_points = profile.time_points; + if(time_points == undefined) time_points = profile.intervals; + + // add name to regular profile timepoint and delete unused end_time key: + time_points.forEach(point => { + point.name = "profileTimepoint" + delete point.end_time; + }); + + //monitor.info("buildTasks: time_points", time_points); + + let currentValue = 0; + if(time_points.length > 0) currentValue = time_points[time_points.length - 1].value; + + + /** + * if astro_clock is true, we create timepoints, that switch on/off relays accordingly. + * we need to manage, astro clock timepoints has the greatest priority - normal timepoints will not switch off/on lines before dusk or dawn + * if dawn/dusk_lux_sensor is true, it has higher priority than astro_clock switching + */ + if(profile.astro_clock == true) + { + + // if astro clock true, we remove all regular profile points + time_points = []; + + let sunCalcResult = calculateDuskDawn(new Date(), line); + + // adding dusk dawn to timpoints + if(profile.dawn_lux_sensor == false) time_points.push({"start_time": sunCalcResult["dawn"], "value": 0, "name":"dawn"}); + if(profile.dusk_lux_sensor == false) time_points.push({"start_time": sunCalcResult["dusk"], "value": 1, "name":"dusk"}); + + //if dusk/dawn is true, lines will switch on/off according to lux_sensor value. In case it fails, we create lux_timepoints, to make sure lines will switch on/off (aby nam to nezostalo svietit) + //force to turn off after timestamp: dawn + dawn_lux_sensor_time_window + if(profile.dawn_lux_sensor == true) + { + let [ahours, aminutes, aseconds] = sunCalcResult["dawn"].split(':'); + let ad = new Date(); + ad.setHours(parseInt(ahours)); + ad.setMinutes(parseInt(aminutes) + profile.dawn_lux_sensor_time_window); + ad.setSeconds(0); + + let strDate = ad.getHours() + ":" + ad.getMinutes(); + + time_points.push({"value": 0, "start_time": strDate, "name": "luxOff"}); + } + + if(profile.dusk_lux_sensor == true) + { + let [ahours, aminutes, aseconds] = sunCalcResult["dusk"].split(':'); + let ad = new Date(); + ad.setHours(parseInt(ahours)); + ad.setMinutes(parseInt(aminutes) + profile.dusk_lux_sensor_time_window); + ad.setSeconds(0); + + let strDate = ad.getHours() + ":" + ad.getMinutes(); + + time_points.push({"value": 1, "start_time": strDate, "name": "luxOn"}); + //time_points.push({"value": 1, "start_time": "15:19", "name": "luxOn"}); //testing + } + } + + //sort time_points + time_points.sort(function (a, b) { + + let [ahours, aminutes, aseconds] = a.start_time.split(':'); + let [bhours, bminutes, bseconds] = b.start_time.split(':'); + + let ad = new Date(); + ad.setHours( parseInt(ahours) ); + ad.setMinutes( parseInt(aminutes) ); + ad.setSeconds(0); + + let bd = new Date(); + bd.setHours( parseInt(bhours) ); + bd.setMinutes( parseInt(bminutes) ); + ad.setSeconds(0); + + return ad.getTime() - bd.getTime(); + }); + + console.log("line timepoints ........", time_points); + + monitor.info("-->comming events turn on/off lines:"); + for(let t = 0; t < time_points.length; t++) + { + + let start_time = new Date(); + let [hours, minutes, seconds] = time_points[t].start_time.split(':'); + + start_time.setHours( parseInt(hours) ); + start_time.setMinutes( parseInt(minutes) ); + start_time.setSeconds(0); + + //task is the past + if(now.getTime() > start_time.getTime()) + { + currentValue = time_points[t].value; + + //timepoint is in past, we add 24 hours + start_time.setDate(start_time.getDate() + 1); + } + + let params = getParams(PRIORITY_TYPES.relay_profile); + params.type = "relay"; + params.line = parseInt(line); + params.value = time_points[t].value; + params.tbname = relaysData[line].tbname; + params.timestamp = start_time.getTime(); + + params.addMinutesToTimestamp = 0; + + // it timepoints are not calculated (dawn, dusk, lux_timepoint), but static points in line profile, we just repeat the task every day + if(time_points[t].name == "profileTimepoint") params.addMinutesToTimestamp = 24*60; + + //astro timepoints will be recalculated dynamically: + params.timePointName = time_points[t].name; + + // if astro timepoint, we save time window: + if(['luxOn', 'luxOff', 'dusk','dawn'].includes(params.timePointName)) + { + params.dawn_lux_sensor_time_window = profile.dawn_lux_sensor_time_window; + params.dusk_lux_sensor_time_window = profile.dusk_lux_sensor_time_window; + } + + if(params.value == 0) params.info = `${params.timePointName}: turn off line: ` + line; + else if(params.value == 1) params.info = `${params.timePointName}: turn on line: ` + line; + + params.debug = true; + + //turn on/off line + tasks.push(params); + monitor.info(params.info, start_time); + + } + + monitor.info("-->time_points final", line, time_points); + + //ensure to turn on/off according to calculated value + let params = getParams(PRIORITY_TYPES.terminal); + params.type = "relay"; + params.line = parseInt(line); + params.tbname = relaysData[line].tbname; + params.value = currentValue; + + params.timestamp = PRIORITY_TYPES.terminal; + params.addMinutesToTimestamp = 0; + params.debug = true; + + //logger.debug(now.toLocaleString("sk-SK")); + monitor.info("-->currentValue for relay", line, currentValue); + + //turn on/off line + if(params.value == 0) params.info = "turn off line on startup: " + line; + else if(params.value == 1) params.info = "turn on line on startup: " + line; + + tasks.push(params); + + } catch (error) { + if(profilestr !=="" ) + { + //errLogger.error(profilestr, error); + errorHandler.sendMessageToService(profilestr + "-" + error, 0, "js_error"); + } + } + + } + + //logger.debug("tasks:"); + //logger.debug(tasks); + } + + + //PROCESS DEFAULT BROADCASTS + + //RPC pre nody / broadcast + //Time of dusk, Time of dawn + //Actual Time + + if(processBroadcast) + { + let addMinutesToTimestamp = 5; + + { + //run broadcast Time of dusk + addMinutesToTimestamp = 60 * 3; //kazde 3 hodiny zisti novy dusk + + let params = getParams(PRIORITY_TYPES.node_broadcast); + + let sunCalcResult = calculateDuskDawn(); + let dusk_hours = sunCalcResult["dusk_hours"]; + let dusk_minutes = sunCalcResult["dusk_minutes"]; + + params.address = 0xffffffff;//broadcast + params.byte1 = dusk_hours;//h + params.byte2 = dusk_minutes;//m + params.byte3 = 0;//s + params.byte4 = 0; + params.recipient = 2;//2 broadcast, + params.register = 6;//Time of dusk - Reg 6 + params.rw = 1;//write + + //other values + params.type = "cmd"; + params.timestamp = Date.now() + 60000; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "Broadcast-duskTime"; + + tasks.push(params); + } + + { + + //run broadcast Time of dawn + // addMinutesToTimestamp = 60*5; + addMinutesToTimestamp = 60 * 3; //kazde 3 hodiny zisti novy dawn + + let params = getParams(PRIORITY_TYPES.node_broadcast); + + let sunCalcResult = calculateDuskDawn(); + let dawn_hours = sunCalcResult["dawn_hours"]; + let dawn_minutes = sunCalcResult["dawn_minutes"]; + + params.address = 0xffffffff;//broadcast + params.byte1 = dawn_hours;//h + params.byte2 = dawn_minutes;//m + params.byte3 = 0;//s + params.byte4 = 0; + params.recipient = 2; //2 broadcast + params.register = 7;//Time of dawn - Reg 6 + params.rw = 1;//write + + //other values + params.type = "cmd"; + params.timestamp = Date.now() + 60000; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "Broadcast-dawnTime"; + + tasks.push(params); + } + + { + //run broadcast //Actual time + addMinutesToTimestamp = 5; + + let params = getParams(PRIORITY_TYPES.node_broadcast); + + var d = new Date(); + let hours = d.getHours(); + let minutes = d.getMinutes(); + let seconds = d.getSeconds(); + + params.address = 0xffffffff;//broadcast + params.byte1 = hours;//h + params.byte2 = minutes;//m + params.byte3 = seconds;//s + params.byte4 = 0; + params.recipient = 2; //2 broadcast + params.register = 87;//Actual time + params.rw = 1;//write + + //other values + params.type = "cmd"; + params.timestamp = Date.now() + 60000; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "run broadcast: Actual time"; + + tasks.push(params); + } + + } + + //process nodes & tasks + //reportovanie pre platformu + if(processNodes) + { + for (let k in nodesData) { + let address = parseInt(k); + let tbname = nodesData[k].tbname; + let register = 0; + + //logger.debug("generated cmd - buildTasks for node:", address); + + //listOfCommands - READ + for(let i = 0; i < listOfCommands.length; i++) + { + register = listOfCommands[i]; + + let params = getParams(PRIORITY_TYPES.node_cmd); + + //core rpc values + params.address = address; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 0; + params.recipient = 1; + params.register = register; + params.rw = 0; + + let addMinutesToTimestamp = priorities[register]; + + let timestampStart = PRIORITY_TYPES.node_cmd; //run imediatelly in function runTasks + if(addMinutesToTimestamp > 1) + { + timestampStart = timestampStart + addMinutesToTimestamp * 60000; + } + + //other values + params.type = "cmd"; + params.tbname = tbname; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "generated cmd - buildTasks (node)"; + + //monitor last node && last command + /* + if(register == listOfCommands[ listOfCommands.length - 1 ]) + { + //if(k == 632) params.debug = true; + if(k == 698) params.debug = true; + } + */ + + tasks.push(params); + + } + } + } + + + //niektore ulohy sa vygeneruju iba 1x pri starte!!! + if(!init) return; + + + //Priebežne (raz za cca 5 minút) je potrebné vyčítať z Master nodu verziu jeho FW. + //Jedná sa o register 10. Rovnaká interpretácia ako pri FW verzii nodu. + //Adresa mastera je 0. V prípade že kedykoľvek nastane situácia že Master Node neodpovedá (napríklad pri vyčítaní telemetrie z nodu nevráti žiadne dáta), + //tak treba vyreportovať string "NOK". + { + let params = getParams(PRIORITY_TYPES.fw_detection); + params.type = "cmd-master"; + params.register = 4; + params.address = 0; + params.timestamp = Date.now() + 60000; + params.addMinutesToTimestamp = 5; + params.tbname = FLOW.OMS_edgeName; + params.info = "Master node FW verzia"; + //params.debug = true; + + //this will set FLOW.OMS_masterNodeIsResponding + + tasks.push(params); + } + + //kazdu hodinu skontrolovat nastavenie profilov + { + let params = getParams(PRIORITY_TYPES.fw_detection); + params.type = "process_profiles"; + params.timestamp = Date.now() + 60000; + params.addMinutesToTimestamp = 60;//60 = every hour + params.info = "detekcia nespracovaných profilov linie a nodov"; + //params.debug = true; + + tasks.push(params); + } + + monitor.info("tasks created:", tasks.length); + } + + + /** + * We process line profile, where "astro_clock": true + * example profile: + * + "dawn_lux_sensor": true, + "dusk_lux_sensor": true, + "dawn_lux_sensor_value": 5, + "dusk_lux_sensor_value": 5, + "dawn_astro_clock_offset": 0, + "dusk_astro_clock_offset": 10, + "dawn_lux_sensor_time_window": 30, + "dusk_lux_sensor_time_window": 30, + "dawn_astro_clock_time_window": 60, + "dusk_astro_clock_time_window": 60 + + * if dawn: if currentTimestamp is in timewindow "dawnTime + and - dawn_lux_sensor_time_window" and lux value >= lux_sensor_value, we switch off the line. + * if dusk: we do oposite + * + * dawn: usvit - lux je nad hranicou - vypnem + * dusk: sumrak - lux je pod hranicou - zapnem + */ + function turnOnOffLinesAccordingToLuxSensor(lux_sensor_value) + { + + let now = new Date(); + let currentTimestamp = now.getTime(); + let keys = Object.keys(relaysData); + + for(let i = 0; i < keys.length; i++) + { + + let line = keys[i]; //line is turned off by default + let profilestr = relaysData[line].profile; + const contactor = relaysData[line].contactor; + + try { + + let profile = JSON.parse(profilestr); + if(Object.keys(profile).length === 0) throw ("turnOnOffLinesAccordingToLuxSensor - profile is not defined"); + + if(profile.astro_clock == true) + { + let sunCalcResult = calculateDuskDawn(now, line); + + //usvit + if(profile.dawn_lux_sensor == true) + { + let lux_sensor_time_window1 = sunCalcResult.dawn_time - (parseInt( profile.dawn_lux_sensor_time_window ) * 1000 * 60); // LUX_SENSOR_TIME_WINDOW x 1000 x 60 --> dostaneme odpocet/pripocitanie minut + let lux_sensor_time_window2 = sunCalcResult.dawn_time + (parseInt( profile.dawn_lux_sensor_time_window ) * 1000 * 60); + + if(currentTimestamp >= lux_sensor_time_window1 && currentTimestamp <= lux_sensor_time_window2) + { + if(lux_sensor_value > profile.dawn_lux_sensor_value) + { + if(contactor) turnOffLine(line, "Profile: dawn - turnOff line according to lux sensor"); + } + } + } + + //sumrak + if(profile.dusk_lux_sensor == true) + { + let lux_sensor_time_window1 = sunCalcResult.dusk_time - (parseInt( profile.dusk_lux_sensor_time_window ) * 1000 * 60); + let lux_sensor_time_window2 = sunCalcResult.dusk_time + (parseInt( profile.dusk_lux_sensor_time_window ) * 1000 * 60); + + if(currentTimestamp >= lux_sensor_time_window1 && currentTimestamp <= lux_sensor_time_window2) + { + if(lux_sensor_value < profile.dusk_lux_sensor_value) + { + if(!contactor) turnOnLine(line, "Profile: dusk - turnOn line according to lux sensor"); + } + } + } + + } + + } catch (error) { + if(profilestr !== "" ) monitor.info('Error parsing profile in turnOnOffLinesAccordingToLuxSensor', error); + } + + } + + } + + /** + * function updates status and time_of_last_communication of node in the dbNodes + * it only updates if conditions are met + * it only updates time_of_last_communication of node, if the last written time was more than 10 minutes ago (600000 miliseconds) + * if newStatus of node is always receiving false, and it is already for more than FLOW.OMS_node_status_nok_time value, we update status to "NOK" in tb + * function returns true, if status of node needs to be updated in TB (newStatus attribute is false in this case). + */ + function updateNodeStatus(node, newStatus) + { + //MASTER + if(node == 0) return; + + let nodeObj = nodesData[node]; + if(nodeObj == undefined) return; + + let nodeCurrentStatus = nodeObj.status; + const now = Date.now(); + + if(newStatus == false && nodeCurrentStatus == false) { + if(node == 638 || node == 637) console.log("false, false, return", node, now) + return true; + } + + if(newStatus == true && nodeCurrentStatus == true && nodeObj.time_of_last_communication > now - TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION){ + + if(node == 638 || node == 637) console.log("true true, return", node, now); + return; + } + + if(newStatus == true && nodeCurrentStatus == true && nodeObj.time_of_last_communication < now - TIME_AFTER_WE_UPDATE_LAST_NODE_COMMUNICATION) + { + dbNodes.modify({ time_of_last_communication: now}).where("node", node).make(function(builder) { + builder.callback(function(err, response) { + if(!err) { + nodeObj.time_of_last_communication = now; + + if(node == 638 || node == 637) console.log('zapisane do db => status true & true', node, now) + } + }); + }); + return; + } + + if(newStatus == false && nodeCurrentStatus == true) + { + if(nodeObj.time_of_last_communication + FLOW.OMS_node_status_nok_time > now) { + if(node == 638 || node == 637) console.log('false true, return', node, now); + return; + } + else { + dbNodes.modify({ status: newStatus}).where("node", node).make(function(builder) { + builder.callback(function(err, response) { + if(!err) { + nodeObj.status = newStatus; + + if(node == 638 || node == 637) console.log('zapisane do db => status false & true', node, now) + } + }); + }); + return true; + } + } + + if(newStatus == true && nodeCurrentStatus == false) + { + dbNodes.modify({ status: newStatus, time_of_last_communication: now}).where("node", node).make(function(builder) { + builder.callback(function(err, response) { + if(!err) { + nodeObj.status = newStatus; + nodeObj.time_of_last_communication = now; + + if(node == 638 || node == 637) console.log('zapisane do db => status false & true', node, now) + } + }); + }); + return; + } + } + + + 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 Manager: calculated Time of dusk", FLOW.OMS_edgeName, "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("CMD Manager: calculated Time of dawn", FLOW.OMS_edgeName, "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) + { + return a.priority - b.priority; + } + return a.timestamp - b.timestamp; + }); + + if(tasks.length == 0 ) + { + instance.send(SEND_TO.debug, "no tasks created"); + interval = setInterval(runTasks, LONG_INTERVAL); + return; + } + + if(!rsPort.isOpen) + { + instance.send(SEND_TO.debug, "!rsPort.isOpen"); + //await rsPort.open(); + } + + let currentTask = tasks[0]; + + if(currentTask.debug) + { + //logger.debug("--->task to process", currentTask); + } + + if(currentTask.timestamp <= currentTimestamp) + { + let params = {...tasks[0]}; + + //allow terminal commands + if(FLOW.OMS_maintenance_mode && params.type !== "cmd-terminal") + { + interval = setInterval(runTasks, LONG_INTERVAL); + return; + } + + let type = params.type; + let tbname = params.tbname; + let nodeAddress = params.address; + + let line = null; + + //rpc related + if(nodesData[nodeAddress] !== undefined) line = nodesData[nodeAddress].line; + if(params.line !== undefined) line = params.line; + + let repeatTask = false; + if(params.addMinutesToTimestamp > 0 || params.timePointName) repeatTask = true; + + if(repeatTask) + { + if(type === "cmd" || type === "cmd-master") + { + //set next start time automatically + tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + } + } + else + { + tasks.shift(); + } + + //kontrola nespracovanych profilov nodov + if(type == "process_profiles") + { + tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + + //vsetky linie kt. su zapnute, a spracuju sa nespracovane profily nodov + loadRelaysData(); + + interval = setInterval(runTasks, SHORT_INTERVAL); + return; + } + + + //relay + if(type == "relay") + { + + const timePointName = params.timePointName; + const value = params.value; + + let date = new Date(); + date.setDate(date.getDate() + 1);//next day + + let sunCalcResult; + sunCalcResult = calculateDuskDawn(date, params.line); + + if(timePointName == "dawn") + { + tasks[0].timestamp = sunCalcResult.dawn_time; + } + else if(timePointName == "dusk") + { + tasks[0].timestamp = sunCalcResult.dusk_time; + } + else if(timePointName == "luxOn") + { + tasks[0].timestamp = sunCalcResult.dusk_time + params.dusk_lux_sensor_time_window * 60000; + } + else if(timePointName == "luxOff") + { + tasks[0].timestamp = sunCalcResult.dawn_time + params.dawn_lux_sensor_time_window * 60000; + } + else if(timePointName == "profileTimepoint") + { + tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + } + + let info = "aplikovany bod profilu"; + let message = ""; + if(value == 1) + { + turnOnLine(params.line, info); + message = "on"; + } + else if(value == 0) + { + turnOffLine(params.line, info); + message = "off"; + } + + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "switching_profile_point_applied_to_line", {line: params.line, value: message}, "", SEND_TO.tb, instance ); + interval = setInterval(runTasks, SHORT_INTERVAL); + return; + } + + //zhodeny hlavny istic + let disconnected = false; + //if(rotary_switch_state == "Off") disconnected = true; + + //state_of_breaker[line] - alebo istic linie + if(state_of_breaker.hasOwnProperty(line)) + { + //if(state_of_breaker[line] == "Off") disconnected = true; + } + + //toto sa reportuje po prijati dat z dido_controlera + if(disconnected) + { + let values = {"status": "OFFLINE"}; + + logger.debug("disconnected", values); + logger.debug("rotary_switch_state", rotary_switch_state); + logger.debug("state_of_breaker", state_of_breaker[line]); + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //report only once! + if(!disconnectedReport.hasOwnProperty(tbname)) disconnectedReport[tbname] = false; + + if(!disconnectedReport[tbname]) + { + //instance.send(SEND_TO.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + } + + interval = setInterval(runTasks, SHORT_INTERVAL); + + return; + } + + disconnectedReport[tbname] = false; + + const register = params.register; + + //high_priority + if(!FLOW.OMS_masterNodeIsResponding) + { + //ak neodpoveda, nebudeme vykonavat ziadne commands, okrem cmd-terminal, a fw version + errorHandler.sendMessageToService("Master node is not responding"); + + let stop = true; + + //fw version - register == 4 + if(type == "cmd-terminal" || register == 4) stop = false; + if(stop) + { + interval = setInterval(runTasks, LONG_INTERVAL); + return; + } + } + + let relayStatus = 1; + if(relaysData[line] != undefined) + { + relayStatus = relaysData[line].contactor; + } + + if(line == 0) relayStatus = 0; + if(type == "cmd-terminal") relayStatus = 1; + + //check if rotary_switch_state == "Off" + + if(relayStatus == 0) + { + //console.log("------------------------------------relayStatus", relayStatus, line); + let values = {"status": "OFFLINE"}; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(SEND_TO.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + + interval = setInterval(runTasks, SHORT_INTERVAL); + return; + } + + if(!rsPort.isOpen) + { + interval = setInterval(runTasks, LONG_INTERVAL); + return; + } + + //RE-CALCULATE VALUES + //set actual time for broadcast + if(register == 87 && params.recipient === 2) + { + var d = new Date(); + let hours = d.getHours(); + let minutes = d.getMinutes(); + let seconds = d.getSeconds(); + + params.byte1 = hours;//h + params.byte2 = minutes;//m + params.byte3 = seconds;//s + params.byte4 = 0; + } + + //SET DUSK/DAWN FOR BROADCAST + //Time of dusk + if(register == 6 && params.recipient === 2) + { + + if(type != "cmd-terminal") + { + let sunCalcResult = calculateDuskDawn(); + let dusk_hours = sunCalcResult["dusk_hours"]; + let dusk_minutes = sunCalcResult["dusk_minutes"]; + + params.byte1 = dusk_hours;//h + params.byte2 = dusk_minutes;//m + params.byte3 = 0;//s + params.byte4 = 0; + + //TODO astrohodiny + let dusk = "Time of dusk: " + sunCalcResult["dusk"]; + } + } + + //Time of dawn + if(register == 7 && params.recipient === 2) + { + if(type != "cmd-terminal") + { + let sunCalcResult = calculateDuskDawn(); + let dawn_hours = sunCalcResult["dawn_hours"]; + let dawn_minutes = sunCalcResult["dawn_minutes"]; + + params.byte1 = dawn_hours;//h + params.byte2 = dawn_minutes;//m + params.byte3 = 0;//s + params.byte4 = 0; + + //TODO astrohodiny + let dawn = "Time of dawn: " + sunCalcResult["dawn"]; + } + + } + //----------------------- + + instance.send(SEND_TO.debug, "address: " + nodeAddress + " register:" + register + "type: " + type); + + var startTime, endTime; + startTime = new Date(); + + let saveToTb = true; + if(!tbname) saveToTb = false; + let itIsNodeCommand = listOfCommands.includes(register); //reading data from node (voltage, current, dimming, status) + + let resp = com_generic(nodeAddress, params.recipient, params.rw, register, params.name, params.byte1, params.byte2, params.byte3, params.byte4); + let readBytes = 11; + let timeout = 4000; + + // await keyword is important, otherwise incorrect data is returned! + await writeData(rsPort, resp, readBytes, timeout).then(function (data) { + + endTime = new Date(); + var timeDiff = endTime - startTime; + + //--1-4 adresa, 5 status ak je status 0 - ok, nasleduju 4 byty data + //let bytes = data.slice(0); + let bytes = data; + let dataBytes = data.slice(5,9); + + let result = detectIfResponseIsValid(bytes); + + //ak sa odpoved zacina 0 - je to v poriadku, inak je NOK + let message = result.message; // OK, NOK + let message_type = result.type; + let error = result.error; + + if(params.debug != "generated cmd") + { + //debug("writeData: done " + message_type + " duration: " + timeDiff + " message_type: " + params.debug, params); + } + + // if(params.hasOwnProperty("debug")) + // { + // if(params.debug) + // { + // console.log("detected response:", result); + + // logger.debug("writeData: done " + message_typetype + " duration: " + timeDiff + " type: " + params.debug, params, result); + // } + // } + + //debug("writeData: done " + message_type + " duration: " + timeDiff + " message_type: " + params.debug); + //debug("writeData done", message_type, "duration", timeDiff, "message_type", params.debug, result); + + let values = {}; + + //CMD FINISHED + if(message == "OK") + { + + updateNodeStatus(nodeAddress, true); + + //write + if(type == "set_node_profile") + { + let result = cmdCounterResolve(nodeAddress); + if(result == 0) + { + dbNodes.modify({ processed: true }).where("node", nodeAddress).make(function(builder) { + builder.callback(function(err, response) { + + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "dimming_profile_was_successfully_received_by_node", {node: nodeAddress}, "", SEND_TO.tb, instance ); + + logger.debug( "--> profil úspešne odoslaný na node č. " + nodeAddress); + nodesData[nodeAddress].processed = true; + }); + }); + } + } + + //parse read response + if(params.rw == 0) + { + values = processResponse(register, dataBytes); //read + } + + if(itIsNodeCommand) + { + values.comm_status = "OK"; + values.status = "OK"; + } + + //master node + if(nodeAddress == 0) + { + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "master_node_is_responding_again", {}, "", SEND_TO.tb, instance, "rvo_status" ); + FLOW.OMS_masterNodeIsResponding = true; + if(register == 4) values["edge_fw_version"] = FLOW.OMS_edge_fw_version; + } + + //odoslanie príkazu z terminálu - dáta + if(type == "cmd-terminal") + { + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "command_was_sent_from_terminal_interface", {}, params, SEND_TO.tb, instance ); + } + + if(params.debug) + { + //logger.debug("saveToTb", saveToTb, tbname, values); + } + + if(saveToTb) + { + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(SEND_TO.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + } + else + { + if(type == "cmd-terminal") + { + terminalCommandResponse(params, "SUCCESS", data); + } + } + + } + else + { + + terminalCommandResponse(params, "ERROR", data) + handleNokResponseOnRsPort("handleNOK else block", params, itIsNodeCommand, saveToTb); + + if(params.hasOwnProperty("debug")) + { + if(params.debug) + { + //logger.debug("writeData err: ", error, result, params); + logger.debug("writeData err: ", tbname, nodeAddress, register, values); + } + } + + //logger.debug(error, result, params); + } + }).catch(function(reason) { + + console.log("writeData catch exception", reason); + instance.send(SEND_TO.debug, reason); + + terminalCommandResponse(params, "FAILURE", null, reason); + handleNokResponseOnRsPort("handleNOK catch block", params, itIsNodeCommand, saveToTb); + + if(params.hasOwnProperty("debug")) + { + if(params.debug) + { + logger.debug("-->WRITE FAILED: " + reason, params.debug, params); + } + } + + }); + + } + else + { + // if(currentTask.debug) + // { + // //currentTask.timestamp <= currentTimestamp + // logger.debug("currentTask is not processed - task is in the future", currentTask); + // } + + interval = setInterval(runTasks, LONG_INTERVAL); + return; + } + + //console.log("----->runTasks - setInterval", new Date()); + interval = setInterval(runTasks, SHORT_INTERVAL); + } + + + function handleNokResponseOnRsPort(message, params, itIsNodeCommand, saveToTb){ + + let node = params.address; + let register = params.register; + let type = params.type; + let tbName = params.tbname; + if(!tbName) return; + + let values = {}; + + // console.log(message); + let updateStatus = updateNodeStatus(node, false); + + //master node + if(node == 0) + { + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "master_node_is_not_responding", {}, "", SEND_TO.tb, instance, "rvo_status"); + logger.debug("master_node_is_not_responding", params); + FLOW.OMS_masterNodeIsResponding = false; + + if(register == 4) values["master_node_version"] = "NOK"; + } + + if(type == "set_node_profile") + { + delete cmdCounter[node]; + logger.debug( "profil nebol úspešne odoslaný na node č. ", params); + sendNotification("CMD Manager: process cmd", tbName, "configuration_of_dimming_profile_to_node_failed", {node: node}, "", SEND_TO.tb, instance ); + } + + if(itIsNodeCommand) + { + values.comm_status = "NOK"; + } + + if(updateStatus) + { + values.status = "NOK"; + } + + // console.log("------",node, register, type, itIsNodeCommand, updateStatus, saveToTb, values); + if(saveToTb && Object.keys(values).length > 0) + { + + let dataToTb = { + [tbName]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + tbHandler.sendToTb(dataToTb, instance); + } + + } + + + /** + * function handles requests from terminal + * responseType can be "SUCCESS", "ERROR" or "FAILURE", depending on rsPort data. + * FAILURE means, that we got into catch block of writeData function. + */ + function terminalCommandResponse(params, responseType, data=null, reason="") { //success, error, failure + + if(params.refFlowdataKey == undefined) + { + //console.log("params.refFlowdataKey is undefined", params); + return; + } + else + { + console.log("params.refFlowdataKey: ", params); + } + + let message = null; + let type = null; + + switch (responseType) { + case "SUCCESS": + message = "cmd-terminal SUCCESS"; + type = "SUCCESS"; + break; + case "ERROR": + message = "cmd-terminal FAILED"; + type = "ERROR"; + break; + case "FAILURE": + message = "ERROR WRITE FAILED: " + reason; + type = "ERROR"; + break; + default: + type = undefined; + } + + logger.debug(message); + logger.debug(params); + + //make http response + let responseObj = {} + responseObj["type"] = type; + + if(responseType == "FAILURE") responseObj["message"] = "ERROR WRITE FAILED: " + reason; + else responseObj["bytes"] = data; + + let refFlowdata = refFlowdataObj[params.refFlowdataKey]; + if(refFlowdata) + { + refFlowdata.data = responseObj; + instance.send(SEND_TO.http_response, refFlowdata); + } + } + + + /** + * function handles tasks, that are not needed to run through masterNode. To make them run smooth without waiting for other tasks to be completed, we moved them in separate function + */ + function reportEdgeDateTimeAndNumberOfLuminaires(){ + + if(!FLOW.OMS_edgeName) return; + + //Number of ok and nok nodes on platform does not equals to total number of nodes. + //possible error is, that nodesData object is changing all the time. To make a proper calculation of ok,nok luminaires, we make a copy of it: + let nodesData_clone = JSON.parse(JSON.stringify(nodesData)); + + const ts = Date.now(); + const keys = Object.keys(nodesData_clone); + + const number_of_luminaires = keys.length; + let number_of_ok_luminaires = 0; + let number_of_nok_luminaires = 0; + + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + let nodeObj = nodesData_clone[key]; + if(nodeObj.tbname == undefined) continue; + + if(nodeObj.status) number_of_ok_luminaires++; + else number_of_nok_luminaires++; + } + + const values = { + "number_of_luminaires": number_of_luminaires, + "number_of_ok_luminaires": number_of_ok_luminaires, + "number_of_nok_luminaires": number_of_nok_luminaires, + "edge_date_time": ts + }; + + let dataToTb = { + [FLOW.OMS_edgeName]: [ + { + "ts": ts, + "values": values + } + ] + } + + tbHandler.sendToTb(dataToTb, instance); + //instance.send(SEND_TO.tb, dataToTb); + } + + //to ensure, edgeDateTime will be send to tb at full minute + customTasksInterval = setInterval(function() { + if(new Date().getSeconds() === 0) reportEdgeDateTimeAndNumberOfLuminaires(); + }, 1000); + + + //! 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(FLOW.OMS_serial_port == "" || FLOW.OMS_serial_port == undefined || FLOW.OMS_serial_port.length === 1) FLOW.OMS_serial_port = "ttymxc4"; + const rsPort = new SerialPort(`/dev/${FLOW.OMS_serial_port}`, { 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 manager - rsPort opened sucess"); + + //loadRelaysData(); + + await runSyncExec(`stty -F /dev/${FLOW.OMS_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); + + logger.debug(0, "RPC runSyncExec - Promise Resolved:" + status); + + //APP START + let dataToInfoSender = {id: FLOW.OMS_projects_id, name: FLOW.OMS_rvo_name}; + dataToInfoSender.fw_version = FLOW.OMS_edge_fw_version; + dataToInfoSender.startdate = new Date().toISOString().slice(0, 19).replace('T', ' '); + dataToInfoSender.__force__ = true; + + instance.send(SEND_TO.infoSender, dataToInfoSender); + + logger.debug(0, "---------------------------->START message send to service", dataToInfoSender); + + }).catch(function (reason) { + instance.send(SEND_TO.debug, "CMD manager - RPC runSyncExec - promise rejected:" + reason); + }); + }); + + rsPort.on('error', function(err) { + + //TODO report to service!!! + //errLogger.error(exports.title, "unable to open port", FLOW.OMS_serial_port, err.message); + errorHandler.sendMessageToService([exports.title, "unable to open port", FLOW.OMS_serial_port, err.message], 0); + + instance.send(SEND_TO.debug, err.message); + }); + + rsPort.on("close", () => { + rsPort.close(); + }); + + rsPort.open(); + + instance.on("close", () => { + clearInterval(interval); + clearInterval(customTasksInterval); + rsPort.close(); + }); + + + instance.on("data", async function(flowdata) { + + //instance.send(SEND_TO.debug, "on Data"); + //instance.send(SEND_TO.debug, flowdata); + + //logger.debug(flowdata.data); + + //just testing functions + if(flowdata.data == "open") + { + if(!rsPort.isOpen) rsPort.open(); + return; + } + else if(flowdata.data == "close") + { + rsPort.close(); + return; + } + else if(flowdata.data == "clean") + { + tasks = []; + return; + } + else if(flowdata.data == "buildtasks") + { + //build & run + return; + } + else if(flowdata.data == "run") + { + //durations = []; + + if(tasks.length == 0) + { + + buildTasks(); + + if(rsPort.isOpen) + { + interval = setInterval(runTasks, 100); + } + else + { + instance.send(SEND_TO.debug, "port is not opened!!!"); + } + } + } + else + { + //terminal data - object + //logger.debug("flowdata", flowdata.data); + + if(typeof flowdata.data === 'object') + { + //logger.debug("dido", flowdata.data); + if(flowdata.data.hasOwnProperty("sender")) + { + //data from dido_controller + if(flowdata.data.sender == "dido_controller") + { + + if(flowdata.data.hasOwnProperty("cmd")) + { + let cmd = flowdata.data.cmd; + + if(cmd == "buildTasks") + { + clearInterval(interval); + + logger.debug("-->CMD MANAGER - BUILD TASKS"); + buildTasks(); + + //logger.debug("tasks:"); + //logger.debug(tasks); + + logger.debug("-->CMD MANAGER - RUN TASKS"); + interval = setInterval(runTasks, LONG_INTERVAL); + } + else if(cmd == "reload_relays") + { + loadRelaysData(flowdata.data.line); + + if(flowdata.data.dataChanged) + { + if(!flowdata.data.value) + { + reportOfflineNodeStatus(flowdata.data.line); + } + else + { + reportOnlineNodeStatus(flowdata.data.line); + } + } + + } + else if(cmd == "rotary_switch_state") + { + //state was changed + if(rotary_switch_state != flowdata.data.value) + { + if(rotary_switch_state == "Off") + { + //vyreportovat vsetky svietdla + reportOfflineNodeStatus(); + } + else reportOnlineNodeStatus(); + + } + + rotary_switch_state = flowdata.data.value; + } + else if(cmd == "lux_sensor") + { + lux_sensor = parseInt(flowdata.data.value); + + // POSSIBLE SOURCE OF PROBLEMS, IF USER SETS LUX TRESHOLD LEVEL GREATER THAN 100 - WE SHOULD BE CHECKING "DUSK/DAWN_LUX_SENSOR_VALUE" IN PROFILE MAYBE ?? + if(lux_sensor < 100) + { + + // we send lux_sensor value to all nodes: + let params = getParams(PRIORITY_TYPES.node_broadcast); + + params.recipient = 2;//2 broadcast, address = 0 + params.address = 0xffffffff;//Broadcast + + let ba = longToByteArray(lux_sensor); + + params.byte3 = ba[1];//msb + params.byte4 = ba[0]; + params.timestamp = PRIORITY_TYPES.node_broadcast; + params.info = "run broadcast: Actual Lux level from cabinet"; + params.register = 95;//Actual Lux level from cabinet + params.rw = 1;//write + + tasks.push(params); + + //process profiles + turnOnOffLinesAccordingToLuxSensor(lux_sensor); + } + } + else if(cmd == "state_of_breaker") + { + //istic linie + let value = flowdata.data.value; + let line = parseInt(flowdata.data.line); + + let dataChanged = false; + if(state_of_breaker[line] != value) dataChanged = true; + + state_of_breaker[line] = value; + + let status = "OK"; + if(value == "Off") status = "NOK"; + + if(dataChanged) { + + if(relaysData.hasOwnProperty(line)) + { + let tbname = relaysData[line].tbname; + + 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 + let values = { + "status": status + }; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(SEND_TO.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + + //current value + if(value == "Off") reportOfflineNodeStatus(line); //vyreportovat vsetky svietidla na linii + else reportOnlineNodeStatus(line); + } + + } + } + else{ + logger.debug("undefined cmd", cmd); + } + } + } + + return; + } + + //data from worksys + if(flowdata.data.hasOwnProperty("topic")) + { + + let data = flowdata.data.content.data; + + let command = data.params.command; + let method = data.method; + let profile = data.params.payload; + if(profile == undefined) profile = ""; + let entity = data.params.entities[0]; + let entity_type = entity.entity_type; + let tbname = entity.tb_name; + + instance.send(SEND_TO.debug, flowdata.data); + logger.debug("--->worksys", flowdata.data, data.params, entity, entity_type, command, method); + logger.debug("----------------------------"); + + if(entity_type == "street_luminaire"|| entity_type === "street_luminaire_v4_1" || entity_type === "street_luminaire_v4_1cez" || entity_type === "street_luminaire_v4") + { + if(method == "set_command") + { + + //let command = data.params.command; + let value = data.params.payload.value; + + if(command == "dimming") + { + + let nodeWasFound = false; + let keys = Object.keys(nodesData); + + //logger.debug("-----", keys); + + for(let i = 0; i < keys.length; i++) + { + let node = keys[i]; + //logger.debug( node, nodesData[node], tbname); + + if(tbname == nodesData[node].tbname.trim()) + { + let params = getParams(PRIORITY_TYPES.high_priority); + + value = parseInt(value); + if(value > 0) value = value + 128; + + //set dimming - LUM1_13 - 647 je node linie 1 kt. dobre vidime + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 1;//dimming + params.recipient = 1;//slave + params.byte4 = value; + params.rw = 1;//write + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'set dimming from platform'; + //params.debug = true; + + //ak linia je + + //debug(params); + logger.debug("dimming", params); + + tasks.push(params); + + setTimeout(function(){ + + //spustime o 4 sekundy neskor, s prioritou PRIORITY_TYPES.high_priority + //a pridame aj vyreportovanie dimmingu + { + let params = getParams(PRIORITY_TYPES.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 1;//dimming + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read dimming (after set dimming from platform)'; + params.debug = true; + + tasks.push(params); + } + + //pridame aj vyreportovanie - vykon + { + let params = getParams(PRIORITY_TYPES.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 76; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read Input Power (after set dimming from platform)'; + params.debug = true; + + tasks.push(params); + } + + //pridame aj vyreportovanie - prud svietidla + { + let params = getParams(PRIORITY_TYPES.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 75; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read Input Current (after set dimming from platform)'; + params.debug = true; + + tasks.push(params); + } + + //pridame aj vyreportovanie - power faktor - ucinnik + { + let params = getParams(PRIORITY_TYPES.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 77; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = PRIORITY_TYPES.high_priority; + params.info = 'read power factor - Cos phi (after set dimming from platform)'; + params.debug = true; + + tasks.push(params); + } + + },4000); + + + nodeWasFound = true; + + break; + } + } + + if(!nodeWasFound) + { + logger.debug("set dimming from platform", "unable to find tbname", tbname); + } + } + else + { + instance.send(SEND_TO.debug, "undefined command " + command); + logger.debug("undefined command", command); + } + + return; + } + else if(method == "set_profile") + { + //nastav profil nodu + logger.debug("-->set_profile for node", data.params); + logger.debug("------profile data", profile); + //instance.send(SEND_TO.debug, "set_profile" + command); + + let keys = Object.keys(nodesData); + for(let i = 0; i < keys.length; i++) + { + let node = keys[i]; + if(tbname == nodesData[node].tbname.trim()) + { + + if(profile != "") profile = JSON.stringify(profile); + dbNodes.modify({ processed: false, profile: profile }).where("node", node).make(function(builder) { + + builder.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 manager", tbname, "dimming_profile_was_processed_for_node", {node: node}, profile, SEND_TO.tb, instance ); + + nodesData[node].processed = false; + nodesData[node].profile = profile; + + let line = nodesData[node].line; + processNodeProfile(node); + + }); + }); + } + } + } + else + { + + instance.send(SEND_TO.debug, "unknown method " + method); + logger.debug("unknown method", method); + + return; + } + } + + //nastav profil linie z platformy + else if(entity_type == "edb_line" || entity_type == "edb" || entity_type == "edb_line_ver4" || entity_type == "edb_ver4_se") + { + //profil linie + //relays.table line:number|tbname:string|contactor:number|profile:string + //najdeme line relaysData + + if(method == "set_profile") + { + + logger.debug("-->set_profile for line", data.params); + logger.debug("profile data:", profile); + + let keys = Object.keys(relaysData); + for(let i = 0; i < keys.length; i++) + { + let line = keys[i]; + if(tbname == relaysData[line].tbname) + { + //zmazeme tasky + 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) { + + //update profile + logger.debug("worksys - update relay profile done:", profile); + instance.send(SEND_TO.debug, "worksys - update relay profile done"); + + loadRelaysData(line).then(function (data) { + logger.debug("loadRelaysData DONE for line", line); + buildTasks({processLineProfiles: true, line: line}); + }); + + sendNotification("CMD manager - set profile from worksys", tbname, "switching_profile_was_processed_for_line", {line: line}, profile, SEND_TO.tb, instance ); + + }); + }); + break; + } + } + } + else if(method == "set_command") + { + let value = data.params.payload.value; + + if(command === "switch") + { + + // if we receive rpc from platform, to switch maintenance mode, we set OMS_maintenance_mode flow variable to value; + if(entity_type === "edb" || entity_type === "edb_ver4_se") FLOW.variables.OMS_maintenance_mode = value; + + let responseRelays = await promisifyBuilder(dbRelays.find().where("tbname", tbname)); + + let line = 0; + if(responseRelays.length == 1) line = responseRelays[0].line; + + if(value == false) turnOffLine(line, "command received form platform"); + else turnOnLine(line, "command received form platform"); + } + } + else + { + instance.send(SEND_TO.debug, "undefined method " + method); + logger.debug("undefined method", method); + } + + return; + } + else + { + instance.send(SEND_TO.debug, "UNKNOW entity_type " + entity_type); + logger.debug("UNKNOW entity_type", entity_type); + } + return; + } + + //terminal + if(!rsPort.isOpen) await rsPort.open(); + + let params = flowdata.data.body; + if(params == undefined) + { + //logger.debug("CMD manager flowdata.data.body is undefined"); + return; + } + + params.priority = PRIORITY_TYPES.terminal; + params.type = "cmd-terminal"; + params.tbname = ""; + params.timestamp = PRIORITY_TYPES.terminal; + params.addMinutesToTimestamp = 0;// do not repeat task!!! + params.debug = true; + + let timestamp = Date.now(); + params.refFlowdataKey = timestamp; + //params.refFlowdata = flowdata; + //refFlowdata = flowdata; + + //console.log("flowdata", flowdata); + + cleanUpRefFlowdataObj(); + + refFlowdataObj[ timestamp ] = flowdata; + + //fix + //params.address = params.adress; + logger.debug("received from terminal", params); + logger.debug("date/time:", new Date()); + logger.debug("tasks length:", tasks.length); + + //tasks = []; + + //add to tasks + tasks.push(params); + + } + } + }) + +} // end of instance.export + + +/** + * setCorrectTime function runs once per hour + * If it is 3 o'clock, it sets actual time, which is got from services + * https://service-prod01.worksys.io/gettime + * If also detects Read Only Filesystem once a day + */ +function setCorrectPlcTimeOnceADay() +{ + + const currentTime = new Date(); + if(currentTime.getHours() != 3) return; + + RESTBuilder.make(function(builder) { + + if(!builder) return; + + builder.method('GET'); + builder.url('http://192.168.252.2:8004/gettime?projects_id=1'); + + builder.callback(function(err, response, output) { + + if (err) { + console.log(err); + return; + } + + const res = output.response; + + try { + + const obj = JSON.parse(res); + let d = new Date(obj.date); + + const now = new Date(); + + let diffInMinutes = now.getTimezoneOffset(); + console.log("---->TimezoneOffset", diffInMinutes); + + if(d instanceof Date) + { + + // monitor.info("----------->setCorrectPlcTimeOnceADay() current js date:", d, d.getHours()); + + let year = d.getFullYear(); + let month = addZeroBefore(d.getMonth() + 1); + let day = addZeroBefore(d.getDate()); + + let hours = addZeroBefore(d.getHours()); + let minutes = addZeroBefore(d.getMinutes() ); + let seconds = addZeroBefore(d.getSeconds()); + + let dateStr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + + exec(`sudo timedatectl set-time "${dateStr}"`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + console.log(dateStr); + + monitor.info("failed timedatectl set-time", err, stderr); + } + else + { + monitor.info("setCorrectPlcTimeOnceADay() --> Nastaveny cas na: ", dateStr); + } + + }); + } + + } catch (error) { + logger.debug("setCorrectPlcTimeOnceADay - function error", error, res); + monitor.info("setCorrectPlcTimeOnceADay - function error", error, res); + } + + // we detect readOnlyFileSystem once an hour as well + detectReadOnlyFilesystem(); + + }); + }); + +} + + +function detectReadOnlyFilesystem() +{ + exec(`sudo egrep " ro,|,ro " /proc/mounts`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + + } else { + //console.log("Read-only", stdout); + + let lines = stdout + ""; + lines = lines.split("\n"); + + let readOnlyDetected = ""; + for(let i = 0; i < lines.length; i++) + { + if(lines[i].startsWith("/dev/mmcblk0p2")) + { + readOnlyDetected = lines[i]; + } + } + + if(readOnlyDetected !== "") + { + errorHandler.sendMessageToService("Detected: Read-only file system: " + readOnlyDetected); + monitor.info("Read only filesystem detected"); + } + + } + }); +} + +let setCorrectTime = setInterval(setCorrectPlcTimeOnceADay, 60000 * 60); // 1 hour +setCorrectPlcTimeOnceADay(); + + + + + + + + + +///helper functions + +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; + + let result = {}; + + var times = SunCalc.getTimes(date, latitude, longitude); + let dawn = new Date(times.sunrise);//usvit + let dusk = new Date(times.sunset);//sumrak + + + //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 + + try { + + let profile = JSON.parse(profilestr); + if(Object.keys(profile).length === 0) throw ("profile is not defined"); + + //Jednoduchý režim + if(profile.astro_clock == false && profile.dusk_lux_sensor == false && profile.dawn_lux_sensor == false) + { + + } + + //Režim astrohodín + if(profile.astro_clock == true) + { + //if(profile.dusk_lux_sensor == false) + { + if(profile.hasOwnProperty("dusk_astro_clock_offset")) dusk_astro_clock_offset = parseInt( profile.dusk_astro_clock_offset ); + } + + //if(profile.dawn_lux_sensor == false) + { + if(profile.hasOwnProperty("dawn_astro_clock_offset")) dawn_astro_clock_offset = parseInt( profile.dawn_astro_clock_offset ); + } + + } + + //dusk - súmrak + //down, sunrise - svitanie + + } catch (error) { + if(profilestr != "") + { + logger.debug(profilestr); + logger.debug(error); + } + } + + result.dusk_no_offset = addZeroBefore(dusk.getHours()) + ":" + addZeroBefore(dusk.getMinutes()); + result.dawn_no_offset = addZeroBefore(dawn.getHours()) + ":" + addZeroBefore(dawn.getMinutes()); + + dusk = new Date(dusk.getTime() + gmtOffset + dusk_astro_clock_offset*60000); + dawn = new Date(dawn.getTime() + gmtOffset + dawn_astro_clock_offset*60000); + + result.dusk = addZeroBefore(dusk.getHours()) + ":" + addZeroBefore(dusk.getMinutes()); + result.dusk_hours = dusk.getHours(); + result.dusk_minutes = dusk.getMinutes(); + + result.dawn = addZeroBefore(dawn.getHours()) + ":" + addZeroBefore(dawn.getMinutes()); + result.dawn_hours = dawn.getHours(); + result.dawn_minutes = dawn.getMinutes(); + + result.dusk_time = dusk.getTime(); + result.dawn_time = dawn.getTime(); + + result.dusk_astro_clock_offset = dusk_astro_clock_offset; + result.dawn_astro_clock_offset = dawn_astro_clock_offset; + + return result; +} + + +function processResponse(register, bytes) +{ + + let values = {}; + + let byte3 = bytes[0]; + let byte2 = bytes[1]; + let byte1 = bytes[2]; + let byte0 = bytes[3]; + + //status + if(register == 0) + { + let statecode = bytesToInt(bytes); + values = {"statecode": statecode}; + return values; + } + + //Dimming, CCT + if(register == 1) + { + let brightness = 0; + let dimming = byte0; + if(dimming > 128) { + //dimming = -128; + brightness = dimming - 128; + } + + //cct + //Ak Byte3 == 1: CCT = (Byte2*256)+Byte1 + let cct; + if(byte3 == 1) cct = byte2*256 + byte1; + else cct = bytesToInt(bytes.slice(0, 3)); + + //cct podla auditu + + values["dimming"] = brightness; + return values; + } + + // + if(register == 4) + { + values["master_node_version"] = bytes[1] + "." + bytes[2]; + //logger.debug("FW Version", register, bytes); + } + + //Napätie + if(register == 74) + { + let voltage = (bytesToInt(bytes) * 0.1).toFixed(1); + values["voltage"] = Number(voltage); + } + + //Prúd + if(register == 75) + { + let current = bytesToInt(bytes); + values["current"] = current; + } + + //výkon + if(register == 76) + { + let power = (bytesToInt(bytes) * 0.1).toFixed(2); + values["power"] = Number(power); + } + + //účinník + if(register == 77) + { + let power_factor = Math.cos(bytesToInt(bytes) * 0.1).toFixed(2); + values["power_factor"] = Number(power_factor); + } + + //frekvencia + if(register == 78) + { + let frequency = (bytesToInt(bytes) * 0.1).toFixed(2); + values["frequency"] = Number(frequency); + } + + //energia + if(register == 79) + { + let energy = bytesToInt(bytes); + + //Energiu treba reportovať v kWh. Teda číslo, ktoré príde treba podeliť 1000. Toto som ti možno zle napísal. + + values["energy"] = energy / 1000; + } + + //doba života + if(register == 80) + { + let lifetime = ( bytesToInt(bytes) / 60).toFixed(2); + values["lifetime"] = Number(lifetime); + } + + //nastavenie profilu + if(register == 8) + { + let time_schedule_settings = bytesToInt(bytes); + values["time_schedule_settings"] = time_schedule_settings; + } + + //skupinová adresa 1 + if(register == 3) + { + let gr_add_1 = bytesToInt(byte0); + values["gr_add_1"] = gr_add_1; + + let gr_add_2 = bytesToInt(byte1); + values["gr_add_2"] = gr_add_2; + + let gr_add_3 = bytesToInt(byte2); + values["gr_add_3"] = gr_add_3; + + let gr_add_4 = bytesToInt(byte3); + values["gr_add_4"] = gr_add_4; + } + + //naklon + if(register == 84) + { + let temp; + if(byte3 >= 128) + { + temp = (byte3 - 128) * (-1); + } + else + { + temp = byte3; + } + + let inclination_x; + if(byte2 >= 128) + { + inclination_x = (byte2 - 128) * (-1); + } + else + { + inclination_x = byte2; + } + + let inclination_y; + if(byte1 >= 128) + { + inclination_y = (byte1 - 128) * (-1); + } + else + { + inclination_y = byte1; + } + + let inclination_z; + if(byte0 >= 128) + { + inclination_z = (byte0 - 128) * (-1); + } + else + { + inclination_z = byte0; + } + + values["temperature"] = temp; + + //náklon x + values["inclination_x"] = inclination_x; + + //náklon y + values["inclination_y"] = inclination_y; + + //náklon z + values["inclination_z"] = inclination_z; + } + + let h = byte3; + let m = byte2; + let s = byte1; + + let timestamp; + + if(register == 87 || register == 6 || register == 7 ) + { + //if(byte3 < 10) h = "0" + byte3; + //if(byte2 < 10) m = "0" + byte2; + //if(byte1 < 10) s = "0" + byte1; + + var d = new Date(); + d.setHours(h); + d.setMinutes(m); + d.setSeconds(s); + + timestamp = d.getTime(); + } + + //aktuálny čas + if(register == 87) + { + //Byte3 - hodiny, Byte 2 - minúty, Byte 1 -sek. + //values["actual_time"] = h + ":" + m + ":" + s; + + values["actual_time"] = timestamp; + } + + //čas súmraku + if(register == 6) + { + //Byte3 - hodiny, Byte 2 - minúty, Byte 1 -sek. + //values["dusk_time"] = h + ":" + m + ":" + s; + + values["dusk_time"] = timestamp; + } + + //čas úsvitu + if(register == 7) + { + //Byte3 - hodiny, Byte 2 - minúty, Byte 1 -sek. + //values["dawn_time"] = h + ":" + m + ":" + s; + + values["dawn_time"] = timestamp; + } + + //FW verzia + if(register == 89) + { + //formát: "Byte3: Byte2.Byte1 (Byte0)" + values["fw_version"] = byte3 + ":" + byte2 + "." + byte1 + "(" + byte0 + ")"; + } + + return values; +} + + +//byte1 MSB = data3, byte2 = data2, byte3 = data1, byte4 = data0 LSB +function com_generic(adresa, rec, rw, register, name, byte1, byte2, byte3, byte4) { + let resp = []; + + let cmd = register; + + if (typeof adresa === 'string') adresa = parseInt(adresa); + if (typeof byte1 === 'string') byte1 = parseInt(byte1); + if (typeof byte2 === 'string') byte2 = parseInt(byte2); + if (typeof byte3 === 'string') byte3 = parseInt(byte3); + if (typeof byte4 === 'string') byte4 = parseInt(byte4); + + if (rw === 0) + { + cmd = cmd + 0x8000; + } + + //master + if(rec === 0) adresa = 0; + + if(rec === 2) + { + adresa = 0xffffffff;//Broadcast + } + + //recipient + if (rec === 3) + { + resp.push(0xFF); + resp.push(0xFF); + resp.push(0xFF); + resp.push(0xFF); + resp.push( adresa & 0xFF );//band + } + else + { + resp.push( (adresa >> 24) & 0xFF);//rshift + resp.push( (adresa >> 16) & 0xFF); + resp.push( (adresa >> 8) & 0xFF); + resp.push( adresa & 0xFF ); + + if (rec === 2) + { + resp.push(0xFF); + } + else resp.push(0); + } + + resp.push( (cmd >> 8) & 0xFF);//rshift + resp.push( cmd & 0xFF );//band + resp.push( byte1 & 0xFF );//band + resp.push( byte2 & 0xFF );//band + resp.push( byte3 & 0xFF );//band + resp.push( byte4 & 0xFF );//band + + //let data = '12345'; + let crc = crc16('ARC', resp); + let c1 = (crc >> 8) & 0xFF; + let c2 = crc & 0xFF; + + resp.push(c1); + resp.push(c2); + + //logger.debug("checksum", crc); + //logger.debug("resp", resp); + + return resp; + +} + + + + + + +// SAMPLE DATA + +const relaysDataExample = +{ + '0': { + line: 0, + tbname: 'jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV', + contactor: 1, + profile: '' + }, + '1': { + line: 1, + tbname: 'MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O', + contactor: 1, + profile: '{"intervals":[{"value":1,"end_time":"13:00","start_time":"13:00"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' + }, + '2': { + line: 2, + tbname: 'jBL12pg63eX4N9P7zy0lJLyEJKmlbkGwZMx0avQV', + contactor: 1, + profile: '{"intervals":[{"value":1,"end_time":"13:00","start_time":"13:00"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' + }, + '3': { + line: 3, + tbname: 'aAOzENGrvpbe0VoK7D6E1a819PZmdg3nl24JLQMk', + contactor: 1, + profile: '{"intervals":[{"value":1,"end_time":"13:00","start_time":"13:00"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' + } +} + + +const rpcSwitchOffLine = +{ + "topic": "v1/gateway/rpc", + "content": { + "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", + "data": { + "id": 8, + "method": "set_command", + "params": { + "entities": [ + { + "entity_type": "edb_line", + "tb_name": "MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O" + } + ], + "command": "switch", + "payload": { + "value": false + } + } + } + } +} + +const rpcSetNodeDimming = +{ + "topic": "v1/gateway/rpc", + "content": { + "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", + "data": { + "id": 10, + "method": "set_command", + "params": { + "entities": [ + { + "entity_type": "street_luminaire", + "tb_name": "jbN4q7JPZmexgdnz2yKbWdDYAWwO0Q3BMX6ERLoV" + } + ], + "command": "dimming", + "payload": { + "value": 5 + } + } + } + } +} + +const rpcLineProfile = +{ + "topic": "v1/gateway/rpc", + "content": { + "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", + "data": { + "id": 9, + "method": "set_profile", + "params": { + "entities": [ + { + "entity_type": "edb_line", + "tb_name": "MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O" + } + ], + "payload": { + "intervals": [ + { + "value": 0, + "end_time": "20:00", + "start_time": "13:00" + }, + { + "value": 1, + "end_time": "05:30", + "start_time": "20:00" + }, + { + "value": 0, + "end_time": "13:00", + "start_time": "05:30" + } + ], + "astro_clock": true, + "dawn_lux_sensor": false, + "dusk_lux_sensor": false, + "dawn_lux_sensor_value": 5, + "dusk_lux_sensor_value": 5, + "dawn_astro_clock_offset": 0, + "dusk_astro_clock_offset": 0, + "dawn_lux_sensor_time_window": 30, + "dusk_lux_sensor_time_window": 30, + "dawn_astro_clock_time_window": 60, + "dusk_astro_clock_time_window": 60 + } + } + } + } +} + + +const rpcNodeProfile = +{ + "topic": "v1/gateway/rpc", + "content": { + "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", + "data": { + "id": 11, + "method": "set_profile", + "params": { + "entities": [ + { + "entity_type": "street_luminaire", + "tb_name": "jbN4q7JPZmexgdnz2yKbWdDYAWwO0Q3BMX6ERLoV" + } + ], + "payload": { + "intervals": [ + { + "cct": 3000, + "value": 0, + "end_time": "17:50", + "start_time": "13:00" + }, + { + "cct": 3000, + "value": 100, + "end_time": "21:30", + "start_time": "17:50" + }, + { + "cct": 3000, + "value": 0, + "end_time": "13:00", + "start_time": "07:10" + }, + { + "cct": 3000, + "value": 50, + "end_time": "00:00", + "start_time": "21:30" + }, + { + "cct": 3000, + "value": 10, + "end_time": "04:30", + "start_time": "00:00" + }, + { + "cct": 3000, + "value": 100, + "end_time": "07:10", + "start_time": "04:30" + } + ], + "astro_clock": true, + "dawn_lux_sensor": false, + "dusk_lux_sensor": false, + "dawn_lux_sensor_value": 5, + "dusk_lux_sensor_value": 5, + "dawn_astro_clock_offset": 30, + "dusk_astro_clock_offset": 20, + "dawn_lux_sensor_time_window": 30, + "dusk_lux_sensor_time_window": 30, + "dawn_astro_clock_time_window": 60, + "dusk_astro_clock_time_window": 60 + } + } + } + } +} + + const sunCalcExample = { + dusk_no_offset: '20:18', + dawn_no_offset: '05:19', + dusk: '20:18', + dusk_hours: 20, + dusk_minutes: 18, + dawn: '05:19', + dawn_hours: 5, + dawn_minutes: 19, + dusk_time: 1715278688962, + dawn_time: 1715224744357, + dusk_astro_clock_offset: 0, + dawn_astro_clock_offset: 0 +} diff --git a/flow/db_init.js b/flow/db_init.js new file mode 100644 index 0000000..5fb447e --- /dev/null +++ b/flow/db_init.js @@ -0,0 +1,108 @@ +exports.id = 'db_init'; +exports.title = 'DB Initialization'; +exports.group = 'Worksys'; +exports.color = '#888600'; +exports.version = '1.0.2'; +exports.icon = 'sign-out'; +exports.input = 1; +exports.output = ["blue"]; + +exports.html = `
+
+
+
Hostname or IP address (if not empty - setting will override db setting)
+
+
+
Port
+
+
+
+
+
@(Client id)
+
+
+
@(Username)
+
+
+
`; + + +exports.readme = ` +# DB initialization +`; + +const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); +const { initNotification } = require('./helper/notification_reporter'); + + +exports.install = async function(instance) { + + const dbNodes = TABLE("nodes"); + const dbRelays = TABLE("relays"); + const dbSettings = TABLE("settings"); + const dbStatus = TABLE("status"); + const dbPins = TABLE("pins"); + const dbNotifications = TABLE("notifications"); + + FLOW.GLOBALS = {}; + const dbs = FLOW.GLOBALS; + + const responseSettings = await promisifyBuilder(dbSettings.find()); + const responseNodes = await promisifyBuilder(dbNodes.find()); + const responsePins = await promisifyBuilder(dbPins.find()); + const responseStatus = await promisifyBuilder(dbStatus.find()); + const responseRelays = await promisifyBuilder(dbRelays.find()); + const response = await promisifyBuilder(dbNotifications.find()); + + dbs.pinsData = makeMapFromDbResult(responsePins, "pin"); + dbs.relaysData = makeMapFromDbResult(responseRelays, "line"); + dbs.nodesData = makeMapFromDbResult(responseNodes, "node"); + dbs.statusData = responseStatus[0]; + dbs.notificationsData = makeMapFromDbResult(response, "key"); + + //+|354|nodesdata.....+|482|nodesdata.... + //for some reason, if last line in nodes.table is not empty, flow wrote more nodes data in one row, + //so we have to add empty line at the bottom of nodes table to avoid this. + //now, remove empty lines from nodesData database: + if(dbs.nodesData.hasOwnProperty("0")) delete dbs.nodesData["0"]; + + dbs.settings = { + edge_fw_version : "2024-11-04", //rok-mesiac-den + language : responseSettings[0]["lang"], + rvo_name : responseSettings[0]["rvo_name"], + project_id : responseSettings[0]["project_id"], + rvoTbName : dbs.relaysData[0]["tbname"], + temperature_address : responseSettings[0]["temperature_address"], + controller_type : responseSettings[0]["controller_type"], + serial_port : responseSettings[0]["serial_port"], + node_status_nok_time : responseSettings[0]["node_status_nok_time"] * 60 * 60 * 1000 ,// hour * minutes * + latitude : responseSettings[0]["latitude"], + longitude : responseSettings[0]["longitude"], + no_voltage : new Set(),//modbus_citysys - elektromer + backup_on_failure : responseSettings[0]["backup_on_failure"], + restore_from_backup : responseSettings[0]["restore_from_backup"], + restore_backup_wait : responseSettings[0]["restore_backup_wait"], + mqtt_host : responseSettings[0]["mqtt_host"], + mqtt_clientid : responseSettings[0]["mqtt_clientid"], + mqtt_username : responseSettings[0]["mqtt_username"], + mqtt_port : responseSettings[0]["mqtt_port"], + phases: responseSettings[0]["phases"], + + //dynamic values + masterNodeIsResponding : true, //cmd_manager + maintenance_mode : false, + } + + FLOW.dbLoaded = true; + initNotification(); + + setTimeout(()=> { + console.log("DB_INIT - data loaded"); + instance.send(0, "_") + }, 5000) + +}; + + + + diff --git a/flow/designer.json b/flow/designer.json index 7d57d69..09abdc3 100644 --- a/flow/designer.json +++ b/flow/designer.json @@ -25,8 +25,8 @@ "component": "debug", "tab": "1611921777196", "name": "ERROR", - "x": 401, - "y": 31, + "x": 598, + "y": 60, "connections": {}, "disabledio": { "input": [], @@ -49,8 +49,8 @@ "component": "wsmqttpublish", "tab": "1612772287426", "name": "WS MQTT publish", - "x": 311.75, - "y": 248, + "x": 310.75, + "y": 263, "connections": { "0": [ { @@ -77,6 +77,12 @@ "index": "0", "id": "1634303685503" } + ], + "3": [ + { + "index": "0", + "id": "1731068754606" + } ] }, "disabledio": { @@ -101,17 +107,17 @@ "component": "virtualwirein", "tab": "1612772287426", "name": "tb-push", - "x": 68.75, - "y": 269, + "x": 85.75, + "y": 352, "connections": { "0": [ { "index": "0", - "id": "1612776786008" + "id": "1612783322136" }, { - "index": "0", - "id": "1612783322136" + "index": "1", + "id": "1612776786008" } ] }, @@ -134,8 +140,8 @@ "component": "debug", "tab": "1612772287426", "name": "to TB", - "x": 317.75, - "y": 154, + "x": 323.75, + "y": 429, "connections": {}, "disabledio": { "input": [ @@ -160,8 +166,8 @@ "component": "debug", "tab": "1612772287426", "name": "errors from MQTT Broker", - "x": 610, - "y": 111, + "x": 743, + "y": 65, "connections": {}, "disabledio": { "input": [ @@ -186,8 +192,8 @@ "component": "debug", "tab": "1615551125555", "name": "Debug", - "x": 755, - "y": 19, + "x": 753, + "y": 150, "connections": {}, "disabledio": { "input": [ @@ -212,8 +218,8 @@ "component": "virtualwireout", "tab": "1615551125555", "name": "tb-push", - "x": 753, - "y": 112, + "x": 761, + "y": 251, "connections": {}, "disabledio": { "input": [], @@ -234,8 +240,8 @@ "component": "debug", "tab": "1615551125555", "name": "CMD_debug", - "x": 750, - "y": 197, + "x": 765, + "y": 350, "connections": {}, "disabledio": { "input": [ @@ -260,8 +266,8 @@ "component": "debug", "tab": "1611921777196", "name": "Debug", - "x": 398.8833312988281, - "y": 528.3500061035156, + "x": 595.8833312988281, + "y": 557.3500061035156, "connections": {}, "disabledio": { "input": [ @@ -286,8 +292,8 @@ "component": "debug", "tab": "1611921777196", "name": "Debug", - "x": 401.8833312988281, - "y": 625.3500061035156, + "x": 598.8833312988281, + "y": 654.3500061035156, "connections": {}, "disabledio": { "input": [ @@ -312,8 +318,8 @@ "component": "virtualwireout", "tab": "1611921777196", "name": "tb-push", - "x": 400.8833312988281, - "y": 328.25, + "x": 594.8833312988281, + "y": 350.25, "connections": {}, "disabledio": { "input": [], @@ -334,17 +340,17 @@ "component": "httproute", "tab": "1615551125555", "name": "POST /terminal", - "x": 72, - "y": 350, + "x": 114, + "y": 546, "connections": { "0": [ { "index": "0", - "id": "1619515097737" + "id": "1684060205000" }, { - "index": "0", - "id": "1684060205000" + "index": "1", + "id": "1619515097737" } ] }, @@ -380,8 +386,8 @@ "component": "httpresponse", "tab": "1615551125555", "name": "HTTP Response", - "x": 751, - "y": 273, + "x": 772, + "y": 443, "connections": {}, "disabledio": { "input": [], @@ -402,8 +408,8 @@ "component": "debug", "tab": "1615551125555", "name": "DIDO_Debug", - "x": 739, - "y": 635, + "x": 669, + "y": 1040, "connections": {}, "disabledio": { "input": [ @@ -428,8 +434,8 @@ "component": "trigger", "tab": "1615551125555", "name": "turnOff line", - "x": 71, - "y": 829, + "x": 88, + "y": 1158, "connections": { "0": [ { @@ -458,8 +464,8 @@ "component": "virtualwireout", "tab": "1615551125555", "name": "tb-push", - "x": 741, - "y": 736, + "x": 669, + "y": 1150, "connections": {}, "disabledio": { "input": [], @@ -480,8 +486,8 @@ "component": "debug", "tab": "1615551125555", "name": "Debug", - "x": 605, - "y": 1024, + "x": 700, + "y": 1495, "connections": {}, "disabledio": { "input": [], @@ -504,8 +510,8 @@ "component": "trigger", "tab": "1615551125555", "name": "start import", - "x": 235, - "y": 1032, + "x": 330, + "y": 1503, "connections": { "0": [ { @@ -534,8 +540,8 @@ "component": "csv_import", "tab": "1615551125555", "name": "CsvImport", - "x": 414, - "y": 1013, + "x": 509, + "y": 1484, "connections": { "0": [ { @@ -563,8 +569,8 @@ "component": "comment", "tab": "1615551125555", "name": "import data from csv", - "x": 401, - "y": 946, + "x": 496, + "y": 1417, "connections": {}, "disabledio": { "input": [], @@ -583,12 +589,12 @@ "component": "trigger", "tab": "1615551125555", "name": "update profile / node", - "x": 80, - "y": 13, + "x": 122, + "y": 209, "connections": { "0": [ { - "index": "0", + "index": "1", "id": "1619515097737" } ] @@ -613,12 +619,12 @@ "component": "trigger", "tab": "1615551125555", "name": "tun tasks", - "x": 77, - "y": 84, + "x": 119, + "y": 280, "connections": { "0": [ { - "index": "0", + "index": "1", "id": "1619515097737" } ] @@ -642,8 +648,8 @@ "component": "debug", "tab": "1612772287426", "name": "wsmqtt-exit1", - "x": 610.8833312988281, - "y": 199, + "x": 742.8833312988281, + "y": 153, "connections": {}, "disabledio": { "input": [], @@ -666,8 +672,8 @@ "component": "debug", "tab": "1612772287426", "name": "wsmqtt-exit2", - "x": 611.8833312988281, - "y": 374, + "x": 1064.8833312988281, + "y": 292, "connections": {}, "disabledio": { "input": [ @@ -692,8 +698,8 @@ "component": "virtualwireout", "tab": "1615551125555", "name": "to-cmd-manager", - "x": 740.8833312988281, - "y": 828, + "x": 668.8833312988281, + "y": 1269, "connections": {}, "disabledio": { "input": [], @@ -714,12 +720,12 @@ "component": "virtualwirein", "tab": "1615551125555", "name": "platform-rpc-call", - "x": 77.88333129882812, - "y": 173, + "x": 119.88333129882812, + "y": 369, "connections": { "0": [ { - "index": "0", + "index": "1", "id": "1619515097737" } ] @@ -743,8 +749,8 @@ "component": "virtualwirein", "tab": "1615551125555", "name": "cmd_to_dido", - "x": 76.88333129882812, - "y": 678, + "x": 93.88333129882812, + "y": 1007, "connections": { "0": [ { @@ -776,8 +782,8 @@ "component": "virtualwireout", "tab": "1615551125555", "name": "cmd_to_dido", - "x": 748.8833312988281, - "y": 373, + "x": 779.8833312988281, + "y": 552, "connections": {}, "disabledio": { "input": [], @@ -798,8 +804,8 @@ "component": "virtualwireout", "tab": "1612772287426", "name": "platform-rpc-call", - "x": 611.8833312988281, - "y": 287, + "x": 739.8833312988281, + "y": 246, "connections": {}, "disabledio": { "input": [], @@ -820,8 +826,8 @@ "component": "trigger", "tab": "1615551125555", "name": "turnOn line", - "x": 72, - "y": 756, + "x": 89, + "y": 1085, "connections": { "0": [ { @@ -850,8 +856,8 @@ "component": "cmd_manager", "tab": "1615551125555", "name": "CMD Manager", - "x": 420, - "y": 156, + "x": 448.1091003417969, + "y": 351.05455017089844, "connections": { "0": [ { @@ -905,17 +911,17 @@ "component": "httproute", "tab": "1615551125555", "name": "GET db", - "x": 73, - "y": 455, + "x": 115, + "y": 651, "connections": { "0": [ { "index": "0", - "id": "1619515097737" + "id": "1684060205000" }, { - "index": "0", - "id": "1684060205000" + "index": "1", + "id": "1619515097737" } ] }, @@ -950,8 +956,8 @@ "component": "trigger", "tab": "1615551125555", "name": "turnOnAlarm", - "x": 68, - "y": 902, + "x": 85, + "y": 1231, "connections": { "0": [ { @@ -980,8 +986,8 @@ "component": "trigger", "tab": "1615551125555", "name": "turnOffAlarm", - "x": 67, - "y": 975, + "x": 84, + "y": 1304, "connections": { "0": [ { @@ -1010,8 +1016,8 @@ "component": "virtualwireout", "tab": "1611921777196", "name": "modbus_to_dido", - "x": 399, - "y": 433, + "x": 596, + "y": 462, "connections": {}, "disabledio": { "input": [], @@ -1121,8 +1127,8 @@ "component": "monitormemory", "tab": "1612772287426", "name": "RAM", - "x": 71.88333129882812, - "y": 609.5, + "x": 77.88333129882812, + "y": 703.5, "connections": { "0": [ { @@ -1136,7 +1142,7 @@ "output": [] }, "state": { - "text": "834.19 MB / 985.68 MB", + "text": "847.42 MB / 985.68 MB", "color": "gray" }, "options": { @@ -1151,8 +1157,8 @@ "component": "monitordisk", "tab": "1612772287426", "name": "disk", - "x": 72.88333129882812, - "y": 706.5, + "x": 78.88333129882812, + "y": 800.5, "connections": { "0": [ { @@ -1166,7 +1172,7 @@ "output": [] }, "state": { - "text": "5.84 GB / 7.26 GB", + "text": "5.79 GB / 7.26 GB", "color": "gray" }, "options": { @@ -1182,8 +1188,8 @@ "component": "virtualwirein", "tab": "1612772287426", "name": "send-to-services", - "x": 5.883331298828125, - "y": 1069.5, + "x": 38.883331298828125, + "y": 1220.5, "connections": { "0": [ { @@ -1191,7 +1197,7 @@ "id": "1634463186563" }, { - "index": "0", + "index": "1", "id": "1634488120710" } ] @@ -1215,8 +1221,8 @@ "component": "virtualwireout", "tab": "1612772287426", "name": "send-to-services", - "x": 428.8833312988281, - "y": 602.5, + "x": 434.8833312988281, + "y": 696.5, "connections": {}, "disabledio": { "input": [], @@ -1237,8 +1243,8 @@ "component": "virtualwireout", "tab": "1612772287426", "name": "send-to-services", - "x": 612.8833312988281, - "y": 462.5, + "x": 740.8833312988281, + "y": 341.5, "connections": {}, "disabledio": { "input": [], @@ -1258,10 +1264,10 @@ "id": "1634303743260", "component": "httprequest", "tab": "1612772287426", - "name": "http://192.168.252.2:8004/sentmessage", + "name": "192.168.252.2:8004/sentmessage", "reference": "", - "x": 439.8833312988281, - "y": 1076.7333374023438, + "x": 467.8833312988281, + "y": 1154.7333374023438, "connections": { "0": [ { @@ -1291,8 +1297,8 @@ "component": "debug", "tab": "1612772287426", "name": "Debug", - "x": 234.75, - "y": 1024, + "x": 267.75, + "y": 1266, "connections": {}, "disabledio": { "input": [ @@ -1317,8 +1323,8 @@ "component": "code", "tab": "1612772287426", "name": "Code", - "x": 255, - "y": 512, + "x": 261, + "y": 606, "connections": { "0": [ { @@ -1352,8 +1358,8 @@ "component": "debug", "tab": "1612772287426", "name": "Debug", - "x": 430, - "y": 508, + "x": 436, + "y": 602, "connections": {}, "disabledio": { "input": [ @@ -1378,8 +1384,8 @@ "component": "code", "tab": "1612772287426", "name": "Code", - "x": 244, - "y": 608, + "x": 250, + "y": 702, "connections": { "0": [ { @@ -1413,8 +1419,8 @@ "component": "debug", "tab": "1612772287426", "name": "Debug", - "x": 431, - "y": 700, + "x": 437, + "y": 794, "connections": {}, "disabledio": { "input": [ @@ -1439,8 +1445,8 @@ "component": "code", "tab": "1612772287426", "name": "Code", - "x": 247, - "y": 702, + "x": 253, + "y": 796, "connections": { "0": [ { @@ -1474,8 +1480,8 @@ "component": "debug", "tab": "1612772287426", "name": "Debug", - "x": 434, - "y": 792, + "x": 440, + "y": 886, "connections": {}, "disabledio": { "input": [ @@ -1500,8 +1506,8 @@ "component": "debug", "tab": "1612772287426", "name": "Send info", - "x": 438, - "y": 1185, + "x": 467, + "y": 1261, "connections": {}, "disabledio": { "input": [ @@ -1526,8 +1532,8 @@ "component": "infosender", "tab": "1612772287426", "name": "Info sender", - "x": 233, - "y": 1122, + "x": 272, + "y": 1158, "connections": { "0": [ { @@ -1559,8 +1565,8 @@ "component": "debug", "tab": "1612772287426", "name": "Debug", - "x": 811.8833312988281, - "y": 1070.5, + "x": 782.8833312988281, + "y": 1149.5, "connections": {}, "disabledio": { "input": [ @@ -1585,8 +1591,8 @@ "component": "virtualwireout", "tab": "1615551125555", "name": "send-to-services", - "x": 748, - "y": 464, + "x": 778, + "y": 656, "connections": {}, "disabledio": { "input": [], @@ -1607,8 +1613,8 @@ "component": "monitorconsumption", "tab": "1612772287426", "name": "CPU", - "x": 71, - "y": 515, + "x": 77, + "y": 609, "connections": { "0": [ { @@ -1622,7 +1628,7 @@ "output": [] }, "state": { - "text": "1.9% / 86.94 MB", + "text": "1.3% / 74.34 MB", "color": "gray" }, "options": { @@ -1641,8 +1647,8 @@ "component": "debug", "tab": "1615551125555", "name": "CMDtoDIDO", - "x": 413, - "y": 654, + "x": 392, + "y": 1012, "connections": {}, "disabledio": { "input": [ @@ -1667,17 +1673,17 @@ "component": "virtualwirein", "tab": "1615551125555", "name": "from-dido-controller", - "x": 71, - "y": 260, + "x": 113, + "y": 456, "connections": { "0": [ { "index": "0", - "id": "1619515097737" + "id": "1684055037116" }, { - "index": "0", - "id": "1684055037116" + "index": "1", + "id": "1619515097737" } ] }, @@ -1700,8 +1706,8 @@ "component": "debug", "tab": "1615551125555", "name": "from dido to cmd", - "x": 423, - "y": 331, + "x": 448, + "y": 519, "connections": {}, "disabledio": { "input": [ @@ -1726,8 +1732,8 @@ "component": "debug", "tab": "1615551125555", "name": "HTTP routes", - "x": 423, - "y": 422, + "x": 447, + "y": 620, "connections": {}, "disabledio": { "input": [ @@ -1752,8 +1758,8 @@ "component": "debug", "tab": "1611921777196", "name": "MDBToDido", - "x": 401, - "y": 118, + "x": 598, + "y": 147, "connections": {}, "disabledio": { "input": [], @@ -1776,8 +1782,8 @@ "component": "dido_controller", "tab": "1615551125555", "name": "DIDO_Controller", - "x": 402, - "y": 736, + "x": 397, + "y": 1131, "connections": { "0": [ { @@ -1821,8 +1827,8 @@ "component": "virtualwirein", "tab": "1615551125555", "name": "modbus_to_dido", - "x": 79, - "y": 595, + "x": 96, + "y": 924, "connections": { "0": [ { @@ -1854,8 +1860,8 @@ "component": "debug", "tab": "1615551125555", "name": "modbusToDido", - "x": 411, - "y": 561, + "x": 388, + "y": 920, "connections": {}, "disabledio": { "input": [ @@ -1880,7 +1886,7 @@ "component": "modbus_reader", "tab": "1611921777196", "name": "Modbus reader", - "x": 102, + "x": 232, "y": 175, "connections": { "0": [ @@ -1931,8 +1937,8 @@ "component": "thermometer", "tab": "1611921777196", "name": "Thermometer", - "x": 107.75, - "y": 449, + "x": 234.75, + "y": 444, "connections": { "0": [ { @@ -1974,8 +1980,8 @@ "component": "debug", "tab": "1611921777196", "name": "MDBToTb", - "x": 402, - "y": 228, + "x": 599, + "y": 257, "connections": {}, "disabledio": { "input": [], @@ -1998,8 +2004,8 @@ "component": "code", "tab": "1611921777196", "name": "device-status", - "x": 588.0833282470703, - "y": 177, + "x": 755.0833282470703, + "y": 209, "connections": { "0": [ { @@ -2033,8 +2039,8 @@ "component": "debug", "tab": "1611921777196", "name": "modbus service", - "x": 802.0833282470703, - "y": 139, + "x": 966.0833282470703, + "y": 152, "connections": {}, "disabledio": { "input": [ @@ -2059,8 +2065,8 @@ "component": "virtualwireout", "tab": "1611921777196", "name": "send-to-services", - "x": 801.0833282470703, - "y": 236, + "x": 968.0833282470703, + "y": 268, "connections": {}, "disabledio": { "input": [], @@ -2081,8 +2087,8 @@ "component": "virtualwirein", "tab": "1612772287426", "name": "tb-push", - "x": 84.75, - "y": 1300, + "x": 64.75, + "y": 1450, "connections": { "0": [ { @@ -2110,8 +2116,8 @@ "component": "slack_filter", "tab": "1612772287426", "name": "Slack Filter", - "x": 278, - "y": 1297, + "x": 283, + "y": 1491, "connections": { "0": [ { @@ -2137,7 +2143,7 @@ "tag_on_include": "[{\"user_id\":\"U072JE5JUQG\", \"includes\":[\"Electrometer\", \"Twilight sensor\"]}]", "message_includes": "[\"is responding again\", \"Lamps have turned\", \"Flow has been restarted\"]", "types": "[\"emergency\", \"critical\", \"error\", \"alert\"]", - "name": "RVO16 Senica - 10.0.0.131" + "name": "rvo_senica_1_10.0.0.141" }, "color": "#30E193", "notes": "" @@ -2147,8 +2153,8 @@ "component": "httprequest", "tab": "1612772287426", "name": "http://192.168.252.2:8004/slack", - "x": 471, - "y": 1354, + "x": 482, + "y": 1573, "connections": { "0": [ { @@ -2178,11 +2184,13 @@ "component": "debug", "tab": "1612772287426", "name": "Debug", - "x": 808, - "y": 1302, + "x": 819, + "y": 1484, "connections": {}, "disabledio": { - "input": [], + "input": [ + 0 + ], "output": [] }, "state": { @@ -2202,8 +2210,8 @@ "component": "trigger", "tab": "1612772287426", "name": "Trigger", - "x": 73, - "y": 1388, + "x": 66, + "y": 1543, "connections": { "0": [ { @@ -2226,7 +2234,553 @@ }, "color": "#F6BB42", "notes": "" + }, + { + "id": "1729855334955", + "component": "virtualwireout", + "tab": "1612772287426", + "name": "platform-rpc-call", + "x": 1058.933334350586, + "y": 488.3500061035156, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "platform-rpc-call", + "color": "gray" + }, + "options": { + "wirename": "platform-rpc-call" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1729855371093", + "component": "debug", + "tab": "1612772287426", + "name": "rpc cloud", + "x": 1066.933334350586, + "y": 393.3500061035156, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1731068658334", + "component": "virtualwirein", + "tab": "1612772287426", + "name": "db-init", + "x": 90.75, + "y": 247, + "connections": { + "0": [ + { + "index": "0", + "id": "1612776786008" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "db-init", + "color": "gray" + }, + "options": { + "wirename": "db-init" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1731068754606", + "component": "cloudmqttconnect", + "tab": "1612772287426", + "name": "MQTT client - to senica-prod01", + "x": 739.75, + "y": 434, + "connections": { + "1": [ + { + "index": "0", + "id": "1729855371093" + }, + { + "index": "0", + "id": "1729855334955" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Connected", + "color": "green" + }, + "options": { + "username": "", + "clientid": "", + "port": "2764", + "host": "192.168.252.2", + "topic": "u118" + }, + "color": "#888600", + "notes": "" + }, + { + "id": "1731069001548", + "component": "db_init", + "tab": "1612772287426", + "name": "DB Initialization", + "x": 91.75, + "y": 55.25, + "connections": { + "0": [ + { + "index": "0", + "id": "1731069033416" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#888600", + "notes": "" + }, + { + "id": "1731069033416", + "component": "virtualwireout", + "tab": "1612772287426", + "name": "db-init", + "x": 343.75, + "y": 50.25, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "db-init", + "color": "gray" + }, + "options": { + "wirename": "db-init" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1731069059135", + "component": "showdb", + "tab": "1612772287426", + "name": "Show db data", + "x": 1076.75, + "y": 745.25, + "connections": { + "0": [ + { + "index": "0", + "id": "1731069079243" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#888600", + "notes": "" + }, + { + "id": "1731069079243", + "component": "debug", + "tab": "1612772287426", + "name": "dbData", + "x": 1270.75, + "y": 784.25, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1731069116691", + "component": "trigger", + "tab": "1612772287426", + "name": "settings", + "x": 863.75, + "y": 622.75, + "connections": { + "0": [ + { + "index": "0", + "id": "1731069059135" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1731069131637", + "component": "trigger", + "tab": "1612772287426", + "name": "relaysData", + "x": 800.75, + "y": 684.75, + "connections": { + "0": [ + { + "index": "1", + "id": "1731069059135" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1731069137374", + "component": "trigger", + "tab": "1612772287426", + "name": "nodesData", + "x": 693.75, + "y": 749.75, + "connections": { + "0": [ + { + "index": "2", + "id": "1731069059135" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1731069179846", + "component": "trigger", + "tab": "1612772287426", + "name": "pinsData", + "x": 755.75, + "y": 802.75, + "connections": { + "0": [ + { + "index": "3", + "id": "1731069059135" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1731069192937", + "component": "trigger", + "tab": "1612772287426", + "name": "sample data", + "x": 812.75, + "y": 856.75, + "connections": { + "0": [ + { + "index": "4", + "id": "1731069059135" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1731069264443", + "component": "virtualwirein", + "tab": "1612772287426", + "name": "db-init", + "x": 50.75, + "y": 1099, + "connections": { + "0": [ + { + "index": "0", + "id": "1634488120710" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "db-init", + "color": "gray" + }, + "options": { + "wirename": "db-init" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1731069334626", + "component": "virtualwirein", + "tab": "1615551125555", + "name": "db-init", + "x": 184.88333129882812, + "y": 129, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "db-init", + "color": "gray" + }, + "options": { + "wirename": "db-init" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1731069548145", + "component": "virtualwirein", + "tab": "1611921777196", + "name": "db-init", + "x": 46.75, + "y": 192, + "connections": { + "0": [ + { + "index": "0", + "id": "1699965957410" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "db-init", + "color": "gray" + }, + "options": { + "wirename": "db-init" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1731069567152", + "component": "virtualwirein", + "tab": "1611921777196", + "name": "db-init", + "x": 44.75, + "y": 465, + "connections": { + "0": [ + { + "index": "0", + "id": "1700411878636" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "db-init", + "color": "gray" + }, + "options": { + "wirename": "db-init" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1731070156936", + "component": "virtualwirein", + "tab": "1615551125555", + "name": "db-init", + "x": 89.88333129882812, + "y": 1381, + "connections": { + "0": [ + { + "index": "2", + "id": "1699963668903" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "db-init", + "color": "gray" + }, + "options": { + "wirename": "db-init" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1731234189516", + "component": "trigger", + "tab": "1612772287426", + "name": "monitor.txt", + "x": 865.75, + "y": 911.75, + "connections": { + "0": [ + { + "index": "5", + "id": "1731069059135" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1731234189551", + "component": "trigger", + "tab": "1612772287426", + "name": "err.txt", + "x": 921.75, + "y": 968.75, + "connections": { + "0": [ + { + "index": "6", + "id": "1731069059135" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#F6BB42", + "notes": "" } ], "version": 615 -} \ No newline at end of file +} diff --git a/flow/dido_controller.js b/flow/dido_controller.js index 24f3269..11e7ae3 100644 --- a/flow/dido_controller.js +++ b/flow/dido_controller.js @@ -3,8 +3,8 @@ exports.title = 'DIDO_Controller'; exports.version = '2.0.0'; exports.group = 'Worksys'; exports.color = '#2134B0'; -exports.input = 2; -exports.output = ["red", "white", "yellow"]; +exports.input = 3; +exports.output = ["red", "white", "yellow", "green"]; exports.click = false; exports.icon = 'bolt'; exports.options = { edge: "undefined" }; @@ -56,25 +56,31 @@ state_of_contactor - podľa indexu stykača sa reportuje jeho stav, teda momentálne sa stav zmení len keď vo flow klikneš aby sa zmenil, ale tá zmena by sa mala ukázať aj na platforme */ +const dbStatus = TABLE("status"); +const { errLogger, logger, monitor } = require('./helper/logger'); +const SerialPort = require('serialport'); +const WebSocket = require('ws'); +//const { exec } = require('child_process'); +const { runSyncExec } = require('./helper/serialport_helper'); +const { bytesToInt, resizeArray } = require('./helper/utils'); +const { sendNotification } = require('./helper/notification_reporter'); +const bitwise = require('bitwise'); -//globals -//FIRMWARE version -//TODO remove FLOW.OMS_edgeName variable, as we have FLOW.OMS_rvo_tbname -FLOW.OMS_edge_fw_version = "2024-09-23";//rok-mesiac-den -FLOW.OMS_edgeName = ""; -FLOW.OMS_maintenance_mode = false; +const DataToTbHandler = require('./helper/DataToTbHandler'); +let tbHandler; -//dynamic values -FLOW.OMS_masterNodeIsResponding = true; //cmd_manager -//FLOW.OMS_brokerready = false //wsmqttpublish -FLOW.OMS_no_voltage = new Set();//modbus_citysys - elektromer +const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler'); +const errorHandler = new ErrorToServiceHandler(); -//see loadSettings() in cmd_manager -FLOW.OMS_language = "en";//cmd_manager -FLOW.OMS_rvo_name = "";//cmd_manager -FLOW.OMS_rvo_tbname = "";//relaysData -FLOW.OMS_temperature_adress = "";//cmd_manager -//----------------------------------------------- +let ws = null; +let rsPort = null; + +let pinsData; +let relaysData; +let rvoTbName; +let GLOBALS; //FLOW global GLOBALS +let SETTINGS; // GLOBALS.settings +let controller_type; let alarmStatus = "OFF"; @@ -87,43 +93,6 @@ const SEND_TO = { const TIME_AFTER_TEMPERATURE_NOK_STATUS = 3600; //seconds const DIFFERENCE_TO_SEND_TEMPERATURE = 0.31; -var log4js = require("log4js"); -var path = require('path'); - -log4js.configure({ - appenders: { - errLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'err.txt') }, - monitorLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'monitor.txt') }, - console: { type: 'console' } - }, - categories: { - errLogs: { appenders: ['console', 'errLogs'], level: 'error' }, - monitorLogs: { appenders: ['console', 'monitorLogs'], level: 'trace' }, - //another: { appenders: ['console'], level: 'trace' }, - default: { appenders: ['console'], level: 'trace' } - } -}); - -const errLogger = log4js.getLogger("errLogs"); -const logger = log4js.getLogger(); -const monitor = log4js.getLogger("monitorLogs"); - -//console.log(path.join(__dirname, 'err.txt', "-----------------------------")); - -/* -process.on('uncaughtException', function (err) { - - errLogger.error('uncaughtException:', err.message) - errLogger.error(err.stack); - - //process.exit(1); -}) -*/ - -//USAGE -//logger.debug("text") -//monitor.info('info'); -//errLogger.error("some error"); exports.install = function(instance) { @@ -149,8 +118,6 @@ exports.install = function(instance) { const twilight_sensor_array = []; let twilightError = false; - let edgeName = ""; - monitor.info("DIDO_Relay_Controller installed"); //key is PIN number , line: 0 = RVO @@ -172,15 +139,6 @@ exports.install = function(instance) { }; */ - const dbPins = TABLE("pins"); - let pinsData = {};//key is pin - - const dbRelays = TABLE("relays"); - let relaysData = {};//key is line - - const dbStatus = TABLE("status"); - let statusData = null; - //status for calculating Statecodes let deviceStatus = { //key is device name: temperature,.... "state_of_main_switch": "Off", //Hlavný istič @@ -190,53 +148,32 @@ exports.install = function(instance) { "temperature": "OK", //templomer "battery": "OK", //Batéria "power_supply": "OK", //Zdroj - "master_node": "OK", //MN - FLOW.OMS_masterNodeIsResponding - "no_voltage": "OK", //FLOW.OMS_no_voltage - výpadok napätia na fáze + "master_node": "OK", //MN - GLOBALS.settings.masterNodeIsResponding + "no_voltage": "OK", //GLOBALS.settings.no_voltage - výpadok napätia na fáze "state_of_breaker": {}, //"Off",//Istič "state_of_contactor": {}, //"Off",//Stykač "twilight_sensor": "OK" //lux sensor }; - const SerialPort = require('serialport'); - const WebSocket = require('ws'); - let ws = null; - let rsPort = null; - - const { runSyncExec } = require('./helper/serialport_helper.js'); - const { bytesToInt, resizeArray } = require('./helper/utils'); - const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); - const { sendNotification } = require('./helper/notification_reporter.js'); - const bitwise = require('bitwise'); - - const DataToTbHandler = require('./helper/DataToTbHandler.js'); - const tbHandler = new DataToTbHandler(SEND_TO.tb); - tbHandler.setSender(exports.title); + function main() { - const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler.js'); - const errorHandler = new ErrorToServiceHandler(); + GLOBALS = FLOW.GLOBALS; + SETTINGS = FLOW.GLOBALS.settings; + rvoTbName = SETTINGS.rvoTbName; + pinsData = GLOBALS.pinsData; + relaysData = GLOBALS.relaysData; + statusData = GLOBALS.statusData; - //let useTurnOffCounter = false; - //let turnOffCounter = 0; - let controller_type = FLOW.OMS_controller_type //"lm" or "unipi" //logicMachine - if(controller_type == "") controller_type = "lm"; + tbHandler = new DataToTbHandler(SEND_TO.tb) + tbHandler.setSender(exports.title); - console.log(exports.title, "controller type: ", controller_type); - - - async function loadAllDb() - { - let responsePins = await promisifyBuilder(dbPins.find()); - pinsData = makeMapFromDbResult(responsePins, "pin"); - - let responseRelays = await promisifyBuilder(dbRelays.find()); - relaysData = makeMapFromDbResult(responseRelays, "line"); - - let responseStatus = await promisifyBuilder(dbStatus.find()); - statusData = responseStatus[0]; // {thermometer: 'OK', em: 'OK', twilight_sensor: 'OK'} + controller_type = SETTINGS.controller_type //"lm" or "unipi" //logicMachine + if(controller_type == "") controller_type = "lm"; + deviceStatus["temperature"] = statusData.thermometer; - FLOW.OMS_rvo_tbname = relaysData[0].tbname; + console.log(exports.title, "controller type: ", controller_type); if(controller_type === "lm") { @@ -249,12 +186,13 @@ exports.install = function(instance) { else { errLogger.debug("UNKNOWN controller_type:", controller_type); } - } + } function initialSetting() { - //force turn off relays & set tbname + //force turn off relays + let keys = Object.keys(pinsData); for(let i = 0; i < keys.length; i++) { @@ -266,15 +204,12 @@ exports.install = function(instance) { if(relaysData[line] != undefined) { pinsData[key].tbname = relaysData[line].tbname; - - relaysData[line].contactor = 0; + //relaysData[line].contactor = 0; } else { errLogger.error("CRITICAL!!! undefined relay", relaysData[line], line); - - //sendNotification("set port ", edgeName, ERRWEIGHT.CRITICAL, "local database is corrupted", "", SEND_TO.tb, instance, null ); - sendNotification("set port ", edgeName, "local_database_is_corrupted", {}, "", SEND_TO.tb, instance ); + sendNotification("set port ", rvoTbName, "local_database_is_corrupted", {}, "", SEND_TO.tb, instance ); } } @@ -285,36 +220,23 @@ exports.install = function(instance) { //this will modify database let forceTurnOff = true; - turnOffLine(line, pin, forceTurnOff, "turn off on startup"); + turnLine("off", line, pin, forceTurnOff, "turn off on startup"); } } //report RVO version relaysData[0].tbname; let values = {}; - values["edge_fw_version"] = FLOW.OMS_edge_fw_version; - values["maintenance_mode"] = FLOW.OMS_maintenance_mode; + values["edge_fw_version"] = SETTINGS.edge_fw_version; + values["maintenance_mode"] = SETTINGS.maintenance_mode; - edgeName = relaysData[0].tbname; - FLOW.OMS_edgeName = edgeName; - - dataToTb = { - [edgeName]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - - instance.send(SEND_TO.tb, dataToTb); + sendTelemetry(values, rvoTbName); let time = 5*1000; setTimeout(function(){ instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "buildTasks"}); - sendNotification("rsPort.open()", edgeName, "flow_start", {}, "", SEND_TO.tb, instance ); - monitor.info("-->FLOW bol spustený", edgeName, FLOW.OMS_edge_fw_version); - + sendNotification("rsPort.open()", rvoTbName, "flow_start", {}, "", SEND_TO.tb, instance); + monitor.info("-->FLOW bol spustený", rvoTbName, SETTINGS.edge_fw_version); }, time); } @@ -339,27 +261,23 @@ exports.install = function(instance) { //set port rsPort.write(Buffer.from(setRSPortData), function(err) { - monitor.info(exports.title + "--->Digital in_out has been set (runSyncExec was sucessfull)"); + if(!err) { + monitor.info(exports.title + "--->Digital in_out has been set (runSyncExec was sucessfull)"); - turnOffAlarm(); - - //useTurnOffCounter = true; - //turnOffCounter = relaysData.length - 1; - - initialSetting(); + turnAlarm("off"); + initialSetting(); + } }) - }).catch(function (reason) { - //instance.send(SEND_TO.debug, exports.title + " runSyncExec - promise rejected:" + reason); - errLogger.error( exports.title + " runSyncExec - promise rejected:" + reason); - - errorHandler.sendMessageToService( exports.title + " runSyncExec - promise rejected:" + reason); + }).catch(function(reason) { + errLogger.error(exports.title + " runSyncExec - promise rejected:" + reason); + errorHandler.sendMessageToService(exports.title + " runSyncExec - promise rejected:" + reason); }); }); - rsPort.on('data', function (data){ + rsPort.on('data', function(data) { rsPortReceivedData = [...rsPortReceivedData, ...data]; @@ -402,7 +320,6 @@ exports.install = function(instance) { }) rsPort.open(); - } @@ -417,7 +334,7 @@ exports.install = function(instance) { ws.onopen = function open() { instance.send(0, exports.title + " running"); - turnOffAlarm(); + turnAlarm("off"); // useTurnOffCounter = true; // turnOffCounter = relaysData.length - 1; @@ -480,7 +397,7 @@ exports.install = function(instance) { { previousValues["temperature"]["value"] = value; values['temperature'] = value; - sendTelemetry(values, FLOW.OMS_rvo_tbname); + sendTelemetry(values, rvoTbName); } return; } @@ -490,7 +407,7 @@ exports.install = function(instance) { let value = item['value']; let pin = item["dev"] + item["circuit"]; // for example "relay1_03" or "input1_01" - if (pin == undefined) return; + if(pin == undefined) return; switchLogic(pin, value); }) } @@ -516,92 +433,59 @@ exports.install = function(instance) { } } - // ! do we need requests every minute ??? - // const startRequests = () => { - // console.log("startRequest function called"); - // start = setInterval(() => { - // // console.log("data from evok requested"); - // ws.send(JSON.stringify({"cmd":"filter", "devices": "neuron"})); - // // ws.send(JSON.stringify({"cmd":"filter", "devices":["input", "relay"]})); - // }, 150000) - // } - instance.on("close", () => { if(rsPort) rsPort.close(); if(ws) ws.close(); - clearInterval(sendRebuildTasksAt11); }) - loadAllDb(); - function getPin(line) { - //conversionTable - let keys = Object.keys(pinsData); - for(let i = 0; i < keys.length; i++) + //conversionTable + let keys = Object.keys(pinsData); + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + + if(pinsData[key].type == "state_of_contactor" && pinsData[key].line == line) { - let key = keys[i]; - - if(pinsData[key].type == "state_of_contactor" && pinsData[key].line == line) - { - if(rsPort) return key - 1; - if(ws) return key; - } + if(rsPort) return key - 1; + if(ws) return key; } + } - logger.debug("no pin detected"); + logger.debug("no pin detected"); - return null; + return null; } - function turnOnAlarm() + function turnAlarm(onOrOff) { - if(FLOW.OMS_maintenance_mode) return; + let value = 0; + if(onOrOff == "on") value = 1; + + if(value == 1 && SETTINGS.maintenance_mode) return; - alarmStatus = "ON"; - - if(rsPort) - { - let arr = [0x55]; - arr.push( 13 ); - arr.push( 0 ); - arr.push( 1 ); - - rsPort.write(Buffer.from(arr), function(err) { - logger.debug("sirena zapnuta"); - }); - } - else if(ws) - { - let cmd = {"cmd": "set", "dev": "relay", "circuit": "1_01", "value": 1}; - ws.send(JSON.stringify(cmd)); - logger.debug("sirena zapnuta"); - } - } - - - function turnOffAlarm() - { alarmStatus = "OFF"; - + if(value == 1) alarmStatus = "ON"; + if(rsPort) { let arr = [0x55]; - arr.push( 13 ); - arr.push( 0 ); - arr.push( 0 ); - + arr.push(13); + arr.push(0); + arr.push(value); + rsPort.write(Buffer.from(arr), function(err) { - logger.debug("sirena vypnuta"); + logger.debug(`sirena - ${onOrOff}`); }); } else if(ws) { - let cmd = {"cmd": "set", "dev": "relay", "circuit": "1_01", "value": 0}; + let cmd = {"cmd": "set", "dev": "relay", "circuit": "1_01", "value": value}; ws.send(JSON.stringify(cmd)); - logger.debug("sirena vypnuta"); + logger.debug(`sirena - ${onOrOff}`); } } @@ -609,9 +493,7 @@ exports.install = function(instance) { function reportLineStatus(line) { //Tá hodnota by mala fungovať tak že LSB bit číslo je stav ističa (1 - On, 0 - Off) a druhý bit je stav stýkača (1 - true, 0 - false). - let tbname = relaysData[line].tbname; - let bits = []; if(deviceStatus["state_of_breaker"][line] == "On") @@ -631,143 +513,47 @@ exports.install = function(instance) { let byte = bitwise.byte.write(bits.reverse()); //console.log("line", line, bits, byte); - - let values = { - statecode: byte - } - - let dataToTb = { - [tbname]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - - //console.log(values); - - instance.send(SEND_TO.tb, dataToTb); + sendTelemetry({statecode: byte}, tbname); } - function turnOnLine(line, pin, force, info) + // turn line on or off + function turnLine(onOrOff, line, pin, force, info) { - - instance.send(SEND_TO.debug, "turn on line " + line ); + //onOrOff => "on" or "off" + let value = 0; + if(onOrOff == "on") value = 1; + if(force == undefined) force = false; if(line == 0) { - if(alarmStatus == "ON") turnOffAlarm(); - FLOW.OMS_maintenance_mode = true; + if(value == 1 && alarmStatus == "ON") turnAlarm("off"); + SETTINGS.maintenance_mode = value? true: false; let values = {}; values["statecode"] = calculateStateCode(); - values["power_mode"] = "maintenance"; - let tbname = relaysData[line].tbname; - sendTelemetry(values, tbname); - - monitor.info("turnOnLine (line, FLOW.OMS_maintenance_mode)", line, FLOW.OMS_maintenance_mode, info); - - return; - } - - if( pin === undefined) pin = getPin(line); - - monitor.info("turnOnLine (line, pin, force)", line, pin, force, info); - - if( pin === undefined) - { - monitor.info("pin is undefined!", line); - return; - } - + values["power_mode"] = value ? "maintenance": "Automatic"; + sendTelemetry(values, rvoTbName); - if(!force) - { - if(relaysData[line].contactor == 1) - { - instance.send(SEND_TO.debug, "line is already on " + line ); - logger.debug("turnOnLine: line is already on: ", line); - - return; - } - } - - // if(!rsPort.isOpen && !ws) - if(!rsPort && !ws) - { - errLogger.error("dido controller - port or websocket is not opened"); + monitor.info(`turnLine ${onOrOff} - (line, SETTINGS.maintenance_mode)`, line, SETTINGS.maintenance_mode, info); return; } - if(rsPort) - { - let arr = [0x55]; - arr.push( pin ); - arr.push( 0 ); - arr.push( 1 ); - - rsPort.write(Buffer.from(arr), function(err) { - if(err === undefined) - { - console.log("turnONLine zapisal do rsPortu", line, arr); - switchLogic(arr); - } - else - { - monitor.info("turnOnLine WRITE error", err); - } - - }); + if(pin === undefined) pin = getPin(line); - } - else if(ws) - { - console.log("turnONLine pin (relay)", pin); - //pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method - let cmd = {"cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": 1}; - ws.send(JSON.stringify(cmd)); - switchLogic(pin, 1) - } - } - - - function turnOffLine(line, pin, force, info) - { - if(force == undefined) force = false; - - if(line == 0) - { - FLOW.OMS_maintenance_mode = false; - - let values = {}; - values["statecode"] = calculateStateCode(); - values["power_mode"] = "automatic"; - let tbname = relaysData[line].tbname; - sendTelemetry(values, tbname); - - return; - } - - if( pin === undefined) pin = getPin(line); - - monitor.info("turnOffLine (line, pin, force)", line, pin, force, info); - - if( pin === undefined) + if(pin === undefined) { errLogger.error("pin is undefined!", line); return; } - + if(!force) { - if(relaysData[line].contactor == 0) + if(relaysData[line].contactor == value) { - instance.send(SEND_TO.debug, "line is already off " + line ); - logger.debug("turnOffLine: line already off:", line); - + instance.send(SEND_TO.debug, `line is already ${onOrOff} ` + line ); + logger.debug(`turnLine: line is already ${onOrOff} `, line); return; } } @@ -782,35 +568,40 @@ exports.install = function(instance) { if(rsPort) { let arr = [0x55]; - arr.push( pin ); - arr.push( 0 ); - arr.push( 0 ); + arr.push(pin); + arr.push(0); + arr.push(value); rsPort.write(Buffer.from(arr), function(err) { if(err === undefined) { - console.log("turnOffLine zapisal do rsPort-u", line, arr); + monitor.info(`turnLine ${onOrOff} zapisal do rsPort-u`, line, pin, arr, info); switchLogic(arr); } else { - monitor.info("turnOffLine WRITE error", err); - } - + monitor.info(`turnLine ${onOrOff} WRITE error`, err); + } }); } else if(ws) { //pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method - //monitor.info("turnOffLine pin (relay)", pin); - let cmd = {"cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": 0}; + monitor.info(`turnLine ${onOrOff} - (line, pin, force)`, line, pin, force, info); + let cmd = {"cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": value}; ws.send(JSON.stringify(cmd)); - switchLogic(pin, 0) + switchLogic(pin, value) } } + // main opening + instance.on("2", _ => { + main(); + }) + + //data from modbus_reader or temperature sensor or twilight sensor or other modbus device instance.on("0", flowdata => { @@ -820,7 +611,7 @@ exports.install = function(instance) { instance.send(SEND_TO.debug, flowdata.data); // we handle nok status from modbus_reader component and thermometer - if(flowdata.data?.status) + if("status" in flowdata.data) { const status = flowdata.data.status; if(status == "NOK-twilight_sensor") @@ -838,7 +629,7 @@ exports.install = function(instance) { deviceStatus["temperature"] = "NOK"; } } - else if(flowdata.data?.values) + else if("values" in flowdata.data) { const values = flowdata.data.values; if(values.hasOwnProperty("twilight_sensor")) @@ -856,10 +647,10 @@ exports.install = function(instance) { else if(values.hasOwnProperty("total_power") || values.hasOwnProperty("total_energy") || values.hasOwnProperty("power_factor") || values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_1_current")) { deviceStatus["em"] = "OK"; - FLOW.OMS_no_voltage.size > 0 ? deviceStatus["no_voltage"] = "NOK": deviceStatus["no_voltage"] = "OK"; + SETTINGS.no_voltage.size > 0 ? deviceStatus["no_voltage"] = "NOK": deviceStatus["no_voltage"] = "OK"; } - sendTelemetry(values, FLOW.OMS_rvo_tbname); + sendTelemetry(values, rvoTbName); } sendRvoStatus(); @@ -878,10 +669,10 @@ exports.install = function(instance) { let force = obj.force; let info = obj.info; - if(obj.command == "turnOn") turnOnLine(line, undefined, force, info); - else if(obj.command == "turnOff") turnOffLine(line, undefined, force, info); - else if(obj.command == "turnOnAlarm") turnOnAlarm(); - else if(obj.command == "turnOffAlarm") turnOffAlarm(); + if(obj.command == "turnOn") turnLine("on", line, undefined, force, info); + else if(obj.command == "turnOff") turnLine("off", line, undefined, force, info); + else if(obj.command == "turnOnAlarm") turnAlarm("on"); + else if(obj.command == "turnOffAlarm") turnAlarm("off"); //! ake data prichadzaju z cmd_manager.js ??? //TODO transform to websocket @@ -899,11 +690,9 @@ exports.install = function(instance) { function calculateStateCode() { - let bytes = []; let bits = []; - //Hlavný istič - state_of_main_switch - //TODO state_of main_switch is door contact in senica rvo - values should be "open" and "closed" + //Hlavný istič - state_of_main_switch => v rvo senica je to druhy door pre silovu cast (EM) if(deviceStatus["state_of_main_switch"] == "closed") { bits.push(0); @@ -914,7 +703,7 @@ exports.install = function(instance) { } //Prevádzkový mód - Manual, Off, Automatic, maintenance_mode = true/false // DAVA 2 BITY - if(!FLOW.OMS_maintenance_mode) + if(!SETTINGS.maintenance_mode) { if(deviceStatus["rotary_switch_state"] == "Manual") { @@ -1040,6 +829,10 @@ exports.install = function(instance) { async function sendRvoStatus() { + // test if dbLoaded is ok to check + //if(!FLOW.dbLoaded) return; + if(SETTINGS === undefined) return; + const table = { "OK": 1, "NOK": 0 @@ -1055,7 +848,7 @@ exports.install = function(instance) { "master_node_status": table[deviceStatus["master_node"]] }; - for (const phase of FLOW.OMS_no_voltage) dataToTb[`phase_${phase}_status`] = 0; + for (const phase of SETTINGS.no_voltage) dataToTb[`phase_${phase}_status`] = 0; //thermometer did not send data for more than a hour. Time in seconds if(deviceStatus["temperature"] === "OK") @@ -1071,7 +864,7 @@ exports.install = function(instance) { dataToTb["status"] = checkRvoStatus(); dataToTb["statecode"] = calculateStateCode(); - sendTelemetry(dataToTb, FLOW.OMS_rvo_tbname); + sendTelemetry(dataToTb, rvoTbName); } @@ -1083,7 +876,8 @@ exports.install = function(instance) { let status = "OK"; for (const [key, value] of Object.entries(deviceStatus)) { - if(["em", "twilight_sensor", "temperature"].includes(key) && value == "NOK") status = "NOK"; + //if(["em", "twilight_sensor", "temperature"].includes(key) && value == "NOK") status = "NOK"; + if(["em", "twilight_sensor"].includes(key) && value == "NOK") status = "NOK"; } if(status == "OK") @@ -1093,7 +887,7 @@ exports.install = function(instance) { for (const pinIndex of pinIndexes) { if (previousValues[pinIndex] === 0) { - if ((pinIndex === 6 || pinIndex === 'input1_01' || pinIndex === 'input1_05') && FLOW.OMS_maintenance_mode) continue; + if ((pinIndex === 6 || pinIndex === 'input1_01' || pinIndex === 'input1_05') && SETTINGS.maintenance_mode) continue; status = "NOK"; break; } @@ -1102,8 +896,8 @@ exports.install = function(instance) { // battery status. If value is 1 - battery is NOK if (previousValues[5] === 1) status = "NOK"; - if(!FLOW.OMS_masterNodeIsResponding) status = "NOK"; - if(FLOW.OMS_no_voltage.size > 0) status = "NOK"; + if(!SETTINGS.masterNodeIsResponding) status = "NOK"; + if(SETTINGS.no_voltage.size > 0) status = "NOK"; return status; } @@ -1135,7 +929,7 @@ exports.install = function(instance) { if(obj == undefined) { previousValues[pinIndex] = newPinValue; - logger.debug("dido-switchLogic ==> no pinIndex", pinIndex); + //logger.debug("dido-switchLogic ==> no pinIndex", pinIndex); return; } @@ -1155,14 +949,14 @@ exports.install = function(instance) { // { // if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) // { - // sendNotification("switchLogic", edgeName, "main_switch_has_been_turned_off", {}, "", SEND_TO.tb, instance , "state_of_main_switch"); + // sendNotification("switchLogic", rvoTbName, "main_switch_has_been_turned_off", {}, "", SEND_TO.tb, instance , "state_of_main_switch"); // values["status"] = "NOK"; // deviceStatus["state_of_main_switch"] = "Off"; // } // else if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) // { - // sendNotification("switchLogic", edgeName, "main_switch_has_been_turned_on", {}, "", SEND_TO.tb, instance , "state_of_main_switch"); + // sendNotification("switchLogic", rvoTbName, "main_switch_has_been_turned_on", {}, "", SEND_TO.tb, instance , "state_of_main_switch"); // deviceStatus["state_of_main_switch"] = "On"; // } @@ -1211,73 +1005,71 @@ exports.install = function(instance) { //console.log("rotary_switch_state pin", pin2, pin3, value); } + //Zdroj - pin 4 else if (type === "power_supply") { if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) { - //sendNotification("switchLogic", edgeName, ERRWEIGHT.ALERT, "Power supply is not OK", "", SEND_TO.tb, instance); - sendNotification("switchLogic", edgeName, "power_supply_has_disconnected_input", {}, "", SEND_TO.tb, instance, "power_supply"); + sendNotification("switchLogic", rvoTbName, "power_supply_has_disconnected_input", {}, "", SEND_TO.tb, instance, "power_supply"); deviceStatus["power_supply"] = "NOK"; } else if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) { - //sendNotification("switchLogic", edgeName, ERRWEIGHT.NOTICE, "Power supply is is OK", "", SEND_TO.tb, instance); - sendNotification("switchLogic", edgeName, "power_supply_works_correctly", {}, "", SEND_TO.tb, instance, "power_supply"); + sendNotification("switchLogic", rvoTbName, "power_supply_works_correctly", {}, "", SEND_TO.tb, instance, "power_supply"); deviceStatus["power_supply"] = "OK"; } } + //Batéria - pin 5 else if (type === "battery") { if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) { - //sendNotification("switchLogic", edgeName, ERRWEIGHT.ERROR, "Battery is not OK", "", SEND_TO.tb, instance); - sendNotification("switchLogic", edgeName, "battery_level_is_low", {}, "", SEND_TO.tb, instance, "battery_level"); + sendNotification("switchLogic", rvoTbName, "battery_level_is_low", {}, "", SEND_TO.tb, instance, "battery_level"); deviceStatus["battery"] = "NOK"; } else if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) { - //sendNotification("switchLogic", edgeName, ERRWEIGHT.NOTICE, "Battery is OK", "", SEND_TO.tb, instance); - sendNotification("switchLogic", edgeName, "battery_level_is_ok", {}, "", SEND_TO.tb, instance, "battery_level"); + sendNotification("switchLogic", rvoTbName, "battery_level_is_ok", {}, "", SEND_TO.tb, instance, "battery_level"); deviceStatus["battery"] = "OK"; } } + //Dverový kontakt - pin 6 //! Po novom mame dva dverove kontakty, nie jeden. Druhy je teraz tam, kde bol digital input "state_of_main_switch" //! preto ked pride z evoku signal z input1_05, co bol predytm "main switch" handlujeme ho teraz ako 'door_condition' else if(type == "door_condition" || type === "state_of_main_switch") { newPinValue === 0 ? value = "open" : value = "closed"; - if (value === "open" && FLOW.OMS_maintenance_mode) + + if (value === "open" && SETTINGS.maintenance_mode) { - sendNotification("switchLogic", edgeName, "door_has_been_open", {}, "", SEND_TO.tb, instance, "rvo_door"); + sendNotification("switchLogic", rvoTbName, "door_opened", {}, "", SEND_TO.tb, instance, "rvo_door"); } - if (value === "open" && !FLOW.OMS_maintenance_mode) + if (value === "open" && !SETTINGS.maintenance_mode) { - //sendNotification("switchLogic", edgeName, ERRWEIGHT.WARNING, "RVO open door out of maintenance mode", "", SEND_TO.tb, instance); - sendNotification("switchLogic", edgeName, "door_has_been_open_without_permision_alarm_is_on", {}, "", SEND_TO.tb, instance, "rvo_door"); + sendNotification("switchLogic", rvoTbName, "door_opened_without_permission", {}, "", SEND_TO.tb, instance, "rvo_door"); // zapneme sirenu // ak sa otvoria dvere len na elektromeri (type === "state_of_main_switch") alarm sa nema spustit. alarm sa spusti len ked sa otvoria hlavne dvere (type === "door_condition") - if(type === "door_condition") turnOnAlarm(); + if(type === "door_condition") turnAlarm("on"); } if (value === "closed") { - if(alarmStatus == "ON") turnOffAlarm(); - //turnOffAlarm(); - - sendNotification("switchLogic", edgeName, "door_has_been_closed", {}, "", SEND_TO.tb, instance, "rvo_door"); + if(alarmStatus == "ON") turnAlarm("off"); + sendNotification("switchLogic", rvoTbName, "door_closed", {}, "", SEND_TO.tb, instance, "rvo_door"); } deviceStatus[type] = value; } + //lux sensor else if(type == "twilight_sensor") { @@ -1309,14 +1101,15 @@ exports.install = function(instance) { { twilightError = true; let value = twilight_sensor_array.shift(); - //sendNotification("switchLogic", edgeName, ERRWEIGHT.ERROR, "Lux sensor error", {"Repeating value": value}, SEND_TO.tb, instance ); + newPinValue = 0; } else if (set.size !== 1 && twilightError) { - //sendNotification("switchLogic", edgeName, ERRWEIGHT.NOTICE, "Lux sensor is working again", "", SEND_TO.tb, instance ); + //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.NOTICE, "Lux sensor is working again", "", SEND_TO.tb, instance ); twilightError = false; twilight_sensor_array.shift(); + newPinValue = value; } else if (set.size === 1 && twilightError) @@ -1339,12 +1132,12 @@ exports.install = function(instance) { //else console.log("lux_sensor", value, diff); } } + else if(type == "state_of_contactor") { - //sendNotification("switchLogic", edgeName, ERRWEIGHT.INFO, `State of contactor ${line} is now ${value}`, "", SEND_TO.tb, instance ); if(!(deviceStatus["state_of_contactor"][line] == value)) { - sendNotification("switchLogic", edgeName, "state_of_contactor_for_line", {line: line, value: value}, "", SEND_TO.tb, instance ); + sendNotification("switchLogic", rvoTbName, "state_of_contactor_for_line", {line: line, value: value}, "", SEND_TO.tb, instance ); } deviceStatus["state_of_contactor"][line] = value; @@ -1353,34 +1146,45 @@ exports.install = function(instance) { if(value === "On") value = true; else if(value === "Off") value = false; + //TODO do we need to modify relays table with contactor value, if we do not use it on startup ?? + let dataChanged = false; + if(relaysData[line].contactor !== newPinValue) { + dataChanged = true; + relaysData[line].contactor = newPinValue; + } + + instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "reload_relays", line: line, value: value, dataChanged: dataChanged}); + reportLineStatus(line); + //modify table relays - dbRelays.modify({ contactor: newPinValue }).where("line", line).make(function(builder) { - builder.callback(function(err, response) { - if(!err) - { - let time = 0; - if(value) time = 1000 * 10;//10 sekund + // dbRelays.modify({ contactor: newPinValue }).where("line", line).make(function(builder) { + // builder.callback(function(err, response) { + // if(!err) + // { + // let time = 0; + // if(value) time = 1000 * 10;//10 sekund - let dataChanged = false; - if(relaysData[line].contactor != value) dataChanged = true; - relaysData[line].contactor = value; + // let dataChanged = false; + // if(relaysData[line].contactor != newPinValue) dataChanged = true; + // relaysData[line].contactor = newPinValue; // 0,1 - //ak bola predchadzajuci stav off a novy stav je on, budu sa nastavovat nespracovane node profiles - //a budu sa odosielat commandy, tie vsak mozu zlyhat, a preto potrebujeme ich spusti trochu neskor - setTimeout(function(){ - instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "reload_relays", line: line, time: time, value: value, dataChanged: dataChanged}); - }, time); + // //ak bola predchadzajuci stav off a novy stav je on, budu sa nastavovat nespracovane node profiles + // //a budu sa odosielat commandy, tie vsak mozu zlyhat, a preto potrebujeme ich spusti trochu neskor + // setTimeout(function(){ + // instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "reload_relays", line: line, time: time, value: value, dataChanged: dataChanged}); + // }, time); - reportLineStatus(line); - } - else - { - errLogger.error("modify table relays failed", err); - } + // reportLineStatus(line); + // } + // else + // { + // errLogger.error("modify table relays failed", err); + // } - }); - }); + // }); + // }); } + else if(type === "state_of_breaker") { @@ -1448,28 +1252,27 @@ exports.install = function(instance) { if(type == "rotary_switch_state") { - if(FLOW.OMS_maintenance_mode) value = "maintenance"; + if(SETTINGS.maintenance_mode) value = "maintenance"; value = value.toLowerCase(); values["power_mode"] = value; } if(newPinValue != previousValues[pinIndex]) previousValues[pinIndex] = newPinValue; - if(FLOW.OMS_rvo_tbname == tbname) sendRvoStatus(); + if(rvoTbName == tbname) sendRvoStatus(); if(Object.keys(values).length > 0 && tbname) sendTelemetry(values, tbname); } - function sendTelemetry(values, tbname) { + function sendTelemetry(values, tbname, date=Date.now()) { let dataToTb = { [tbname]: [ { - "ts": Date.now(), + "ts": date, "values": values } ] }; - // instance.send(SEND_TO.tb, dataToTb); tbHandler.sendToTb(dataToTb, instance); } @@ -1496,8 +1299,6 @@ exports.install = function(instance) { return (typeof item === "object" && !Array.isArray(item) && item !== null); } - - } //end of instance diff --git a/flow/helper/DataToTbHandler.js b/flow/helper/DataToTbHandler.js index f60c296..a89b75b 100644 --- a/flow/helper/DataToTbHandler.js +++ b/flow/helper/DataToTbHandler.js @@ -49,13 +49,8 @@ class DataToTbHandler { for(let i = 0; i < arrayOfValues.length; i++) { ts = arrayOfValues[i].ts; - - //console.log("sendToTb------------>before", arrayOfValues[i].values, tbname); - let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values); - //console.log("sendToTb------------>after", values); - if(!this.isEmptyObject(values)) { arrayOfValuesToSend.push({ts: ts, values: values}); @@ -68,17 +63,6 @@ class DataToTbHandler { return; } - /* - let dataToTb = { - [tbname]: [ - { - "ts": Date.now(), - "values": values - } - ] - } - */ - this.messageCounter++; let dataToTbModified = { diff --git a/flow/helper/ErrorToServiceHandler.js b/flow/helper/ErrorToServiceHandler.js index dc60446..97ee9d4 100644 --- a/flow/helper/ErrorToServiceHandler.js +++ b/flow/helper/ErrorToServiceHandler.js @@ -97,7 +97,7 @@ class ErrorToServiceHandler console.log("ErrorToServiceHandler------------------------>send to service", dataToInfoSender); //TODO UGLY!!! - if(this.projects_id === undefined) this.projects_id = FLOW.OMS_projects_id; + if(this.projects_id === undefined) this.projects_id = FLOW.GLOBALS.settings.project_id; /* if(this.projects_id === undefined) diff --git a/flow/helper/error_reporter.js b/flow/helper/error_reporter.js deleted file mode 100644 index 2b84369..0000000 --- a/flow/helper/error_reporter.js +++ /dev/null @@ -1,72 +0,0 @@ - -//key is device, value = str -let sentValues= {}; - -function sendError(func, device, weight, str, extra, tb_output, instance, type) { - // if ((weight === ERRWEIGHT.DEBUG) && (instance.CONFIG.debug === false)){ - // return; // Allow debug messages only if CONFIG.debug is active - // } - - let key = device; - if(type != undefined) key = type; - - if(sentValues.hasOwnProperty(key)) - { - if(sentValues[key] == str) - { - return; - } - } - - sentValues[key] = str; - - let content = { - "type": weight, - "status": "new", - "source": { - "func":func, - "component":instance.id, - "component_name":instance.name, - "edge":device - }, - "message":str, - "message_data": extra - }; - - let msg = {}; - msg[device] = [ - { - "ts": Date.now(), - "values": { - "_event":content - } - } - ]; - - // Msg can be outputted from components only after configuration - /*if (canSendErrData()){ - sendBufferedErrors(); - } else { - bufferError(msg); - }*/ - instance.send(tb_output, msg); // Even if error server is unavailable, send this message to output, for other possible component connections - -} - - -let ERRWEIGHT = { - EMERGENCY: "emergency", // System unusable - ALERT: "alert", // Action must be taken immidiately - CRITICAL: "critical", // Component unable to function - ERROR: "error", // Error, but component able to recover from it - WARNING: "warning", // Possibility of error, system running futher - NOTICE: "notice", // Significant message but not an error, things user might want to know about - INFO: "informational", // Info - DEBUG: "debug" // Debug - only if CONFIG.debug is enabled -}; - - -module.exports = { - sendError, - ERRWEIGHT -} \ No newline at end of file diff --git a/flow/helper/error_reporting.js b/flow/helper/error_reporting.js deleted file mode 100644 index 62dace5..0000000 --- a/flow/helper/error_reporting.js +++ /dev/null @@ -1,56 +0,0 @@ -const ERRWEIGHT = { - EMERGENCY: "emergency", // System unusable - ALERT: "alert", // Action must be taken immidiately - CRITICAL: "critical", // Component unable to function - ERROR: "error", // Error, but component able to recover from it - WARNING: "warning", // Possibility of error, system running futher - NOTICE: "notice", // Significant message but not an error, things user might want to know about - INFO: "informational", // Info - DEBUG: "debug" // Debug - only if CONFIG.debug is enabled -}; - - - - -function sendError(func, device, weight, str, extra){ - if ((weight === ERRWEIGHT.DEBUG) && (instance.CONFIG.debug === false)){ - return; // Allow debug messages only if CONFIG.debug is active - } - - let content = { - "type": weight, - "status": "new", - "source": { - "func":func, - "component":instance.id, - "component_name":instance.name, - "edge":myEdge - }, - "message":str, - "message_data": extra - }; - let msg = {}; - msg[device] = [ - { - "ts": Date.now(), - "values": { - "_event":content - } - } - ]; - - // Msg can be outputted from components only after configuration - /*if (canSendErrData()){ - sendBufferedErrors(); - } else { - bufferError(msg); - }*/ - instance.send(0, msg); // Even if error server is unavailable, send this message to output, for other possible component connections - -} - - -module.exports = { - sendError, - ERRWEIGHT, -} \ No newline at end of file diff --git a/flow/helper/logger.js b/flow/helper/logger.js new file mode 100644 index 0000000..2585639 --- /dev/null +++ b/flow/helper/logger.js @@ -0,0 +1,30 @@ +//https://github.com/log4js-node/log4js-node/blob/master/examples/example.js +//file: { type: 'file', filename: path.join(__dirname, 'log/file.log') } + +var log4js = require("log4js"); +var path = require('path'); + +log4js.configure({ +appenders: { + errLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../../", 'err.txt') }, + monitorLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../../", 'monitor.txt') }, + console: { type: 'console' } +}, +categories: { + errLogs: { appenders: ['console', 'errLogs'], level: 'error' }, + monitorLogs: { appenders: ['console', 'monitorLogs'], level: 'trace' }, + //another: { appenders: ['console'], level: 'trace' }, + default: { appenders: ['console'], level: 'trace' } +} +}); + +const errLogger = log4js.getLogger("errLogs"); +const logger = log4js.getLogger(); +const monitor = log4js.getLogger("monitorLogs"); + +//USAGE +//logger.debug("text") +//monitor.info('info'); +//errLogger.error("some error"); + +module.exports = { errLogger, logger, monitor }; \ No newline at end of file diff --git a/flow/helper/notification_reporter.js b/flow/helper/notification_reporter.js index 84b2345..61c0aef 100644 --- a/flow/helper/notification_reporter.js +++ b/flow/helper/notification_reporter.js @@ -1,10 +1,6 @@ - -const { promisifyBuilder, makeMapFromDbResult } = require('./db_helper.js'); -const dbNotifications = TABLE("notifications"); - //key is device, value = str let sentValues= {}; -let notificationsData = {}; +let notificationsData = null; let ERRWEIGHT = { EMERGENCY: "emergency", // System unusable @@ -24,12 +20,9 @@ function getKey(map, val) { //https://stackoverflow.com/questions/41117799/string-interpolation-on-variable var template = (tpl, args) => tpl.replace(/\${(\w+)}/g, (_, v) => args[v]); -async function initNotifications() -{ - let response = await promisifyBuilder(dbNotifications.find()); - notificationsData = makeMapFromDbResult(response, "key"); - console.log("initNotifications done" ); +function initNotification() { + notificationsData = FLOW.GLOBALS.notificationsData; } function sendNotification(func, device, key, params, extra, tb_output, instance, saveKey) { @@ -39,7 +32,7 @@ function sendNotification(func, device, key, params, extra, tb_output, instance, let storeToSendValues = true; if(saveKey == undefined) storeToSendValues = false; - let lang = FLOW.OMS_language; + let lang = FLOW.GLOBALS.settings.language; if(lang != "en" || lang != "sk") lang = "en"; let tpl = key; @@ -55,7 +48,8 @@ function sendNotification(func, device, key, params, extra, tb_output, instance, } else { - console.error("sendNotification: Notifications: undefined key", key, func, notificationsData); + //console.error("sendNotification: Notifications: undefined key", key, func, notificationsData); + console.error("sendNotification: Notifications: undefined key", key, func ); return false; } @@ -91,7 +85,7 @@ function sendNotification(func, device, key, params, extra, tb_output, instance, if(storeToSendValues) sentValues[saveKey] = tpl; - let str = FLOW.OMS_rvo_name; + let str = FLOW.GLOBALS.settings.rvo_name; if(str != "") str = str + ": "; str = str + tpl; @@ -132,6 +126,6 @@ function sendNotification(func, device, key, params, extra, tb_output, instance, module.exports = { sendNotification, - initNotifications, - ERRWEIGHT -} \ No newline at end of file + ERRWEIGHT, + initNotification +} diff --git a/flow/helper/serialport_helper.js b/flow/helper/serialport_helper.js index 98aa235..4a0edd5 100644 --- a/flow/helper/serialport_helper.js +++ b/flow/helper/serialport_helper.js @@ -40,18 +40,17 @@ async function writeData(port, data, readbytes, timeout){ return new Promise((resolve, reject) => { // If first item in data array is 255, we just write broadcast command to rsPort - // We wait 3 seconds and resolve([ "b", "r", "o", "a", "d", "c", "a", "s", "t" ]) + // We wait 3 seconds and resolve(["broadcast"]) // It is important to resolve with array if(data[0] == 255) { port.write(Buffer.from(data), function(err) { if (err) { - port.removeListener('data', callback); reject(err.message); } }); - setTimeout(resolve, 3000, [ "b", "r", "o", "a", "d", "c", "a", "s", "t" ]); + setTimeout(resolve, 3000, ["broadcast"]); return; } diff --git a/flow/infosender.js b/flow/infosender.js index a83499c..c7afd83 100644 --- a/flow/infosender.js +++ b/flow/infosender.js @@ -3,12 +3,9 @@ exports.title = 'Info sender'; exports.version = '1.0.0'; exports.group = 'Worksys'; exports.color = '#2134B0'; -exports.input = 1; +exports.input = 2; exports.output = 1 -exports.click = false; -exports.author = 'oms-is'; exports.icon = 'bolt'; -exports.options = { edge: "undefined" }; const { networkInterfaces } = require('os'); @@ -22,13 +19,12 @@ exports.html = `
exports.readme = `# send all data to projects.worksys.io, required to monitor status of controller(unipi)`; -const fs = require('fs'); -var path = require('path'); +exports.install = function(instance) { -exports.install = async function(instance) { - + let id; let allValues = {}; let sendAllValuesInterval; + let configured = false; let now = new Date(); console.log(exports.title, "INSTALLED", now.toLocaleString("sk-SK")); @@ -47,23 +43,19 @@ exports.install = async function(instance) { } } } - function sendValues() { - const id = FLOW.OMS_projects_id; + if(!configured) return; if(Object.keys(allValues).length > 0) { - if(id !== undefined) + if(id) { delete allValues.__force__; let dataToSend = {...allValues}; dataToSend.id = id; dataToSend.ipAddresses = ipAddresses; - //dataToSend.notify_date = new Date().toISOString().slice(0, 19).replace('T', ' '); - - //console.log(exports.title, "------------>sendValues", dataToSend); instance.send(0, dataToSend); @@ -71,7 +63,7 @@ exports.install = async function(instance) { } else { - console.log(exports.title, "unable to send data, id is undefined"); + console.log(exports.title, "unable to send data, no id"); } } @@ -81,10 +73,14 @@ exports.install = async function(instance) { clearInterval(sendAllValuesInterval); }) - instance.on("data", (flowdata) => { + instance.on("0", _ => { + id = FLOW.GLOBALS.settings.project_id; + configured = true; + }) + + instance.on("1", flowdata => { allValues = { ...allValues, ...flowdata.data}; - //console.log("DATA RECEIVED", flowdata.data); //__force__ diff --git a/flow/modbus_reader.js b/flow/modbus_reader.js index 24a9580..b0908ae 100644 --- a/flow/modbus_reader.js +++ b/flow/modbus_reader.js @@ -3,6 +3,7 @@ exports.title = 'Modbus reader'; exports.version = '2.0.0'; exports.group = 'Worksys'; exports.color = '#2134B0'; +exports.input = 1; exports.output = ["red", "white", "yellow"]; exports.click = false; exports.author = 'Rastislav Kovac'; @@ -31,7 +32,10 @@ const SEND_TO = { const numberOfNotResponding = {}; let tbName = null; let mainSocket; - +//number of phases inRVO +let phases; +//phases where voltage is 0 (set) +let noVoltage; exports.install = function(instance) { @@ -55,10 +59,19 @@ exports.install = function(instance) { // lampSwitchNotification helper variables this.onNotificationSent = false; this.offNotificationSent = false; + this.phases = this.buildPhases(); this.startSocket(); } + buildPhases = () => { + let a = []; + for (let i = 1; i<= phases; i++) { + a.push(`Phase_${i}_voltage`) + } + return a; + } + startSocket = () => { let obj = this; @@ -228,9 +241,7 @@ exports.install = function(instance) { this.allValues = {}; break; } - } - } setNewStream = () => @@ -282,7 +293,7 @@ exports.install = function(instance) { if(!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return; Object.keys(values).map(singleValue => { - if (["Phase_1_voltage", "Phase_2_voltage", "Phase_3_voltage"].includes(singleValue)) + if (this.phases.includes(singleValue)) { let l = singleValue.split("_"); let phase = parseInt(l[1]); @@ -291,13 +302,13 @@ exports.install = function(instance) { if(values[singleValue] == 0) { - FLOW.OMS_no_voltage.add(phase); + noVoltage.add(phase); sendNotification("modbus_reader: checkNullVoltage", tbName, "no_voltage_on_phase", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase ); // console.log('no voltage') } else { - FLOW.OMS_no_voltage.delete(phase); + noVoltage.delete(phase); // console.log('voltage detected') sendNotification("modbus_reader: checkNullVoltage", tbName, "voltage_on_phase_restored", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase); } @@ -330,18 +341,23 @@ exports.install = function(instance) { } const isObjectEmpty = (objectName) => { - return Object.keys(objectName).length === 0 && objectName.constructor === Object; + return Object.keys(objectName).length === 0 && objectName.constructor === Object; } - setTimeout(() => { - + function main() { + + phases = FLOW.GLOBALS.settings.phases; + tbName = FLOW.GLOBALS.settings.rvoTbName; + noVoltage = FLOW.GLOBALS.settings.no_voltage; mainSocket = new SocketWithClients(); - tbName = FLOW.OMS_rvo_tbname; // this notification is to show, that flow (unipi) has been restarted sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance); + } - }, 25000); + instance.on("0", function(_) { + main(); + }) } diff --git a/flow/show_dbdata.js b/flow/show_dbdata.js new file mode 100644 index 0000000..d01eecd --- /dev/null +++ b/flow/show_dbdata.js @@ -0,0 +1,241 @@ +exports.id = 'showdb'; +exports.title = 'Show db data'; +exports.group = 'Worksys'; +exports.color = '#888600'; +exports.version = '1.0.2'; +exports.icon = 'sign-out'; +exports.input = 7; +exports.output = 1; + +const { exec } = require('child_process'); + +exports.install = function(instance) { + + instance.on("0", _ => { + instance.send(0, FLOW.GLOBALS.settings); + }) + instance.on("1", _ => { + instance.send(0, FLOW.GLOBALS.relaysData); + }) + instance.on("2", _ => { + instance.send(0, FLOW.GLOBALS.nodesData); + }) + instance.on("3", _ => { + instance.send(0, FLOW.GLOBALS.pinsData); + }) + instance.on("4", _ => { + instance.send(0, {rpcSwitchOffLine, rpcSetNodeDimming, rpcLineProfile, rpcNodeProfile, sunCalcExample, dataFromTerminalBroadcast}) + }) + instance.on("5", _ => { + exec("sudo tail -n 25 monitor.txt" , (err, stdout, stderr) => { + if (err || stderr) instance.send(0,{err, stderr}); + else instance.send(0,stdout); + }) + }) + instance.on("6", _ => { + exec("sudo tail -n 25 err.txt" , (err, stdout, stderr) => { + if (err || stderr) instance.send(0,{err, stderr}); + else instance.send(0,stdout); + }) + }) +}; + + + +const rpcSwitchOffLine = +{ + "topic": "v1/gateway/rpc", + "content": { + "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", + "data": { + "id": 8, + "method": "set_command", + "params": { + "entities": [ + { + "entity_type": "edb_line", + "tb_name": "MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O" + } + ], + "command": "switch", + "payload": { + "value": false + } + } + } + } +} + +const rpcSetNodeDimming = +{ + "topic": "v1/gateway/rpc", + "content": { + "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", + "data": { + "id": 10, + "method": "set_command", + "params": { + "entities": [ + { + "entity_type": "street_luminaire", + "tb_name": "jbN4q7JPZmexgdnz2yKbWdDYAWwO0Q3BMX6ERLoV" + } + ], + "command": "dimming", + "payload": { + "value": 5 + } + } + } + } +} + +const rpcLineProfile = +{ + "topic": "v1/gateway/rpc", + "content": { + "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", + "data": { + "id": 9, + "method": "set_profile", + "params": { + "entities": [ + { + "entity_type": "edb_line", + "tb_name": "MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O" + } + ], + "payload": { + "intervals": [ + { + "value": 0, + "end_time": "20:00", + "start_time": "13:00" + }, + { + "value": 1, + "end_time": "05:30", + "start_time": "20:00" + }, + { + "value": 0, + "end_time": "13:00", + "start_time": "05:30" + } + ], + "astro_clock": true, + "dawn_lux_sensor": false, + "dusk_lux_sensor": false, + "dawn_lux_sensor_value": 5, + "dusk_lux_sensor_value": 5, + "dawn_astro_clock_offset": 0, + "dusk_astro_clock_offset": 0, + "dawn_lux_sensor_time_window": 30, + "dusk_lux_sensor_time_window": 30, + "dawn_astro_clock_time_window": 60, + "dusk_astro_clock_time_window": 60 + } + } + } + } +} + + +const rpcNodeProfile = +{ + "topic": "v1/gateway/rpc", + "content": { + "device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV", + "data": { + "id": 11, + "method": "set_profile", + "params": { + "entities": [ + { + "entity_type": "street_luminaire", + "tb_name": "jbN4q7JPZmexgdnz2yKbWdDYAWwO0Q3BMX6ERLoV" + } + ], + "payload": { + "intervals": [ + { + "cct": 3000, + "value": 0, + "end_time": "17:50", + "start_time": "13:00" + }, + { + "cct": 3000, + "value": 100, + "end_time": "21:30", + "start_time": "17:50" + }, + { + "cct": 3000, + "value": 0, + "end_time": "13:00", + "start_time": "07:10" + }, + { + "cct": 3000, + "value": 50, + "end_time": "00:00", + "start_time": "21:30" + }, + { + "cct": 3000, + "value": 10, + "end_time": "04:30", + "start_time": "00:00" + }, + { + "cct": 3000, + "value": 100, + "end_time": "07:10", + "start_time": "04:30" + } + ], + "astro_clock": true, + "dawn_lux_sensor": false, + "dusk_lux_sensor": false, + "dawn_lux_sensor_value": 5, + "dusk_lux_sensor_value": 5, + "dawn_astro_clock_offset": 30, + "dusk_astro_clock_offset": 20, + "dawn_lux_sensor_time_window": 30, + "dusk_lux_sensor_time_window": 30, + "dawn_astro_clock_time_window": 60, + "dusk_astro_clock_time_window": 60 + } + } + } + } +} + + const sunCalcExample = { + dusk_no_offset: '20:18', + dawn_no_offset: '05:19', + dusk: '20:18', + dusk_hours: 20, + dusk_minutes: 18, + dawn: '05:19', + dawn_hours: 5, + dawn_minutes: 19, + dusk_time: 1715278688962, + dawn_time: 1715224744357, + dusk_astro_clock_offset: 0, + dawn_astro_clock_offset: 0 +} + + +const dataFromTerminalBroadcast = { + address: 4294967295, + byte1: 0, + byte2: 0, + byte3: 0, + byte4: 96, + name: "Time Schedule settings", + recipient: 2, + register: 8, + rw: 1 +} diff --git a/flow/slack_filter.js b/flow/slack_filter.js index a27c9e1..753c24a 100644 --- a/flow/slack_filter.js +++ b/flow/slack_filter.js @@ -11,9 +11,6 @@ exports.options = { 'name':'', 'types': '["emergency", "critical", "error", "ale exports.html = `
-
-
@(Name of this server)
-
@(Slack channel to receive the alerts)
@@ -170,11 +167,12 @@ exports.install = function(instance) { FLOW["savedSlackMessages"] = []; } + instance.options.name = FLOW.GLOBALS.settings.rvo_name; if (instance.options.name) { instance.status('Running'); running = true; } else { - instance.status('Please enter name', 'red'); + instance.status('Please run options again', 'red'); running = false; } } catch (e) { @@ -183,5 +181,7 @@ exports.install = function(instance) { }; instance.on('options', instance.reconfigure); - instance.reconfigure(); -}; \ No newline at end of file + setTimeout(instance.reconfigure, 10000); + + +}; diff --git a/flow/thermometer.js b/flow/thermometer.js index b5ee420..b3bb350 100644 --- a/flow/thermometer.js +++ b/flow/thermometer.js @@ -2,6 +2,7 @@ exports.id = 'thermometer'; exports.title = 'Thermometer'; exports.group = 'Worksys'; exports.color = '#5CB36D'; +exports.input = 1; exports.version = '1.0.3'; exports.output = ["red", "white", "blue"]; exports.author = 'Rastislav Kovac'; @@ -9,7 +10,9 @@ exports.icon = 'thermometer-three-quarters'; exports.readme = `# Getting temperature values for RVO. In case of LM, you need device address. In case of unipi, evok sends values, in case thermometer is installed`; -const instanceSendTo = { +const { errLogger, logger, monitor } = require('./helper/logger'); + +const SEND_TO = { debug: 0, tb: 1, dido_controller: 2 @@ -18,56 +21,16 @@ const instanceSendTo = { //read temperature - frequency let timeoutMin = 5;//minutes -var path = require('path'); -var log4js = require("log4js"); - -log4js.configure({ - appenders: { - errLogs: { type: 'file', filename: path.join(__dirname + "/../", 'err.txt') }, - monitorLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'monitor.txt') }, - console: { type: 'console' } - }, - categories: { - errLogs: { appenders: ['console', 'errLogs'], level: 'error' }, - monitorLogs: { appenders: ['console', 'monitorLogs'], level: 'trace' }, - //another: { appenders: ['console'], level: 'trace' }, - default: { appenders: ['console'], level: 'trace' } - } -}); - -const errLogger = log4js.getLogger("errLogs"); -const logger = log4js.getLogger(); -const monitor = log4js.getLogger("monitorLogs"); - -//logger.debug("text") -//monitor.info('info'); -//errLogger.error("some error"); - -const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper'); -const dbSettings = TABLE("settings"); -let temperatureAddress = ""; - -async function loadSettings() -{ - //todo global FLOW.OMS_edgeName is making problem, so we load it here as well, it should not be - let responseSettings = await promisifyBuilder(dbSettings.find()); - temperatureAddress = responseSettings[0]["temperature_adress"]; -} - -loadSettings(); - exports.install = function(instance) { const { exec } = require('child_process'); - const { sendNotification, ERRWEIGHT } = require('./helper/notification_reporter'); + const { sendNotification } = require('./helper/notification_reporter'); let startRead; - let dataToTb; let counter = 0; - - let edgeName = ""; - + let rvoTbName = ""; + let temperatureAddress = ""; logger.debug(exports.title, "installed"); @@ -76,11 +39,11 @@ exports.install = function(instance) { }) - const start = function() { + const main = function() { try { - if(FLOW.OMS_controller_type === "unipi") + if(FLOW.GLOBALS.settings.controller_type === "unipi") { clearInterval(startRead); return; @@ -88,71 +51,23 @@ exports.install = function(instance) { if(temperatureAddress === "") throw "gettemperature: temperatureAddress is not defined"; - logger.debug("FLOW.OMS_temperature_adress", FLOW.OMS_temperature_adress); - exec(`owread -C ${temperatureAddress}/temperature`, (error, stdout, stderr) => { - edgeName = FLOW.OMS_edgeName; - - if(edgeName !== "") + if(!error) { - if(error) - { - - if(FLOW.OMS_brokerready == undefined) - { - logger.debug("gettemparature - FLOW.OMS_brokerready is undefined"); - - setTimeout(function(){ - start(); - }, 3000); - - return; - } - - if(FLOW.OMS_brokerready) - { - //sendNotification("start", edgeName, ERRWEIGHT.WARNING, "Thermometer is not responding", {"Error": error}, instanceSendTo.tb, instance, "thermometer"); - sendNotification("start", edgeName, "thermometer_is_not_responding", {}, {"Error": error}, instanceSendTo.tb, instance, "thermometer"); - } - - let status = "NOK"; - dataToTb = { - [edgeName]: [ - { - "ts": Date.now(), - "values": { - "status": status - } - } - ] - } - - monitor.info("Thermometer is not responding", error, FLOW.OMS_brokerready); - - // instance.send(instanceSendTo.tb, dataToTb); // poslat stav nok do tb, ak to handluje dido_controller ?? - instance.send(instanceSendTo.dido_controller, {status: "NOK-thermometer"}); - } - else parseData(stdout); - } - else - { - monitor.info("gettemperature: edgeName is not defined", FLOW.OMS_edgeName); - - setTimeout(function(){ - start(); - }, 3000); - + parseData(stdout) return; } - - //instance.send({"Temp":stdout,"stderr":stderr,"err":error}); + sendNotification("main", rvoTbName, "thermometer_is_not_responding", {}, {"Error": error}, SEND_TO.tb, instance, "thermometer"); + monitor.info("Thermometer is not responding", error); + instance.send(SEND_TO.dido_controller, {status: "NOK-thermometer"}); }); } catch(err) { errLogger.error(exports.title, err); + clearInterval(startRead); } } @@ -166,30 +81,17 @@ exports.install = function(instance) { if(counter > 290) { - instance.send(instanceSendTo.debug, "[Get temperature component] - temperature data are comming again from RVO after more than 1 day break"); - - //sendNotification("parseData", edgeName, ERRWEIGHT.NOTICE, "Thermometer is working again", "", instanceSendTo.tb, instance, "thermometer"); - if(FLOW.OMS_brokerready) sendNotification("parseData", edgeName, "thermometer_is_responding_again", {}, "", instanceSendTo.tb, instance, "thermometer"); + instance.send(SEND_TO.debug, "[Get temperature component] - temperature data are comming again from RVO after more than 1 day break"); + sendNotification("parseData", rvoTbName, "thermometer_is_responding_again", {}, "", SEND_TO.tb, instance, "thermometer"); } logger.debug("gettemperature", data); + const values = { "temperature": Number(data.toFixed(2)), - "status": "OK" } - dataToTb = { - [edgeName]: [ - { - "ts": Date.now(), - "values":values - } - ] - } - - instance.send(instanceSendTo.tb, dataToTb); - instance.send(instanceSendTo.dido_controller, values); - + instance.send(SEND_TO.dido_controller, {values: values}); counter = 0; } else { @@ -200,24 +102,21 @@ exports.install = function(instance) { //ked je problem 1 den let day = 24 * 60 / timeoutMin; if ( counter > day && counter < day + 2 ) { - //sendNotification("parseData", edgeName, ERRWEIGHT.WARNING, "Thermometer receives invalid data", "", instanceSendTo.tb, instance, "thermometer"); - sendNotification("parseData", edgeName, "thermometer_sends_invalid_data", {}, "", instanceSendTo.tb, instance, "thermometer"); + //sendNotification("parseData", rvoTbName, ERRWEIGHT.WARNING, "Thermometer receives invalid data", "", SEND_TO.tb, instance, "thermometer"); + sendNotification("parseData", rvoTbName, "thermometer_sends_invalid_data", {}, "", SEND_TO.tb, instance, "thermometer"); - instance.send(instanceSendTo.debug, "[Get temperature component] - no temperature data from RVO for more than 1 day"); - instance.send(instanceSendTo.dido_controller, {status: "NOK-thermometer"}); + instance.send(SEND_TO.debug, "[Get temperature component] - no temperature data from RVO for more than 1 day"); + instance.send(SEND_TO.dido_controller, {status: "NOK-thermometer"}); } } } - setTimeout(function(){ - start(); - }, 10000); - - startRead = setInterval(start, timeoutMin * 1000 * 60); - - //testing - //setInterval(() => {instance.send(instanceSendTo.dido_controller, {status: "NOK-thermometer"})}, 180000); - + instance.on("data", _ => { + temperatureAddress = FLOW.GLOBALS.settings.temperature_address; + rvoTbName = FLOW.GLOBALS.settings.rvoTbName; + startRead = setInterval(main, timeoutMin * 1000 * 60); + main(); + }) }; \ No newline at end of file diff --git a/flow/wsmqttpublish.js b/flow/wsmqttpublish.js index 20f64fa..e11d9eb 100644 --- a/flow/wsmqttpublish.js +++ b/flow/wsmqttpublish.js @@ -4,30 +4,27 @@ exports.group = 'MQTT'; exports.color = '#888600'; exports.version = '1.0.2'; exports.icon = 'sign-out'; -exports.input = 1; -exports.output = ["red", "white", "blue"]; -exports.author = 'Daniel Segeš'; +exports.input = 2; +exports.output = 4; exports.options = { host: 'tb-stage.worksys.io', port: 1883, clientid: "", username: "" }; -exports.npm = ['mqtt']; - exports.html = `
-
-
-
Hostname or IP address (if not empty - setting will override db setting)
-
-
-
Port
-
-
-
-
-
@(Client id)
-
-
-
@(Username)
-
-
+
+
+
Hostname or IP address (if not empty - setting will override db setting)
+
+
+
Port
+
+
+
+
+
@(Client id)
+
+
+
@(Username)
+
+
`; @@ -41,21 +38,22 @@ Added: - rpc response `; -const instanceSendTo = { +const { promisifyBuilder } = require('./helper/db_helper'); +const { errLogger, monitor } = require('./helper/logger'); +const fs = require('fs'); +const mqtt = require('mqtt'); + +const SEND_TO = { debug: 0, rpcCall: 1, services: 2 } -const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); - //CONFIG -let useLog4js = true; let createTelemetryBackup = true; let saveTelemetryOnError = true;//backup_on_failure overrides this value //------------------------ -var fs = require('fs'); let rollers; if(createTelemetryBackup) rollers = require('streamroller'); @@ -64,56 +62,18 @@ let insertNoSqlCounter = 0; let insertBackupNoSqlCounter = 0; let processingData = false; -let backup_on_failure = false;//== saveTelemetryOnError - create backup broker send failure +let backup_on_failure = false;//== saveTelemetryOnError - create backup client send failure let restore_from_backup = 0; //how many rows process at once? let restore_backup_wait = 0;//wait seconds let lastRestoreTime = 0; -let errLogger; -let logger; -let monitor; - -//TODO brokerready and sendBrokerError seems to be the same. Moreover, we use FLOW_OMS_brokerready variable!! -// -// if there is an error in broker connection, flow logs to monitor.txt. Not to log messages every second, we use sendBrokerError variable -let sendBrokerError = true; - -if(useLog4js) -{ - var path = require('path'); - var log4js = require("log4js"); - - log4js.configure({ - appenders: { - errLogs: { type: 'file', filename: path.join(__dirname + "/../", 'err.txt') }, - monitorLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'monitor.txt') }, - console: { type: 'console' } - }, - categories: { - errLogs: { appenders: ['console', 'errLogs'], level: 'error' }, - monitorLogs: { appenders: ['console', 'monitorLogs'], level: 'trace' }, - //another: { appenders: ['console'], level: 'trace' }, - default: { appenders: ['console'], level: 'trace' } - } - }); - - errLogger = log4js.getLogger("errLogs"); - logger = log4js.getLogger(); - monitor = log4js.getLogger("monitorLogs"); - - //USAGE - //logger.debug("text"); - //monitor.info('info'); - //errLogger.error("some error"); -} +// 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; process.on('uncaughtException', function (err) { - if(errLogger) - { - errLogger.error('uncaughtException:', err.message) - errLogger.error(err.stack); - } + errLogger.error('uncaughtException:', err.message) + errLogger.error(err.stack); //TODO //send to service @@ -127,13 +87,9 @@ const nosqlBackup = NOSQL('/backup/tbdata'); exports.install = function(instance) { - var broker; + var client; var opts; - var brokerready = false; - - instance.on('options', loadSettings); - - mqtt = require('mqtt'); + var clientReady = false; // wsmqtt status for notification purposes on projects.worksys.io database let wsmqttName = null; @@ -142,21 +98,28 @@ exports.install = function(instance) { function getWsmqttName(host) { - if(host == "tb-demo.worksys.io" || host == '192.168.252.4') return 'wsmqtt_demo'; - else if(host == "tb-qas01.worksys.io" || host == '192.168.252.5') return 'wsmqtt_qas01'; - else if(host == "tb-prod01.worksys.io" || host == '192.168.252.1') return 'wsmqtt_prod01'; + if(host == "tb-demo.worksys.io" || host == '192.168.252.4') return 'wsmqtt_demo'; + else if(host == "tb-qas01.worksys.io" || host == '192.168.252.5') return 'wsmqtt_qas01'; + else if(host == "tb-prod01.worksys.io" || host == '192.168.252.1') return 'wsmqtt_prod01'; } function sendWsStatus() { - instance.send(instanceSendTo.services, {[wsmqttName]: wsmqtt_status}); + instance.send(SEND_TO.services, {[wsmqttName]: wsmqtt_status}); } - sendWsStatusVar = setInterval(sendWsStatus, 180000); + function main() + { + if(!FLOW.dbLoaded) return; + + loadSettings(); + clearInterval(sendWsStatus); + sendWsStatusVar = setInterval(sendWsStatus, 180000); + } //set opts according to db settings - async function loadSettings() + function loadSettings() { if(instance.options.host !== "") @@ -179,21 +142,17 @@ exports.install = function(instance) { else { - const dbSettings = TABLE("settings"); - let responseSettings = await promisifyBuilder(dbSettings.find()); - - backup_on_failure = responseSettings[0]["backup_on_failure"]; + const SETTINGS = FLOW.GLOBALS.settings; + backup_on_failure = SETTINGS.backup_on_failure; saveTelemetryOnError = backup_on_failure; - restore_from_backup = responseSettings[0]["restore_from_backup"]; - restore_backup_wait = responseSettings[0]["restore_backup_wait"]; + restore_from_backup = SETTINGS.restore_from_backup; + restore_backup_wait = SETTINGS.restore_backup_wait; - let mqtt_host = responseSettings[0]["mqtt_host"]; - let mqtt_clientid = responseSettings[0]["mqtt_clientid"]; - let mqtt_username = responseSettings[0]["mqtt_username"]; - let mqtt_port = responseSettings[0]["mqtt_port"]; - - console.log("wsmqttpublich -> loadSettings from db", responseSettings[0]); + let mqtt_host = SETTINGS.mqtt_host; + let mqtt_clientid = SETTINGS.mqtt_clientid; + let mqtt_username = SETTINGS.mqtt_username; + let mqtt_port = SETTINGS.mqtt_port; opts = { host: mqtt_host, @@ -216,27 +175,23 @@ exports.install = function(instance) { var url = "mqtt://" + opts.host + ":" + opts.port; console.log("MQTT URL: ", url); - broker = mqtt.connect(url, opts); + client = mqtt.connect(url, opts); - broker.on('connect', function() { + client.on('connect', function() { instance.status("Connected", "green"); - monitor.info("MQTT broker connected"); + monitor.info("MQTT client connected"); - sendBrokerError = true; - - brokerready = true; - FLOW.OMS_brokerready = brokerready; + sendClientError = true; + clientReady = true; wsmqtt_status = 'connected'; }); - broker.on('reconnect', function() { + client.on('reconnect', function() { instance.status("Reconnecting", "yellow"); - brokerready = false; - - FLOW.OMS_brokerready = brokerready; + clientReady = false; }); - broker.on('message', function(topic, message) { + client.on('message', function(topic, message) { // message is type of buffer message = message.toString(); if (message[0] === '{') { @@ -244,50 +199,53 @@ exports.install = function(instance) { message = JSON.parse(message); if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) { - broker.publish(topic, `{"device": ${message.device}, "id": ${message.data.id}, "data": {"success": true}}`, {qos:1}); - instance.send(instanceSendTo.rpcCall, {"device": message.device, "id": message.data.id, "RPC response": {"success": true}}); + 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)); } - instance.send(instanceSendTo.rpcCall, {"topic":topic, "content":message }); + instance.send(SEND_TO.rpcCall, {"topic":topic, "content":message }); }); - broker.on('close', function(err) { - brokerready = false; - FLOW.OMS_brokerready = brokerready; + client.on('close', function(err) { + clientReady = false; wsmqtt_status = 'disconnected'; if (err && err.toString().indexOf('Error')) { instance.status("Err: "+err.code, "red"); - instance.send(instanceSendTo.debug, {"message":"Broker CLOSE signal received !", "error":err, "opt":opts }); + instance.send(SEND_TO.debug, {"message":"Client CLOSE signal received !", "error":err, "opt":opts }); } else { instance.status("Disconnected", "red"); - instance.send(instanceSendTo.debug, {"message":"Broker CLOSE signal received !", "error":err, "opt":opts }); + instance.send(SEND_TO.debug, {"message":"Client CLOSE signal received !", "error":err, "opt":opts }); } - broker.reconnect(); + client.reconnect(); }); - broker.on('error', function(err) { + client.on('error', function(err) { instance.status("Err: "+ err.code, "red"); - instance.send(instanceSendTo.debug, {"message":"Broker ERROR signal received !", "error":err, "opt":opts }); - if(sendBrokerError) { - monitor.info('MQTT broker error', err); - sendBrokerError = false; + instance.send(SEND_TO.debug, {"message":"Client ERROR signal received !", "error":err, "opt":opts }); + if(sendClientError) { + monitor.info('MQTT client error', err); + sendClientError = false; } - brokerready = false; - FLOW.OMS_brokerready = brokerready; + clientReady = false; wsmqtt_status = 'disconnected'; - }); } - instance.on('data', function(data) { - if (brokerready) + instance.on("0", _ => { + main(); + }) + + + instance.on('1', function(data) { + + if(clientReady) { //do we have some data in backup file? //if any, process data from database @@ -296,13 +254,14 @@ exports.install = function(instance) { //read telemetry data and send back to server if(!processingData) processDataFromDatabase(); } - } - if (brokerready) + if(clientReady) { let stringifiedJson = JSON.stringify(data.data); - broker.publish("v1/gateway/telemetry", stringifiedJson, {qos: 1}); + client.publish("v1/gateway/telemetry", stringifiedJson, {qos: 1}); + + instance.send(3, stringifiedJson); //backup telemetry if(createTelemetryBackup) @@ -327,8 +286,8 @@ exports.install = function(instance) { else { - if(logger) logger.debug("Broker unavailable. Data not sent !", JSON.stringify(data.data)); - instance.send(instanceSendTo.debug, {"message":"Broker unavailable. Data not sent !", "data": data.data }); + //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) { @@ -344,9 +303,9 @@ exports.install = function(instance) { }); - instance.close = function(done) { - if (brokerready){ - broker.end(); + instance.close = function(done) { + if(clientReady){ + client.end(); clearInterval(sendWsStatusVar); } }; @@ -373,7 +332,7 @@ exports.install = function(instance) { 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") @@ -443,10 +402,7 @@ 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(); @@ -478,7 +434,7 @@ exports.install = function(instance) { for(let i = 0; i < records.length; i++) { - if (brokerready) { + if(clientReady) { let item = records[i]; let id = item.id; @@ -487,18 +443,19 @@ exports.install = function(instance) { { //console.log("------------processDataFromDatabase - remove", id, dataBase, i); - try{ + try { let o = JSON.parse(JSON.stringify(item)); delete o.id; let message = JSON.stringify(o); - broker.publish("v1/gateway/telemetry", message, {qos:1}); + client.publish("v1/gateway/telemetry", message, {qos:1}); + instance.send(3, message); //remove from database await promisifyBuilder(nosql.remove().where("id", id)); - } catch (error) { + } catch(error) { //process error console.log("processDataFromDatabase", error); } @@ -533,8 +490,6 @@ exports.install = function(instance) { } - loadSettings(); - - //instance.on('options', instance.reconfigure); - //instance.reconfigure(); + instance.on('options', main); + //instance.reconfigure(); };