Compare commits

..

10 commits

23 changed files with 4802 additions and 1891 deletions

36
addSwitch.py Normal file
View file

@ -0,0 +1,36 @@
import os
def process_set_file():
"""
Checks if /root/flowserver exists, reads set.txt, and modifies the second line.
"""
default_folder = "/root/flowserver" if os.path.exists("/root/flowserver") else "/home/unipi/flowserver"
flag = 1 if default_folder == "/root/flowserver" else 0
try:
with open("/home/unipi/flowserver/databases/settings.table", "r") as f:
lines = f.readlines()
if len(lines) >= 2:
lines[0] = lines[0].rstrip('\n') + "|has_main_switch:boolean\n"
second_line = lines[1].strip() # remove trailing newline
last_pipe_index = second_line.rfind("|")
if last_pipe_index != -1:
modified_line = second_line[:last_pipe_index + 1] + str(flag) + "|" + second_line[last_pipe_index + 1:]
lines[1] = modified_line
else:
print("Warning: No '|' character found in the second line of set.txt")
with open("/home/unipi/flowserver/databases/settings.table", "w") as f:
f.writelines(lines)
else:
print("Warning: settings.table has less than two lines.")
except FileNotFoundError:
print("Error: settings.table not found.")
except Exception as e:
print(e)
# if __name__ == "__main__":
process_set_file()

2
config
View file

@ -7,6 +7,6 @@ package#flow (Object) : { url: '/' }
table.relays : line:number|tbname:string|contactor:number|profile:string table.relays : line:number|tbname:string|contactor:number|profile:string
table.nodes : node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number table.nodes : node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number
table.settings : rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string table.settings : rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string|has_main_switch:boolean
table.pins : pin:string|type:string|line:number table.pins : pin:string|type:string|line:number
table.notifications : key:string|weight:string|sk:string|en:string table.notifications : key:string|weight:string|sk:string|en:string

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,2 @@
node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number
+|638|rDbQ84xzwgdqEoPm3kbJQWk9anOZY1RXyBv2LVM6|3|{"intervals":[{"cct":3000,"value":20,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"05:30","start_time":"20:00"},{"cct":3000,"value":20,"end_time":"13:00","start_time":"05:30"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":-20,"dusk_astro_clock_offset":20,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|0|1725885127396|............................................................................................................................................................................................................................................................|................. +|638|rDbQ84xzwgdqEoPm3kbJQWk9anOZY1RXyBv2LVM6|3|{"intervals":[{"cct":3000,"value":20,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"05:30","start_time":"20:00"},{"cct":3000,"value":20,"end_time":"13:00","start_time":"05:30"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":-20,"dusk_astro_clock_offset":20,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|0|1725885127396|............................................................................................................................................................................................................................................................

View file

@ -20,9 +20,12 @@ key:string|weight:string|sk:string|en:string
+|power_supply_works_correctly|NOTICE|Napájací zdroj pracuje správne|Power supply works correctly|............... +|power_supply_works_correctly|NOTICE|Napájací zdroj pracuje správne|Power supply works correctly|...............
+|battery_level_is_low|ERROR|Batéria má nízku úroveň napätia|Battery level is low|............... +|battery_level_is_low|ERROR|Batéria má nízku úroveň napätia|Battery level is low|...............
+|battery_level_is_ok|NOTICE|Batéria má správnu úroveň napätia|Battery level is OK|............... +|battery_level_is_ok|NOTICE|Batéria má správnu úroveň napätia|Battery level is OK|...............
+|door_opened|NOTICE|Dvere boli otvorené|Door has been opeed|............... +|door_main_open|NOTICE|Hlavné dvere boli otvorené|Main door has been opened|...............
+|door_closed|NOTICE|Dvere boli zatvorené|Door has been closed|............... +|door_em_open|NOTICE|Dvere silovej časti boli otvorené|Power door has been opened|...............
+|door_opened_without_permission|WARNING|Dvere boli otvorené bez povolenia - zapnutá siréna|Door opened without permision - alarm is on|............... +|door_main_open_without_permission|WARNING|Hlavné dvere boli otvorené bez povolenia - zapnutá siréna|Main door has been opened without permission - alarm is on|...............
+|door_em_open_without_permission|WARNING|Dvere silovej časti boli otvorené bez povolenia|Power door has been opened without permission|...............
+|door_main_close|NOTICE|Hlavné dvere boli zatvorené|Main door has been closed|...............
+|door_em_close|NOTICE|Dvere silovej časti boli zatvorené|Power door has been closed|...............
+|state_of_contactor_for_line|INFORMATIONAL|Stav stýkača pre líniu č. ${line} je ${value}|State of contactor for line no. ${line} is ${value}|............... +|state_of_contactor_for_line|INFORMATIONAL|Stav stýkača pre líniu č. ${line} je ${value}|State of contactor for line no. ${line} is ${value}|...............
+|local_database_is_corrupted|CRITICAL|||............... +|local_database_is_corrupted|CRITICAL|||...............
+|electrometer_nok|ERROR|Elektromer neodpovedá|Electrometer is not responding|............... +|electrometer_nok|ERROR|Elektromer neodpovedá|Electrometer is not responding|...............
@ -34,5 +37,5 @@ key:string|weight:string|sk:string|en:string
+|twilight_sensor_ok|NOTICE|Sensor súmraku znovu odpovedá|Twilight sensor is responding again|............... +|twilight_sensor_ok|NOTICE|Sensor súmraku znovu odpovedá|Twilight sensor is responding again|...............
+|lamps_have_turned_on|NOTICE|Lampy sa zapli|Lamps have turned on|............... +|lamps_have_turned_on|NOTICE|Lampy sa zapli|Lamps have turned on|...............
+|lamps_have_turned_off|NOTICE|Lampy sa vypli|Lamps have turned off|............... +|lamps_have_turned_off|NOTICE|Lampy sa vypli|Lamps have turned off|...............
+|flow_restart|NOTICE|Restart flowu|Flow has been restarted|............... +|flow_restart|NOTICE|FLOW bol reštartovaný|FLOW has been restarted|...............
+|nodes_db_changed|NOTICE|Zmena v node databaze|Node db has changed|............... +|nodes_db_changed|NOTICE|Zmena v node databáze|Node db has changed|...............

View file

@ -1,2 +1,2 @@
rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string|has_main_switch:boolean
+|rvo_senica_22_ip10.0.0.109|en|28.F46E9D0E0000|48.70826502|17.28455203|192.168.252.1|rvo_senica_22_ip10.0.0.109|9excvr7yBcF3gl3kYZGY|1883|0|48|unipi|ttyUSB0|1|20|5|6|3|u109|........................................... +|rvo_senica_22_ip10.0.0.109|en|28.F46E9D0E0000|48.70826502|17.28455203|192.168.252.1|rvo_senica_22_ip10.0.0.109|9excvr7yBcF3gl3kYZGY|1883|0|48|unipi|ttyUSB0|1|20|5|6|3|u109|0|...........................................

37
databases/total_energy.js Normal file
View file

@ -0,0 +1,37 @@
//key is rvo_number, value is max energy when lamps are on
const total_energy = {
1: 580,
2: 1100,
3: 3700,
4: 4100,
7: 360,
12: 1700,
13: 5400,
14: 440,
15: 6100,
16: 4800,
20: 1600,
21: 1000,
22: 2600,
23: 1000,
25: 2600,
33: 240,
34: 4000,
35: 2700,
36: 820,
37: 1400,
35: 3500,
39: 1170,
41: 740,
42: 660,
43: 4900,
45: 930,
46: 700,
47: 1100,
48: 1500,
50: 3200,
55: 1000,
56: 5500
}
module.exports = total_energy;

File diff suppressed because it is too large Load diff

60
flow/count.js Normal file
View file

@ -0,0 +1,60 @@
exports.id = 'count';
exports.title = 'Count';
exports.version = '1.0.1';
exports.author = 'John Graves';
exports.color = '#656D78';
exports.icon = 'plus-square';
exports.input = 2;
exports.output = 1;
exports.options = { increment: 1, initialvalue: 1 };
exports.readme = `# Counter
Counter Number of times called.`;
exports.html = `<div class="padding">
<div data-jc="textbox" data-jc-path="initialvalue" data-jc-config="placeholder:1;increment:true;type:number;align:center">@(Initial Value)</div>
<div data-jc="textbox" data-jc-path="increment" data-jc-config="placeholder:1;increment:true;type:number;align:center">@(Increment)</div>
<p><a href="https://youtu.be/NuUbTm1oRE0" target="_blank">Example Video</a></p>
</div>`;
exports.readme = `# Count
This component counts the number of messages received.
__Response:__
Integer value based on the initial value and increment settings.
__Arguments:__
- Initial Value: What number should be output on the receipt of the first message.
- Increment: What should the increment be for each following message received.`;
exports.install = function(instance) {
var count = 0;
var initialCall = true;
instance.on('data', function(flowdata) {
var index = flowdata.index;
if (index) {
instance.debug('Reset Count.');
count = instance.options.initialvalue;
initialCall = true;
} else {
// If this is the first time, set the value to 'initial value'
if(initialCall) {
initialCall = false;
count = instance.options.initialvalue;
} else
count = count+instance.options.increment;
instance.status('Count:' + count);
instance.send2(count);
}
});
instance.on('options', function() {
count = instance.options.initialvalue;
initialCall = true;
});
};

View file

@ -4,103 +4,106 @@ exports.group = 'Worksys';
exports.color = '#888600'; exports.color = '#888600';
exports.version = '1.0.2'; exports.version = '1.0.2';
exports.icon = 'sign-out'; exports.icon = 'sign-out';
exports.input = 1; exports.output = 2;
exports.output = ["blue"];
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="host" data-jc-config="placeholder:test.mosquitto.org;required:false" class="m">Hostname or IP address (if not empty - setting will override db setting)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:1883;required:true" class="m">Port</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="clientid">@(Client id)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="username" class="m">@(Username)</div>
</div>
</div>
</div>`;
exports.readme = ` exports.readme = `
# DB initialization # DB initialization
`; `;
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
const { initNotification } = require('./helper/notification_reporter'); const { initNotification } = require('./helper/notification_reporter');
const errorHandler = require('./helper/ErrorToServiceHandler');
const total_energy = require('../databases/total_energy');
const SEND_TO = {
exports.install = async function(instance) { db_init: 0,
infoSender: 1
const dbNodes = TABLE("nodes");
const dbRelays = TABLE("relays");
const dbSettings = TABLE("settings");
const dbPins = TABLE("pins");
const dbNotifications = TABLE("notifications");
FLOW.GLOBALS = {};
const dbs = FLOW.GLOBALS;
const responseSettings = await promisifyBuilder(dbSettings.find());
const responseNodes = await promisifyBuilder(dbNodes.find());
const responsePins = await promisifyBuilder(dbPins.find());
const responseRelays = await promisifyBuilder(dbRelays.find());
const response = await promisifyBuilder(dbNotifications.find());
dbs.pinsData = makeMapFromDbResult(responsePins, "pin");
dbs.relaysData = makeMapFromDbResult(responseRelays, "line");
dbs.nodesData = makeMapFromDbResult(responseNodes, "node");
dbs.notificationsData = makeMapFromDbResult(response, "key");
//+|354|nodesdata.....+|482|nodesdata....
//for some reason, if last line in nodes.table is not empty, flow wrote more nodes data in one row,
//so we have to add empty line at the bottom of nodes table to avoid this.
//now, remove empty lines from nodesData database:
if(dbs.nodesData.hasOwnProperty("0")) delete dbs.nodesData["0"];
dbs.settings = {
edge_fw_version : "2025-01-02", //rok-mesiac-den
language : responseSettings[0]["lang"],
rvo_name : responseSettings[0]["rvo_name"],
project_id : responseSettings[0]["project_id"],
rvoTbName : dbs.relaysData[0]["tbname"],
temperature_address : responseSettings[0]["temperature_address"],
controller_type : responseSettings[0]["controller_type"],
serial_port : responseSettings[0]["serial_port"],
node_status_nok_time : responseSettings[0]["node_status_nok_time"] * 60 * 60 * 1000 ,// hour * minutes *
latitude : responseSettings[0]["latitude"],
longitude : responseSettings[0]["longitude"],
no_voltage : new Set(),//modbus_citysys - elektromer
backup_on_failure : responseSettings[0]["backup_on_failure"],
restore_from_backup : responseSettings[0]["restore_from_backup"],
restore_backup_wait : responseSettings[0]["restore_backup_wait"],
mqtt_host : responseSettings[0]["mqtt_host"],
mqtt_clientid : responseSettings[0]["mqtt_clientid"],
mqtt_username : responseSettings[0]["mqtt_username"],
mqtt_port : responseSettings[0]["mqtt_port"],
phases: responseSettings[0]["phases"],
cloud_topic: responseSettings[0]["cloud_topic"],
//dynamic values
masterNodeIsResponding : true, //cmd_manager
maintenance_mode : false,
}
FLOW.dbLoaded = true;
initNotification();
setTimeout(()=> {
console.log("DB_INIT - data loaded");
instance.send(0, "_")
}, 5000)
}; };
exports.install = async function(instance) {
const dbNodes = TABLE("nodes");
const dbRelays = TABLE("relays");
const dbSettings = TABLE("settings");
const dbPins = TABLE("pins");
const dbNotifications = TABLE("notifications");
FLOW.GLOBALS = {};
const dbs = FLOW.GLOBALS;
const responseSettings = await promisifyBuilder(dbSettings.find());
const responseNodes = await promisifyBuilder(dbNodes.find());
const responsePins = await promisifyBuilder(dbPins.find());
const responseRelays = await promisifyBuilder(dbRelays.find());
const response = await promisifyBuilder(dbNotifications.find());
dbs.pinsData = makeMapFromDbResult(responsePins, "pin");
dbs.relaysData = makeMapFromDbResult(responseRelays, "line");
dbs.nodesData = makeMapFromDbResult(responseNodes, "node");
dbs.notificationsData = makeMapFromDbResult(response, "key");
//+|354|nodesdata.....+|482|nodesdata....
//for some reason, if last line in nodes.table is not empty, flow wrote more nodes data in one row,
//so we have to add empty line at the bottom of nodes table to avoid this.
//now, remove empty lines from nodesData database:
if (dbs.nodesData.hasOwnProperty("0")) delete dbs.nodesData["0"];
Object.keys(dbs.nodesData).forEach(node => dbs.nodesData[node].readout = {})
dbs.settings = {
edge_fw_version: "2025-07-08", //rok-mesiac-den
language: responseSettings[0]["lang"],
rvo_name: responseSettings[0]["rvo_name"],
project_id: responseSettings[0]["project_id"],
rvoTbName: dbs.relaysData[0]["tbname"],
temperature_address: responseSettings[0]["temperature_address"],
controller_type: responseSettings[0]["controller_type"],
serial_port: responseSettings[0]["serial_port"],
node_status_nok_time: responseSettings[0]["node_status_nok_time"] * 60 * 60 * 1000,// hour * minutes *
latitude: responseSettings[0]["latitude"],
longitude: responseSettings[0]["longitude"],
no_voltage: new Set(),//modbus_citysys - elektromer
backup_on_failure: responseSettings[0]["backup_on_failure"],
restore_from_backup: responseSettings[0]["restore_from_backup"],
restore_backup_wait: responseSettings[0]["restore_backup_wait"],
mqtt_host: responseSettings[0]["mqtt_host"],
mqtt_clientid: responseSettings[0]["mqtt_clientid"],
mqtt_username: responseSettings[0]["mqtt_username"],
mqtt_port: responseSettings[0]["mqtt_port"],
phases: responseSettings[0]["phases"],
cloud_topic: responseSettings[0]["cloud_topic"],
has_main_switch: responseSettings[0]["has_main_switch"],
//dynamic values
masterNodeIsResponding: true, //cmd_manager
maintenance_mode: false,
}
let rvo_number = responseSettings[0]["rvo_name"].match(/\D+(\d{1,2})_/)[1];
dbs.settings.energy_to_switch_lamps = total_energy[rvo_number];
if (dbs.settings.energy_to_switch_lamps === undefined) console.log('=============== db_init.js: energy_to_switch_lamps is undefined');
FLOW.dbLoaded = true;
errorHandler.setProjectId(dbs.settings.project_id);
initNotification();
//APP START - send to data services
const toService = {
id: dbs.settings.project_id,
name: dbs.settings.rvo_name,
fw_version: dbs.settings.edge_fw_version,
startdate: new Date().toISOString().slice(0, 19).replace('T', ' '),
js_error: "",
error_message: ""
};
instance.send(SEND_TO.infoSender, toService);
console.log("----------------> START - message send to service", toService);
setTimeout(() => {
console.log("DB_INIT - data loaded");
instance.send(SEND_TO.db_init, "_")
}, 5000)
};

File diff suppressed because it is too large Load diff

View file

@ -34,32 +34,22 @@ Currently we are interested in pins no. 1,2,3,6,8,9,10,16
pins number 11, 12, 13 (we receive 10,11,12 in rsPortReceivedData) are "stykace" pins number 11, 12, 13 (we receive 10,11,12 in rsPortReceivedData) are "stykace"
When port receives data, it must be exactly 4 bytes long. Second byte is pin, that changed its value, fourth byte is value itself. When port receives data, it must be exactly 4 bytes long. Second byte is pin, that changed its value, fourth byte is value itself.
After that, we set this value to "previousValues[allPins[whichpin]]" variable After that, we set this value to "previousValues[allPins[whichpin]]" variable
*/
state_of_main_switch - reportovat stav hlaveho istica : 0-> off 1-> on
rotary_switch_state - sem by sa mal reportovat stav vstupov manual a auto pola nasledovnej logiky: Manual = 1 a Auto = 0 -> Manu
Manual = 0 a Auto = 0 -> Off, Manual = 0 a Auto = 1 -> Automatic
/* door_condition - pin 6, dverový kontakt -> 1 -> vyreportuje Closed, 0 -> vyreportuje Ope
RVO objekt: twilight_sensor - hodnotu, ktoru vracia ten analogovy vstup (17) treba poslat sem ako float number. Zrejme tu potom pridame nejaky koeficient prevodu na luxy
state_of_main_switch - sem sa bude reportovať stav hlavného ističa : 0-> off 1-> on (toto nie je na platforme, ale Rado to do entity type doplnil)
rotary_switch_state - sem by sa mal reportovať stav vstupov manual a auto podľa nasledovnej logiky:
Manual = 1 a Auto = 0 -> vyreportuje Manual
Manual = 0 a Auto = 0 -> vyreportuje Off
Manual = 0 a Auto = 1 -> vyreportuje Automatic
door_condition - tuto ide pin 6, dverový kontakt -> 1 -> vyreportuje Closed, 0 -> vyreportuje Open Na kazdu liniu
twilight_sensor - hodnotu, ktorú vracia ten analógový vstup (17) treba poslať sem ako float number. Zrejme tu potom pridáme nejaký koeficient prevodu na luxy state_of_breaker - podla indexu istica sa reportuje jeho stav, teda istic na liniu 1: 0-> off, 1-> on
state_of_contactor - podla indexu stkaca sa reportuje jeho stav, teda stykac 1 na liniu 1: 0-> off, 1-> on
zjavne nám v jsone chýba stav hlavného ističa. Musíme to potom doplniť
Na každú líniu:
state_of_breaker - podľa indexu ističa sa reportuje jeho stav, teda istič 1 na líniu 1: 0-> off 1-> on
state_of_contactor - podľa indexu stykača sa reportuje jeho stav, teda stykač 1 na líniu 1: 0-> off 1-> on
momentálne sa stav zmení len keď vo flow klikneš aby sa zmenil, ale zmena by sa mala ukázať aj na platforme
*/ */
const { errLogger, logger, monitor } = require('./helper/logger'); const { errLogger, logger, monitor } = require('./helper/logger');
const SerialPort = require('serialport'); const SerialPort = require('serialport');
const WebSocket = require('ws'); const WebSocket = require('ws');
//const { exec } = require('child_process');
const { runSyncExec } = require('./helper/serialport_helper'); const { runSyncExec } = require('./helper/serialport_helper');
const { bytesToInt, resizeArray } = require('./helper/utils'); const { bytesToInt, resizeArray } = require('./helper/utils');
const { sendNotification } = require('./helper/notification_reporter'); const { sendNotification } = require('./helper/notification_reporter');
@ -68,8 +58,7 @@ const bitwise = require('bitwise');
const DataToTbHandler = require('./helper/DataToTbHandler'); const DataToTbHandler = require('./helper/DataToTbHandler');
let tbHandler; let tbHandler;
const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler'); const errorHandler = require('./helper/ErrorToServiceHandler');
const errorHandler = new ErrorToServiceHandler();
let ws = null; let ws = null;
let rsPort = null; let rsPort = null;
@ -80,6 +69,7 @@ let rvoTbName;
let GLOBALS; //FLOW global GLOBALS let GLOBALS; //FLOW global GLOBALS
let SETTINGS; // GLOBALS.settings let SETTINGS; // GLOBALS.settings
let controller_type; let controller_type;
let hasMainSwitch;
let alarmStatus = "OFF"; let alarmStatus = "OFF";
@ -107,7 +97,7 @@ exports.install = function(instance) {
let previousValues = {}; let previousValues = {};
let rsPortReceivedData = []; let rsPortReceivedData = [];
//to be able to get proper twilight values, when //to be able to get proper twilight values
let twilight_sensor_interval = 5;//minutes let twilight_sensor_interval = 5;//minutes
let twilight_sensor = []; let twilight_sensor = [];
const twilight_sensor_array = []; const twilight_sensor_array = [];
@ -134,21 +124,22 @@ exports.install = function(instance) {
}; };
*/ */
//status for calculating Statecodes //status for calculating Statecodes-we make it global to see it from outside
let deviceStatus = { //key is device name: temperature,.... FLOW.deviceStatus = { //key is device name: temperature,....
"state_of_main_switch": "Off", //Hlavný istič "state_of_main_switch": "Off", //Hlavny istic (alebo druhy dverovy kontakt)
"rotary_switch_state": "Off", //Prevádzkový mód "rotary_switch_state": "Off", //Prevadzkovy
"door_condition": "closed", //Dverový kontakt "door_condition": "closed", //Dverový kontakt
"em": "OK", //elektromer rvo "em": "OK", //elektromer rvo
"temperature": "OK", //templomer "temperature": "OK", //templomer
"battery": "OK", //Batéria "battery": "OK", //Bateria
"power_supply": "OK", //Zdroj "power_supply": "OK", //Zdroj
"master_node": "OK", //MN - GLOBALS.settings.masterNodeIsResponding "master_node": "OK", //MN - GLOBALS.settings.masterNodeIsResponding
"no_voltage": "OK", //GLOBALS.settings.no_voltage - výpadok napätia na fáze "no_voltage": "OK", //GLOBALS.settings.no_voltage - vypadok napatia na faze
"state_of_breaker": {}, //"Off",//Istič "state_of_breaker": {}, //"Off",//Istic
"state_of_contactor": {}, //"Off",//Stykač "state_of_contactor": {}, //"Off",//Stykac
"twilight_sensor": "OK" //lux sensor "twilight_sensor": "OK" //lux sensor
}; };
let deviceStatus = FLOW.deviceStatus;
function main() { function main() {
@ -159,10 +150,12 @@ exports.install = function(instance) {
pinsData = GLOBALS.pinsData; pinsData = GLOBALS.pinsData;
relaysData = GLOBALS.relaysData; relaysData = GLOBALS.relaysData;
tbHandler = new DataToTbHandler(SEND_TO.tb) tbHandler = new DataToTbHandler(SEND_TO.tb);
tbHandler.setSender(exports.title); tbHandler.setSender(exports.title);
controller_type = SETTINGS.controller_type //"lm" or "unipi" //logicMachine controller_type = SETTINGS.controller_type; //"lm" or "unipi"
hasMainSwitch = SETTINGS.has_main_switch;
if (controller_type == "") controller_type = "lm"; if (controller_type == "") controller_type = "lm";
console.log(exports.title, "controller type: ", controller_type); console.log(exports.title, "controller type: ", controller_type);
@ -201,10 +194,6 @@ exports.install = function(instance) {
if (pinsData[key].type == "state_of_contactor") { if (pinsData[key].type == "state_of_contactor") {
let pin = key - 1; let pin = key - 1;
if (controller_type === "unipi") pin = key; if (controller_type === "unipi") pin = key;
//this will modify database
let forceTurnOff = true;
turnLine("off", line, pin, forceTurnOff, "turn off on startup");
} }
} }
@ -215,29 +204,25 @@ exports.install = function(instance) {
sendTelemetry(values, rvoTbName); sendTelemetry(values, rvoTbName);
let time = 5 * 1000; instance.send(SEND_TO.cmd_manager, { sender: "dido_controller", cmd: "buildTasks" });
setTimeout(function() {
instance.send(SEND_TO.cmd_manager, { sender: "dido_controller", cmd: "buildTasks" });
sendNotification("rsPort.open()", rvoTbName, "flow_start", {}, "", SEND_TO.tb, instance); sendNotification("rsPort.open()", rvoTbName, "flow_start", {}, "", SEND_TO.tb, instance);
monitor.info("-->FLOW bol spustený", rvoTbName, SETTINGS.edge_fw_version); monitor.info("-->FLOW bol spustený", rvoTbName, SETTINGS.edge_fw_version);
}, time);
} }
function handleRsPort() { function handleRsPort() {
if (rsPort) {
rsPort.removeAllListeners();
rsPort = null;
}
//TODO build according to pins!!! //TODO build according to pins!!!
//! rsPort to open are the same for lm and unipi and electromer ("/dev/ttymxc0") //! rsPort to open are the same for lm and unipi and electromer ("/dev/ttymxc0")
const setRSPortData = [0xAA, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 1, 1, 1, 1, 0, 0, 10, 10, 10, 10, 10, 10, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0]; const setRSPortData = [0xAA, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 1, 1, 1, 1, 0, 0, 10, 10, 10, 10, 10, 10, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0];
rsPort = new SerialPort("/dev/ttymxc0", { autoOpen: false }); rsPort = new SerialPort("/dev/ttymxc0", { autoOpen: false });
rsPort.on('error', function(err) {
logger.debug("rsPort opened error - failed", err.message);
instance.send(SEND_TO.debug, err.message);
errorHandler.sendMessageToService(exports.title + " rsPort opened error - failed: " + err.message);
})
rsPort.on('open', async function() { rsPort.on('open', async function() {
await runSyncExec("stty -F /dev/ttymxc0 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke").then(function(status) { await runSyncExec("stty -F /dev/ttymxc0 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke").then(function(status) {
@ -298,8 +283,18 @@ exports.install = function(instance) {
}); });
rsPort.on('error', err => {
let message = "Dido: rsPort error: " + err.message;
logger.debug(message);
monitor.info(message);
errorHandler.sendMessageToService(message);
})
rsPort.on("close", () => { rsPort.on("close", () => {
rsPort.close(); let message = "Dido: rsPort closed - reconnecting ...";
logger.debug(message);
monitor.info(message);
setTimeout(handleRsPort, 1000);
}) })
rsPort.open(); rsPort.open();
@ -308,6 +303,11 @@ exports.install = function(instance) {
function handleWebSocket() { function handleWebSocket() {
if (ws) {
ws.removeAllListeners();
ws = null;
}
//to keep websocket opened, we send request every 150 seconds //to keep websocket opened, we send request every 150 seconds
let startRequests = null; let startRequests = null;
@ -319,16 +319,12 @@ exports.install = function(instance) {
instance.send(0, exports.title + " running"); instance.send(0, exports.title + " running");
turnAlarm("off"); turnAlarm("off");
// useTurnOffCounter = true;
// turnOffCounter = relaysData.length - 1;
initialSetting(); initialSetting();
ws.send(JSON.stringify({ "cmd": "all" }));
setTimeout(function() { ws.send(JSON.stringify({ cmd: "all" })) }, 5000);
// we request dev info about neuron device from evok to keep websocket connection alive // we request dev info about neuron device from evok to keep websocket connection alive
// for some reason this request returns no data, but connection keeps alive // for some reason this request returns no data, but connection keeps alive
// https://evok.api-docs.io/1.0/mpqzDwPwirsoq7i5A/websocket
startRequests = setInterval(() => { startRequests = setInterval(() => {
// console.log(" *** data from evok requested");
ws.send(JSON.stringify({ "cmd": "filter", "dev": ["neuron"] })); ws.send(JSON.stringify({ "cmd": "filter", "dev": ["neuron"] }));
}, 150000) }, 150000)
}; };
@ -375,23 +371,13 @@ exports.install = function(instance) {
}) })
} }
ws.on('error', err => {
ws.on('error', (err) => { logger.debug('Dido: websocket error', err);
monitor.info('websocket error, reconnect')
instance.send(SEND_TO.debug, err.message);
clearInterval(startRequests);
ws = null;
setTimeout(handleWebSocket, 1000);
}) })
ws.onclose = function() { ws.onclose = function() {
// connection closed, discard old websocket and create a new one in 5s logger.debug('Dido: websocket onclose, reconnecting...')
// stopRequests();
monitor.info('websocket onclose, reconnect')
clearInterval(startRequests); clearInterval(startRequests);
ws = null;
console.log("ws is null now, reconnecting...");
setTimeout(handleWebSocket, 1000); setTimeout(handleWebSocket, 1000);
} }
} }
@ -401,7 +387,6 @@ exports.install = function(instance) {
if (ws) ws.close(); if (ws) ws.close();
}) })
function getPin(line) { function getPin(line) {
//conversionTable //conversionTable
let keys = Object.keys(pinsData); let keys = Object.keys(pinsData);
@ -502,14 +487,14 @@ exports.install = function(instance) {
if (!force) { if (!force) {
if (relaysData[line].contactor == value) { if (relaysData[line].contactor == value) {
instance.send(SEND_TO.debug, `line is already ${onOrOff} ` + line); instance.send(SEND_TO.debug, `line is already ${onOrOff} ` + line);
logger.debug(`turnLine: line is already ${onOrOff} `, line); logger.debug(`Dido: turnLine: line is already ${onOrOff} `, line);
return; return;
} }
} }
// if(!rsPort.isOpen && !ws) // if(!rsPort.isOpen && !ws)
if (!rsPort && !ws) { if (!rsPort && !ws) {
errLogger.error("dido controller - port or websocket is not opened"); errLogger.error("Dido - port or websocket is not opened");
return; return;
} }
@ -521,21 +506,20 @@ exports.install = function(instance) {
rsPort.write(Buffer.from(arr), function(err) { rsPort.write(Buffer.from(arr), function(err) {
if (err === undefined) { if (err === undefined) {
monitor.info(`turnLine ${onOrOff} zapisal do rsPort-u`, line, pin, arr, info); monitor.info(`Dido: turnLine ${onOrOff} zapisal do rsPort-u`, line, pin, arr, info);
switchLogic(arr); switchLogic(arr);
} }
else { else {
monitor.info(`turnLine ${onOrOff} WRITE error`, err); monitor.info(`Dido: turnLine ${onOrOff} WRITE error`, err);
} }
}); });
} }
else if (ws) { else if (ws) {
//pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method //pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method
monitor.info(`turnLine ${onOrOff} - (line, pin, force)`, line, pin, force, info); monitor.info(`Dido: turnLine ${onOrOff} - (line, pin, force)`, line, pin, force, info);
let cmd = { "cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": value }; let cmd = { "cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": value };
ws.send(JSON.stringify(cmd)); ws.send(JSON.stringify(cmd));
switchLogic(pin, value)
} }
//if rvo is 24/7, it has just one switching profile point at 13:00. we do not want to send notification as it repeats every day. //if rvo is 24/7, it has just one switching profile point at 13:00. we do not want to send notification as it repeats every day.
@ -593,10 +577,12 @@ exports.install = function(instance) {
}) })
// we expect array as flowdata.data // we expect array as flowdata.data
instance.on("1", flowdata => { instance.on("1", flowdata => {
console.log(flowdata.data); //console.log(flowdata.data);
if (!flowdata.data instanceof Object) return; if (!flowdata.data instanceof Object) return;
@ -609,17 +595,6 @@ exports.install = function(instance) {
else if (obj.command == "off") turnLine("off", line, undefined, force, info); else if (obj.command == "off") turnLine("off", line, undefined, force, info);
else if (obj.command == "turnOnAlarm") turnAlarm("on"); else if (obj.command == "turnOnAlarm") turnAlarm("on");
else if (obj.command == "turnOffAlarm") turnAlarm("off"); else if (obj.command == "turnOffAlarm") turnAlarm("off");
//! ake data prichadzaju z cmd_manager.js ???
//TODO transform to websocket
if (Array.isArray(obj)) {
rsPort.write(Buffer.from(obj), function(err) {
switchLogic(obj);
instance.send(SEND_TO.debug, { "WRITE": obj });
});
}
}) })
@ -627,27 +602,27 @@ exports.install = function(instance) {
let bits = []; let bits = [];
//Hlavný istič - state_of_main_switch => v rvo senica je to druhy door pre silovu cast (EM) //Hlavny istic - state_of_main_switch => v rvo senica je to druhy door pre silovu cast (EM)
if (deviceStatus["state_of_main_switch"] == "closed") { if (deviceStatus["state_of_main_switch"] === "closed" || deviceStatus["state_of_main_switch"] === "Off") {
bits.push(0); bits.push(0);
} }
else { else {
bits.push(1); bits.push(1);
} }
//Prevádzkový mód - Manual, Off, Automatic, maintenance_mode = true/false // DAVA 2 BITY //Prevadzkovy mod - Manual, Off, Automatic, maintenance_mode = true/false // DAVA 2 BITY
if (!SETTINGS.maintenance_mode) { if (!SETTINGS.maintenance_mode) {
if (deviceStatus["rotary_switch_state"] == "Manual") { if (deviceStatus["rotary_switch_state"] === "Manual") {
bits.push(0); bits.push(0);
bits.push(1); bits.push(1);
} }
if (deviceStatus["rotary_switch_state"] == "Automatic") { if (deviceStatus["rotary_switch_state"] === "Automatic") {
bits.push(0); bits.push(0);
bits.push(0); bits.push(0);
} }
if (deviceStatus["rotary_switch_state"] == "Off") { if (deviceStatus["rotary_switch_state"] === "Off") {
bits.push(1); bits.push(1);
bits.push(0); bits.push(0);
} }
@ -657,8 +632,8 @@ exports.install = function(instance) {
bits.push(1); bits.push(1);
} }
//Dverový kontakt //Dverovy kontakt
if (deviceStatus["door_condition"] == "closed") { if (deviceStatus["door_condition"] === "closed") {
bits.push(0); bits.push(0);
} }
else { else {
@ -666,7 +641,7 @@ exports.install = function(instance) {
} }
//EM //EM
if (deviceStatus["em"] == "NOK") { if (deviceStatus["em"] === "NOK") {
bits.push(1); bits.push(1);
} }
else { else {
@ -674,7 +649,7 @@ exports.install = function(instance) {
} }
//Teplomer //Teplomer
if (deviceStatus["temperature"] == "NOK") { if (deviceStatus["temperature"] === "NOK") {
bits.push(1); bits.push(1);
} }
else { else {
@ -682,7 +657,7 @@ exports.install = function(instance) {
} }
//Batéria //Batéria
if (deviceStatus["battery"] == "NOK") { if (deviceStatus["battery"] === "NOK") {
bits.push(1); bits.push(1);
} }
else { else {
@ -690,7 +665,7 @@ exports.install = function(instance) {
} }
//Zdroj //Zdroj
if (deviceStatus["power_supply"] == "NOK") { if (deviceStatus["power_supply"] === "NOK") {
bits.push(1); bits.push(1);
} }
else { else {
@ -698,7 +673,7 @@ exports.install = function(instance) {
} }
//MN //MN
if (deviceStatus["master_node"] == "NOK") { if (deviceStatus["master_node"] === "NOK") {
bits.push(1); bits.push(1);
} }
else { else {
@ -706,14 +681,14 @@ exports.install = function(instance) {
} }
//výpadok napätia na fáze //výpadok napätia na fáze
if (deviceStatus["no_voltage"] == "NOK") { if (deviceStatus["no_voltage"] === "NOK") {
bits.push(1); bits.push(1);
} }
else { else {
bits.push(0); bits.push(0);
} }
if (deviceStatus["twilight_sensor"] == "NOK") { if (deviceStatus["twilight_sensor"] === "NOK") {
bits.push(1); bits.push(1);
} }
else { else {
@ -769,6 +744,26 @@ exports.install = function(instance) {
} }
function pinsForRvoStatus(controllerType, hasMainSwitch) {
let pins = [];
if (controllerType === "lm") {
pins = [1, 4, 6];
if (hasMainSwitch === 1) {
pins = [4, 6];
}
} else if (controllerType === "unipi") {
pins = ["input1_01", "input1_04", "input1_05"];
if (hasMainSwitch === 1) {
pins = ["input1_01", "input1_04"];
}
}
return pins;
}
function checkRvoStatus() { function checkRvoStatus() {
// we check if any of these pins values are 0 --> it means status RVO is "NOK" // we check if any of these pins values are 0 --> it means status RVO is "NOK"
@ -777,12 +772,12 @@ exports.install = function(instance) {
let status = "OK"; let status = "OK";
for (const [key, value] of Object.entries(deviceStatus)) { for (const [key, value] of Object.entries(deviceStatus)) {
if (["em", "twilight_sensor", "temperature", "master_node"].includes(key) && value == "NOK") status = "NOK"; if (["em", "twilight_sensor", "temperature", "master_node"].includes(key) && value === "NOK") status = "NOK";
} }
if (status == "OK") { if (status === "OK") {
let pinIndexes = [1, 4, 6];
if (controller_type == 'unipi') pinIndexes = ['input1_01', 'input1_04', 'input1_05']; let pinIndexes = pinsForRvoStatus(controller_type, hasMainSwitch);
for (const pinIndex of pinIndexes) { for (const pinIndex of pinIndexes) {
if (previousValues[pinIndex] === 0) { if (previousValues[pinIndex] === 0) {
@ -838,28 +833,20 @@ exports.install = function(instance) {
let value = "On"; let value = "On";
if (newPinValue === 0) value = "Off"; if (newPinValue === 0) value = "Off";
//Hlavný istič //Hlavny istic
//! po novom uz 'state of main switch' nemame. Namiesto neho je 'door_condition', kedze mame dvoje dveri if (type === "state_of_main_switch" && hasMainSwitch) {
//! takze ked pride z evoku signal pre 'input1_05', handlujeme ho ako 'door_condition' if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) {
// if(type === "!!!state_of_main_switch") sendNotification("switchLogic", rvoTbName, "main_switch_has_been_turned_off", {}, "", SEND_TO.tb, instance, "state_of_main_switch");
// { deviceStatus["state_of_main_switch"] = "Off";
// if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) }
// { else if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) {
// sendNotification("switchLogic", rvoTbName, "main_switch_has_been_turned_off", {}, "", SEND_TO.tb, instance , "state_of_main_switch"); sendNotification("switchLogic", rvoTbName, "main_switch_has_been_turned_on", {}, "", SEND_TO.tb, instance, "state_of_main_switch");
// values["status"] = "NOK"; deviceStatus["state_of_main_switch"] = "On";
}
}
// deviceStatus["state_of_main_switch"] = "Off"; //Prevadzkovy mod
// } else if (type == "rotary_switch_state") {
// else if (newPinValue === 1 && newPinValue !== previousValues[pinIndex])
// {
// sendNotification("switchLogic", rvoTbName, "main_switch_has_been_turned_on", {}, "", SEND_TO.tb, instance , "state_of_main_switch");
// deviceStatus["state_of_main_switch"] = "On";
// }
// }
//Prevádzkový mód
if (type == "rotary_switch_state") {
// combination of these two pins required to get result // combination of these two pins required to get result
let pin2, pin3; let pin2, pin3;
if (pinIndex == 2 || pinIndex == "input1_02") { if (pinIndex == 2 || pinIndex == "input1_02") {
@ -911,7 +898,7 @@ exports.install = function(instance) {
} }
} }
//Batéria - pin 5 //Bateria - pin 5
else if (type === "battery") { else if (type === "battery") {
if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) { if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) {
sendNotification("switchLogic", rvoTbName, "battery_level_is_low", {}, "", SEND_TO.tb, instance, "battery_level"); sendNotification("switchLogic", rvoTbName, "battery_level_is_low", {}, "", SEND_TO.tb, instance, "battery_level");
@ -925,27 +912,30 @@ exports.install = function(instance) {
} }
} }
//Dverový kontakt - pin 6 //Dverovy kontakt - pin 6
//! Po novom mame dva dverove kontakty, nie jeden. Druhy je teraz tam, kde bol digital input "state_of_main_switch" //! Ak je rvo s dvoma dverovymi kontaktami, ked pride z evoku signal z input1_05, co bol predytm "state_of_main switch" handlujeme ho teraz ako 'door_condition'
//! preto ked pride z evoku signal z input1_05, co bol predytm "main switch" handlujeme ho teraz ako 'door_condition'
else if (type == "door_condition" || type === "state_of_main_switch") { else if (type == "door_condition" || type === "state_of_main_switch") {
newPinValue === 0 ? value = "open" : value = "closed"; newPinValue === 0 ? value = "open" : value = "closed";
if (value === "open" && SETTINGS.maintenance_mode) { let door = "door_main";
sendNotification("switchLogic", rvoTbName, "door_opened", {}, "", SEND_TO.tb, instance, "rvo_door"); if (type === "state_of_main_switch") door = "door_em";
if (value === "open") {
if (SETTINGS.maintenance_mode) {
sendNotification("switchLogic", rvoTbName, door + "_open", {}, "", SEND_TO.tb, instance, door);
} else {
sendNotification("switchLogic", rvoTbName, door + "_open_without_permission", {}, "", SEND_TO.tb, instance, door);
// zapneme sirenu
// ak sa otvoria dvere len na elektromeri (type === "state_of_main_switch") alarm sa nema spustit. alarm sa spusti len ked sa otvoria hlavne dvere (type === "door_condition")
if (type === "door_condition") turnAlarm("on");
}
} }
if (value === "open" && !SETTINGS.maintenance_mode) {
sendNotification("switchLogic", rvoTbName, "door_opened_without_permission", {}, "", SEND_TO.tb, instance, "rvo_door");
// zapneme sirenu
// ak sa otvoria dvere len na elektromeri (type === "state_of_main_switch") alarm sa nema spustit. alarm sa spusti len ked sa otvoria hlavne dvere (type === "door_condition")
if (type === "door_condition") turnAlarm("on");
}
if (value === "closed") { if (value === "closed") {
if (alarmStatus == "ON") turnAlarm("off"); if (alarmStatus == "ON") turnAlarm("off");
sendNotification("switchLogic", rvoTbName, "door_closed", {}, "", SEND_TO.tb, instance, "rvo_door"); sendNotification("switchLogic", rvoTbName, door + "_close", {}, "", SEND_TO.tb, instance, door);
} }
deviceStatus[type] = value; deviceStatus[type] = value;
@ -1028,34 +1018,6 @@ exports.install = function(instance) {
instance.send(SEND_TO.cmd_manager, { sender: "dido_controller", cmd: "reload_relays", line: line, value: value, dataChanged: dataChanged }); instance.send(SEND_TO.cmd_manager, { sender: "dido_controller", cmd: "reload_relays", line: line, value: value, dataChanged: dataChanged });
reportLineStatus(line); reportLineStatus(line);
//modify table relays
// dbRelays.modify({ contactor: newPinValue }).where("line", line).make(function(builder) {
// builder.callback(function(err, response) {
// if(!err)
// {
// let time = 0;
// if(value) time = 1000 * 10;//10 sekund
// let dataChanged = false;
// if(relaysData[line].contactor != newPinValue) dataChanged = true;
// relaysData[line].contactor = newPinValue; // 0,1
// //ak bola predchadzajuci stav off a novy stav je on, budu sa nastavovat nespracovane node profiles
// //a budu sa odosielat commandy, tie vsak mozu zlyhat, a preto potrebujeme ich spusti trochu neskor
// setTimeout(function(){
// instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "reload_relays", line: line, time: time, value: value, dataChanged: dataChanged});
// }, time);
// reportLineStatus(line);
// }
// else
// {
// errLogger.error("modify table relays failed", err);
// }
// });
// });
} }
else if (type === "state_of_breaker") { else if (type === "state_of_breaker") {

View file

@ -4,15 +4,14 @@ class DataToTbHandler {
this.index = index; this.index = index;
// time, after new value for the given key will be resend to tb (e.g. {status: "OK"}) // time, after new value for the given key will be resend to tb (e.g. {status: "OK"})
this.timeToHoldTbValue = 30*60; //30 minutes this.timeToHoldTbValue = 30 * 60; //30 minutes
this.previousValues = {}; this.previousValues = {};
this.debug = false; this.debug = false;
this.messageCounter = 0; this.messageCounter = 0;
this.itIsNodeReadout = false;
this.sender = ""; this.sender = "";
// if attribute difference is less than limit value, we do not send to tb. // if attribute change difference is less than limit value, we do not send to tb.
this.attributeChangeLimit = { this.attributeChangeLimit = {
temperature: 0.5, temperature: 0.5,
Phase_1_voltage: 2, Phase_1_voltage: 2,
@ -25,11 +24,12 @@ class DataToTbHandler {
Phase_2_power: 2, Phase_2_power: 2,
Phase_3_power: 2, Phase_3_power: 2,
total_power: 2, total_power: 2,
total_energy: 1,
Phase_1_pow_factor: 0.1, Phase_1_pow_factor: 0.1,
Phase_2_pow_factor: 0.1, Phase_2_pow_factor: 0.1,
Phase_3_pow_factor: 0.1, Phase_3_pow_factor: 0.1,
power_factor: 0.1, power_factor: 0.1,
lifetime: 0.5, lifetime: 2,
voltage: 2, voltage: 2,
power: 2, power: 2,
frequency: 3, frequency: 3,
@ -39,6 +39,7 @@ class DataToTbHandler {
inclination_y: 10, inclination_y: 10,
inclination_z: 10 inclination_z: 10
}; };
} }
dump() { dump() {
@ -52,41 +53,43 @@ class DataToTbHandler {
} }
isEmptyObject(obj) { isEmptyObject(obj) {
for (var name in obj) { for (var _ in obj) {
return false; return false;
} }
return true; return true;
} }
sendToTb(dataToTb, instance) {
let keys = Object.keys(dataToTb); sendToTb(data, instance) {
if(keys.length == 0) //not to modify data object, we do deep copy:
{ let dataCopy = JSON.parse(JSON.stringify(data));
if(this.debug) console.log("sendToTb received empty object", dataToTb);
let keys = Object.keys(dataCopy);
if (keys.length == 0) {
if (this.debug) console.log("sendToTb received empty object", dataCopy);
return; return;
} }
let tbname = keys[0]; let tbname = keys[0];
let ts; let ts;
let arrayOfValues = dataToTb[tbname]; let arrayOfValues = dataCopy[tbname];
let arrayOfValuesToSend = []; let arrayOfValuesToSend = [];
for(let i = 0; i < arrayOfValues.length; i++) for (let i = 0; i < arrayOfValues.length; i++) {
{
ts = arrayOfValues[i].ts; ts = arrayOfValues[i].ts;
let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values); let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values);
if(!this.isEmptyObject(values)) if (!this.isEmptyObject(values)) {
{ arrayOfValuesToSend.push({ ts: ts, values: values });
arrayOfValuesToSend.push({ts: ts, values: values});
} }
} }
if(arrayOfValuesToSend.length == 0) if (arrayOfValuesToSend.length == 0) {
{
//if(this.debug) console.log("data not sent - empty array"); //if(this.debug) console.log("data not sent - empty array");
return; return;
} }
@ -94,7 +97,7 @@ class DataToTbHandler {
this.messageCounter++; this.messageCounter++;
let dataToTbModified = { let dataToTbModified = {
[tbname]: arrayOfValuesToSend [tbname]: arrayOfValuesToSend
} }
//console.log(this.sender + " DATA SEND TO TB ", tbname, this.messageCounter, new Date(ts), dataToTbModified[tbname][0].values, this.instance); //console.log(this.sender + " DATA SEND TO TB ", tbname, this.messageCounter, new Date(ts), dataToTbModified[tbname][0].values, this.instance);
@ -102,56 +105,74 @@ class DataToTbHandler {
instance.send(this.index, dataToTbModified); instance.send(this.index, dataToTbModified);
} }
getDiffTimestamp(key) { getDiffTimestamp(key) {
//TODO set different value for given key!!! //TODO set different value for given key!!!
//if(key == "status") this.timeToHoldTbValue = 2*60*60;//2h //if(key == "status") this.timeToHoldTbValue = 2*60*60;//2h
return this.timeToHoldTbValue * 1000; return this.timeToHoldTbValue * 1000;
} }
prepareValuesForTb(tbname, timestamp, values) { prepareValuesForTb(tbname, timestamp, values) {
let keys = Object.keys(values); let keys = Object.keys(values);
if(!this.previousValues.hasOwnProperty(tbname))
{ if (keys.includes("lifetime")) this.itIsNodeReadout = true;
if (!this.previousValues.hasOwnProperty(tbname)) {
this.previousValues[tbname] = {}; this.previousValues[tbname] = {};
} }
//if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values); //if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values);
for(let i = 0; i < keys.length; i++) for (let i = 0; i < keys.length; i++) {
{
let key = keys[i]; let key = keys[i];
let value = values[key]; let value = values[key];
if(!this.previousValues[tbname].hasOwnProperty(key)) if (!this.previousValues[tbname].hasOwnProperty(key)) {
{ this.previousValues[tbname][key] = { ts: timestamp, value: value };
this.previousValues[tbname][key] = {ts: timestamp, value: value};
continue; continue;
} }
// attributeData ==> voltage: {ts:333333, value:5} // attributeData ==> {voltage: {ts:333333, value:5}}
let attributeData = this.previousValues[tbname][key]; let attributeData = this.previousValues[tbname][key];
let attributeToChange = false; let attributeToChange = false;
if(key in this.attributeChangeLimit) attributeToChange = true; if (key in this.attributeChangeLimit) attributeToChange = true;
let limit = this.attributeChangeLimit[key]; let limit = this.attributeChangeLimit[key];
let timestampDiffToRemoveKey;
//this will ensure "node statecode" will be sent just once an hour
if (this.itIsNodeReadout && key === "statecode") {
attributeData.value = value;
this.itIsNodeReadout = false;
timestampDiffToRemoveKey = 1 * 60 * 60 * 1000; // 1 hour
}
if (key === "twilight_sensor" && value > 100) {
attributeData.value = value;
}
//if edge, master or node version do not change, send just once a day:
if (["edge_fw_version", "master_node_version", "fw_version"].includes(key)) {
timestampDiffToRemoveKey = 24 * 60 * 60 * 1000;
}
if (attributeData.value === value || attributeToChange && Math.abs(attributeData.value - value) < limit) {
if(attributeData.value === value || attributeToChange && Math.abs(attributeData.value - value) < limit)
{
let diff = timestamp - attributeData.ts; let diff = timestamp - attributeData.ts;
let timestampDiffToRemoveKey = this.getDiffTimestamp(key); if (!timestampDiffToRemoveKey) timestampDiffToRemoveKey = this.getDiffTimestamp(key);
if(diff > timestampDiffToRemoveKey)
{ if (diff > timestampDiffToRemoveKey) {
attributeData.ts = Date.now(); attributeData.ts = Date.now();
//if(this.debug) console.log(this.sender + ": update ts for key", key, "diff is", diff, "messageCounter", this.messageCounter); //if(this.debug) console.log(this.sender + ": update ts for key", key, "diff is", diff, "messageCounter", this.messageCounter);
} }
else else {
{
delete values[key]; delete values[key];
//if(this.debug) console.log(this.sender + ": delete key", key, "diff is", diff, "messageCounter", this.messageCounter, timestampDiffToRemoveKey); //if(this.debug) console.log(this.sender + ": delete key", key, "diff is", diff, "messageCounter", this.messageCounter, timestampDiffToRemoveKey);
} }
} }
else else {
{
attributeData.value = value; attributeData.value = value;
attributeData.ts = timestamp; attributeData.ts = timestamp;
} }
@ -162,5 +183,5 @@ class DataToTbHandler {
} }
} }
module.exports = DataToTbHandler; module.exports = DataToTbHandler;

View file

@ -1,126 +1,91 @@
const { MD5 } = require('./md5.js'); const { MD5 } = require('./md5.js');
const { networkInterfaces } = require('os'); const { networkInterfaces } = require('os');
class ErrorToServiceHandler class ErrorToServiceHandler {
{
constructor() { constructor() {
this.previousValues = {}; this.previousValues = {};
this.projects_id = undefined; this.project_id = undefined;
const nets = networkInterfaces(); const nets = networkInterfaces();
this.ipAddresses = Object.create(null); // Or just '{}', an empty object this.ipAddresses = {};
for (const name of Object.keys(nets)) { for (const name of Object.keys(nets)) {
for (const net of nets[name]) { for (const net of nets[name]) {
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) { if (net.family === 'IPv4' && !net.internal) {
if (!this.ipAddresses[name]) { if (!this.ipAddresses[name]) {
this.ipAddresses[name] = []; this.ipAddresses[name] = [];
} }
this.ipAddresses[name].push(net.address); this.ipAddresses[name].push(net.address);
} }
} }
} }
//console.log(this.ipAddresses);
} }
setProjectsId(projects_id) setProjectId(project_id) {
{ this.project_id = project_id;
this.projects_id = projects_id;
} }
processMessage(message, seconds, message_type) processMessage(message, seconds) {
{ if (Array.isArray(message)) message = message.join(', ');
if(message_type == undefined) message_type = "error_message";
if(Array.isArray(message)) message = message.join(', ');
let key = MD5(message);
let timestamp = new Date().getTime();
//keep in memory - default value is 1h
if (seconds === undefined) seconds = 60*60;
if(!this.previousValues.hasOwnProperty(key))
{
this.previousValues[key] = {ts: timestamp, duration: seconds};
}
let diff = (timestamp - this.previousValues[key].ts);
if(diff < this.previousValues[key].duration*1000) return false;
this.previousValues[key].ts = timestamp;
return true;
}
sendMessageToService(message, seconds, message_type)
{
let f = this.processMessage(message, seconds, message_type);
if(!f) return;
/*
//-------------
if(message_type == undefined) message_type = "error_message";
if(Array.isArray(message)) message = message.join(', ');
let key = MD5(message); let key = MD5(message);
let timestamp = new Date().getTime(); let ts = Date.now();
//keep in memory //keep in memory - default value is 1h
if (seconds === undefined) seconds = 60*60; if (seconds === undefined) seconds = 60 * 60;
if(!this.previousValues.hasOwnProperty(key)) if (!this.previousValues.hasOwnProperty(key)) {
{ this.previousValues[key] = { ts: ts, duration: seconds };
this.previousValues[key] = {ts: timestamp, duration: seconds};
} }
let diff = (timestamp - this.previousValues[key].ts); let diff = (ts - this.previousValues[key].ts);
if(diff < this.previousValues[key].duration*1000) return; if (diff < this.previousValues[key].duration * 1000) return false;
this.previousValues[key].ts = timestamp; this.previousValues[key].ts = ts;
*/
//------------------------- return message;
}
//send to service sendMessageToService(message, seconds, message_type) {
let dataToInfoSender = {id: this.projects_id}; // if error occures too early FLOW.GLOBALS.settings.project_id is still undefined
if (this.project_id === undefined) {
console.log("ErrorToServiceHandler.js: no project_id");
return;
}
let f = this.processMessage(message, seconds);
if (f === false) return;
if (message_type === undefined) message_type = "error_message";
let toService = {
id: this.project_id,
ipAddresses: this.ipAddresses
};
//js_error || error_message //js_error || error_message
dataToInfoSender[message_type] = message; toService[message_type] = message;
dataToInfoSender.ipAddresses = this.ipAddresses;
console.log("ErrorToServiceHandler------------------------>send to service", dataToInfoSender); console.log("ErrorToServiceHandler------------------------>send to service", toService);
//TODO UGLY!!!
// if error occures too early FLOW.GLOBALs.settings.project_id is still undefined
// if(this.projects_id === undefined) this.projects_id = FLOW.GLOBALS.settings.project_id;
if(this.projects_id === undefined) return;
/*
if(this.projects_id === undefined)
{
console.log("this.projects_id is undefined");
return;
}
*/
RESTBuilder.make(function(builder) { RESTBuilder.make(function(builder) {
builder.method('POST'); builder.method('POST');
builder.post(dataToInfoSender); builder.post(toService);
builder.url('http://192.168.252.2:8004/sentmessage'); builder.url('http://192.168.252.2:8004/sentmessage');
builder.callback(function(err, response, output) { builder.callback(function(err, response, output) {
console.log("process.on error send", err, response, output, dataToInfoSender); console.log("process.on error send", err, response, output, toService);
}); });
}); });
} }
} }
module.exports = ErrorToServiceHandler; const errorHandler = new ErrorToServiceHandler();
module.exports = errorHandler;
//module.exports = ErrorToServiceHandler;

View file

@ -1,6 +1,9 @@
//key is device, value = str //key is device, value = message {}
let sentValues= {}; let sentValues = {};
let notificationsData = null; let notificationsData = null;
let rvoName;
//sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "dimming_profile_was_successfully_received_by_node", { node: node }, "", SEND_TO.tb, instance);
let ERRWEIGHT = { let ERRWEIGHT = {
EMERGENCY: "emergency", // System unusable EMERGENCY: "emergency", // System unusable
@ -23,82 +26,67 @@ var template = (tpl, args) => tpl.replace(/\${(\w+)}/g, (_, v) => args[v]);
function initNotification() { function initNotification() {
notificationsData = FLOW.GLOBALS.notificationsData; notificationsData = FLOW.GLOBALS.notificationsData;
rvoName = FLOW.GLOBALS.settings.rvo_name;
} }
function sendNotification(func, device, key, params, extra, tb_output, instance, saveKey) { function sendNotification(func, device, key, params, extra, tb_output, instance, saveKey) {
// return;
let storeToSendValues = true; let storeToSendValues = true;
if(saveKey == undefined) storeToSendValues = false; if (saveKey == undefined) storeToSendValues = false;
let lang = FLOW.GLOBALS.settings.language;
if(lang != "en" || lang != "sk") lang = "en";
let tpl = key;
let weight = ""; let weight = "";
let message = {};
if(notificationsData[key]) let notification = notificationsData[key];
{
weight = notificationsData[key].weight;
weight = weight.toLowerCase();
tpl = notificationsData[key][lang]; if (notification) {
tpl = template(tpl, params); weight = notification.weight.toLowerCase();
Object.keys(notification).forEach(item => {
if (["en", "sk", "de", "cz", "it", "es"].includes(item)) {
message[item] = rvoName + ": " + template(notification[item], params);
}
})
} }
else else {
{
//console.error("sendNotification: Notifications: undefined key", key, func, notificationsData); //console.error("sendNotification: Notifications: undefined key", key, func, notificationsData);
console.error("sendNotification: Notifications: undefined key", key, func ); console.error("sendNotification: Notifications: undefined key", key, func);
return false; return false;
} }
//detect invalid err weight //detect invalid err weight
if(getKey(ERRWEIGHT, weight) == undefined) if (getKey(ERRWEIGHT, weight) == undefined) {
{
console.error("sendNotification: Notifications: undefined weight", weight, key, func); console.error("sendNotification: Notifications: undefined weight", weight, key, func);
return false; return false;
} }
if(sentValues.hasOwnProperty(saveKey)) if (sentValues.hasOwnProperty(saveKey)) {
{ if (JSON.stringify(sentValues[saveKey]) == JSON.stringify(message)) {
if(sentValues[saveKey] == tpl)
{
return false; return false;
} }
} }
if(sentValues[saveKey] == undefined) if (sentValues[saveKey] == undefined) {
{ if (storeToSendValues) {
if(storeToSendValues)
{
//do not send - flow is was started //do not send - flow is was started
sentValues[saveKey] = tpl; sentValues[saveKey] = message;
return false; return false;
} }
} }
if(saveKey == "rvo_door") if (storeToSendValues) sentValues[saveKey] = message;
{
//console.log("******", saveKey, sentValues[saveKey], tpl);
}
if(storeToSendValues) sentValues[saveKey] = tpl;
let str = FLOW.GLOBALS.settings.rvo_name;
if(str != "") str = str + ": ";
str = str + tpl;
let content = { let content = {
"type": weight, "type": weight,
"status": "new", "status": "new",
"source": { "source": {
"func":func, "func": func,
"component":instance.id, "component": instance.id,
"component_name":instance.name, "component_name": instance.name,
"edge":device "edge": device
}, },
"message":str, "message": message,
"message_data": extra "message_data": extra
}; };
@ -107,7 +95,7 @@ function sendNotification(func, device, key, params, extra, tb_output, instance,
{ {
"ts": Date.now(), "ts": Date.now(),
"values": { "values": {
"_event":content "_event": content
} }
} }
]; ];
@ -118,6 +106,7 @@ function sendNotification(func, device, key, params, extra, tb_output, instance,
} else { } else {
bufferError(msg); bufferError(msg);
}*/ }*/
instance.send(tb_output, msg); // Even if error server is unavailable, send this message to output, for other possible component connections instance.send(tb_output, msg); // Even if error server is unavailable, send this message to output, for other possible component connections
return true; return true;
@ -129,3 +118,4 @@ module.exports = {
ERRWEIGHT, ERRWEIGHT,
initNotification initNotification
} }

View file

@ -1,6 +1,6 @@
const { exec } = require('child_process'); const { exec } = require('child_process');
function openPort(port){ function openPort(port) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var callbackError = function(err) { var callbackError = function(err) {
@ -25,24 +25,24 @@ function openPort(port){
}) })
} }
function runSyncExec(command){ function runSyncExec(command) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => { exec(command, (error, stdout, stderr) => {
if(error == null) resolve(stdout); if (error == null) resolve(stdout);
reject(error); reject(error);
}); });
}) })
} }
async function writeData(port, data, readbytes, timeout){ async function writeData(port, data, readbytes, timeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// If first item in data array is 255, we just write broadcast command to rsPort // If first item in data array is 255, we just write broadcast command to rsPort
// We wait 3 seconds and resolve(["broadcast"]) // We wait 3 seconds and resolve(["broadcast"])
// It is important to resolve with array // It is important to resolve with array
if(data[0] == 255) { if (data[0] == 255) {
port.write(Buffer.from(data), function(err) { port.write(Buffer.from(data), function(err) {
if (err) { if (err) {
@ -60,8 +60,7 @@ async function writeData(port, data, readbytes, timeout){
rsPortReceivedData.push(...data); rsPortReceivedData.push(...data);
let l = rsPortReceivedData.length; let l = rsPortReceivedData.length;
if(l >= readbytes) if (l >= readbytes) {
{
port.removeListener('data', callback); port.removeListener('data', callback);
clearTimeout(t); clearTimeout(t);
@ -74,7 +73,7 @@ async function writeData(port, data, readbytes, timeout){
let t = setTimeout(() => { let t = setTimeout(() => {
port.removeListener('data', callback); port.removeListener('data', callback);
console.log("serialport helper: writeData TIMEOUT READING", rsPortReceivedData); //console.log("serialport helper: writeData TIMEOUT READING", rsPortReceivedData);
reject("TIMEOUT READING"); reject("TIMEOUT READING");
}, timeout); }, timeout);

View file

@ -1,35 +1,23 @@
function bytesToInt(bytes, numberOfBytes) function bytesToInt(bytes, numberOfBytes) {
{ let buffer = [];
let buffer = []; if (Array.isArray(bytes)) {
if(Array.isArray(bytes)) buffer = bytes.slice(0);
{ if (numberOfBytes != undefined) {
buffer = bytes.slice(0); buffer = bytes.slice(bytes.length - numberOfBytes);
if(numberOfBytes != undefined) }
{
buffer = bytes.slice(bytes.length - numberOfBytes);
} }
} else buffer.push(bytes);
else buffer.push(bytes);
//var decimal = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]; let result = 0;
let l = (buffer.length - 1) * 8; for (let i = 0; i < buffer.length; i++) {
result = (result << 8) | buffer[i];
}
let decimal = 0; return result >>> 0; //ensure it's an unsigned 32-bit number
for(let i = 0; i < buffer.length; i++)
{
var s = buffer[i] << l;
if(l < 8) s = buffer[i]
decimal = decimal + s;
l = l - 8;
}
return decimal;
} }
function resizeArray(arr, newSize, defaultValue) { function resizeArray(arr, newSize, defaultValue) {
while(newSize > arr.length) while (newSize > arr.length)
arr.push(defaultValue); arr.push(defaultValue);
arr.length = newSize; arr.length = newSize;
} }
@ -38,14 +26,14 @@ longToByteArray = function(/*long*/long) {
// we want to represent the input as a 8-bytes array // we want to represent the input as a 8-bytes array
var byteArray = [0, 0, 0, 0, 0, 0, 0, 0]; var byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
for ( var index = 0; index < byteArray.length; index ++ ) { for (var index = 0; index < byteArray.length; index++) {
var byte = long & 0xff; var byte = long & 0xff;
byteArray [ index ] = byte; byteArray[index] = byte;
long = (long - byte) / 256 ; long = (long - byte) / 256;
} }
return byteArray; return byteArray;
}; };
function addDays(date, days) { function addDays(date, days) {
var result = new Date(date); var result = new Date(date);
@ -61,31 +49,31 @@ sleep(2000).then(() => {
}); });
*/ */
function sleep (time) { function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time)); return new Promise((resolve) => setTimeout(resolve, time));
} }
function isEmptyObject( obj ) { function isEmptyObject(obj) {
for ( var name in obj ) { for (var name in obj) {
return false; return false;
} }
return true; return true;
} }
function convertUTCDateToLocalDate(date) { function convertUTCDateToLocalDate(date) {
var newDate = new Date(date); var newDate = new Date(date);
newDate.setMinutes(date.getMinutes() + date.getTimezoneOffset()); newDate.setMinutes(date.getMinutes() + date.getTimezoneOffset());
return newDate; return newDate;
} }
function addZeroBefore(n) { function addZeroBefore(n) {
return (n < 10 ? '0' : '') + n; return (n < 10 ? '0' : '') + n;
} }
var convertBase = function () { var convertBase = function() {
function convertBase(baseFrom, baseTo) { function convertBase(baseFrom, baseTo) {
return function (num) { return function(num) {
return parseInt(num, baseFrom).toString(baseTo); return parseInt(num, baseFrom).toString(baseTo);
}; };
@ -110,7 +98,7 @@ var convertBase = function () {
convertBase.hex2dec = convertBase(16, 10); convertBase.hex2dec = convertBase(16, 10);
return convertBase; return convertBase;
}(); }();
module.exports = { module.exports = {
bytesToInt, bytesToInt,

View file

@ -33,39 +33,28 @@ exports.install = function(instance) {
let ipAddresses = Object.create(null); // Or just '{}', an empty object let ipAddresses = Object.create(null); // Or just '{}', an empty object
for (const name of Object.keys(nets)) { for (const name of Object.keys(nets)) {
for (const net of nets[name]) { for (const net of nets[name]) {
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) { if (net.family === 'IPv4' && !net.internal) {
if (!ipAddresses[name]) { if (!ipAddresses[name]) {
ipAddresses[name] = []; ipAddresses[name] = [];
} }
ipAddresses[name].push(net.address); ipAddresses[name].push(net.address);
}
} }
}
} }
function sendValues() function sendValues() {
{ if (!configured) return;
if(!configured) return;
if(Object.keys(allValues).length > 0) if (Object.keys(allValues).length > 0) {
{ let dataToSend = { ...allValues };
if(id) dataToSend.id = id;
{ dataToSend.ipAddresses = ipAddresses;
delete allValues.__force__;
let dataToSend = {...allValues};
dataToSend.id = id;
dataToSend.ipAddresses = ipAddresses;
instance.send(0, dataToSend); instance.send(0, dataToSend);
allValues = {};
}
else
{
console.log(exports.title, "unable to send data, no id");
}
allValues = {};
} }
} }
@ -75,27 +64,18 @@ exports.install = function(instance) {
instance.on("0", _ => { instance.on("0", _ => {
id = FLOW.GLOBALS.settings.project_id; id = FLOW.GLOBALS.settings.project_id;
configured = true; if (id) configured = true;
else console.log(exports.title, "InfoSender: Unable to send data, no id");
}) })
instance.on("1", flowdata => { instance.on("1", flowdata => {
allValues = { ...allValues, ...flowdata.data };
allValues = { ...allValues, ...flowdata.data};
//console.log("DATA RECEIVED", flowdata.data); //console.log("DATA RECEIVED", flowdata.data);
//__force__
if(flowdata.data.hasOwnProperty("__force__"))
{
if(flowdata.data.__force__)
{
sendValues();
}
}
}) })
sendAllValuesInterval = setInterval(() => { sendAllValuesInterval = setInterval(() => {
sendValues(); sendValues();
}, 60000*3); }, 60000 * 3);
} }

View file

@ -36,12 +36,13 @@ let mainSocket;
let phases; let phases;
//phases where voltage is 0 (set) //phases where voltage is 0 (set)
let noVoltage; let noVoltage;
let energyToSwitchLamps;
exports.install = function(instance) { exports.install = function(instance) {
class SocketWithClients { class SocketWithClients {
constructor () { constructor() {
this.stream = null; this.stream = null;
this.socket = null; this.socket = null;
this.clients = {}; this.clients = {};
@ -66,7 +67,7 @@ exports.install = function(instance) {
buildPhases = () => { buildPhases = () => {
let a = []; let a = [];
for (let i = 1; i<= phases; i++) { for (let i = 1; i <= phases; i++) {
a.push(`Phase_${i}_voltage`) a.push(`Phase_${i}_voltage`)
} }
return a; return a;
@ -76,30 +77,30 @@ exports.install = function(instance) {
let obj = this; let obj = this;
if (this.socket) {
this.socket.removeAllListeners();
this.socket = null;
}
this.socket = new SerialPort("/dev/ttymxc0", { this.socket = new SerialPort("/dev/ttymxc0", {
baudRate: 9600, baudRate: 9600,
}) })
// we create a client for every deviceAddress ( = address) in list and push them into dictionary // we create a client for every deviceAddress ( = address) in list and push them into dictionary
for( let i = 0; i < deviceConfig.length; i++) for (let i = 0; i < deviceConfig.length; i++) {
{
this.clients[deviceConfig[i].deviceAddress] = new modbus.client.RTU(this.socket, deviceConfig[i].deviceAddress, 2000); // 2000 is timeout in register request, default is 5000, which is too long this.clients[deviceConfig[i].deviceAddress] = new modbus.client.RTU(this.socket, deviceConfig[i].deviceAddress, 2000); // 2000 is timeout in register request, default is 5000, which is too long
} }
this.socket.on('error', function(e) { this.socket.on('error', function(e) {
console.log('socket connection error', e); console.log('Modbus_reader: Socket connection error', e); //'ECONNREFUSED' or 'ECONNRESET' ??
if(e.code == 'ECONNREFUSED' || e.code == 'ECONNRESET') {
console.log(exports.title + ' Waiting 10 seconds before trying to connect again');
setTimeout(obj.startSocket, 10000);
}
}); });
this.socket.on('close', function() { this.socket.on('close', function() {
console.log('Socket connection closed ' + exports.title + ' Waiting 10 seconds before trying to connect again'); console.log('Modbus_reader: Socket connection closed - Waiting 10 seconds before connecting again');
setTimeout(obj.startSocket, 10000); setTimeout(obj.startSocket, 10000);
}); });
this.socket.on('open', function () { this.socket.on('open', function() {
console.log("socket connected"); console.log("socket connected");
obj.getActualStreamAndDevice(); obj.getActualStreamAndDevice();
obj.timeoutInterval = timeoutInterval - DELAY_BETWEEN_DEVICES; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES obj.timeoutInterval = timeoutInterval - DELAY_BETWEEN_DEVICES; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES
@ -116,7 +117,8 @@ exports.install = function(instance) {
this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number
this.device = dev.device; //em340, twilight_sensor this.device = dev.device; //em340, twilight_sensor
if(this.indexInDeviceConfig == 0) setTimeout(this.readRegisters, this.timeoutInterval); //if we just start to loop devices from the beginning, or there is just 1 device in config, we wait whole timeoutInterval
if (this.indexInDeviceConfig == 0 || deviceConfig.length === 1) setTimeout(this.readRegisters, this.timeoutInterval);
else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES); else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES);
} }
@ -130,21 +132,18 @@ exports.install = function(instance) {
let obj = this; let obj = this;
this.clients[this.deviceAddress].readHoldingRegisters(register, size) this.clients[this.deviceAddress].readHoldingRegisters(register, size)
.then( function (resp) { .then(function(resp) {
resp = resp.response._body.valuesAsArray; //resp is array of length 1 or 2, f.e. [2360,0] resp = resp.response._body.valuesAsArray; //resp is array of length 1 or 2, f.e. [2360,0]
// console.log(deviceAddress, register, tbAttribute, resp); // console.log(deviceAddress, register, tbAttribute, resp);
//device is responding again after NOK status //device is responding again after NOK status
if(numberOfNotResponding.hasOwnProperty(obj.device)) if (numberOfNotResponding.hasOwnProperty(obj.device)) {
{
let message = ""; let message = "";
if(obj.device == "em340") if (obj.device == "em340") {
{
message = "electrometer_ok"; message = "electrometer_ok";
} }
else if(obj.device == "twilight_sensor") else if (obj.device == "twilight_sensor") {
{
message = "twilight_sensor_ok"; message = "twilight_sensor_ok";
} }
message && sendNotification("modbus_reader: readRegisters", tbName, message, {}, "", SEND_TO.tb, instance); message && sendNotification("modbus_reader: readRegisters", tbName, message, {}, "", SEND_TO.tb, instance);
@ -157,25 +156,21 @@ exports.install = function(instance) {
obj.index++; obj.index++;
obj.readAnotherRegister(); obj.readAnotherRegister();
}).catch (function () { }).catch(function() {
//console.log("errors pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute); //console.log("errors pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute);
obj.errors++; obj.errors++;
if(obj.errors == obj.lengthOfActualDeviceStream) if (obj.errors == obj.lengthOfActualDeviceStream) {
{ instance.send(SEND_TO.dido_controller, { status: "NOK-" + obj.device }); // NOK-em340, NOK-em111, NOK-twilight_sensor, NOK-thermometer
instance.send(SEND_TO.dido_controller, {status: "NOK-" + obj.device}); // NOK-em340, NOK-em111, NOK-twilight_sensor, NOK-thermometer
//todo - neposlalo notification, ked sme vypojili twilight a neposle to do tb, ale do dido ?? //todo - neposlalo notification, ked sme vypojili twilight a neposle to do tb, ale do dido ??
if(!numberOfNotResponding.hasOwnProperty(obj.device)) if (!numberOfNotResponding.hasOwnProperty(obj.device)) {
{
let message = ""; let message = "";
if(obj.device == "twilight_sensor") if (obj.device == "twilight_sensor") {
{
message = "twilight_sensor_nok"; message = "twilight_sensor_nok";
} }
else if(obj.device == "em340") else if (obj.device == "em340") {
{
message = "electrometer_nok"; message = "electrometer_nok";
} }
message && sendNotification("modbus_reader: readingTimeouted", tbName, message, {}, "", SEND_TO.tb, instance); message && sendNotification("modbus_reader: readingTimeouted", tbName, message, {}, "", SEND_TO.tb, instance);
@ -191,9 +186,8 @@ exports.install = function(instance) {
// })) // }))
// if reading out of device's last register returns error, we send accumulated allValues to dido_controller (if allValues are not an empty object) // if reading out of device's last register returns error, we send accumulated allValues to dido_controller (if allValues are not an empty object)
if(obj.index + 1 >= obj.lengthOfActualDeviceStream) if (obj.index + 1 >= obj.lengthOfActualDeviceStream) {
{ if (!isObjectEmpty(obj.allValues)) instance.send(SEND_TO.dido_controller, { values: obj.allValues });
if(!isObjectEmpty(obj.allValues)) instance.send(SEND_TO.dido_controller, {values: obj.allValues});
obj.allValues = {}; obj.allValues = {};
} }
obj.index++; obj.index++;
@ -203,7 +197,7 @@ exports.install = function(instance) {
}; };
readAnotherRegister = () => { readAnotherRegister = () => {
if(this.index < this.lengthOfActualDeviceStream) setTimeout(this.readRegisters, 0); if (this.index < this.lengthOfActualDeviceStream) setTimeout(this.readRegisters, 0);
else this.setNewStream(); else this.setNewStream();
} }
@ -212,8 +206,7 @@ exports.install = function(instance) {
for (let i = 0; i < this.lengthOfActualDeviceStream; i++) { for (let i = 0; i < this.lengthOfActualDeviceStream; i++) {
let a = this.stream[i]; let a = this.stream[i];
if (a.register === register) if (a.register === register) {
{
let tbAttribute = a.tbAttribute; let tbAttribute = a.tbAttribute;
let multiplier = a.multiplier; let multiplier = a.multiplier;
@ -222,8 +215,7 @@ exports.install = function(instance) {
// if(tbName == undefined) return; // if(tbName == undefined) return;
if(this.index + 1 < this.lengthOfActualDeviceStream) if (this.index + 1 < this.lengthOfActualDeviceStream) {
{
this.allValues[tbAttribute] = value; this.allValues[tbAttribute] = value;
return; return;
} }
@ -236,7 +228,7 @@ exports.install = function(instance) {
this.checkNullVoltage(values); this.checkNullVoltage(values);
this.lampSwitchNotification(values); this.lampSwitchNotification(values);
instance.send(SEND_TO.dido_controller, {values: values}); instance.send(SEND_TO.dido_controller, { values: values });
this.allValues = {}; this.allValues = {};
break; break;
@ -244,16 +236,12 @@ exports.install = function(instance) {
} }
} }
setNewStream = () => setNewStream = () => {
{ if (this.lengthOfActualDeviceStream == this.index) {
if(this.lengthOfActualDeviceStream == this.index) if (this.indexInDeviceConfig + 1 == deviceConfig.length) {
{
if(this.indexInDeviceConfig + 1 == deviceConfig.length)
{
this.indexInDeviceConfig = 0; this.indexInDeviceConfig = 0;
} }
else else {
{
this.indexInDeviceConfig += 1; this.indexInDeviceConfig += 1;
} }
@ -261,25 +249,22 @@ exports.install = function(instance) {
} }
} }
calculateValue = (response, multiplier) => calculateValue = (response, multiplier) => {
{
let value = 0; let value = 0;
let l = response.length; let l = response.length;
if (l === 2) if (l === 2) {
{ value = (response[1] * (2 ** 16) + response[0]);
value = (response[1]*(2**16) + response[0]);
if(value >= (2**31)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x80000000), ak vieš robiť logický súčin if (value >= (2 ** 31)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x80000000), ak vieš robiť logický súčin
{ {
value = value - "0xFFFFFFFF" + 1; value = value - "0xFFFFFFFF" + 1;
} }
} }
else if (l === 1) else if (l === 1) {
{
value = response[0]; value = response[0];
if(value >= (2**15)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x8000), ak vieš robiť logický súčin if (value >= (2 ** 15)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x8000), ak vieš robiť logický súčin
{ {
value = value - "0xFFFF" + 1; value = value - "0xFFFF" + 1;
} }
@ -290,27 +275,24 @@ exports.install = function(instance) {
checkNullVoltage = (values) => { checkNullVoltage = (values) => {
if(!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return; if (!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return;
Object.keys(values).map(singleValue => { Object.keys(values).map(singleValue => {
if (this.phases.includes(singleValue)) if (this.phases.includes(singleValue)) {
{
let l = singleValue.split("_"); let l = singleValue.split("_");
let phase = parseInt(l[1]); let phase = parseInt(l[1]);
// console.log(values[singleValue], tbName); // console.log(values[singleValue], tbName);
if(values[singleValue] == 0) if (values[singleValue] == 0) {
{
noVoltage.add(phase); noVoltage.add(phase);
sendNotification("modbus_reader: checkNullVoltage", tbName, "no_voltage_on_phase", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase ); sendNotification("modbus_reader: checkNullVoltage", tbName, "no_voltage_on_phase", { phase: phase }, "", SEND_TO.tb, instance, "voltage" + phase);
// console.log('no voltage') // console.log('no voltage')
} }
else else {
{
noVoltage.delete(phase); noVoltage.delete(phase);
// console.log('voltage detected') // console.log('voltage detected')
sendNotification("modbus_reader: checkNullVoltage", tbName, "voltage_on_phase_restored", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase); sendNotification("modbus_reader: checkNullVoltage", tbName, "voltage_on_phase_restored", { phase: phase }, "", SEND_TO.tb, instance, "voltage" + phase);
} }
} }
}) })
@ -321,21 +303,16 @@ exports.install = function(instance) {
*/ */
lampSwitchNotification = (values) => { lampSwitchNotification = (values) => {
if(!values.hasOwnProperty("total_power")) return; if (!values.hasOwnProperty("total_power")) return;
const actualTotalPower = values.total_power; const actualTotalPower = values.total_power;
const numberOfNodes = Object.keys(FLOW.GLOBALS.nodesData).length; if (actualTotalPower > energyToSwitchLamps && this.onNotificationSent == false) {
if(numberOfNodes == 0) numberOfNodes = 20; // to make sure, we send notification if totalPower is more than 300
if(actualTotalPower > numberOfNodes * 15 && this.onNotificationSent == false)
{
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_on", {}, "", SEND_TO.tb, instance); sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_on", {}, "", SEND_TO.tb, instance);
this.onNotificationSent = true; this.onNotificationSent = true;
this.offNotificationSent = false; this.offNotificationSent = false;
} }
else if(actualTotalPower <= numberOfNodes * 15 && this.offNotificationSent == false) else if (actualTotalPower <= energyToSwitchLamps && this.offNotificationSent == false) {
{
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_off", {}, "", SEND_TO.tb, instance); sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_off", {}, "", SEND_TO.tb, instance);
this.onNotificationSent = false; this.onNotificationSent = false;
this.offNotificationSent = true; this.offNotificationSent = true;
@ -353,7 +330,9 @@ exports.install = function(instance) {
phases = FLOW.GLOBALS.settings.phases; phases = FLOW.GLOBALS.settings.phases;
tbName = FLOW.GLOBALS.settings.rvoTbName; tbName = FLOW.GLOBALS.settings.rvoTbName;
noVoltage = FLOW.GLOBALS.settings.no_voltage; noVoltage = FLOW.GLOBALS.settings.no_voltage;
mainSocket = new SocketWithClients(); energyToSwitchLamps = FLOW.GLOBALS.settings.energy_to_switch_lamps / 2.5; //half value is enought to show if lamps are turned on or off
if (deviceConfig.length) mainSocket = new SocketWithClients();
else console.log("Modbus_reader: no modbus device in configuration");
// this notification is to show, that flow (unipi) has been restarted // this notification is to show, that flow (unipi) has been restarted
sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance); sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance);

View file

@ -4,7 +4,7 @@ exports.group = 'Worksys';
exports.color = '#888600'; exports.color = '#888600';
exports.version = '1.0.2'; exports.version = '1.0.2';
exports.icon = 'sign-out'; exports.icon = 'sign-out';
exports.input = 7; exports.input = 8;
exports.output = 1; exports.output = 1;
const { exec } = require('child_process'); const { exec } = require('child_process');
@ -24,24 +24,26 @@ exports.install = function(instance) {
instance.send(0, FLOW.GLOBALS.pinsData); instance.send(0, FLOW.GLOBALS.pinsData);
}) })
instance.on("4", _ => { instance.on("4", _ => {
instance.send(0, {rpcSwitchOffLine, rpcSetNodeDimming, rpcLineProfile, rpcNodeProfile, sunCalcExample, dataFromTerminalBroadcast}) instance.send(0, { rpcSwitchOffLine, rpcSetNodeDimming, rpcLineProfile, rpcNodeProfile, sunCalcExample, dataFromTerminalBroadcast })
}) })
instance.on("5", _ => { instance.on("5", _ => {
exec("sudo tail -n 25 monitor.txt" , (err, stdout, stderr) => { exec("sudo tail -n 25 monitor.txt", (err, stdout, stderr) => {
if (err || stderr) instance.send(0,{err, stderr}); if (err || stderr) instance.send(0, { err, stderr });
else instance.send(0,stdout); else instance.send(0, stdout);
}) })
}) })
instance.on("6", _ => { instance.on("6", _ => {
exec("sudo tail -n 25 err.txt" , (err, stdout, stderr) => { exec("sudo tail -n 25 err.txt", (err, stdout, stderr) => {
if (err || stderr) instance.send(0,{err, stderr}); if (err || stderr) instance.send(0, { err, stderr });
else instance.send(0,stdout); else instance.send(0, stdout);
}) })
}) })
instance.on("7", _ => {
instance.send(0, FLOW.deviceStatus);
})
}; };
const rpcSwitchOffLine = const rpcSwitchOffLine =
{ {
"topic": "v1/gateway/rpc", "topic": "v1/gateway/rpc",
@ -212,7 +214,7 @@ const rpcNodeProfile =
} }
} }
const sunCalcExample = { const sunCalcExample = {
dusk_no_offset: '20:18', dusk_no_offset: '20:18',
dawn_no_offset: '05:19', dawn_no_offset: '05:19',
dusk: '20:18', dusk: '20:18',

View file

@ -7,7 +7,7 @@ exports.output = 1;
exports.author = 'Jakub Klena'; exports.author = 'Jakub Klena';
exports.icon = 'plug'; exports.icon = 'plug';
exports.version = '1.0.8'; exports.version = '1.0.8';
exports.options = { 'name':'', 'types': '["emergency", "critical", "error", "alert"]', 'message_includes':'["is responding again"]', 'tag_on_include':'[{"user_id":"U072JE5JUQG", "includes":["Electrometer", "Twilight sensor"]}]', 'slack_channel':'' }; exports.options = { 'name': '', 'types': '["emergency", "critical", "error", "alert"]', 'message_includes': '["is responding again"]', 'tag_on_include': '[{"user_id":"U072JE5JUQG", "includes":["Electrometer", "Twilight sensor"]}]', 'slack_channel': '' };
exports.html = `<div class="padding"> exports.html = `<div class="padding">
<div class="row"> <div class="row">
@ -57,11 +57,11 @@ exports.install = function(instance) {
let icon = ':totaljs:'; let icon = ':totaljs:';
let type = value[k[0]][0]['values']['_event']['type']; let type = value[k[0]][0]['values']['_event']['type'];
let source = value[k[0]][0]['values']['_event']['source']['func']; let source = value[k[0]][0]['values']['_event']['source']['func'];
let message = value[k[0]][0]['values']['_event']['message']; let message = value[k[0]][0]['values']['_event']['message']['en'];
let message_data = value[k[0]][0]['values']['_event']['message_data']; let message_data = value[k[0]][0]['values']['_event']['message_data'];
let tag = ''; let tag = '';
switch(type){ switch (type) {
case 'debug': case 'debug':
icon = ':beetle:'; icon = ':beetle:';
break; break;
@ -89,15 +89,15 @@ exports.install = function(instance) {
} }
// Check if this message includes one of the strings we are watching for // Check if this message includes one of the strings we are watching for
for (const msg of msg_incl){ for (const msg of msg_incl) {
if (message.includes(msg)){ if (message.includes(msg)) {
if (msg == 'is responding again') icon = ':large_green_circle:'; if (msg == 'is responding again') icon = ':large_green_circle:';
can = true; can = true;
break; break;
} }
} }
// Check if message is one of the types we are watching for // Check if message is one of the types we are watching for
if (interested.includes(type)){ if (interested.includes(type)) {
can = true; can = true;
} }
@ -105,10 +105,10 @@ exports.install = function(instance) {
// Check for each person tags based on what the message includes // Check for each person tags based on what the message includes
for (const person of tags){ for (const person of tags) {
for (const msg of person.includes){ for (const msg of person.includes) {
if (message.includes(msg)){ if (message.includes(msg)) {
tag += '<@'+person.user_id+'> '; tag += '<@' + person.user_id + '> ';
break; // Break out from this person checks as they are already tagged now break; // Break out from this person checks as they are already tagged now
} }
} }
@ -116,46 +116,46 @@ exports.install = function(instance) {
// Now that all people are tagged add new line symbol // Now that all people are tagged add new line symbol
if (tag != '') tag += '\n'; if (tag != '') tag += '\n';
let send_data = tag+instance.options.name+' '+type.toUpperCase()+'\n*Source*: '+source+'\n*Message*: '+message; let send_data = tag + instance.options.name + ' ' + type.toUpperCase() + '\n*Source*: ' + source + '\n*Message*: ' + message;
if (message_data) { if (message_data) {
send_data += '\nData: '+message_data; send_data += '\nData: ' + message_data;
} }
let ignore_msg = false let ignore_msg = false
if (message.includes('Configuration of dimming profile to node no')){ if (message.includes('Configuration of dimming profile to node no')) {
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++){ for (let i = 0; i < FLOW["savedSlackMessages"].length; i++) {
if (FLOW["savedSlackMessages"][i].message == message){ if (FLOW["savedSlackMessages"][i].message == message) {
ignore_msg = true; ignore_msg = true;
break; break;
} }
} }
if (!ignore_msg){ if (!ignore_msg) {
FLOW["savedSlackMessages"].push({message, 'dateandtime': Date.now()}); FLOW["savedSlackMessages"].push({ message, 'dateandtime': Date.now() });
if (timer === null){ if (timer === null) {
timer = setTimeout(checkSavedMessages, 60*60000); timer = setTimeout(checkSavedMessages, 60 * 60000);
} }
} }
} }
if (!ignore_msg){ if (!ignore_msg) {
instance.send2({'msg':send_data,'bot_name':instance.options.name+' '+type.toUpperCase(),'bot_icon':icon,'channel':instance.options.slack_channel}); instance.send2({ 'msg': send_data, 'bot_name': instance.options.name + ' ' + type.toUpperCase(), 'bot_icon': icon, 'channel': instance.options.slack_channel });
} }
}); });
function checkSavedMessages(){ function checkSavedMessages() {
var d = Date.now(); var d = Date.now();
d = d - 86400000; // older then 24hr d = d - 86400000; // older then 24hr
var a = []; var a = [];
//Remove msgs older then 24hr //Remove msgs older then 24hr
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++){ for (let i = 0; i < FLOW["savedSlackMessages"].length; i++) {
if (FLOW["savedSlackMessages"][i].dateandtime > d){ if (FLOW["savedSlackMessages"][i].dateandtime > d) {
a.push(FLOW["savedSlackMessages"][i]); a.push(FLOW["savedSlackMessages"][i]);
} }
} }
FLOW["savedSlackMessages"] = a; FLOW["savedSlackMessages"] = a;
if (FLOW["savedSlackMessages"].length > 0) { if (FLOW["savedSlackMessages"].length > 0) {
timer = setTimeout(checkSavedMessages, 60*60000); timer = setTimeout(checkSavedMessages, 60 * 60000);
} else { } else {
timer = null; timer = null;
} }
@ -163,7 +163,7 @@ exports.install = function(instance) {
instance.reconfigure = function() { instance.reconfigure = function() {
try { try {
if (!FLOW["savedSlackMessages"]){ if (!FLOW["savedSlackMessages"]) {
FLOW["savedSlackMessages"] = []; FLOW["savedSlackMessages"] = [];
} }
@ -183,5 +183,4 @@ exports.install = function(instance) {
instance.on('options', instance.reconfigure); instance.on('options', instance.reconfigure);
setTimeout(instance.reconfigure, 10000); setTimeout(instance.reconfigure, 10000);
}; };

View file

@ -33,7 +33,7 @@ exports.install = function(instance) {
logger.debug(exports.title, "installed"); logger.debug(exports.title, "installed");
instance.on("close", function(){ instance.on("close", function() {
clearInterval(startRead); clearInterval(startRead);
}) })
@ -42,24 +42,25 @@ exports.install = function(instance) {
try { try {
if(temperatureAddress === "") throw "Thermometer: temperatureAddress is not defined"; if (temperatureAddress === "") throw "Thermometer: temperatureAddress is not defined";
exec(`owread -C ${temperatureAddress}/temperature`, (error, stdout, stderr) => { exec(`owread -C ${temperatureAddress}/temperature`, (error, stdout, stderr) => {
if(!error) if (!error) {
{
parseData(stdout) parseData(stdout)
return; return;
} }
counter++; counter++;
if(counter == NUMBER_OF_FAILURES_TO_SEND_ERROR) sendNotification("Thermometer_main", rvoTbName, "thermometer_is_not_responding", {}, {"Error": error}, SEND_TO.tb, instance, "thermometer"); if (counter == NUMBER_OF_FAILURES_TO_SEND_ERROR) {
monitor.info("Thermometer is not responding", error); sendNotification("Thermometer_main", rvoTbName, "thermometer_is_not_responding", {}, { "Error": error }, SEND_TO.tb, instance, "thermometer");
instance.send(SEND_TO.dido_controller, {status: "NOK-thermometer"}); monitor.info("Thermometer is not responding", error);
}
instance.send(SEND_TO.dido_controller, { status: "NOK-thermometer" });
}); });
} }
catch(err) { catch (err) {
errLogger.error(exports.title, err); errLogger.error(exports.title, err);
clearInterval(startRead); clearInterval(startRead);
} }
@ -70,12 +71,12 @@ exports.install = function(instance) {
data = parseFloat(data); data = parseFloat(data);
//logger.debug("Thermometer", data); //logger.debug("Thermometer", data);
if(isNaN(data)) { if (isNaN(data)) {
errLogger.error("Thermometer sends invalid data"); errLogger.error("Thermometer sends invalid data");
return; return;
} }
if(counter > NUMBER_OF_FAILURES_TO_SEND_ERROR) //1 hour if (counter > NUMBER_OF_FAILURES_TO_SEND_ERROR) //1 hour
{ {
instance.send(SEND_TO.debug, "Thermometer - temperature data are comming again"); instance.send(SEND_TO.debug, "Thermometer - temperature data are comming again");
sendNotification("Thermometer_parseData", rvoTbName, "thermometer_is_responding_again", {}, "", SEND_TO.tb, instance, "thermometer"); sendNotification("Thermometer_parseData", rvoTbName, "thermometer_is_responding_again", {}, "", SEND_TO.tb, instance, "thermometer");
@ -85,7 +86,7 @@ exports.install = function(instance) {
"temperature": Number(data.toFixed(2)), "temperature": Number(data.toFixed(2)),
} }
instance.send(SEND_TO.dido_controller, {values: values}); instance.send(SEND_TO.dido_controller, { values: values });
counter = 0; counter = 0;
} }

View file

@ -55,7 +55,7 @@ let saveTelemetryOnError = true;//backup_on_failure overrides this value
//------------------------ //------------------------
let rollers; let rollers;
if(createTelemetryBackup) rollers = require('streamroller'); if (createTelemetryBackup) rollers = require('streamroller');
const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304 const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304
let insertNoSqlCounter = 0; let insertNoSqlCounter = 0;
@ -70,7 +70,7 @@ let lastRestoreTime = 0;
// if there is an error in client connection, flow logs to monitor.txt. Not to log messages every second, we use sendClientError variable // if there is an error in client connection, flow logs to monitor.txt. Not to log messages every second, we use sendClientError variable
let sendClientError = true; let sendClientError = true;
process.on('uncaughtException', function (err) { process.on('uncaughtException', function(err) {
errLogger.error('uncaughtException:', err.message) errLogger.error('uncaughtException:', err.message)
errLogger.error(err.stack); errLogger.error(err.stack);
@ -96,22 +96,19 @@ exports.install = function(instance) {
let sendWsStatusVar = null; let sendWsStatusVar = null;
let wsmqtt_status = 'disconnected'; let wsmqtt_status = 'disconnected';
function getWsmqttName(host) function getWsmqttName(host) {
{ if (host == "tb-demo.worksys.io" || host == '192.168.252.4') return 'wsmqtt_demo';
if(host == "tb-demo.worksys.io" || host == '192.168.252.4') return 'wsmqtt_demo'; else if (host == "tb-qas01.worksys.io" || host == '192.168.252.5') return 'wsmqtt_qas01';
else if(host == "tb-qas01.worksys.io" || host == '192.168.252.5') return 'wsmqtt_qas01'; else if (host == "tb-prod01.worksys.io" || host == '192.168.252.1') return 'wsmqtt_prod01';
else if(host == "tb-prod01.worksys.io" || host == '192.168.252.1') return 'wsmqtt_prod01';
} }
function sendWsStatus() function sendWsStatus() {
{ instance.send(SEND_TO.services, { [wsmqttName]: wsmqtt_status });
instance.send(SEND_TO.services, {[wsmqttName]: wsmqtt_status});
} }
function main() function main() {
{ if (!FLOW.dbLoaded) return;
if(!FLOW.dbLoaded) return;
loadSettings(); loadSettings();
clearInterval(sendWsStatus); clearInterval(sendWsStatus);
@ -119,11 +116,9 @@ exports.install = function(instance) {
} }
//set opts according to db settings //set opts according to db settings
function loadSettings() function loadSettings() {
{
if(instance.options.host !== "") if (instance.options.host !== "") {
{
//override settings from database //override settings from database
var o = instance.options; var o = instance.options;
opts = { opts = {
@ -139,8 +134,7 @@ exports.install = function(instance) {
console.log("wsmqttpublich -> loadSettings from instance.options", instance.options); console.log("wsmqttpublich -> loadSettings from instance.options", instance.options);
} }
else else {
{
const SETTINGS = FLOW.GLOBALS.settings; const SETTINGS = FLOW.GLOBALS.settings;
backup_on_failure = SETTINGS.backup_on_failure; backup_on_failure = SETTINGS.backup_on_failure;
@ -170,8 +164,7 @@ exports.install = function(instance) {
connectToTbServer(); connectToTbServer();
} }
function connectToTbServer() function connectToTbServer() {
{
var url = "mqtt://" + opts.host + ":" + opts.port; var url = "mqtt://" + opts.host + ":" + opts.port;
console.log("MQTT URL: ", url); console.log("MQTT URL: ", url);
@ -179,7 +172,7 @@ exports.install = function(instance) {
client.on('connect', function() { client.on('connect', function() {
instance.status("Connected", "green"); instance.status("Connected", "green");
monitor.info("MQTT client connected"); //monitor.info("MQTT client connected");
sendClientError = true; sendClientError = true;
clientReady = true; clientReady = true;
@ -198,15 +191,15 @@ exports.install = function(instance) {
TRY(function() { TRY(function() {
message = JSON.parse(message); message = JSON.parse(message);
if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) { if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) {
client.publish(topic, `{"device": ${message.device}, "id": ${message.data.id}, "data": {"success": true}}`, {qos:1}); client.publish(topic, `{"device": ${message.device}, "id": ${message.data.id}, "data": {"success": true}}`, { qos: 1 });
instance.send(SEND_TO.rpcCall, {"device": message.device, "id": message.data.id, "RPC response": {"success": true}}); instance.send(SEND_TO.rpcCall, { "device": message.device, "id": message.data.id, "RPC response": { "success": true } });
} }
}, () => instance.debug('MQTT: Error parsing data', message)); }, () => instance.debug('MQTT: Error parsing data', message));
} }
instance.send(SEND_TO.rpcCall, {"topic":topic, "content":message }); instance.send(SEND_TO.rpcCall, { "topic": topic, "content": message });
}); });
client.on('close', function() { client.on('close', function() {
@ -214,15 +207,15 @@ exports.install = function(instance) {
wsmqtt_status = 'disconnected'; wsmqtt_status = 'disconnected';
instance.status("Disconnected", "red"); instance.status("Disconnected", "red");
instance.send(SEND_TO.debug, {"message":"Client CLOSE signal received !"}); instance.send(SEND_TO.debug, { "message": "Client CLOSE signal received !" });
}); });
client.on('error', function(err) { client.on('error', function(err) {
instance.status("Err: "+ err.code, "red"); instance.status("Err: " + err.code, "red");
instance.send(SEND_TO.debug, {"message":"Client ERROR signal received !", "error":err, "opt":opts }); instance.send(SEND_TO.debug, { "message": "Client ERROR signal received !", "error": err, "opt": opts });
if(sendClientError) { if (sendClientError) {
monitor.info('MQTT client error', err); monitor.info('MQTT client error', err);
sendClientError = false; sendClientError = false;
} }
clientReady = false; clientReady = false;
wsmqtt_status = 'disconnected'; wsmqtt_status = 'disconnected';
@ -238,28 +231,24 @@ exports.install = function(instance) {
instance.on('1', function(data) { instance.on('1', function(data) {
if(clientReady) if (clientReady) {
{ //do we have some data in backup file? if any, process data from database
//do we have some data in backup file? if any, process data from database if (saveTelemetryOnError) {
if(saveTelemetryOnError)
{
//read telemetry data and send back to server //read telemetry data and send back to server
if(!processingData) processDataFromDatabase(); if (!processingData) processDataFromDatabase();
} }
let stringifiedJson = JSON.stringify(data.data); let stringifiedJson = JSON.stringify(data.data);
client.publish("v1/gateway/telemetry", stringifiedJson, {qos: 1}); client.publish("v1/gateway/telemetry", stringifiedJson, { qos: 1 });
//backup telemetry //backup telemetry
if(createTelemetryBackup) if (createTelemetryBackup) {
{
data.data.id = UID(); data.data.id = UID();
nosqlBackup.insert(data.data); nosqlBackup.insert(data.data);
insertBackupNoSqlCounter++; insertBackupNoSqlCounter++;
if(insertBackupNoSqlCounter > 150) if (insertBackupNoSqlCounter > 150) {
{ let options = { compress: true };
let options = {compress: true};
let path = __dirname + "/../databases/backup/tbdata.nosql"; let path = __dirname + "/../databases/backup/tbdata.nosql";
var stream = new rollers.RollingFileStream(path, noSqlFileSizeLimit, 150, options); var stream = new rollers.RollingFileStream(path, noSqlFileSizeLimit, 150, options);
stream.write(""); stream.write("");
@ -270,13 +259,11 @@ exports.install = function(instance) {
} }
} }
else else {
{
//logger.debug("Client unavailable. Data not sent !", JSON.stringify(data.data)); //logger.debug("Client unavailable. Data not sent !", JSON.stringify(data.data));
instance.send(SEND_TO.debug, {"message":"Client unavailable. Data not sent !", "data": data.data }); instance.send(SEND_TO.debug, { "message": "Client unavailable. Data not sent !", "data": data.data });
if(saveTelemetryOnError) if (saveTelemetryOnError) {
{
//create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql //create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql
makeBackupFromDbFile(); makeBackupFromDbFile();
@ -289,50 +276,42 @@ exports.install = function(instance) {
instance.close = function(done) { instance.close = function(done) {
if(clientReady){ if (clientReady) {
client.end(); client.end();
clearInterval(sendWsStatusVar); clearInterval(sendWsStatusVar);
} }
}; };
function getDbBackupFileCounter(type) function getDbBackupFileCounter(type) {
{
var files = fs.readdirSync(__dirname + "/../databases"); var files = fs.readdirSync(__dirname + "/../databases");
let counter = 0; let counter = 0;
for(var i = 0; i < files.length; i++) for (var i = 0; i < files.length; i++) {
{
if(files[i] == "tbdata.nosql") continue; if (files[i] == "tbdata.nosql") continue;
if(files[i].endsWith(".nosql")) if (files[i].endsWith(".nosql")) {
{
let pos = files[i].indexOf("."); let pos = files[i].indexOf(".");
if(pos > -1) if (pos > -1) {
{
let fileCounter = counter; let fileCounter = counter;
let firstDigit = files[i].slice(0, pos); let firstDigit = files[i].slice(0, pos);
fileCounter = parseInt(firstDigit); fileCounter = parseInt(firstDigit);
if(isNaN(fileCounter)) fileCounter = 0; if (isNaN(fileCounter)) fileCounter = 0;
//console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type); //console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type);
if(type == "max") if (type == "max") {
{ if (fileCounter > counter) {
if(fileCounter > counter)
{
counter = fileCounter; counter = fileCounter;
} }
} }
else if(type == "min") else if (type == "min") {
{ if (counter == 0) counter = fileCounter;
if(counter == 0) counter = fileCounter;
if(fileCounter < counter) if (fileCounter < counter) {
{
counter = fileCounter; counter = fileCounter;
} }
} }
@ -341,20 +320,19 @@ exports.install = function(instance) {
} }
if(type == "max") counter++; if (type == "max") counter++;
return counter; return counter;
} }
const makeBackupFromDbFile = async () => { const makeBackupFromDbFile = async () => {
if(!saveTelemetryOnError) return; if (!saveTelemetryOnError) return;
//to avoid large file: tbdata.nosql //to avoid large file: tbdata.nosql
//init value is 0! //init value is 0!
if(insertNoSqlCounter > 0) if (insertNoSqlCounter > 0) {
{
--insertNoSqlCounter; --insertNoSqlCounter;
return; return;
} }
@ -366,8 +344,7 @@ exports.install = function(instance) {
var stats = fs.statSync(source); var stats = fs.statSync(source);
var fileSizeInBytes = stats.size; var fileSizeInBytes = stats.size;
if(fileSizeInBytes > noSqlFileSizeLimit) if (fileSizeInBytes > noSqlFileSizeLimit) {
{
let counter = 1; let counter = 1;
counter = getDbBackupFileCounter("max"); counter = getDbBackupFileCounter("max");
@ -387,15 +364,14 @@ exports.install = function(instance) {
const processDataFromDatabase = async () => { const processDataFromDatabase = async () => {
if(restore_from_backup <= 0) return; if (restore_from_backup <= 0) return;
//calculate diff //calculate diff
const now = new Date(); const now = new Date();
let currentTime = now.getTime(); let currentTime = now.getTime();
let diff = currentTime - lastRestoreTime; let diff = currentTime - lastRestoreTime;
if( (diff / 1000) < restore_backup_wait) if ((diff / 1000) < restore_backup_wait) {
{
//console.log("*********restore_backup_wait", diff, restore_backup_wait); //console.log("*********restore_backup_wait", diff, restore_backup_wait);
return; return;
} }
@ -409,7 +385,7 @@ exports.install = function(instance) {
let dataBase = 'tbdata'; let dataBase = 'tbdata';
var nosql; var nosql;
if(counter == 0) dataBase = 'tbdata'; if (counter == 0) dataBase = 'tbdata';
else dataBase = counter + "." + 'tbdata'; else dataBase = counter + "." + 'tbdata';
nosql = NOSQL(dataBase); nosql = NOSQL(dataBase);
@ -417,15 +393,13 @@ exports.install = function(instance) {
//select all data - use limit restore_from_backup //select all data - use limit restore_from_backup
let records = await promisifyBuilder(nosql.find().take(restore_from_backup)); let records = await promisifyBuilder(nosql.find().take(restore_from_backup));
for(let i = 0; i < records.length; i++) for (let i = 0; i < records.length; i++) {
{ if (clientReady) {
if(clientReady) {
let item = records[i]; let item = records[i];
let id = item.id; let id = item.id;
if(id !== undefined) if (id !== undefined) {
{
//console.log("------------processDataFromDatabase - remove", id, dataBase, i); //console.log("------------processDataFromDatabase - remove", id, dataBase, i);
try { try {
@ -433,12 +407,12 @@ exports.install = function(instance) {
let message = JSON.parse(JSON.stringify(item)); let message = JSON.parse(JSON.stringify(item));
delete message.id; delete message.id;
client.publish("v1/gateway/telemetry", JSON.stringify(message), {qos:1}); client.publish("v1/gateway/telemetry", JSON.stringify(message), { qos: 1 });
//remove from database //remove from database
await promisifyBuilder(nosql.remove().where("id", id)); await promisifyBuilder(nosql.remove().where("id", id));
} catch(error) { } catch (error) {
//process error //process error
console.log("processDataFromDatabase", error); console.log("processDataFromDatabase", error);
} }
@ -446,23 +420,20 @@ exports.install = function(instance) {
} }
} }
else else {
{
processingData = false; processingData = false;
return; return;
} }
} }
if(records.length > 0) if (records.length > 0) {
{
//clean backup file //clean backup file
if(counter > 0) nosql.clean(); if (counter > 0) nosql.clean();
} }
//no data in db, remove //no data in db, remove
if(records.length == 0) if (records.length == 0) {
{ if (counter > 0) nosql.drop();
if(counter > 0) nosql.drop();
} }
const d = new Date(); const d = new Date();