2800 lines
89 KiB
Text
Executable file
2800 lines
89 KiB
Text
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 errorHandler = require('./helper/ErrorToServiceHandler');
|
||
const { sendNotification } = require('./helper/notification_reporter');
|
||
const process = require('process');
|
||
const { errLogger, logger, monitor } = require('./helper/logger');
|
||
|
||
//for accelerometer purposes
|
||
const { naklony } = require("../databases/accelerometer_db");
|
||
|
||
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 sendNodeReadout = null; // interval for sending agregate data from node
|
||
|
||
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["1"] = minutes; // dimming
|
||
priorities["76"] = minutes; // power
|
||
|
||
minutes = 5;
|
||
priorities["75"] = minutes; // current
|
||
priorities["79"] = minutes; // energy
|
||
priorities["87"] = minutes; // aktualny cas
|
||
//priorities["84"] = minutes;
|
||
|
||
minutes = 10;
|
||
priorities["74"] = minutes; // voltage
|
||
priorities["77"] = minutes; // power factor
|
||
priorities["78"] = minutes; // frequency
|
||
|
||
minutes = 60;
|
||
priorities["0"] = minutes; // statecode
|
||
priorities["6"] = minutes; // dusk
|
||
priorities["7"] = minutes; // dawn
|
||
priorities["8"] = minutes; // profile
|
||
|
||
minutes = 60 * 24;
|
||
priorities["89"] = minutes; // verzia fw
|
||
priorities["80"] = minutes; // lifetime
|
||
|
||
//prikazy kt sa budu spustat na dany node - see config.js in terminal-oms.app. (1 - dimming)
|
||
let listOfCommands = [0, 1, 6, 7, 8, 74, 75, 76, 77, 78, 79, 80, 87, 89];
|
||
|
||
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 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();
|
||
|
||
//we expect to get current temperature in Senica from senica-prod01
|
||
let temperatureInSenica = null;
|
||
let accelerometerInterval = null;
|
||
|
||
//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);
|
||
|
||
let now = new Date();
|
||
console.log("Cmd-mngr 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();
|
||
|
||
customTasksInterval = setInterval(function() {
|
||
reportEdgeDateTimeAndNumberOfLuminaires();
|
||
}, 120000);
|
||
reportEdgeDateTimeAndNumberOfLuminaires();
|
||
|
||
setCorrectTime = setInterval(setCorrectPlcTimeOnceADay, 60000 * 60); // 1 hour
|
||
setCorrectPlcTimeOnceADay();
|
||
|
||
sendNodeReadout = setInterval(sendNodesData, 150000);
|
||
accelerometerInterval = setInterval(accelerometerData, 60000 * 30); //30 min
|
||
}
|
||
|
||
|
||
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
|
||
// if node regular readout does not respond, we repeat request
|
||
params.repeatCounter = 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-mngr: 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.byte4 = 96;
|
||
params.recipient = 1;
|
||
params.register = 8;
|
||
params.rw = 1;//write
|
||
params.timestamp = timestamp;
|
||
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.byte4 = 96;
|
||
params.recipient = 1;
|
||
params.register = 8;
|
||
params.rw = 1;//write
|
||
params.timestamp = timestamp;
|
||
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).
|
||
*/
|
||
|
||
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.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.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
|
||
{
|
||
|
||
logger.debug("processNodeProfile: Threshold lux level for DUSK/DAWN", node);
|
||
|
||
let params = getParams();
|
||
params.type = "set_node_profile";
|
||
params.address = node;
|
||
params.register = 96;
|
||
params.recipient = 1;
|
||
params.rw = 1;//write
|
||
params.timestamp = timestamp;
|
||
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();
|
||
params.type = "set_node_profile";
|
||
params.address = node;
|
||
params.register = 97;
|
||
params.recipient = 1;
|
||
params.rw = 1;//write
|
||
params.timestamp = timestamp;
|
||
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.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) {
|
||
//Po zapnutí línie broadcastovo aktualizovať predtým čas a o 3 sek neskor - status, brightness
|
||
|
||
logger.debug("Cmd-mngr: ----->reportOnlineNodeStatus for line", line);
|
||
|
||
const d = new Date();
|
||
|
||
// broadcast actual time
|
||
let params = getParams();
|
||
params.address = 0xffffffff;//Broadcast
|
||
params.byte1 = d.getHours();
|
||
params.byte2 = d.getMinutes();
|
||
params.recipient = 2;//2 broadcast, address = 0
|
||
params.register = 87;//Actual time
|
||
params.rw = 1;//write
|
||
params.type = "node-onetime-write";
|
||
params.timestamp = d.getTime() + 30000;
|
||
params.info = "run broadcast: Actual time";
|
||
//params.debug = true;
|
||
|
||
tasks.push(params);
|
||
|
||
let sec = 3;
|
||
setTimeout(function() {
|
||
//Po zapnutí línie - spraviť hromadný refresh stavu práve zapnutých svietidiel
|
||
|
||
let time = Date.now();
|
||
|
||
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].node_status_before_offline === true || nodesData[k].status === true) {
|
||
status = "OK";
|
||
nodesData[k].time_of_last_communication = time;
|
||
}
|
||
|
||
nodesData[k].readout.status = status;
|
||
|
||
updateNodeStatus(k, status === "OK" ? true : false);
|
||
if (nodesData[k].hasOwnProperty("node_status_before_offline")) delete nodesData[k].node_status_before_offline;
|
||
sendTelemetry({ status: status }, tbname, time);
|
||
|
||
//vyreportovanie dimming, current, input power pre liniu pre vsetky nody
|
||
//Prud
|
||
{
|
||
let params = getParams();
|
||
|
||
params.type = "node-onetime-read";
|
||
params.tbname = tbname;
|
||
params.address = node;
|
||
params.register = 75;//prud
|
||
params.recipient = 1;//slave
|
||
params.rw = 0;//read
|
||
params.timestamp = time + 4000;
|
||
params.info = 'read current';
|
||
//params.debug = true;
|
||
tasks.push(params);
|
||
}
|
||
|
||
//vykon
|
||
{
|
||
let params = getParams();
|
||
|
||
params.type = "node-onetime-read";
|
||
params.tbname = tbname;
|
||
params.address = node;
|
||
params.register = 76;//výkon
|
||
params.recipient = 1;//slave
|
||
params.rw = 0;//read
|
||
params.timestamp = time + 4100;
|
||
params.info = 'read power';
|
||
//params.debug = true;
|
||
|
||
tasks.push(params);
|
||
}
|
||
//dimming
|
||
{
|
||
let params = getParams();
|
||
|
||
params.type = "node-onetime-read";
|
||
params.tbname = tbname;
|
||
params.address = node;
|
||
params.register = 1;//dimming
|
||
params.recipient = 1;//slave
|
||
params.rw = 0;//read
|
||
params.timestamp = time + 4200;
|
||
params.info = 'read dimming';
|
||
//params.debug = true;
|
||
|
||
tasks.push(params);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
}, sec * 1000);
|
||
}
|
||
|
||
|
||
function reportOfflineNodeStatus(line) {
|
||
|
||
logger.info("Cmd-mngr: ------>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();
|
||
|
||
Object.keys(nodesData).forEach(node => {
|
||
|
||
//potrebujem nody k danej linii
|
||
if (line == nodesData[node].line || line == undefined) {
|
||
|
||
let tbname = nodesData[node].tbname;
|
||
let nodeStatus = nodesData[node].status;
|
||
|
||
//in case we have reported offline node status, we return (continue with next node)
|
||
if (nodeStatus === "OFFLINE") return;
|
||
|
||
nodesData[node].node_status_before_offline = nodeStatus;
|
||
nodesData[node].status = "OFFLINE";
|
||
nodesData[node].readout = {};
|
||
|
||
sendTelemetry({ ...values }, tbname, date);
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
|
||
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) {
|
||
|
||
//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;
|
||
}
|
||
|
||
let now = new Date();
|
||
|
||
//process line profiles
|
||
if (processLineProfiles) {
|
||
|
||
let keys = Object.keys(relaysData);
|
||
|
||
for (let i = 0; i < keys.length; i++) {
|
||
|
||
let line = parseInt(keys[i]);
|
||
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();
|
||
params.type = "relay";
|
||
params.line = parseInt(line);
|
||
params.value = time_points[t].value;
|
||
params.tbname = relaysData[line].tbname;
|
||
params.timestamp = start_time.getTime();
|
||
|
||
// 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();
|
||
params.type = "relay";
|
||
params.line = parseInt(line);
|
||
params.tbname = relaysData[line].tbname;
|
||
params.value = currentValue;
|
||
params.timestamp = i;
|
||
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);
|
||
console.log(`Cmd_mngr: Unable to process line profile ${line}. Error: `, error);
|
||
errorHandler.sendMessageToService(profilestr + "-" + error, 0, "js_error");
|
||
} else {
|
||
turnLine("off", line, "No line profile. Switching it off on startup");
|
||
}
|
||
}
|
||
|
||
}
|
||
//logger.debug("tasks:");
|
||
//logger.debug(tasks);
|
||
}
|
||
|
||
|
||
//NOTE: PROCESS DEFAULT BROADCASTS - Time of dusk, Time of dawn, Actual Time
|
||
if (processBroadcast) {
|
||
|
||
let d = new Date();
|
||
let time = d.getTime();
|
||
let sunCalcResult = calculateDuskDawn();
|
||
|
||
{
|
||
let params = getParams();
|
||
|
||
params.address = 0xffffffff;//broadcast
|
||
params.byte1 = sunCalcResult["dusk_hours"];
|
||
params.byte2 = sunCalcResult["dusk_minutes"];
|
||
params.recipient = 2;//2 broadcast,
|
||
params.register = 6;//Time of dusk
|
||
params.rw = 1;//write
|
||
params.type = "node-regular-write";
|
||
params.timestamp = time + 60000;
|
||
params.addMinutesToTimestamp = 60 * 3; //kazde 3 hodiny zisti novy dusk
|
||
params.info = "Broadcast-duskTime";
|
||
|
||
tasks.push(params);
|
||
}
|
||
|
||
{
|
||
let params = getParams();
|
||
|
||
params.address = 0xffffffff;//broadcast
|
||
params.byte1 = sunCalcResult["dawn_hours"];
|
||
params.byte2 = sunCalcResult["dawn_minutes"];
|
||
params.recipient = 2; //2 broadcast
|
||
params.register = 7;//Time of dawn
|
||
params.rw = 1;//write
|
||
params.type = "node-regular-write";
|
||
params.timestamp = time + 60001;
|
||
params.addMinutesToTimestamp = 60 * 3; //kazde 3 hodiny zisti novy dawn
|
||
params.info = "Broadcast-dawnTime";
|
||
|
||
tasks.push(params);
|
||
}
|
||
|
||
{
|
||
let params = getParams();
|
||
|
||
params.address = 0xffffffff;//broadcast
|
||
params.byte1 = d.getHours();
|
||
params.byte2 = d.getMinutes();
|
||
params.recipient = 2; //2 broadcast
|
||
params.register = 87;//Actual time
|
||
params.rw = 1;//write
|
||
params.type = "node-regular-write";
|
||
params.timestamp = time + 60002;
|
||
params.addMinutesToTimestamp = 5;
|
||
params.info = "run broadcast: Actual time";
|
||
|
||
tasks.push(params);
|
||
}
|
||
|
||
}
|
||
|
||
//process nodes & tasks - read node's data
|
||
if (processNodes) {
|
||
|
||
let time = Date.now();
|
||
|
||
for (let k in nodesData) {
|
||
let address = parseInt(k);
|
||
let tbname = nodesData[k].tbname;
|
||
let register = 0;
|
||
|
||
for (let i = 0; i < listOfCommands.length; i++) {
|
||
|
||
register = listOfCommands[i];
|
||
let addMinutesToTimestamp = priorities[register];
|
||
|
||
let params = getParams();
|
||
|
||
params.address = address;
|
||
params.recipient = 1;
|
||
params.register = register;
|
||
params.type = register == 1 ? "node-dimming-read" : "node-regular-read";
|
||
params.tbname = tbname;
|
||
params.timestamp = time + 5000 + i * 500 + addMinutesToTimestamp * 1000; //to make slight time difference
|
||
params.addMinutesToTimestamp = addMinutesToTimestamp;
|
||
params.info = "Node regular read command";
|
||
|
||
tasks.push(params);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
|
||
//niektore ulohy sa vygeneruju iba 1x pri starte!!!
|
||
if (!init) return;
|
||
|
||
|
||
//Master node FW version - modifies SETTINGS.masterNodeIsResponding
|
||
{
|
||
let params = getParams();
|
||
params.type = "cmd-master";
|
||
params.register = 4;
|
||
params.address = 0;
|
||
params.timestamp = 0;
|
||
params.addMinutesToTimestamp = 5;
|
||
params.tbname = SETTINGS.rvoTbName;
|
||
params.info = "Master node FW verzia";
|
||
//params.debug = true;
|
||
|
||
tasks.push(params);
|
||
}
|
||
|
||
//kazdu hodinu skontrolovat nastavenie profilov
|
||
{
|
||
let params = getParams();
|
||
params.type = "process_profiles";
|
||
params.timestamp = Date.now() + 60001;
|
||
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 (nodeCurrentStatus === "OFFLINE") {
|
||
data = { status: newStatus };
|
||
nodeDbStatusModify(node, data);
|
||
return;
|
||
}
|
||
else 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-mngr: calculated Time of dusk", SETTINGS.rvoTbName, "dusk_has_occured", { value: sunCalcResult["dusk"] }, "", SEND_TO.tb, instance);
|
||
reportDuskDawn.dusk_time_reported = sunCalcResult.dusk_time;
|
||
}
|
||
}
|
||
|
||
var nextDay = new Date();
|
||
nextDay.setDate(nextDay.getDate() + 1);
|
||
|
||
sunCalcResult = calculateDuskDawn(nextDay);
|
||
reportDuskDawn.dusk_time = sunCalcResult.dusk_time;
|
||
}
|
||
|
||
if (reportDuskDawn.dawn_time < currentTimestamp) {
|
||
//vyreportuj iba ak nie je velky rozdiel napr. 60 sekund
|
||
if ((currentTimestamp - reportDuskDawn.dawn_time) < 60 * 1000) {
|
||
//reportovali sme?
|
||
if (reportDuskDawn.dawn_time_reported != sunCalcResult.dawn_time) {
|
||
//sendNotification(": calculated Time of dawn", SETTINGS.rvoTbName, "dawn_has_occured", { value: sunCalcResult["dawn"] }, "", SEND_TO.tb, instance);
|
||
reportDuskDawn.dawn_time_reported = sunCalcResult.dawn_time;
|
||
}
|
||
}
|
||
|
||
var nextDay = new Date();
|
||
nextDay.setDate(nextDay.getDate() + 1);
|
||
|
||
sunCalcResult = calculateDuskDawn(nextDay);
|
||
reportDuskDawn.dawn_time = sunCalcResult.dawn_time;
|
||
|
||
}
|
||
//--------------------------------------------------------
|
||
|
||
//sort tasks based on timestamp
|
||
tasks.sort(function(a, b) {
|
||
if (a.timestamp <= currentTimestamp && b.timestamp <= currentTimestamp) {
|
||
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-mngr: !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 register = params.register;
|
||
let line = null;
|
||
let itIsNodeCommand;
|
||
|
||
if (nodesData[node] !== undefined) {
|
||
line = nodesData[node].line;
|
||
itIsNodeCommand = true;
|
||
}
|
||
|
||
if (params.line !== undefined) line = params.line;
|
||
|
||
if (params.addMinutesToTimestamp > 0 || params.timePointName) {
|
||
tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000;
|
||
} else {
|
||
tasks.shift();
|
||
}
|
||
|
||
//kontrola nespracovanych profilov nodov
|
||
if (type == "process_profiles") {
|
||
//na vsetky zapnutych liniach sa spracuju 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, LONG_INTERVAL);
|
||
return;
|
||
}
|
||
|
||
if (!SETTINGS.masterNodeIsResponding) {
|
||
//ak neodpoveda, nebudeme vykonavat ziadne commands, okrem cmd-terminal cmd-master
|
||
errorHandler.sendMessageToService("Master node is not responding");
|
||
|
||
let stop = true;
|
||
|
||
if (type === "cmd-terminal" || type === "cmd-master") 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 || FLOW.deviceStatus.state_of_breaker[line] === "Off") {
|
||
interval = setInterval(runTasks, LONG_INTERVAL);
|
||
return;
|
||
}
|
||
|
||
// TODO: -> status offline for rvo if rotary_switch_state is OFF, this is source of errors
|
||
// check if rotary_switch_state == "Off"
|
||
// state_of_braker: disconnected = true?
|
||
|
||
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
|
||
}
|
||
|
||
//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
|
||
}
|
||
}
|
||
|
||
//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
|
||
}
|
||
}
|
||
//-----------------------
|
||
|
||
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 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) {
|
||
|
||
//sometimes happens, that status of node changes to OK, NOK even if line was turned off and should be status OFFLINE. To prevent this, we return if line contactor is 0:
|
||
if (itIsNodeCommand && line && relaysData[line].contactor !== 1) return;
|
||
|
||
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;
|
||
|
||
if (params.hasOwnProperty("debug")) {
|
||
if (params.debug) {
|
||
console.log("detected response:", result);
|
||
logger.debug("Cmd-mngr: writeData done " + message_type + " duration: " + timeDiff + " type: " + params.debug, params, 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-mngr: 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";
|
||
nodesData[node].readout = { ...nodesData[node].readout, ...values };
|
||
}
|
||
|
||
//master node
|
||
if (node == 0) {
|
||
sendNotification("Cmd-mngr: 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;
|
||
}
|
||
|
||
if (params.debug) {
|
||
//logger.debug("saveToTb", saveToTb, tbname, values);
|
||
}
|
||
|
||
if (saveToTb && type != "node-regular-read") {
|
||
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);
|
||
}
|
||
|
||
|
||
// if node does not respond to request, we repeat request 3 times:
|
||
function repeatCommand(params) {
|
||
params.repeatCounter++;
|
||
if (params.repeatCounter < 4) {
|
||
params.timestamp = 0;
|
||
params.addMinutesToTimestamp = 0;
|
||
tasks.push(params);
|
||
}
|
||
}
|
||
|
||
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 = {};
|
||
|
||
let updateStatus = updateNodeStatus(node, false);
|
||
|
||
if (itIsNodeCommand) {
|
||
values.comm_status = "NOK";
|
||
nodesData[node].readout.comm_status = "NOK";
|
||
repeatCommand(params);
|
||
}
|
||
|
||
if (updateStatus) {
|
||
values.status = "NOK";
|
||
nodesData[node].readout.status = "NOK";
|
||
}
|
||
|
||
if (type === "node-regular-read") return;
|
||
|
||
//master node
|
||
if (node == 0) {
|
||
sendNotification("Cmd-mngr: 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-mngr: process cmd", tbName, "configuration_of_dimming_profile_to_node_failed", { node: node }, "", SEND_TO.tb, instance);
|
||
nodeProfileSendFail.add(node);
|
||
}
|
||
}
|
||
|
||
// console.log("------",node, register, type, itIsNodeCommand, updateStatus, saveToTb, values);
|
||
if (saveToTb) {
|
||
sendTelemetry(values, tbName);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
function sendNodesData() {
|
||
Object.keys(nodesData).forEach(node => {
|
||
if (nodesData[node]["status"] !== "OFFLINE") {
|
||
sendTelemetry(nodesData[node].readout, nodesData[node].tbname);
|
||
nodesData[node].readout = {};
|
||
}
|
||
})
|
||
}
|
||
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
|
||
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);
|
||
|
||
//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 === "OFFLINE") {
|
||
nodeObj.node_status_before_offline === true ? number_of_ok_luminaires++ : number_of_nok_luminaires++;
|
||
}
|
||
else if (nodeObj.status == true) 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 - ts % 60000 //round to full minute
|
||
};
|
||
|
||
sendTelemetry(values, SETTINGS.rvoTbName, ts);
|
||
}
|
||
|
||
|
||
function handleRsPort() {
|
||
|
||
if (rsPort) {
|
||
rsPort.removeAllListeners();
|
||
rsPort = null;
|
||
}
|
||
|
||
//! 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-mngr: rsPort opened success");
|
||
|
||
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);
|
||
|
||
}).catch(function(reason) {
|
||
instance.send(SEND_TO.debug, "Cmd-mngr: RPC runSyncExec - promise rejected:" + reason);
|
||
});
|
||
});
|
||
|
||
rsPort.on('error', function(err) {
|
||
errorHandler.sendMessageToService([exports.title, "unable to open port", SETTINGS.serial_port, err.message], 0);
|
||
monitor.info("Cmd-mngr: Error on rsPort", err.message);
|
||
});
|
||
|
||
rsPort.on("close", () => {
|
||
monitor.info("Cmd-mngr: rsPort closed, reconnecting...");
|
||
setTimeout(handleRsPort, 1000);
|
||
});
|
||
|
||
rsPort.open();
|
||
}
|
||
|
||
|
||
instance.on("close", () => {
|
||
clearInterval(interval);
|
||
clearInterval(customTasksInterval);
|
||
clearInterval(setCorrectTime);
|
||
clearInterval(sendNodeReadout);
|
||
clearInterval(accelerometerInterval);
|
||
rsPort.close();
|
||
});
|
||
|
||
instance.on("0", _ => {
|
||
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-mngr: BUILD TASKS");
|
||
buildTasks();
|
||
|
||
//logger.debug("tasks:");
|
||
//logger.debug(tasks);
|
||
|
||
logger.debug("-->Cmd-mngr: RUN TASKS");
|
||
interval = setInterval(runTasks, 5000);
|
||
}
|
||
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-mngr: onData", tbname, "circuit_breaker_was_turned_off_line", { line: line }, "", SEND_TO.tb, instance, "circuit_breaker");
|
||
else sendNotification("Cmd-mngr: onData", tbname, "circuit_breaker_was_turned_on_line", { line: line }, "", SEND_TO.tb, instance, "circuit_breaker");
|
||
|
||
//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 we get temperature in senica from senica-prod01
|
||
let temperature = getNested(flowdata.data, "content", "senica_temperature");
|
||
|
||
if (temperature !== undefined) {
|
||
temperatureInSenica = temperature;
|
||
return;
|
||
}
|
||
|
||
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;
|
||
|
||
params.type = "node-onetime-write";
|
||
params.tbname = tbname;
|
||
params.address = node;
|
||
params.register = 1;
|
||
params.recipient = 1;
|
||
params.byte4 = value;
|
||
params.rw = 1;
|
||
params.timestamp = PRIORITY_TYPES.high_priority;
|
||
params.info = 'set dimming from platform';
|
||
//params.debug = true;
|
||
|
||
//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 = "node-onetime-read";
|
||
params.tbname = tbname;
|
||
params.address = node;
|
||
params.register = 1;
|
||
params.recipient = 1;
|
||
params.rw = 0;
|
||
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 = "node-onetime-read";
|
||
params.tbname = tbname;
|
||
params.address = node;
|
||
params.register = 76;
|
||
params.recipient = 1;
|
||
params.rw = 0;
|
||
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 = "node-onetime-read";
|
||
params.tbname = tbname;
|
||
params.address = node;
|
||
params.register = 75;
|
||
params.recipient = 1;
|
||
params.rw = 0;
|
||
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 = "node-onetime-read";
|
||
params.tbname = tbname;
|
||
params.address = node;
|
||
params.register = 77;
|
||
params.recipient = 1;
|
||
params.rw = 0;
|
||
params.timestamp = PRIORITY_TYPES.high_priority;
|
||
params.info = 'read power factor (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-mngr", 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)
|
||
logger.debug("loadRelaysData DONE for line", line);
|
||
|
||
buildTasks({ processLineProfiles: true, line: line });
|
||
|
||
sendNotification("Cmd-mngr: 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-mngr: 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
|
||
else 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;
|
||
}
|
||
|
||
//
|
||
else if (register == 4) {
|
||
values["master_node_version"] = bytes[1] + "." + bytes[2];
|
||
//logger.debug("FW Version", register, bytes);
|
||
}
|
||
|
||
//Napätie
|
||
else if (register == 74) {
|
||
let voltage = (bytesToInt(bytes) * 0.1).toFixed(1);
|
||
values["voltage"] = Number(voltage);
|
||
}
|
||
|
||
//Prúd
|
||
else if (register == 75) {
|
||
let current = bytesToInt(bytes);
|
||
values["current"] = current;
|
||
}
|
||
|
||
//výkon
|
||
else if (register == 76) {
|
||
let power = (bytesToInt(bytes) * 0.1).toFixed(2);
|
||
values["power"] = Number(power);
|
||
}
|
||
|
||
//účinník
|
||
else if (register == 77) {
|
||
let power_factor = Math.cos(bytesToInt(bytes) * 0.1 * (Math.PI / 180)).toFixed(2);
|
||
values["power_factor"] = Number(power_factor);
|
||
}
|
||
|
||
//frekvencia
|
||
else if (register == 78) {
|
||
let frequency = (bytesToInt(bytes) * 0.1).toFixed(2);
|
||
values["frequency"] = Number(frequency);
|
||
}
|
||
|
||
//energia
|
||
else if (register == 79) {
|
||
let energy = bytesToInt(bytes);
|
||
values["energy"] = energy / 1000; //energia v kWh -> delit 1000
|
||
}
|
||
|
||
//doba života
|
||
else if (register == 80) {
|
||
let lifetime = (bytesToInt(bytes) / 60).toFixed(2);
|
||
values["lifetime"] = Number(lifetime);
|
||
}
|
||
|
||
//nastavenie profilu
|
||
else if (register == 8) {
|
||
let time_schedule_settings = bytesToInt(bytes);
|
||
values["time_schedule_settings"] = time_schedule_settings;
|
||
}
|
||
|
||
//naklon - nateraz sa z nodu nevycitava! kvoli problemom s accelerometrom a vracanymi hodnotami, posielame temp a x y z vo funkcii accelerometerData()
|
||
else if (register == 84) {
|
||
values["temperature"] = byte3 >= 128 ? (byte3 - 128) * (-1) : byte3;
|
||
values["inclination_x"] = byte2 >= 128 ? (byte2 - 128) * (-1) : byte2;
|
||
values["inclination_y"] = byte1 >= 128 ? (byte1 - 128) * (-1) : byte1;
|
||
values["inclination_z"] = byte0 >= 128 ? (byte0 - 128) * (-1) : byte0;
|
||
}
|
||
|
||
//FW verzia nodu
|
||
else if (register == 89) {
|
||
//formát: "Byte3: Byte2.Byte1 (Byte0)"
|
||
values["fw_version"] = byte3 + ":" + byte2 + "." + byte1 + "(" + byte0 + ")";
|
||
}
|
||
|
||
else if (register == 87 || register == 6 || register == 7) {
|
||
var d = new Date();
|
||
d.setHours(byte3, byte2, 0, 0);
|
||
let timestamp = d.getTime();
|
||
|
||
//aktuálny čas
|
||
if (register == 87) values["actual_time"] = timestamp;
|
||
//čas súmraku
|
||
else if (register == 6) values["dusk_time"] = timestamp;
|
||
//čas úsvitu
|
||
else if (register == 7) values["dawn_time"] = timestamp;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
|
||
// we fake data, that should be received from accelerometer, as they are a bit unreliable. (temperature, x,y,z)
|
||
function accelerometerData() {
|
||
|
||
if (temperatureInSenica === null) return;
|
||
|
||
//clone nodesData and relaysData objects
|
||
let nodesData_clone = JSON.parse(JSON.stringify(nodesData));
|
||
let relaysData_clone = JSON.parse(JSON.stringify(relaysData));
|
||
|
||
for (const key in relaysData_clone) {
|
||
|
||
const lineData = relaysData_clone[key];
|
||
const lineNumber = lineData.line;
|
||
const contactor = lineData.contactor;
|
||
|
||
if (lineNumber === 0) continue;
|
||
|
||
if (contactor === 1) {
|
||
|
||
let date = Date.now();
|
||
|
||
Object.keys(nodesData_clone).forEach((node, index) => {
|
||
|
||
setTimeout(function() {
|
||
|
||
if (nodesData_clone[node].line === lineNumber) {
|
||
|
||
// NOTE: if status of luminaire is NOK or OFFLINE, we do not send data;
|
||
let status = nodesData_clone[node].status;
|
||
if (status === "OFFLINE" || !status) return;
|
||
|
||
let x = null;
|
||
if (naklony.hasOwnProperty(node)) x = naklony[node].naklon;
|
||
if (x === null) x = 0;
|
||
|
||
sendTelemetry({ temperature: Math.round(temperatureInSenica + 10 + Math.floor(Math.random() * 3)), inclination_x: x, inclination_y: 0, inclination_z: 0 }, nodesData_clone[node].tbname, date);
|
||
}
|
||
|
||
}, (index + 1) * 500);
|
||
})
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
} // end of instance.export
|
||
|