2988 lines
93 KiB
JavaScript
Executable file
2988 lines
93 KiB
JavaScript
Executable file
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'];
|
||
exports.input = 3;
|
||
exports.icon = 'cloud-upload';
|
||
|
||
exports.html = `
|
||
<div class="padding">
|
||
<div class="row">
|
||
<div class="col-md-12">
|
||
<div>RPC - run RPC calls</div><br>
|
||
</div>
|
||
</div>
|
||
<div data-jc="textbox" data-jc-path="username" class="m" data-jc-config="required:true">@(User)</div>
|
||
<div class="row">
|
||
<div class="col-md-6 m">
|
||
<div data-jc="textbox" data-jc-path="userpassword" data-jc-config="required:true">@(Password)</div>
|
||
</div>
|
||
<div class="col-md-6 m">
|
||
<div data-jc="textbox" data-jc-path="edge" data-jc-config="required:true">@(My edge)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
exports.readme = `Manager for CMD calls`;
|
||
|
||
|
||
exports.install = function(instance) {
|
||
|
||
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');
|
||
|
||
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');
|
||
|
||
const dbNodes = TABLE("nodes");
|
||
const dbRelays = TABLE("relays");
|
||
|
||
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
|
||
|
||
//if sending of profile to node fails, we send notification and push node into set, so we do not send notification twice
|
||
const nodeProfileSendFail = new Set();
|
||
|
||
//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) {
|
||
//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) {
|
||
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`);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
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 (value.contactor == 1) processAllNodeProfilesOnLine(value.line);
|
||
}
|
||
}
|
||
|
||
|
||
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;
|
||
}
|
||
|
||
sendTelemetry({ status: status }, tbname, time);
|
||
|
||
//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";
|
||
|
||
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) => {
|
||
|
||
setTimeout(function() {
|
||
|
||
//potrebujem nody k danej linii
|
||
if (line == nodesData[node].line || line == undefined) {
|
||
let tbname = nodesData[node].tbname;
|
||
sendTelemetry(values, tbname, date)
|
||
}
|
||
|
||
}, (index + 1) * 1000);
|
||
})
|
||
|
||
}
|
||
|
||
|
||
function turnLine(onOrOff, line, info) {
|
||
let obj = {
|
||
line: line,
|
||
command: onOrOff,
|
||
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.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;
|
||
|
||
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 SETTINGS.edge_fw_version as fw_version
|
||
//report date as startdate
|
||
|
||
//return;
|
||
console.log("buidTAaasks start ****************", params);
|
||
|
||
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 vsetky linie:
|
||
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.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);
|
||
|
||
|
||
/**
|
||
* 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] = sunCalcResult["dawn"].split(':');
|
||
let ad = new Date();
|
||
ad.setHours(parseInt(ahours), parseInt(aminutes) + profile.dawn_lux_sensor_time_window, 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] = sunCalcResult["dusk"].split(':');
|
||
let ad = new Date();
|
||
ad.setHours(parseInt(ahours), parseInt(aminutes) + profile.dusk_lux_sensor_time_window, 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] = a.start_time.split(':');
|
||
let [bhours, bminutes] = b.start_time.split(':');
|
||
|
||
let ad = new Date();
|
||
ad.setHours(parseInt(ahours), parseInt(aminutes), 0);
|
||
|
||
let bd = new Date();
|
||
bd.setHours(parseInt(bhours), parseInt(bminutes), 0);
|
||
|
||
return ad.getTime() - bd.getTime();
|
||
});
|
||
|
||
console.log("line timepoints ........", time_points);
|
||
|
||
let currentValue = 0;
|
||
if (time_points.length > 0) currentValue = time_points[time_points.length - 1].value;
|
||
|
||
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] = time_points[t].start_time.split(':');
|
||
start_time.setHours(parseInt(hours), parseInt(minutes), 0);
|
||
|
||
//task is in 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("TimePoint params: ", params.info, start_time);
|
||
}
|
||
|
||
monitor.info("-->time_points final", line, time_points);
|
||
|
||
//ensure to turn on/off according to calculated currentValue
|
||
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
|
||
//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 * 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)";
|
||
|
||
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 = SETTINGS.rvoTbName;
|
||
params.info = "Master node FW verzia";
|
||
//params.debug = true;
|
||
|
||
//this will set SETTINGS.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) turnLine("off", 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) turnLine("on", 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 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) {
|
||
//MASTER
|
||
if (node == 0) return;
|
||
|
||
let nodeObj = nodesData[node];
|
||
if (nodeObj == undefined) return;
|
||
|
||
let nodeCurrentStatus = nodeObj.status;
|
||
const now = Date.now();
|
||
|
||
let data = null;
|
||
|
||
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;
|
||
}
|
||
else if (newStatus == false && nodeCurrentStatus == false) return true;
|
||
else if (newStatus == false && nodeCurrentStatus == true) {
|
||
if (nodeObj.time_of_last_communication + SETTINGS.node_status_nok_time > now) return;
|
||
else {
|
||
data = { status: newStatus };
|
||
nodeDbStatusModify(node, data);
|
||
return true;
|
||
}
|
||
}
|
||
else if (newStatus == true && nodeCurrentStatus == false) {
|
||
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 };
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
|
||
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", SETTINGS.rvoTbName, "dusk_has_occured", { value: sunCalcResult["dusk"] }, "", SEND_TO.tb, instance);
|
||
reportDuskDawn.dusk_time_reported = sunCalcResult.dusk_time;
|
||
}
|
||
}
|
||
|
||
var nextDay = new Date();
|
||
nextDay.setDate(nextDay.getDate() + 1);
|
||
|
||
sunCalcResult = calculateDuskDawn(nextDay);
|
||
reportDuskDawn.dusk_time = sunCalcResult.dusk_time;
|
||
}
|
||
|
||
if (reportDuskDawn.dawn_time < currentTimestamp) {
|
||
//vyreportuj iba ak nie je velky rozdiel napr. 60 sekund
|
||
if ((currentTimestamp - reportDuskDawn.dawn_time) < 60 * 1000) {
|
||
//reportovali sme?
|
||
if (reportDuskDawn.dawn_time_reported != sunCalcResult.dawn_time) {
|
||
//sendNotification("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;
|
||
}
|
||
}
|
||
|
||
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();
|
||
//console.log("Cmd_manager - !rsPort.isOpen");
|
||
}
|
||
|
||
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 (SETTINGS.maintenance_mode && params.type !== "cmd-terminal") {
|
||
interval = setInterval(runTasks, LONG_INTERVAL);
|
||
return;
|
||
}
|
||
|
||
let type = params.type;
|
||
let tbname = params.tbname;
|
||
let node = params.address;
|
||
|
||
let line = null;
|
||
|
||
//rpc related
|
||
if (nodesData[node] !== undefined) line = nodesData[node].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;
|
||
if (timePointName) 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 onOrOff = "";
|
||
value == 1 ? onOrOff = "on" : onOrOff = "off";
|
||
|
||
turnLine(onOrOff, params.line, info);
|
||
|
||
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]);
|
||
|
||
//report only once!
|
||
if (!disconnectedReport.hasOwnProperty(tbname)) disconnectedReport[tbname] = false;
|
||
|
||
if (!disconnectedReport[tbname]) {
|
||
sendTelemetry(values, tbname)
|
||
}
|
||
|
||
interval = setInterval(runTasks, SHORT_INTERVAL);
|
||
|
||
return;
|
||
}
|
||
|
||
disconnectedReport[tbname] = false;
|
||
|
||
const register = params.register;
|
||
|
||
//high_priority
|
||
if (!SETTINGS.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 contactorStatus = 1;
|
||
if (relaysData[line] != undefined) contactorStatus = relaysData[line].contactor;
|
||
|
||
if (line == 0 || contactorStatus == 0) {
|
||
interval = setInterval(runTasks, LONG_INTERVAL);
|
||
return;
|
||
}
|
||
|
||
// TODO: -> status offline for rvo if rotary_switch_state is OFF, this is source of errors
|
||
//
|
||
// 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" };
|
||
|
||
// if(tbname) sendTelemetry(values, tbname)
|
||
|
||
// 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();
|
||
params.byte1 = d.getHours();//h
|
||
params.byte2 = d.getMinutes();//m
|
||
params.byte3 = 0;//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();
|
||
params.byte1 = sunCalcResult["dusk_hours"];//h
|
||
params.byte2 = sunCalcResult["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();
|
||
params.byte1 = sunCalcResult["dawn_hours"];//h
|
||
params.byte2 = sunCalcResult["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: " + node + " 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(node, 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;
|
||
|
||
//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(data);
|
||
|
||
//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(node, true);
|
||
|
||
//write
|
||
if (type == "set_node_profile") {
|
||
let result = cmdCounterResolve(node);
|
||
if (result == 0) {
|
||
dbNodes.modify({ processed: true }).where("node", node).make(function(builder) {
|
||
builder.callback(function(err, response) {
|
||
|
||
sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "dimming_profile_was_successfully_received_by_node", { node: node }, "", SEND_TO.tb, instance);
|
||
|
||
logger.debug("--> profil úspešne odoslaný na node č. " + node);
|
||
nodesData[node].processed = true;
|
||
nodeProfileSendFail.delete(node);
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
//parse read response
|
||
if (params.rw == 0) {
|
||
values = processResponse(register, dataBytes); //read
|
||
}
|
||
|
||
if (itIsNodeCommand) {
|
||
values.comm_status = "OK";
|
||
values.status = "OK";
|
||
}
|
||
|
||
//master node
|
||
if (node == 0) {
|
||
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", SETTINGS.rvoTbName, "command_was_sent_from_terminal_interface", {}, params, SEND_TO.tb, instance);
|
||
}
|
||
|
||
if (params.debug) {
|
||
//logger.debug("saveToTb", saveToTb, tbname, values);
|
||
}
|
||
|
||
if (saveToTb) {
|
||
sendTelemetry(values, tbname)
|
||
}
|
||
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, node, 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", SETTINGS.rvoTbName, "master_node_is_not_responding", {}, "", SEND_TO.tb, instance, "rvo_status");
|
||
logger.debug("master_node_is_not_responding", params);
|
||
SETTINGS.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);
|
||
|
||
if (!nodeProfileSendFail.has(node)) {
|
||
sendNotification("CMD Manager: process cmd", tbName, "configuration_of_dimming_profile_to_node_failed", { node: node }, "", SEND_TO.tb, instance);
|
||
nodeProfileSendFail.add(node);
|
||
}
|
||
}
|
||
|
||
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) {
|
||
sendTelemetry(values, tbName)
|
||
}
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
* 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]; //holds reference to httprequest flowdata
|
||
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() {
|
||
|
||
//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
|
||
};
|
||
|
||
sendTelemetry(values, SETTINGS.rvoTbName, ts);
|
||
}
|
||
|
||
|
||
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
|
||
|
||
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() {
|
||
|
||
logger.debug("CMD manager - rsPort opened success");
|
||
|
||
//loadRelaysData();
|
||
|
||
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);
|
||
|
||
//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);
|
||
|
||
}).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", 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);
|
||
});
|
||
|
||
rsPort.on("close", () => {
|
||
setTimeout(() => rsPort.open(), 1000);
|
||
});
|
||
|
||
rsPort.open();
|
||
}
|
||
|
||
|
||
instance.on("close", () => {
|
||
clearInterval(interval);
|
||
clearInterval(customTasksInterval);
|
||
clearInterval(setCorrectTime);
|
||
rsPort.close();
|
||
});
|
||
|
||
|
||
instance.on("0", flowdata => {
|
||
main();
|
||
})
|
||
|
||
instance.on("1", 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") {
|
||
let value = flowdata.data.value;
|
||
|
||
//state was changed
|
||
if (rotary_switch_state != value) {
|
||
if (value == "Off") {
|
||
//vyreportovat vsetky svietdla
|
||
reportOfflineNodeStatus();
|
||
}
|
||
|
||
rotary_switch_state = 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
|
||
sendTelemetry({ status: status }, tbname)
|
||
|
||
//current value
|
||
if (value == "Off") reportOfflineNodeStatus(line); //vyreportovat vsetky svietidla na linii
|
||
}
|
||
|
||
}
|
||
}
|
||
else {
|
||
logger.debug("undefined cmd", cmd);
|
||
}
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
//data from worksys
|
||
if (flowdata.data.hasOwnProperty("topic")) {
|
||
|
||
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;
|
||
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) {
|
||
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) {
|
||
|
||
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;
|
||
|
||
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");
|
||
|
||
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);
|
||
|
||
});
|
||
});
|
||
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 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 (isObject(relayObject)) line = relayObject.line;
|
||
|
||
// v relaysData je contactor bud 0 alebo 1, ale z platformy prichadza true, false;
|
||
if (value == false) turnLine("off", line, "command received from platform");
|
||
else turnLine("on", line, "command received from platform");
|
||
}
|
||
}
|
||
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);
|
||
|
||
}
|
||
}
|
||
})
|
||
|
||
|
||
//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)
|
||
}
|
||
|
||
|
||
/**
|
||
* 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");
|
||
}
|
||
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
///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) {
|
||
|
||
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 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, m, 0, 0);
|
||
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;
|
||
|
||
}
|
||
|
||
function getObjectByTbValue(object, tbname) {
|
||
return object[Object.keys(object).find(key => object[key].tbname === tbname)];
|
||
}
|
||
|
||
function isObject(item) {
|
||
return (typeof item === "object" && !Array.isArray(item) && item !== null);
|
||
}
|
||
|
||
} // end of instance.export
|
||
|