Initial commit

This commit is contained in:
rasta5man 2025-08-07 21:49:01 +02:00
commit c53c7085bc
60 changed files with 23885 additions and 0 deletions

10
config Normal file
View file

@ -0,0 +1,10 @@
name : Total.js Flow
default_timezone: Europe/Bratislava
// Packages settings
package#flow (Object) : { url: '/', auth: ['admin:aaaAAA111'] }
table.pins : pin:string|type:string|tbname_demo:string|tbname_qas01:string|tbname_prod01:string|contactor:number
table.settings : temperature_adress:string|latitude:number|longitude:number|projects_id:number
table.zenitel : allCalls:string
table.departures: departures:string

View file

190
databases/bus_departures.js Normal file
View file

@ -0,0 +1,190 @@
module.exports = [
["3","04:09","Závodu míru",["25"]],
["3","04:29","Závodu míru",["X"]],
["1","04:50","Březová, aut. st.",["X"]],
["3","04:52","Závodu míru",["X"]],
["3","04:56","Závodu míru",["6+","25"]],
["3","05:02","Závodu míru",["X"]],
["4","05:05","Závodu míru",["X"]],
["6","05:07","Sídl.Michal škola",["X","42"]],
["6","05:07","Závodu míru",["X","32"]],
["3","05:09","Závodu míru",["X","42"]],
["3","05:22","Závodu míru",["X"]],
["1","05:23","Březová, aut. st.",["X"]],
["6","05:34","Sídl.Michal škola",["X","42"]],
["4","05:35","Stará Ovčárna",["X"]],
["3","05:36","sídl.Michal škola",["X","42"]],
["3","05:38","Sídliště Michal",["X","32"]],
["1","05:47","Březová, aut. st.",["6+","25"]],
["1","05:51","Březová, aut. st.",["X"]],
["3","05:57","Závodu míru",["6+","25"]],
["3","05:58","Závodu míru",["X"]],
["6","06:04","Sídl.Michal škola",["X","42"]],
["1","06:13","Březová, aut. st.",["X"]],
["3","06:16","Závodu míru",["X","42"]],
["3","06:19","Závodu míru",["X","32"]],
["3","06:24","Sídliště Michal",["X"]],
["3","06:24","Závodu míru",["6+","25"]],
["4","06:31","Závodu míru",["X","42"]],
["6","06:32","Sídl.Michal škola",["X","32"]],
["3","06:34","Závodu míru",["X"]],
["6","06:34","Sídl.Michal škola",["X","42"]],
["1","06:46","Březová, aut. st.",["X"]],
["33","06:49","sídl.Michal škola",["X","42"]],
["3","06:56","Závodu míru",["X","32"]],
["3","06:56","Závodu míru",["X","42"]],
["3","06:56","Závodu míru",["6+"]],
["1","06:57","Březová, aut. st.",["6+","25"]],
["3","07:04","Závodu míru",["X"]],
["1","07:05","Březová, aut. st.",["X"]],
["3","07:14","Závodu míru",["X"]],
["3","07:19","Závodu míru",["6+"]],
["6","07:23","Sídl.Michal škola",["X","42"]],
["33","07:27","sídl.Michal škola",["17"]],
["3","07:27","Sídliště Michal",["X","32"]],
["2","07:31","Sídliště Michal",["X","42"]],
["3","07:34","Závodu míru",["X","42"]],
["4","07:34","Závodu míru",["X"]],
["1","07:35","Březová, aut. st.",["X"]],
["3","07:45","Sídliště Michal",["X"]],
["3","07:51","Závodu míru",["6+"]],
["33","07:54","sídl.Michal škola",["X","42"]],
["3","07:54","Závodu míru",["17"]],
["1","07:55","Březová, aut. st.",["X"]],
["1","07:57","Březová, aut. st.",["6+"]],
["3","07:58","Závodu míru",["X","42"]],
["3","08:10","Závodu míru",["X","32"]],
["3","08:15","Závodu míru",["X","42"]],
["3","08:15","Závodu míru",["6+"]],
["1","08:18","Březová, aut. st.",["X"]],
["33","08:29","sídl.Michal škola",["X"]],
["3","08:34","Závodu míru",["X"]],
["3","08:50","Stará Ovčárna",["X"]],
["3","08:51","Závodu míru",["6+"]],
["33","08:54","sídl.Michal škola",["X"]],
["1","08:57","Březová, aut. st.",["6+"]],
["3","09:04","Závodu míru",["X"]],
["1","09:09","Březová, aut. st.",["X"]],
["3","09:20","Závodu míru",["6+"]],
["3","09:24","Závodu míru",["X"]],
["33","09:34","sídl.Michal škola",["X"]],
["4","09:41","Sídliště Michal",["X"]],
["1","09:44","Březová, aut. st.",["6+"]],
["3","09:45","Závodu míru",["X"]],
["3","09:51","Závodu míru",["6+"]],
["1","09:58","Březová, aut. st.",["X"]],
["3","09:59","Jezero Michal",["X"]],
["7","10:14","Březová, aut.st.",["X"]],
["3","10:19","Závodu míru",["6+"]],
["33","10:24","sídl.Michal škola",["X"]],
["3","10:40","Závodu míru",["X"]],
["1","10:55","Březová, aut. st.",["X"]],
["3","10:55","Závodu míru",["X"]],
["1","10:57","Březová, aut. st.",["6+"]],
["3","11:02","Závodu míru",["6+"]],
["3","11:17","Závodu míru",["X"]],
["33","11:29","sídl.Michal škola",["X"]],
["3","11:29","Závodu míru",["6+"]],
["3","11:44","Závodu míru",["X"]],
["1","11:46","Březová, aut. st.",["X"]],
["1","11:49","Březová, aut. st.",["6+"]],
["3","11:51","Závodu míru",["6+"]],
["33","11:56","sídl.Michal škola",["X"]],
["3","12:05","Závodu míru",["X"]],
["3","12:19","Závodu míru",["6+"]],
["3","12:24","Jezero Michal",["X"]],
["1","12:33","Březová, aut. st.",["X"]],
["3","12:44","Závodu míru",["X"]],
["3","12:51","Závodu míru",["6+"]],
["1","12:57","Březová, aut. st.",["6+"]],
["3","12:59","Závodu míru",["X"]],
["6","12:59","Sídl.Michal škola",["X","42"]],
["33","13:04","sídl.Michal škola",["X"]],
["1","13:06","Březová, aut. st.",["X"]],
["3","13:15","Závodu míru",["X"]],
["2","13:17","Sídliště Michal",["X"]],
["1","13:22","Březová, aut. st.",["X"]],
["3","13:24","Stará Ovčárna",["X"]],
["3","13:28","Závodu míru",["6+"]],
["33","13:39","sídl.Michal škola",["X"]],
["1","13:48","Březová, aut. st.",["X"]],
["3","13:51","Závodu míru",["6+"]],
["1","13:57","Březová, aut. st.",["6+"]],
["33","14:04","sídl.Michal škola",["X"]],
["6","14:04","Sídl.Michal škola",["X"]],
["4","14:06","Stará Ovčárna",["X"]],
["3","14:09","Závodu míru",["X"]],
["1","14:10","Březová, aut. st.",["X"]],
["3","14:19","Závodu míru",["6+"]],
["3","14:21","Závodu míru",["X"]],
["1","14:34","Březová, aut. st.",["X"]],
["6","14:34","Sídl.Michal škola",["X"]],
["4","14:42","Závodu míru",["X"]],
["3","14:43","Závodu míru",["X"]],
["33","14:46","sídl.Michal škola",["X"]],
["1","14:49","Březová, aut. st.",["6+"]],
["3","14:51","Závodu míru",["6+"]],
["3","14:52","Závodu míru",["X"]],
["1","15:09","Březová, aut. st.",["X"]],
["33","15:09","sídl.Michal škola",["X"]],
["3","15:13","Závodu míru",["X"]],
["6","15:14","Sídl.Michal škola",["X"]],
["3","15:19","Závodu míru",["6+"]],
["3","15:23","Sokolov, Hrušková",["X","42"]],
["3","15:30","Závodu míru",["X"]],
["1","15:31","Březová, aut. st.",["X"]],
["33","15:39","sídl.Michal škola",["X"]],
["3","15:39","Jezero Michal",["X"]],
["3","15:51","Závodu míru",["6+"]],
["7","15:54","Březová, aut.st.",["X"]],
["1","15:57","Březová, aut. st.",["6+"]],
["3","16:00","sídl.Michal škola",["X","42"]],
["4","16:06","Závodu míru",["X"]],
["33","16:14","sídl.Michal škola",["X"]],
["3","16:14","Závodu míru",["X"]],
["1","16:21","Březová, aut. st.",["X"]],
["3","16:28","Závodu míru",["6+"]],
["7","16:29","Březová, aut.st.",["X"]],
["3","16:35","Závodu míru",["X"]],
["6","16:44","Sídl.Michal škola",["X","42"]],
["3","16:48","Závodu míru",["X"]],
["4","16:51","Závodu míru",["X","42"]],
["3","16:53","Závodu míru",["6+"]],
["1","16:57","Březová, aut. st.",["6+"]],
["7","16:59","Březová, aut.st.",["X"]],
["3","17:09","Jezero Michal",["X"]],
["3","17:14","Závodu míru",["6+"]],
["3","17:18","Závodu míru",["X"]],
["3","17:34","Závodu míru",["X"]],
["1","17:38","Březová, aut. st.",["X"]],
["3","17:51","Závodu míru",["6+"]],
["3","17:57","Závodu míru",["X"]],
["3","18:14","Závodu míru",["X"]],
["3","18:21","Závodu míru",["X"]],
["3","18:21","Stará Ovčárna",["6+"]],
["1","18:24","Březová, aut. st.",["6+"]],
["1","18:26","Březová, aut. st.",["X"]],
["3","18:34","Závodu míru",["X"]],
["3","18:51","Závodu míru",["6+"]],
["3","18:54","Závodu míru",["X"]],
["1","19:08","Březová, aut. st.",["X"]],
["3","19:14","Závodu míru",["6+","24"]],
["3","19:19","Závodu míru",["X","31"]],
["3","19:34","Závodu míru",["X","31"]],
["1","19:53","Březová, aut. st.",["X","31"]],
["3","19:54","Závodu míru",["X","31","6+","24"]],
["1","19:57","Březová, aut. st.",["6+","24"]],
["3","20:19","Závodu míru",["X","31"]],
["1","20:28","Březová, aut. st.",["6+","24"]],
["3","20:49","Závodu míru",["X","31","6+","24"]],
["1","20:55","Březová, aut. st.",["X","31"]],
["3","21:09","Závodu míru",["X","31"]],
["1","21:23","Březová, aut. st.",["6+","24"]],
["3","21:24","Stará Ovčárna",["X","31"]],
["3","21:24","Závodu míru",["6+","24"]],
["1","21:38","Březová, aut. st.",["X","31"]],
["3","22:04","Závodu míru",["6+","24"]],
["3","22:12","Stará Ovčárna",["X","31"]],
["1","22:28","Březová, aut. st.",["X","31"]],
["3","22:41","Závodu míru",["X","31"]],
];

File diff suppressed because one or more lines are too long

14
databases/pins.table Normal file
View file

@ -0,0 +1,14 @@
pin:string|type:string|tbname_demo:string|tbname_qas01:string|tbname_prod01:string|contactor:number
+|al_osvetlenie|state_of_relay|p2rwdP7aGoOQLJNgAynEdKD6xWXbmMe3nvZqlzkV|o8ZzVA4jrXLmRPnvGBkDDak6ayWbg32Y9KwdxqJN|nJL5lPMwBx23YpqRe0rpZ47damXvWVbOrD4gNzy8|0|.............
+|al_defibrilator|state_of_relay|rQx3NGKgVMRaXYAo9y19OQyZzkWnj1le6bdOLE20|2qKyjDVBNowRvLzWxd5LBRk1JXY4mp9PA3gl6OGZ|XMBbew5z4ELrZa2mRAd3Q978vPN6gy3DdVYlpKjq|0|.............
+|al_poe_switch|state_of_relay|nreBJ6PMqgz20pYEL82JMk8G1jkWwdQxZVNAOlmK|9rKRNEDXVYzWb0qZmlQjnqQn3jByxv42a6LPJ1oM|gYbDLqlyZVoRerQpB72MovkWJnwM5z24POKa8Exj|1|.............
+|al_obrazovka|state_of_relay|klN4JpQAx362o9XYZDNPpQ5grWw1P7GEbdBM0vRV|dYBAenlq4zxv9jEZgaQqLq52ODyLoWKmGR06V1JP|zdQO8GwxDqjRgP4137YVPoANyKlpem2nL65rvVJY|1|.............
+|al_zasuvky|state_of_relay|ZmYXEbw9lVWRv1jLxDeJQYydgAMz4PKQnNJ6eB23|dYBAenlq4zxv9jEZgaQqLq52ODyLoWKmGR06V1JP|WlVJBygjDZMeKX3vnAMR5L08NqdmG2x1Y69LQ4P5|1|.............
+|al_breaker_12v|breaker_12V_on|zXBoWbEZjO0lrpqnRyoO0GykmVeaNAGdL9g4QKxP|6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar|E6Kg9oDnLWyzPRMva7v5YykJxp4VG58qO2w1lZYe|0|.............
+|al_breaker_48v|breaker_48V_on|zXBoWbEZjO0lrpqnRyoO0GykmVeaNAGdL9g4QKxP|6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar|E6Kg9oDnLWyzPRMva7v5YykJxp4VG58qO2w1lZYe|0|.............
+|al_istic_obrazovka|screen_breaker_on|zXBoWbEZjO0lrpqnRyoO0GykmVeaNAGdL9g4QKxP|6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar|E6Kg9oDnLWyzPRMva7v5YykJxp4VG58qO2w1lZYe|0|.............
+|al_istic_socket|socket_breaker_on|zXBoWbEZjO0lrpqnRyoO0GykmVeaNAGdL9g4QKxP|6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar|E6Kg9oDnLWyzPRMva7v5YykJxp4VG58qO2w1lZYe|0|.............
+|al_istic_heater|heater_breaker_on|zXBoWbEZjO0lrpqnRyoO0GykmVeaNAGdL9g4QKxP|6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar|E6Kg9oDnLWyzPRMva7v5YykJxp4VG58qO2w1lZYe|0|.............
+|al_dverovy_kontakt|door_condition|zXBoWbEZjO0lrpqnRyoO0GykmVeaNAGdL9g4QKxP|6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar|E6Kg9oDnLWyzPRMva7v5YykJxp4VG58qO2w1lZYe|0|.............
+|28ACE575D0013CDE|temperature_out|zXBoWbEZjO0lrpqnRyoO0GykmVeaNAGdL9g4QKxP|6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar|E6Kg9oDnLWyzPRMva7v5YykJxp4VG58qO2w1lZYe|0|.............
+|28667676E0013C21|temperature|zXBoWbEZjO0lrpqnRyoO0GykmVeaNAGdL9g4QKxP|6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar|E6Kg9oDnLWyzPRMva7v5YykJxp4VG58qO2w1lZYe|0|.............

2
databases/settings.table Normal file
View file

@ -0,0 +1,2 @@
temperature_adress:string|latitude:number|longitude:number|projects_id:number
+|28.635577911802|49.4366840086877|18.794087022543|39|...........

0
databases/tbdata.nosql Normal file
View file

2
databases/zenitel.table Normal file
View file

@ -0,0 +1,2 @@
allCalls:string
+|{"no_of_incoming":12016,"no_of_outgoing":40451}|...................

10092
dayDepartures.txt Normal file

File diff suppressed because it is too large Load diff

7
err.txt Normal file
View file

@ -0,0 +1,7 @@
[2025-07-13T19:56:22.298] [ERROR] errLogs - uncaughtException: write EPIPE
[2025-07-13T19:56:22.304] [ERROR] errLogs - Error: write EPIPE
at process.target._send (internal/child_process.js:841:20)
at process.target.send (internal/child_process.js:712:19)
at Framework.F.stop.F.kill (/home/unipi/flowserver/node_modules/total4/index.js:2873:12)
at process.forcestop (/home/unipi/flowserver/node_modules/total4/index.js:18380:4)
at process.emit (events.js:400:28)

99
flow/bussdepartures.js Normal file
View file

@ -0,0 +1,99 @@
exports.id = 'busdepartures';
exports.title = 'Bus departures';
exports.group = 'Worksys';
exports.color = '#5D9CEC';
exports.version = '0.0.1';
exports.output = ['red', 'white'];
exports.input = true;
exports.author = 'Rastislav Kovac';
exports.icon = 'cloud-upload';
exports.readme = `Handle bus departures`;
exports.install = function(instance) {
// we get start data after 30 seconds we start flow
setTimeout(() => {
instance.send(0, {departures:[]})
}, 20000)
setTimeout(() => {
instance.send(1, {delays:[]})
}, 30000)
// let day = new Date().getDay();
// function checkIfNewDay() {
// let currentDay = new Date().getDay();
// console.log('new day check -----',day, currentDay);
// if(currentDay == day) return;
// instance.send(0, {departures:[]})
// day = currentDay;
// }
// //we check if day changed, if yes, we get bus departures
// setInterval(checkIfNewDay, 1800000);
// we check delays every minute
setInterval(() => {
instance.send(1, {delays:[]})
}, 60000);
// we check departures every 15 minutes
setInterval(() => {
instance.send(0, {departures:[]})
}, 900000);
// instance.on('data', flowdata => {
// console.log('neuspesny departures req ++++++++++++++ ',flowdata.data);
// if(flowdata.data == 'repeatDepartureRequest')
// {
// setTimeout(() => {
// instance.send(0, {departures:[]})
// }, 60000)
// }
// })
}
// {
// "data": "{\"delays\":[[\"25\",0],[\"94\",0],[\"27\",0],[\"106\",0],[\"84\",0],[\"29\",0],[\"6\",0],[\"96\",0],[\"31\",0],[\"4\",0]]}",
// "status": 200,
// "headers": {
// "cache-control": "private, no-cache, no-store, max-age=0",
// "vary": "Accept-Encoding, Last-Modified, User-Agent",
// "expires": "-1",
// "x-powered-by": "Total.js",
// "content-type": "application/json; charset=utf-8",
// "date": "Mon, 21 Nov 2022 13:23:51 GMT",
// "connection": "close",
// "transfer-encoding": "chunked"
// },
// "host": "192.168.252.2:8004"
// }
// {
// "data": "{\"departures\":[[\"2\",\"3\",\"22.11.22 04:05\",\"Závodu míru\"],[\"290\",\"3\",\"22.11.22 04:25\",\"Závodu míru\"],[\"292\",\"3\",\"22.11.22 04:48\",\"Závodu míru\"],[\"296\",\"3\",\"22.11.22 04:58\",\"Závodu míru\"],[\"6\",\"3\",\"22.11.22 05:05\",\"Závodu míru\"],[\"300\",\"3\",\"22.11.22 05:18\",\"Závodu míru\"],[\"8\",\"3\",\"22.11.22 05:55\",\"Závodu míru\"],[\"16\",\"3\",\"22.11.22 06:30\",\"Závodu míru\"],[\"1\",\"33\",\"22.11.22 06:45\",\"sídl. Michal škola\"],[\"18\",\"3\",\"22.11.22 06:52\",\"Hrušková\"],[\"310\",\"3\",\"22.11.22 07:00\",\"Závodu míru\"],[\"22\",\"3\",\"22.11.22 07:10\",\"Závodu míru\"],[\"5\",\"33\",\"22.11.22 07:50\",\"sídl. Michal škola\"],[\"32\",\"3\",\"22.11.22 08:10\",\"Závodu míru\"],[\"7\",\"33\",\"22.11.22 08:25\",\"sídl. Michal škola\"],[\"100\",\"3\",\"22.11.22 08:30\",\"Závodu míru\"],[\"9\",\"33\",\"22.11.22 08:50\",\"sídl. Michal škola\"],[\"34\",\"3\",\"22.11.22 09:00\",\"Závodu míru\"],[\"38\",\"3\",\"22.11.22 09:20\",\"Závodu míru\"],[\"11\",\"33\",\"22.11.22 09:30\",\"sídl. Michal škola\"],[\"298\",\"3\",\"22.11.22 09:55\",\"Závodu míru\"],[\"2\",\"7\",\"22.11.22 10:10\",\"Březová, aut. st.\"],[\"13\",\"33\",\"22.11.22 10:20\",\"sídl. Michal škola\"],[\"48\",\"3\",\"22.11.22 10:50\",\"Závodu míru\"],[\"50\",\"3\",\"22.11.22 11:13\",\"Závodu míru\"],[\"15\",\"33\",\"22.11.22 11:25\",\"sídl. Michal škola\"],[\"52\",\"3\",\"22.11.22 11:40\",\"Závodu míru\"],[\"17\",\"33\",\"22.11.22 11:52\",\"sídl. Michal škola\"],[\"56\",\"3\",\"22.11.22 12:20\",\"Závodu míru\"],[\"62\",\"3\",\"22.11.22 12:40\",\"Závodu míru\"],[\"64\",\"3\",\"22.11.22 12:55\",\"Závodu míru\"],[\"19\",\"33\",\"22.11.22 13:00\",\"sídl. Michal škola\"],[\"66\",\"3\",\"22.11.22 13:20\",\"Stará ovčárna\"],[\"21\",\"33\",\"22.11.22 13:35\",\"sídl. Michal škola\"],[\"23\",\"33\",\"22.11.22 14:00\",\"sídl. Michal škola\"],[\"72\",\"3\",\"22.11.22 14:05\",\"Závodu míru\"],[\"25\",\"33\",\"22.11.22 14:42\",\"sídl. Michal škola\"],[\"94\",\"3\",\"22.11.22 14:48\",\"Závodu míru\"],[\"27\",\"33\",\"22.11.22 15:05\",\"sídl. Michal škola\"],[\"106\",\"3\",\"22.11.22 15:09\",\"Závodu míru\"],[\"84\",\"3\",\"22.11.22 15:35\",\"Závodu míru\"],[\"29\",\"33\",\"22.11.22 15:35\",\"sídl. Michal škola\"],[\"6\",\"7\",\"22.11.22 15:50\",\"Březová, aut. st.\"],[\"96\",\"3\",\"22.11.22 16:10\",\"Závodu míru\"],[\"31\",\"33\",\"22.11.22 16:10\",\"sídl. Michal škola\"],[\"4\",\"7\",\"22.11.22 16:25\",\"Březová, aut. st.\"],[\"102\",\"3\",\"22.11.22 16:30\",\"Závodu míru\"],[\"302\",\"3\",\"22.11.22 16:44\",\"Závodu míru\"],[\"8\",\"7\",\"22.11.22 16:55\",\"Březová, aut. st.\"],[\"108\",\"3\",\"22.11.22 17:05\",\"Stará ovčárna\"],[\"112\",\"3\",\"22.11.22 17:30\",\"Závodu míru\"],[\"114\",\"3\",\"22.11.22 17:53\",\"Závodu míru\"],[\"118\",\"3\",\"22.11.22 18:10\",\"Závodu míru\"],[\"120\",\"3\",\"22.11.22 18:30\",\"Závodu míru\"],[\"122\",\"3\",\"22.11.22 18:50\",\"Závodu míru\"],[\"124\",\"3\",\"22.11.22 19:15\",\"Závodu míru\"],[\"126\",\"3\",\"22.11.22 19:30\",\"Závodu míru\"],[\"130\",\"3\",\"22.11.22 19:50\",\"Závodu míru\"],[\"132\",\"3\",\"22.11.22 20:15\",\"Závodu míru\"],[\"134\",\"3\",\"22.11.22 20:45\",\"Závodu míru\"],[\"136\",\"3\",\"22.11.22 21:05\",\"Závodu míru\"],[\"256\",\"3\",\"22.11.22 21:20\",\"Stará ovčárna\"],[\"140\",\"3\",\"22.11.22 22:08\",\"Stará ovčárna\"]]}",
// "status": 200,
// "headers": {
// "cache-control": "private, no-cache, no-store, max-age=0",
// "vary": "Accept-Encoding, Last-Modified, User-Agent",
// "expires": "-1",
// "x-powered-by": "Total.js",
// "content-type": "application/json; charset=utf-8",
// "date": "Mon, 21 Nov 2022 13:23:51 GMT",
// "connection": "close",
// "transfer-encoding": "chunked"
// },
// "host": "192.168.252.2:8004"
// }

54
flow/check_if_new_day.js Normal file
View file

@ -0,0 +1,54 @@
exports.id = 'checknewday';
exports.title = 'Check if new day';
exports.group = 'Worksys';
exports.color = '#5D9CEC';
exports.version = '0.0.1';
exports.output = ['white'];
exports.input = true;
exports.author = 'Rastislav Kovac';
exports.icon = 'cloud-upload';
exports.readme = `Checks, if day changed. If yes, it sends it to connected components`;
const { execSync } = require('child_process');
const weekday = ["Nedele","Pondeli","Uteri","Streda","Ctvrtek","Patek","Sobota"];
function getDate() {
// for some reason new Date() function does not set month and year in local timezone, so we use "timedatectl" command
let dateFromCommand = execSync("timedatectl", {}).toString();
let first = dateFromCommand.search("time:");
let last = dateFromCommand.search(" CE");
dateFromCommand = dateFromCommand.slice(first, last); //Thu 2022-04-07 13:38:03
const d = new Date(dateFromCommand);
return d;
}
exports.install = function(instance) {
let date = null;
const checkNewDay = () =>
{
const d = getDate();
const today = d.getDate(); //napr 21 (dvadsiateho prveho)
if(today !== date)
{
date = today;
const month = d.getMonth(); // 0-11
const day = d.getDay(); // 0-6 (0 = nedela)
const datum = `${today}.${month + 1}`;
const result = [datum, weekday[day]];
instance.send(0, result); // ['22.12', 'Streda']
}
}
instance.on('data', flowdata => {
checkNewDay();
})
}

90
flow/code.js Normal file
View file

@ -0,0 +1,90 @@
exports.id = 'code';
exports.title = 'Code';
exports.group = 'Common';
exports.color = '#656D78';
exports.input = true;
exports.output = 1;
exports.author = 'Peter Širka';
exports.icon = 'code';
exports.version = '1.2.0';
exports.options = { outputs: 1, code: 'send(0, value);', keepmessage: true };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-3">
<div data-jc="textbox" data-jc-path="outputs" data-jc-config="type:number;validation:value > 0;increment:true;maxlength:3">@(Number of outputs)</div>
<div class="help m">@(Minimum is 1)</div>
</div>
</div>
<div data-jc="codemirror" data-jc-path="code" data-jc-config="type:javascript;required:true;height:500;tabs:true;trim:true" class="m">@(Code)</div>
<div data-jc="checkbox" data-jc-path="keepmessage">@(Keep message instance)</div>
</div>
<script>
var code_outputs_count;
ON('open.code', function(component, options) {
code_outputs_count = options.outputs = options.outputs || 1;
});
ON('save.code', function(component, options) {
if (code_outputs_count !== options.outputs) {
if (flow.version < 511) {
component.connections = {};
setState(MESSAGES.apply);
}
component.output = options.outputs || 1;
}
});
</script>`;
exports.readme = `# Code
This component executes custom JavaScript code as it is and it doesn't contain any secure scope.
\`\`\`javascript
// value {Object} contains received data
// send(outputIndex, newValue) sends a new value
// error(value) sends an error
// instance {Object} a current component instance
// flowdata {Object} a current flowdata
// repository {Object} a current repository of flowdata
// Example:
// send() can be execute multiple times
send(0, value);
\`\`\``;
exports.install = function(instance) {
var fn;
instance.on('data', function(response) {
if (fn) {
try {
fn(response.data, instance, response, instance.options, response.repository, require);
} catch (e) {
response.data = e;
instance.throw(response);
}
}
});
instance.reconfigure = function() {
try {
if (instance.options.code) {
instance.status('');
var code = 'var send = function(index, value) { if (options.keepmessage) { flowdata.data = value; instance.send2(index, flowdata); } else instance.send2(index, value);}; var error = function(err) { instance.throw(err); }; ' + instance.options.code;
fn = new Function('value', 'instance', 'flowdata', 'options', 'repository', 'require', code);
} else {
instance.status('Not configured', 'red');
fn = null;
}
} catch (e) {
fn = null;
instance.error('Code: ' + e.message);
}
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
};

11
flow/comment.js Normal file
View file

@ -0,0 +1,11 @@
exports.id = 'comment';
exports.title = 'Comment';
exports.group = 'Common';
exports.color = '#704cff';
exports.author = 'Martin Smola';
exports.icon = 'comment';
exports.traffic = false;
exports.version = '1.0.0';
exports.readme = '# Comment';
exports.install = function() {};

87
flow/csv_import.js Normal file
View file

@ -0,0 +1,87 @@
exports.id = 'csv_import';
exports.title = 'CsvImport';
exports.version = '1.0.0';
exports.group = 'Worksys';
exports.color = '#2134B0';
exports.input = 1;
exports.output = ["red", "white"];
exports.click = false;
exports.author = 'Daniel Segeš';
exports.icon = 'file-import';
exports.options = { edge: "undefined" };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">CSV Import</div>
</div>
</div>
</div>`;
exports.readme = `# load csv to table db`;
//config
let delimiter = ";";
let uniqueColumn = "node";
let path = "flow/audit_test_panel.csv";
let startFrom = 1;
let table = "nodes";
let mapImport = {
2: "node",
4: "tbname",
3: "line"
};
const fs = require('fs');
exports.install = function(instance) {
//console.log("csv import installed");
instance.on("close", () => {
})
instance.on("data", (flowdata) => {
var db = TABLE(table);
db.clear();
let keys = Object.keys(mapImport);
try {
const data = fs.readFileSync(path, 'utf8')
let lines = data.split("\n");
for(let i = startFrom; i < lines.length; i++)
{
let line = lines[i];
if(line === "") continue;
let data = line.split(delimiter);
if(data.length == 0) continue;
let insertData = {};
keys.map(function(key){
let k = mapImport[key];
insertData[k] = data[key];
});
//console.log(insertData);
db.insert(insertData, true).where(uniqueColumn, insertData[uniqueColumn]);
}
console.log("csv import finished");
instance.send(0, "csv import finished");
} catch (err) {
console.error(err)
}
})
}

216
flow/davkovac.js Normal file
View file

@ -0,0 +1,216 @@
exports.id = 'davkovac';
exports.title = 'Davkovac';
exports.version = '1.0.0';
exports.group = 'Worksys';
exports.color = '#5CB36D';
exports.output = ["red", "white"];
exports.input = true;
exports.author = 'Rastislav Kovac';
exports.icon = 'poo';
exports.options = { ip: '0.0.0.0', port: 8421, edge: "M6ogKQW09bOXewAYvZyvkn5JrV1aRnPGE37p42Nx" };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="ip" data-jc-config="placeholder:0.0.0.0;required:true" class="m">IP</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:8421;required:true" class="m">Port</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:M6ogKQW09bOXewAYvZyvkn5JrV1aRnPGE37p42Nx;required:true" class="m">Edge TB Name</div>
</div>
</div>
</div>`;
exports.readme = `# Davkovac
Prijima tcp spravy od klienta a posiela na TB
- *Red* - ERROR output
- *White* - Transormed message output
`;
exports.install = function(instance) {
let net = require('net');
let server = null;
let myip = "0.0.0.0";
let myport = 8421;
let myedge = "M6ogKQW09bOXewAYvZyvkn5JrV1aRnPGE37p42Nx";
let dataToTb;
let ERRWEIGHT = {
EMERGENCY: "emergency", // System unusable
ALERT: "alert", // Action must be taken immidiately
CRITICAL: "critical", // Component unable to function
ERROR: "error", // Error, but component able to recover from it
WARNING: "warning", // Possibility of error, system running futher
NOTICE: "notice", // Significant message but not an error, things user might want to know about
INFO: "informational", // Info
DEBUG: "debug" // Debug - only if CONFIG.debug is enabled
}
setTimeout(function(){
if (server !== null){
if (server.listening){
instance.status("Listening", "green");
} else {
instance.status("Not listening", "red");
}
}
}, 10000);
function resetServer(){
sendError(myedge, "resetServer", ERRWEIGHT.DEBUG, "resetServer called !", {});
if (server !== null){
sendError(myedge, "resetServer", ERRWEIGHT.DEBUG, "Server already exists", {});
server.close(function(){
sendError(myedge, "resetServer", ERRWEIGHT.DEBUG, "Server closed intentionally", {});
server = null;
resetServer();
});
} else {
sendError(myedge, "resetServer", ERRWEIGHT.DEBUG, "Server doesnt exist", {});
server = net.createServer(socket => {
sendError(myedge, "resetServer", ERRWEIGHT.INFO, "New client connected !", {"ip":socket.localAddress, "port":socket.localPort});
socket.on("data", (data) => {
console.log('data ', {"ip":socket.localAddress,"port":socket.localPort,"data": data});
//let bufferL = Buffer.byteLength(data, "utf-8");
//data = JSON.stringify(data);
//console.log(data, "data");
//console.log(bufferL, "Bufferlength");
data = JSON.parse(JSON.stringify(data));
let value = data.data;
//console.log("value",value)
if (Array.isArray(value) && value.length == 18)
{
const first = value[0];
const last = value[value.length-1];
if (first == 2 && last ==3)
{
let part1 = String.fromCharCode(value[10]);
let part2 = String.fromCharCode(value[11]);
let part3 = String.fromCharCode(value[12]);
let part4 = String.fromCharCode(value[13]);
let part5 = String.fromCharCode(value[14]);
let result = part1 + part2 + part3 + part4 + part5
result = parseInt(result);
//send(0, typeof result)
//send(0, result)
dataToTb = {
"mp93b2nvd7OoqgBeEyE7N18kjlAV1Y4ZNXwW0zLG": [
{
"ts": Date.now(),
"values": {dispenser_count: result}
}
]
}
instance.send(1, dataToTb);
}
}
});
socket.on('end', () => {
sendError(myedge, "resetServer", ERRWEIGHT.INFO, "Client disconnected !", {"ip":socket.localAddress, "port":socket.localPort});
});
}).on('error', (err) => {
console.log("[Davkovac error- resetServer] - ", err);
});
server.listen(myport, myip);
}
};
instance.reconfigure = function() {
//code
myip = instance.options.ip;
myport = instance.options.port;
myedge = instance.options.edge;
setTimeout(resetServer, 5000);
};
instance.close = function() {
// close sockets and such
if (server !== null){
server.close(function(){});
}
};
function resetCounterToZero() {
server = net.createServer(socket => {
socket.write([2,78,49,50,51,52,53,54,55,56,70,51,3])
socket.on("data", data => {
console.log({"ip":socket.localAddress,"port":socket.localPort,"data": data});
socket.end();
});
}).on('error', (err) => {
console.log("[Davkovac error - resetCounterToZero] - ", err);
});
server.listen(myport, myip);
}
instance.on("data", function(flowdata) {
if(server)
{
server.close();
resetCounterToZero();
resetServer();
}
else
{
resetCounterToZero();
resetServer();
}
});
function sendError(device, func, weight, str, extra){
let content = {
"type": weight,
"status": "new",
"source": {
"function":func,
"component":instance.id,
"component_name":instance.name
},
"message":str,
"message_data": extra
};
let error = {};
error[device] = [
{
"ts": Date.now(),
"values": {
"_event":content
}
}
];
instance.send(0, error);
}
instance.on('options', instance.reconfigure);
instance.reconfigure();
};

100
flow/debug.js Normal file
View file

@ -0,0 +1,100 @@
exports.id = 'debug';
exports.title = 'Debug';
exports.author = 'Peter Širka';
exports.color = '#967ADC';
exports.click = true;
exports.input = true;
exports.icon = 'bug';
exports.version = '2.0.4';
exports.options = { enabled: true, repository: false, type: 'data' };
exports.readme = `# Debug
Prints data to the debug tab.`;
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-12">
<div data-jc="dropdown" data-jc-path="type" data-jc-config="items:Message data|data,Message repository|repository,Message data + Message repository|both;required:true" class="m">@(Output type)</div>
<div data-jc="textbox" data-jc-path="property" data-jc-config="placeholder: @(e.g. address.street)" class="m">@(Path to the property (leave empty to show the whole data object))</div>
<div data-jc="textbox" data-jc-path="group" data-jc-config="placeholder: @(e.g. Temperature)" class="m">@(A group name)</div>
<div data-jc="checkbox" data-jc-path="enabled">@(Enabled)</div>
</div>
</div>
</div>`;
exports.install = function(instance) {
instance.on('data', function(response) {
if (instance.options.enabled) {
var opt = instance.options;
var rep = response.repository;
var val = response.data;
var id = response.id;
switch (instance.options.type){
case 'both':
var data = {};
data.repository = rep;
data.data = val instanceof Error ? { error: val.message, stack: val.stack } : val;
instance.debug(safeparse(opt.property ? U.get(data, opt.property) : data), undefined, opt.group, id);
break;
case 'repository':
instance.debug(safeparse(opt.property ? U.get(rep, opt.property) : rep), undefined, opt.group, id);
break;
case 'data':
default:
if (val instanceof Error)
instance.debug({ error: val.message, stack: val.stack }, undefined, opt.group, id);
else
instance.debug(safeparse(opt.property ? U.get(val, opt.property) : val), undefined, opt.group, id);
break;
}
}
});
instance.on('click', function() {
instance.options.enabled = !instance.options.enabled;
instance.custom.status();
instance.save();
});
instance.on('options', function() {
instance.custom.status();
});
instance.custom.status = function() {
instance.status(instance.options.enabled ? 'Enabled' : 'Disabled');
};
instance.custom.status();
function safeparse(o) {
if (o instanceof Buffer)
return o;
if (o === undefined)
return 'undefined';
if (o === null)
return 'null';
var cache = [];
var str = JSON.stringify(o, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
try {
return JSON.parse(JSON.stringify(value));
} catch (error) {
return;
}
}
cache.push(value);
}
return value;
});
cache = null;
return JSON.parse(str);
}
};

1931
flow/designer.json Normal file

File diff suppressed because it is too large Load diff

1905
flow/designer.json_oorig.txt Normal file

File diff suppressed because one or more lines are too long

951
flow/dido_controller_sbs.js Normal file
View file

@ -0,0 +1,951 @@
// https://medium.com/voodoo-engineering/websockets-on-production-with-node-js-bdc82d07bb9f
// https://evok-14.api-docs.io/1.11/bdymrhx7kgihpxpgm/websocket
exports.id = 'dido_controller_sbs';
exports.title = 'DI_DO_controller';
exports.group = 'Worksys';
exports.color = '#2134B0';
exports.input = 2;
exports.output = 3;
exports.icon = 'bolt';
exports.version = '1.0.3';
exports.readme = `# DI DO controller receives periodically status of digital inputs (state of main switch, rotary switch, state of braker)
It is also able to set new state for contactors (switch it off or on via digital output). Version v1.0.3 - after getting departures from sokolov server and temperature from services-prod01.worksys.io`;
const SEND_TO = {
debug: 0,
prod01: 1,
qas01: 2,
};
exports.install = function(instance) {
const SerialPort = require('serialport');
const WebSocket = require('ws');
const dbPins = TABLE("pins");
const { exec } = require('child_process');
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper');
const sunCalc = require('./helper/suncalc')
const WEBSOCKET_ADDRESS = 'ws:/10.0.0.30:1234/ws';
const WS_RECONNECT_DELAY = 5000;
const RSPORT_RECONNECT_DELAY = 5000;
let ws = null;
let previousValues = {};
let start;
let rsPort = null;
let rsPortReceivedData = [];
let pinsData = {};//key is pin
let tbName_prod;
function handleRsPort() {
if (rsPort) {
rsPort.removeAllListeners();
rsPort = null;
}
rsPort = new SerialPort("/dev/ttyACM0", { autoOpen: false });
rsPort.on('open', function() {
console.log("Setting up rsPort called !");
exec("stty -F /dev/ttyACM0 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke", (error, stdout, stderr) => {
console.log({ "stdout": stdout, "stderr": stderr, "err": error });
});
});
rsPort.on('data', function(data) {
rsPortReceivedData = [...rsPortReceivedData, ...data];
// console.log('rsport -----',rsPortReceivedData);
console.log("rsport ok");
if (rsPortReceivedData.length >= 11) {
instance.send(0, { "ADD": rsPortReceivedData.slice(0, 4), "RESP_STATUS": rsPortReceivedData.slice(4, 5), "DATA": rsPortReceivedData.slice(5, 9), "CRC": rsPortReceivedData.slice(9, 11) });
}
rsPortReceivedData = [];
});
rsPort.on('error', function(err) {
console.log("Error on rsPort", err);
});
rsPort.on("close", () => {
setTimeout(handleRsPort, RSPORT_RECONNECT_DELAY);
});
}
// pin:string|type:string|tbname_demo:string|tbname_qas01:string|tbname_prod01:string|contactor:number
// +|al_osvetlenie|state_of_relay|p2rwdP7aGoOQLJNgAynEdKD6xWXbmMe3nvZqlzkV|o8ZzVA4jrXLmRPnvGBkDDak6ayWbg32Y9KwdxqJN|nJL5lPMwBx23YpqRe0rpZ47damXvWVbOrD4gNzy8|1|.............
// +|al_defibrilator|state_of_relay|rQx3NGKgVMRaXYAo9y19OQyZzkWnj1le6bdOLE20|2qKyjDVBNowRvLzWxd5LBRk1JXY4mp9PA3gl6OGZ|XMBbew5z4ELrZa2mRAd3Q978vPN6gy3DdVYlpKjq|0|.............
async function loadAllDb() {
let responsePins = await promisifyBuilder(dbPins.find());
pinsData = makeMapFromDbResult(responsePins, "pin");
//console.log("-------pins data",pinsData);
// main tbname - can be accessed with anything else (breakers, ...)
tbName_prod = pinsData["al_dverovy_kontakt"].tbname_prod01;
handleRsPort();
//! for some reason mqtt server connects to tb for about 1 minute. Thats why we start websocket after 30 seconds
setTimeout(handleWebsocket, 15000);
}
setTimeout(loadAllDb, 15000);
function handleWebsocket() {
console.log("handleWebsocket function called");
if (ws) {
ws.removeAllListeners();
ws = null;
}
ws = new WebSocket(WEBSOCKET_ADDRESS);
ws.onopen = function open() {
//console.log('pins data', pinsData);
let relay;
let toSend;
//we switch off screen, when flow restarts, to make sure touchscreen works fine
//let cmd = { "cmd": "set", "dev": "relay", "circuit": "al_obrazovka", "value": 0 };
//ws.send(JSON.stringify(cmd));
//! turn on "al_poe_switch", "al_zasuvky", "al_obrazovka"
Object.keys(pinsData).map(item => {
toSend = false;
//if(["al_poe_switch", "al_zasuvky"].includes(item))
if (["al_poe_switch", "al_zasuvky", "al_obrazovka"].includes(item)) {
relay = 1;
toSend = true;
}
//else if(["al_osvetlenie", "al_defibrilator", "al_obrazovka"].includes(item))
else if (["al_osvetlenie", "al_defibrilator"].includes(item)) {
relay = 0;
toSend = true;
}
if (toSend) {
const values = {
"state_of_relay": relay,
"status": "OK"
}
let tbName = pinsData[item].tbname_prod01;
previousValues[item] = relay;
let cmd = { "cmd": "set", "dev": "relay", "circuit": item, "value": relay };
// console.log("--cmd ---------", cmd);
if (item == 'al_obrazovka') {
setTimeout(() => {
ws.send(JSON.stringify(cmd));
sendToTb(values, tbName);
}, 60000)
}
else {
ws.send(JSON.stringify(cmd));
sendToTb(values, tbName);
}
}
})
//ws.send(JSON.stringify({"cmd":"all"}))
startRequests();
};
ws.onmessage = function(data) {
data = JSON.parse(data.data);
//console.log("-------data web socket: ", data);
if (!Array.isArray(data)) return;
data.map(item => {
let value = item['value'];
let alias = item["alias"];
if (alias == undefined) return;
switchLogic(alias, value);
})
}
ws.on('error', (err) => {
console.log("Dido_controller_sbs: error on websocket, ", err);
})
ws.onclose = function() {
stopRequests();
console.log("ws connection closed, reconnecting in 5 seconds");
setTimeout(handleWebsocket, WS_RECONNECT_DELAY);
}
}
const startRequests = () => {
console.log("startRequest function called");
start = setInterval(() => {
// console.log("data from evok requested");
ws.send(JSON.stringify({ "cmd": "all" }));
// ws.send(JSON.stringify({"cmd":"filter", "devices":["input", "relay"]}));
}, 120000)
}
const stopRequests = () => {
console.log("stopRequests function called")
clearInterval(start);
}
instance.on("close", () => {
rsPort.close();
ws.close();
});
function generateCommand(rpcReceived) {
let values = deepGetByPaths(rpcReceived, 'content.data.params.entities[0].entity_type', 'content.data.params.entities[0].tb_name', 'content.data.params.payload.value');
let [entity_type, tb_name, value] = values;
if (value === undefined || tb_name === undefined || entity_type === undefined) {
console.log("Dido_controller_sbs: bad rpc received");
return;
}
value ? value = 1 : value = 0;
if (entity_type === "generic_relays") {
let entries = Object.entries(pinsData);
for (const [alias, data] of entries) {
//if(data.tbname_prod01 === tb_name || data.tbname_qas01 === tb_name) {
if (data.tbname_prod01 === tb_name) {
let cmd = { "cmd": "set", "dev": "relay", "circuit": alias, "value": value };
// console.log("--cmd ---------", cmd);
ws.send(JSON.stringify(cmd));
}
//}
}
}
}
//function gets value of a nested property in an object and returns undefined if it does not exists:
function getNested(obj, ...args) {
return args.reduce((obj, level) => obj && obj[level], obj)
}
const deepGet = (obj, keys) => keys.reduce((xs, x) => xs?.[x] ?? undefined, obj);
const deepGetByPaths = (obj, ...paths) =>
paths.map(path =>
deepGet(
obj,
path
.replace(/\[([^\[\]]*)\]/g, '.$1.')
.split('.')
.filter(t => t !== '')
)
);
instance.on("data", flowdata => {
//console.log('flowdaaata: ', flowdata.data);
if (flowdata.data instanceof Object) {
if (flowdata.data.hasOwnProperty("topic") && flowdata.data.hasOwnProperty("content")) {
const entity_type = deepGetByPaths(flowdata.data, 'content.data.params.entities[0].entity_type')[0];
if (entity_type == "bus_stop_mmcite") {
if (!rsPort.isOpen) {
handleRsPort();// console.log("openning rsport asyn");
//rsPort.open();
}
console.log("running ..");
let toSend = true;
const beginning = [0, 0, 0, 0, 0, 0, 0];
let value = getNested(flowdata.data, "content", "data", "params", "payload", "value");
if (value === undefined) console.log("Dido_controller_sbs: flowdata.data value from platform is undefined");
let bytes = [];
if (value == 0) {
bytes = [0, 0, 0, 1];
}
else {
value = value * 1000;
bytes[0] = (value >> 24) & 0xFF;
bytes[1] = (value >> 16) & 0xFF;
bytes[2] = (value >> 8) & 0xFF;
bytes[3] = value & 0xFF;
}
let finalCommand = beginning.concat(bytes);
console.log(finalCommand);
// maybe not necessary - I started from 1, to make 0,2s delay in execution - just to make sure rsPort is set.
for (let i = 1; i < 4; i++) {
setTimeout(function timer() {
finalCommand[6] = i - 1;
rsPort.write(Buffer.from(calculateCRC16(finalCommand)), function(err) {
if (err === undefined) {
// console.log("data zapisane do rsPortu");
if (toSend) {
const values = { dimming: value / 1000 };
sendToTb(values, tbName_prod);
toSend = false;
}
}
else {
console.log("rsPort WRITE error", err);
}
});
finalCommand.splice(11, 2);
}, i * 200);
}
}
else {
generateCommand(flowdata.data);
}
}
else {
ws.send(JSON.stringify(flowdata.data));
}
}
});
setInterval(reportTimeAndVersion, 300000);
function reportTimeAndVersion() {
let values = {};
if (previousValues["edge_fw_version"] != exports.version) {
values["edge_fw_version"] = exports.version;
previousValues["edge_fw_version"] = exports.version;
}
let ts = Date.now();
values["edge_date_time"] = ts - ts % 60000 //round to full minute
sendToTb(values, tbName_prod);
}
let times = null;
function getSunriseSunsetTimes() {
const d = new Date();
//to make sure times are calculated for local timezone with current day and months, we add "d.getFullYear(), d.getMonth(), d.getDate(), 12, 0, 0, 0, 0"
times = sunCalc.getTimes(new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12, 0, 0, 0, 0), '50.10', '12.38');
console.log("Sunrise and sunset times set: ", times);
}
// if new day comes, we get new dusk and dawn times
function checkForNewDay() {
// Get the current date and time
const currentDate = new Date();
// Get the date and time from 2 hours ago
const previousDate = new Date();
previousDate.setHours(currentDate.getHours() - 2);
// Compare the dates to check if a new day has started
if (currentDate.getDate() !== previousDate.getDate()) {
console.log("A new day has started!");
getSunriseSunsetTimes();
}
}
getSunriseSunsetTimes(); //to get times right after flow starts
setInterval(checkForNewDay, 2 * 60 * 60 * 1000);
function toggleBusLampIfSunriseOrSunset() {
const lampState = previousValues['al_osvetlenie'];
console.log('lampState', lampState)
const date = Date.now();
const sunrise = new Date(times.sunrise).getTime(); //1680236326458
const sunset = new Date(times.sunset).getTime(); //1680286387995
console.log(sunrise, sunset);
if (date > sunrise && date < sunset && lampState === 1) {
let cmd = { "cmd": "set", "dev": "relay", "circuit": "al_osvetlenie", "value": 0 };
ws.send(JSON.stringify(cmd));
console.log('---- Vypnute osvetlenie');
}
else if (date > sunset && lampState === 0) {
let cmd = { "cmd": "set", "dev": "relay", "circuit": "al_osvetlenie", "value": 1 };
ws.send(JSON.stringify(cmd));
console.log('++++ Zapnute osvetlenie');
}
else if (date < sunrise && lampState == 0) {
let cmd = { "cmd": "set", "dev": "relay", "circuit": "al_osvetlenie", "value": 1 };
ws.send(JSON.stringify(cmd));
console.log('---- Je novy den, nastavil sa novy sunset sunrise a zaplo sa osvetlenie');
}
else {
console.log('S osvetlenim sa nic nespravilo');
}
}
setTimeout(toggleBusLampIfSunriseOrSunset, 60000); // to switch light on, if neccessarry, after program starts, it must be after websocket started ( 30 seconds and more)
setInterval(toggleBusLampIfSunriseOrSunset, 900000); // than we check every 15 minutes if light needs to be turned on or off
const switchLogic = (alias, newValue) => {
let values = { status: "OK" };
let pinIndex = alias;
let newPinValue = newValue;
let obj = pinsData[pinIndex];
if (obj == undefined) {
previousValues[pinIndex] = newPinValue;
return;
}
let type = obj.type;
//default value
let value = true;
if (newPinValue === 0) value = false;
if (type == "state_of_relay") {
value ? value = 1 : value = 0;
pinsData[pinIndex].contactor = value;
}
values[obj.type] = value;
if (pinsData.hasOwnProperty(pinIndex)) {
let insertIntoTb = false;
if (newPinValue != previousValues[pinIndex]) insertIntoTb = true;
if (insertIntoTb) {
let tbName = obj.tbname_prod01;
sendToTb(values, tbName);
//pin was changed
previousValues[pinIndex] = newPinValue;
// console.log("previous ----", previousValues);
}
}
else {
console.log("no pinIndex", pinIndex, pinsData);
}
};
/**
* @param {object} values - values to be sent to TB qas01 and demo
*/
const sendToTb = (values, tbName) => {
const tbarray = [
{
"ts": Date.now(),
"values": values
}
];
let dataToTb = { [tbName]: tbarray };
instance.send(SEND_TO.prod01, dataToTb);
}
};
function calculateCRC16(bytes) {
let crc = 0;
let out = 0;
let CRC16 = 0x8005;
let bits_read = 0;
let bit_flag = 0;
for (let i = 0; i < bytes.length; i++) {
for (let j = 0; j < 8; j++) {
bit_flag = out >> 15;
out = out << 1;
out = out & 0xFFFF;
out = out | ((bytes[i] >> bits_read) & 1);
out = out & 0xFFFF;
bits_read = bits_read + 1;
if (bit_flag > 0) {
out = out ^ CRC16;
out = out & 0xFFFF;
}
}
bits_read = 0;
}
for (let i = 0; i < 16; i++) {
bit_flag = out >> 15;
out = out << 1;
out = out & 0xFFFF;
if (bit_flag > 0) {
out = out ^ CRC16;
out = out & 0xFFFF;
}
}
let i = 0x8000;
let j = 0x0001;
while (i > 0) {
if ((i & out) > 0) {
crc = crc | j;
}
i = i >> 1;
j = j << 1;
}
bytes.push((crc >> 8) & 0xFF);
bytes.push(crc & 0xFF);
return bytes;
}
const dimmer = {
"topic": "v1/gateway/rpc",
"content": {
"device": "L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1",
"data": {
"id": 11,
"method": "set_command",
"params": {
"entities": [
{
"entity_type": "bus_stop_mmcite",
"tb_name": "L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1"
}
],
"command": "dimming",
"payload": {
"value": 8
}
}
}
}
}
let rpcReceived = {
"topic": "v1/gateway/rpc",
"content": {
"device": "mp93b2nvd7OoqgBeEyE7N18kjlAV1Y4ZNXwW0zLG",
"data": {
"id": 46,
"method": "set_command",
"params": {
"entities": [
{
"entity_type": "generic_relays",
"tb_name": "9YkRpoB2vVa0mKqEO8Zr198jW43eXnJML6GxzbwQ"
}
],
"command": "switch",
"payload": {
"value": 1
}
}
}
}
}
const previous = {
al_breaker_12v: 1,
al_breaker_48: 1,
al_istic_obrazovka: 1,
al_istic_socket: 1,
al_dverovy_kontakt: 0,
'28667676E0013C21': 25.8,
temperature_out: 23.6,
'26A33E6802000081': 27.4
}
const pinsddata = {
al_osvetlenie: {
pin: 'al_osvetlenie',
type: 'state_of_relay',
tbname_demo: 'YnBzbeGaAL62jowRv59vVm8Xq9QpZ0K7O1dg4xVl',
tbname_qas01: 'o8ZzVA4jrXLmRPnvGBkDDak6ayWbg32Y9KwdxqJN',
contactor: 1
},
al_switch: {
pin: 'al_switch',
type: 'state_of_relay',
tbname_demo: 'zXBoWbEZjO0lrpqnRyoObvykmVeaNAGdL9g4QKxP',
tbname_qas01: '9rKRNEDXVYzWb0qZmlQjnqQn3jByxv42a6LPJ1oM',
contactor: 1
},
al_media_player: {
pin: 'al_media_player',
type: 'state_of_relay',
tbname_demo: 'p2rwdP7aGoOQLJNgAynE1wD6xWXbmMe3nvZqlzkV',
tbname_qas01: '2qKyjDVBNowRvLzWxd5LBRk1JXY4mp9PA3gl6OGZ',
contactor: 1
},
al_aibox: {
pin: 'al_aibox',
type: 'state_of_relay',
tbname_demo: 'rQx3NGKgVMRaXYAo9y19dpyZzkWnj1le6bdOLE20',
tbname_qas01: '1JD0MvzbwqAoZ36dPO7NBZ7LlmpgxGrBnNEK4Ry9',
contactor: 0
},
al_poe_switch1: {
pin: 'al_poe_switch1',
type: 'state_of_relay',
tbname_demo: 'nreBJ6PMqgz20pYEL82JeK8G1jkWwdQxZVNAOlmK',
tbname_qas01: '28LgqDR9braJKYmxd37EB95XBpEnyjNwPGAeO0o6',
contactor: 1
},
al_poe_switch2: {
pin: 'al_poe_switch2',
type: 'state_of_relay',
tbname_demo: 'klN4JpQAx362o9XYZDNPQ45grWw1P7GEbdBM0vRV',
tbname_qas01: 'P1Xabdx9AwGJr2WE4zQ2eV5NBmDMlvKoeLOqn8Z6',
contactor: 1
},
al_obrazovka1: {
pin: 'al_obrazovka1',
type: 'state_of_relay',
tbname_demo: 'ZmYXEbw9lVWRv1jLxDeJgzydgAMz4PKQnNJ6eB23',
tbname_qas01: 'dYBAenlq4zxv9jEZgaQqLq52ODyLoWKmGR06V1JP',
contactor: 1
},
al_obrazovka2: {
pin: 'al_obrazovka2',
type: 'state_of_relay',
tbname_demo: 'EonaKBOGbj9034MgJ8WvWe5qXvxNWVkAPQz21R6L',
tbname_qas01: 'GAqD3MNdpxwXVnj6z17J8yQrlKgZR0m9bB2aOWPY',
contactor: 1
},
al_breaker_12v: {
pin: 'al_breaker_12v',
type: 'breaker_12V_on',
tbname_demo: 'L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1',
tbname_qas01: '6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar',
contactor: 0
},
al_breaker_48v: {
pin: 'al_breaker_48v',
type: 'breaker_48V_on',
tbname_demo: 'L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1',
tbname_qas01: '6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar',
contactor: 0
},
al_istic_obrazovka: {
pin: 'al_istic_obrazovka',
type: 'screen_breaker_on',
tbname_demo: 'L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1',
tbname_qas01: '6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar',
contactor: 0
},
al_istic_socket: {
pin: 'al_istic_socket',
type: 'socket_breaker_on',
tbname_demo: 'L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1',
tbname_qas01: '6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar',
contactor: 0
},
al_dverovy_kontakt: {
pin: 'al_dverovy_kontakt',
type: 'door_condition',
tbname_demo: 'L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1',
tbname_qas01: '6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar',
contactor: 0
},
'26A33E6802000081': {
pin: '26A33E6802000081',
type: 'humidity_out',
tbname_demo: 'L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1',
tbname_qas01: '6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar',
contactor: 0
},
'28667676E0013C21': {
pin: '28667676E0013C21',
type: 'temperature',
tbname_demo: 'L2jNOVpdARa9XvoeJDPELbybkmPBxqn7Ww3gzGQ1',
tbname_qas01: '6nO4xlGE3zKVJdRXYZkMBK7BA28DoyMLg1pe9bar',
contactor: 0
}
}
const datawebsocket = [
{
glob_dev_id: 1,
modes: ['Simple'],
value: 0,
circuit: '1_08',
alias: 'al_obrazovka2',
pending: false,
relay_type: 'physical',
dev: 'relay',
mode: 'Simple'
},
{
glob_dev_id: 1,
modes: ['Simple'],
value: 0,
circuit: '1_01',
alias: 'al_osvetlenie',
pending: false,
relay_type: 'physical',
dev: 'relay',
mode: 'Simple'
},
{
glob_dev_id: 1,
modes: ['Simple'],
value: 0,
circuit: '1_02',
alias: 'al_switch',
pending: false,
relay_type: 'physical',
dev: 'relay',
mode: 'Simple'
},
{
glob_dev_id: 1,
modes: ['Simple'],
value: 1,
circuit: '1_03',
alias: 'al_media_player',
pending: false,
relay_type: 'physical',
dev: 'relay',
mode: 'Simple'
},
{
glob_dev_id: 1,
modes: ['Simple'],
value: 1,
circuit: '1_04',
alias: 'al_aibox',
pending: false,
relay_type: 'physical',
dev: 'relay',
mode: 'Simple'
},
{
glob_dev_id: 1,
modes: ['Simple'],
value: 1,
circuit: '1_05',
alias: 'al_poe_switch1',
pending: false,
relay_type: 'physical',
dev: 'relay',
mode: 'Simple'
},
{
glob_dev_id: 1,
modes: ['Simple'],
value: 1,
circuit: '1_06',
alias: 'al_poe_switch2',
pending: false,
relay_type: 'physical',
dev: 'relay',
mode: 'Simple'
},
{
glob_dev_id: 1,
modes: ['Simple'],
value: 0,
circuit: '1_07',
alias: 'al_obrazovka1',
pending: false,
relay_type: 'physical',
dev: 'relay',
mode: 'Simple'
},
{
counter_modes: ['Enabled', 'Disabled'],
glob_dev_id: 1,
modes: ['Simple', 'DirectSwitch'],
value: 0,
circuit: '1_08',
debounce: 50,
counter: 0,
counter_mode: 'Enabled',
dev: 'input',
mode: 'Simple'
},
{
counter_mode: 'Enabled',
counter_modes: ['Enabled', 'Disabled'],
glob_dev_id: 1,
dev: 'input',
modes: ['Simple', 'DirectSwitch'],
debounce: 50,
counter: 0,
value: 0,
alias: 'al_breaker_12v',
mode: 'Simple',
circuit: '1_01'
},
{
counter_mode: 'Enabled',
counter_modes: ['Enabled', 'Disabled'],
glob_dev_id: 1,
dev: 'input',
modes: ['Simple', 'DirectSwitch'],
debounce: 50,
counter: 0,
value: 0,
alias: 'al_breaker_48',
mode: 'Simple',
circuit: '1_02'
},
{
counter_mode: 'Enabled',
counter_modes: ['Enabled', 'Disabled'],
glob_dev_id: 1,
dev: 'input',
modes: ['Simple', 'DirectSwitch'],
debounce: 50,
counter: 0,
value: 0,
alias: 'al_istic_obrazovka',
mode: 'Simple',
circuit: '1_03'
},
{
counter_mode: 'Enabled',
counter_modes: ['Enabled', 'Disabled'],
glob_dev_id: 1,
dev: 'input',
modes: ['Simple', 'DirectSwitch'],
debounce: 50,
counter: 0,
value: 0,
alias: 'al_istic_socket',
mode: 'Simple',
circuit: '1_04'
},
{
counter_modes: ['Enabled', 'Disabled'],
glob_dev_id: 1,
modes: ['Simple', 'DirectSwitch'],
value: 0,
circuit: '1_05',
debounce: 50,
counter: 0,
counter_mode: 'Enabled',
dev: 'input',
mode: 'Simple'
},
{
counter_modes: ['Enabled', 'Disabled'],
glob_dev_id: 1,
modes: ['Simple', 'DirectSwitch'],
value: 0,
circuit: '1_06',
debounce: 50,
counter: 0,
counter_mode: 'Enabled',
dev: 'input',
mode: 'Simple'
},
{
counter_modes: ['Enabled', 'Disabled'],
glob_dev_id: 1,
modes: ['Simple', 'DirectSwitch'],
value: 0,
circuit: '1_07',
debounce: 50,
counter: 0,
counter_mode: 'Enabled',
dev: 'input',
mode: 'Simple'
},
{
interval: 3,
value: 24.75,
circuit: '28667676E0013C21',
address: '28667676E0013C21',
time: 1645103817.127406,
typ: 'DS18B20',
lost: false,
dev: 'temp'
},
{
vis: '0.0209926',
dev: '1wdevice',
typ: 'DS2438',
lost: false,
temp: '24.7812',
interval: 3,
vad: '1.6',
humidity: 23.710019217090384,
vdd: '5.29',
circuit: '26A33E6802000081',
time: 1645103815.074027
},
{
bus: '/dev/i2c-2',
interval: 3,
dev: 'owbus',
scan_interval: 300,
circuit: '1',
do_scan: false,
do_reset: false
},
{
glob_dev_id: 1,
last_comm: 0.012907028198242188,
ver2: '0.1',
sn: 162,
circuit: '1',
model: 'S207',
dev: 'neuron',
board_count: 1
},
{
circuit: '1_01',
value: 0,
glob_dev_id: 1,
dev: 'wd',
timeout: 5000,
was_wd_reset: 0,
nv_save: 0
}
]

137
flow/function.js Normal file
View file

@ -0,0 +1,137 @@
exports.id = 'function';
exports.title = 'Function';
exports.group = 'Common';
exports.color = '#656D78';
exports.icon = 'code';
exports.input = true;
exports.output = 1;
exports.version = '1.1.3';
exports.author = 'Martin Smola';
exports.options = {
outputs: 1,
code: 'send(\'Hello world!\');'
};
exports.readme = `# Function
Allows you to do sync operation on data. If \`send\` function isn't called the data flow will not continue.
__Custom function__:
\`\`\`javascript
data; // received data
send; // send data to next component, optionaly specify output index -> send(0, data);
instance; // ref to value.instance, available methods get, set, rem for storing temporary data related to this instance of Function component and debug, status and error for sending data to designer
global; // ref to value.global, available methods get, set, rem for storing persistent data globally accessible in any component
flowdata; // ref to value.flowdata, instance of FlowData - available methods get, set, rem for storing temporary data related to current flow
flowdata.data; // user defined data recieved from previous component
// Example:
send('Hello world.'); // sends data to all outputs
send(0, 'Hello world.'); // sends data only to first output
// Calling send without any argument will pass incomming data to next components
send();
\`\`\``;
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-3">
<div data-jc="textbox" data-jc-path="outputs" data-jc-config="type:number;validation:value > 0;increment:true;maxlength:3">@(Number of outputs)</div>
<div class="help m">@(Minimum is 1)</div>
</div>
</div>
<div data-jc="codemirror" data-jc-path="code" data-jc-config="type:javascript;required:true;height:500">@(Code)</div>
</div>
<script>
var function_outputs_count;
ON('open.function', function(component, options) {
function_outputs_count = options.outputs = options.outputs || 1;
});
ON('save.function', function(component, options) {
if (function_outputs_count !== options.outputs) {
if (flow.version < 511) {
component.connections = {};
setState(MESSAGES.apply);
}
component.output = options.outputs > 0 ? options.outputs : 1;
}
});
</script>`;
exports.install = function(instance) {
var fn;
var ready = false;
var VALUE = {
instance: {
get: instance.get.bind(instance),
set: instance.set.bind(instance),
rem: instance.rem.bind(instance),
error: instance.error.bind(instance),
debug: instance.debug.bind(instance),
status: instance.status.bind(instance),
send: function(flowdata, index, data){
if (data === undefined) {
flowdata = flowdata.clone();
flowdata.data = index;
instance.send2(flowdata);
} else {
flowdata = flowdata.clone();
flowdata.data = data;
instance.send2(index, flowdata);
}
}
},
global: {
get: FLOW.get,
set: FLOW.set,
rem: FLOW.rem,
variable: FLOW.variable
},
Date: Date,
Object: Object
};
instance.custom.reconfigure = function() {
if (F.is4) {
fn = new Function('value', 'next', 'var model=value;var now=function(){return new Date()};var instance=value.instance;var flowdata=value.flowdata;var data=flowdata.data;var global=value.global;var send=function(index,data){value.instance.send(value.flowdata,index,data)};try{' + instance.options.code + '}catch(e){next(e)}');
} else {
fn = SCRIPT(`
var instance = value.instance;
var flowdata = value.flowdata;
var data = flowdata.data;
var Date = value.Date;
var Object = value.Object;
var global = value.global;
var send = function(index, data){
value.instance.send(value.flowdata, index, data);
}
${instance.options.code}
next(value);
`);
}
if (typeof(fn) !== 'function') {
ready = false;
instance.error(fn.message);
return;
}
ready = true;
};
instance.on('data', function(flowdata) {
VALUE.flowdata = flowdata;
ready && fn(VALUE, function(err) {
if (err)
return instance.error('Error while processing function ' + err);
});
});
instance.on('options', instance.custom.reconfigure);
instance.custom.reconfigure();
};

613
flow/get_departures.js Normal file

File diff suppressed because one or more lines are too long

68
flow/gettemperature.js Normal file
View file

@ -0,0 +1,68 @@
exports.id = 'gettemperature';
exports.title = 'Get RVO temperature';
exports.group = 'Worksys';
exports.color = '#5CB36D';
exports.version = '1.0.2';
exports.output = ["red", "white"];
exports.author = 'Rastislav Kovac';
exports.icon = 'thermometer-three-quarters';
exports.readme = `# Getting temperature values from RVO`;
exports.install = function(instance) {
const { exec } = require('child_process');
let startRead;
let dataToTb;
let counter;
instance.on("close", function(){
clearInterval(startRead);
})
const start = function(){
//console.log("start function called");
exec("owread -C 28.427B45920702/temperature", (error, stdout, stderr) => {
parseData(stdout);
//instance.send({"Temp":stdout,"stderr":stderr,"err":error});
});
}
const parseData = function(data) {
data = parseFloat(data);
if (!isNaN(data)){
if ( counter > 290 ) {
instance.send(0, "[Get temperature component] - temperature data are comming again from RVO after more than 1 day break");
}
dataToTb = {
"KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV": [
{
"ts": Date.now(),
"values": {
"temperature": data.toFixed(2)
}
}
]
}
instance.send(1, dataToTb);
counter = 0;
} else {
counter++;
if ( counter > 288 && counter < 290 ) {
instance.send(0, "[Get temperature component] - no temperature data from RVO for more than 1 day");
}
}
}
start();
startRead = setInterval(start, 300000);
};

1261
flow/handledepartures.js Normal file

File diff suppressed because it is too large Load diff

29
flow/helper/db_helper.js Normal file
View file

@ -0,0 +1,29 @@
function promisifyBuilder(builder)
{
return new Promise((resolve, reject) => {
builder.callback(function(err, response) {
if(err != null) reject(err);
resolve(response);
});
})
}
function makeMapFromDbResult(response, key)
{
let data = {};
for(let i = 0; i < response.length; i++)
{
let record = response[i];
data[ record[key] ] = record;
}
return data;
}
module.exports = {
promisifyBuilder,
makeMapFromDbResult
}

View file

@ -0,0 +1,67 @@
//key is device, value = str
let sentValues= {};
function sendError(func, device, weight, str, extra, tb_output, instance) {
// if ((weight === ERRWEIGHT.DEBUG) && (instance.CONFIG.debug === false)){
// return; // Allow debug messages only if CONFIG.debug is active
// }
let sendFlag = true;
if(sentValues.hasOwnProperty(device))
{
if(sentValues[device] == str) return;
}
sentValues[device] = str;
let content = {
"type": weight,
"status": "new",
"source": {
"func":func,
"component":instance.id,
"component_name":instance.name,
"edge":device
},
"message":str,
"message_data": extra
};
let msg = {};
msg[device] = [
{
"ts": Date.now(),
"values": {
"_event":content
}
}
];
// Msg can be outputted from components only after configuration
/*if (canSendErrData()){
sendBufferedErrors();
} else {
bufferError(msg);
}*/
instance.send(tb_output, msg); // Even if error server is unavailable, send this message to output, for other possible component connections
}
let ERRWEIGHT = {
EMERGENCY: "emergency", // System unusable
ALERT: "alert", // Action must be taken immidiately
CRITICAL: "critical", // Component unable to function
ERROR: "error", // Error, but component able to recover from it
WARNING: "warning", // Possibility of error, system running futher
NOTICE: "notice", // Significant message but not an error, things user might want to know about
INFO: "informational", // Info
DEBUG: "debug" // Debug - only if CONFIG.debug is enabled
};
module.exports = {
sendError,
ERRWEIGHT
}

View file

@ -0,0 +1,86 @@
const { exec } = require('child_process');
function openPort(port){
return new Promise((resolve, reject) => {
var callbackError = function(err) {
port.removeListener('error', callbackError);
port.removeListener('open', callbackError);
reject(err.message);
};
var callbackOpen = function(data) {
port.removeListener('error', callbackError);
port.removeListener('open', callbackOpen);
resolve("port open: ok");
};
port.on('error', callbackError);
port.on('open', callbackOpen);
port.open();
})
}
function runSyncExec(command){
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if(error == null) resolve(stdout);
reject(error);
});
})
}
async function writeData(port, data, readbytes, timeout){
return new Promise((resolve, reject) => {
if(readbytes == undefined) readbytes = 0;
if(timeout == undefined) timeout = 15000;
var callback = function(data) {
rsPortReceivedData.push(...data);
let l = rsPortReceivedData.length;
if(l >= readbytes)
{
port.removeListener('data', callback);
clearTimeout(t);
resolve(rsPortReceivedData);
}
};
let t = setTimeout(() => {
port.removeListener('data', callback);
reject("TIMEOUT READING");
}, timeout);
let rsPortReceivedData = [];
if(readbytes > 0) port.on('data', callback);
port.write(Buffer.from(data), function(err) {
if (err) {
port.removeListener('data', callback);
reject(err.message);
}
if(readbytes == 0)
{
resolve(rsPortReceivedData);
}
});
})
}
module.exports = {
openPort,
runSyncExec,
writeData
}

317
flow/helper/suncalc.js Normal file
View file

@ -0,0 +1,317 @@
/*
(c) 2011-2015, Vladimir Agafonkin
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
https://github.com/mourner/suncalc
*/
(function () { 'use strict';
// shortcuts for easier to read formulas
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180;
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
// date/time constants and conversions
var dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588,
J2000 = 2451545;
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
function toDays(date) { return toJulian(date) - J2000; }
// general calculations for position
var e = rad * 23.4397; // obliquity of the Earth
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
function astroRefraction(h) {
if (h < 0) // the following formula works for positive altitudes only.
h = 0; // if h = -0.08901179 a div/0 would occur.
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
}
// general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
function eclipticLongitude(M) {
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
P = rad * 102.9372; // perihelion of the Earth
return M + C + P + PI;
}
function sunCoords(d) {
var M = solarMeanAnomaly(d),
L = eclipticLongitude(M);
return {
dec: declination(L, 0),
ra: rightAscension(L, 0)
};
}
var SunCalc = {};
// calculates sun position for a given date and latitude/longitude
SunCalc.getPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = sunCoords(d),
H = siderealTime(d, lw) - c.ra;
return {
azimuth: azimuth(H, phi, c.dec),
altitude: altitude(H, phi, c.dec)
};
};
// sun times configuration (angle, morning name, evening name)
var times = SunCalc.times = [
[-0.833, 'sunrise', 'sunset' ],
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
[ -6, 'dawn', 'dusk' ],
[ -12, 'nauticalDawn', 'nauticalDusk'],
[ -18, 'nightEnd', 'night' ],
[ 6, 'goldenHourEnd', 'goldenHour' ]
];
// adds a custom time to the times config
SunCalc.addTime = function (angle, riseName, setName) {
times.push([angle, riseName, setName]);
};
// calculations for sun times
var J0 = 0.0009;
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
// returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L) {
var w = hourAngle(h, phi, dec),
a = approxTransit(w, lw, n);
return solarTransitJ(a, M, L);
}
// calculates sun times for a given date, latitude/longitude, and, optionally,
// the observer height (in meters) relative to the horizon
SunCalc.getTimes = function (date, lat, lng, height) {
height = height || 0;
var lw = rad * -lng,
phi = rad * lat,
dh = observerAngle(height),
d = toDays(date),
n = julianCycle(d, lw),
ds = approxTransit(0, lw, n),
M = solarMeanAnomaly(ds),
L = eclipticLongitude(M),
dec = declination(L, 0),
Jnoon = solarTransitJ(ds, M, L),
i, len, time, h0, Jset, Jrise;
var result = {
solarNoon: fromJulian(Jnoon),
nadir: fromJulian(Jnoon - 0.5)
};
for (i = 0, len = times.length; i < len; i += 1) {
time = times[i];
h0 = (time[0] + dh) * rad;
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);
result[time[1]] = fromJulian(Jrise);
result[time[2]] = fromJulian(Jset);
}
return result;
};
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
M = rad * (134.963 + 13.064993 * d), // mean anomaly
F = rad * (93.272 + 13.229350 * d), // mean distance
l = L + rad * 6.289 * sin(M), // longitude
b = rad * 5.128 * sin(F), // latitude
dt = 385001 - 20905 * cos(M); // distance to the moon in km
return {
ra: rightAscension(l, b),
dec: declination(l, b),
dist: dt
};
}
SunCalc.getMoonPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = moonCoords(d),
H = siderealTime(d, lw) - c.ra,
h = altitude(H, phi, c.dec),
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
h = h + astroRefraction(h); // altitude correction for refraction
return {
azimuth: azimuth(H, phi, c.dec),
altitude: h,
distance: c.dist,
parallacticAngle: pa
};
};
// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
SunCalc.getMoonIllumination = function (date) {
var d = toDays(date || new Date()),
s = sunCoords(d),
m = moonCoords(d),
sdist = 149598000, // distance from Earth to Sun in km
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
return {
fraction: (1 + cos(inc)) / 2,
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
angle: angle
};
};
function hoursLater(date, h) {
return new Date(date.valueOf() + h * dayMs / 24);
}
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
var t = new Date(date);
if (inUTC) t.setUTCHours(0, 0, 0, 0);
else t.setHours(0, 0, 0, 0);
var hc = 0.133 * rad,
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
for (var i = 1; i <= 24; i += 2) {
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
a = (h0 + h2) / 2 - h1;
b = (h2 - h0) / 2;
xe = -b / (2 * a);
ye = (a * xe + b) * xe + h1;
d = b * b - 4 * a * h1;
roots = 0;
if (d >= 0) {
dx = Math.sqrt(d) / (Math.abs(a) * 2);
x1 = xe - dx;
x2 = xe + dx;
if (Math.abs(x1) <= 1) roots++;
if (Math.abs(x2) <= 1) roots++;
if (x1 < -1) x1 = x2;
}
if (roots === 1) {
if (h0 < 0) rise = i + x1;
else set = i + x1;
} else if (roots === 2) {
rise = i + (ye < 0 ? x2 : x1);
set = i + (ye < 0 ? x1 : x2);
}
if (rise && set) break;
h0 = h2;
}
var result = {};
if (rise) result.rise = hoursLater(t, rise);
if (set) result.set = hoursLater(t, set);
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
return result;
};
// export as Node module / AMD module / browser variable
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
else if (typeof define === 'function' && define.amd) define(SunCalc);
else window.SunCalc = SunCalc;
}());

238
flow/httprequest.js Normal file
View file

@ -0,0 +1,238 @@
exports.id = 'httprequest';
exports.title = 'HTTP Request';
exports.group = 'HTTP';
exports.color = '#5D9CEC';
exports.input = true;
exports.version = '2.0.6';
exports.output = 1;
exports.author = 'Peter Širka';
exports.icon = 'cloud-upload';
exports.html = `<div class="padding">
<div data-jc="textbox" data-jc-path="url" class="m" data-jc-config="required:true;maxlength:500;placeholder:@(E.g. https\\://www.totaljs.com)">@(URL address)</div>
<div class="row">
<div class="col-md-6 m">
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE">@(HTTP method)</div>
</div>
<div class="col-md-6 m">
<div data-jc="dropdown" data-jc-path="stringify" data-jc-config="required:true;items:,URL encoded|encoded,JSON|json,RAW|raw,None|none">@(Serialization)</div>
</div>
</div>
<div data-jc="checkbox" data-jc-path="chunks">@(Download the content <b>in chunks</b>)</div>
<div data-jc="checkbox" data-jc-path="persistentcookies">@(Keep persistent cookies)</div>
<div data-jc="checkbox" data-jc-path="nodns">@(Disable DNS cache)</div>
<div data-jc="checkbox" data-jc-path="keepalive">@(Keep alive connection)</div>
<div data-jc="checkbox" data-jc-path="keepmessage">@(Keep message instance)</div>
</div>
<hr class="nmt nmb" />
<div class="padding">
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
</div>
<div class="padding bg-smoke">
<section>
<label><i class="fa fa-lock"></i>@(HTTP basic access authentication)</label>
<div class="padding npb">
<div class="row">
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="username">@(User)</div>
</div>
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="userpassword">@(Password)</div>
</div>
</div>
</div>
</section>
</div>`;
exports.readme = `# Request
This component creates a request with received data.
__Response:__
\`\`\`javascript
{
data: String,
headers: Object,
status: Number,
host: String
}
\`\`\`
__Dynamic arguments__:
Are performed via FlowData repository and can be used for URL address or for custom headers/cookies/auth. Use \`repository\` component for creating of dynamic arguments. Dynamic values are replaced in the form \`{key}\`:
- url address e.g. \`https://.../{key}/\`
- headers values e.g. \`{token}\`
- cookies values e.g. \`{token}\``;
exports.install = function(instance) {
var can = false;
var flags = null;
var cookies2 = null;
instance.on('data', function(response) {
can && instance.custom.send(response);
});
instance.custom.send = function(flowdata) {
var options = instance.options;
var headers = null;
var cookies = null;
if (options.headers) {
headers = {};
for (var key in options.headers)
headers[key] = flowdata.arg(options.headers[key]);
}
if (options.username && options.userpassword) {
!headers && (headers = {});
headers.Authorization = 'Basic ' + U.createBuffer(flowdata.arg(options.username + ':' + options.userpassword)).toString('base64');
}
if (options.cookies) {
for (var key in options.cookies) {
!cookies && (cookies = {});
cookies[key] = flowdata.arg(options.cookies[key]);
}
}
if (F.is4) {
var opt = {};
opt.method = options.method;
opt.url = options.url;
opt.headers = headers;
opt.cookies = cookies;
if (options.keepalive)
opt.keepalive = true;
opt.dnscache = options.nodns ? false : true;
if (options.chunks) {
opt.custom = true;
opt.callback = function(err, response) {
if (err)
instance.error(err);
else if (response && response.stream) {
response.stream.on('data', function(chunks) {
if (options.keepmessage) {
flowdata.data = chunks;
instance.send2(flowdata);
} else
instance.send2(chunks);
});
}
};
} else {
opt.callback = function(err, response) {
if (response && !err) {
var msg = { data: response.body, status: response.status, headers: response.headers, host: response.host, cookies: response.cookies };
if (options.keepmessage) {
flowdata.data = msg;
instance.send2(flowdata);
} else
instance.send2(msg);
} else if (err)
instance.error(err, response);
};
}
switch (options.stringify) {
case 'json':
opt.body = JSON.stringify(flowdata.data);
opt.type = 'json';
break;
case 'raw':
opt.body = flowdata.data instanceof Buffer ? flowdata.data : Buffer.from(flowdata.data);
opt.type = 'raw';
break;
case 'encoded':
if (opt.method === 'GET' || opt.method === 'HEAD') {
opt.query = U.toURLEncode(flowdata.data);
} else {
opt.body = U.toURLEncode(flowdata.data);
opt.type = 'urlencoded';
}
break;
}
REQUEST(opt);
} else {
if (options.chunks) {
U.download(flowdata.arg(options.url), flags, options.stringify === 'none' ? null : flowdata.data, function(err, response) {
response.on('data', function(chunks) {
if (options.keepmessage) {
flowdata.data = chunks;
instance.send2(flowdata);
} else
instance.send2(chunks);
});
}, cookies || cookies2, headers);
} else {
U.request(flowdata.arg(options.url), flags, options.stringify === 'none' ? null : flowdata.data, function(err, data, status, headers, host) {
if (flowdata && !err) {
var msg = { data: data, status: status, headers: headers, host: host };
if (options.keepmessage) {
flowdata.data = msg;
instance.send2(flowdata);
} else
instance.send2(msg);
} else if (err)
instance.error(err, flowdata);
}, cookies || cookies2, headers);
}
}
};
instance.reconfigure = function() {
var options = instance.options;
can = options.url && options.url && options.method && options.stringify ? true : false;
instance.status(can ? '' : 'Not configured', can ? undefined : 'red');
if (!can)
return;
if (F.is4) {
flags = {};
flags.method = options.method.toUpperCase();
if (!options.nodns)
flags.resolve = true;
flags.keepalive = options.keepalive;
if (options.stringify && options.stringify !== 'none')
options.type = options.stringify;
if (options.persistentcookies) {
flags.cook = true;
cookies2 = {};
} else
cookies2 = null;
} else {
flags = [];
flags.push(options.method.toLowerCase());
options.stringify === 'json' && flags.push('json');
options.stringify === 'raw' && flags.push('raw');
options.keepalive && flags.push('keepalive');
!options.nodns && flags.push('dnscache');
if (options.persistentcookies) {
flags.push('cookies');
cookies2 = {};
} else
cookies2 = null;
}
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
};

76
flow/httpresponse.js Normal file
View file

@ -0,0 +1,76 @@
exports.id = 'httpresponse';
exports.title = 'HTTP Response';
exports.group = 'HTTP';
exports.color = '#5D9CEC';
exports.icon = 'arrow-right';
exports.input = true;
exports.output = ['#666D76'];
exports.version = '2.0.0';
exports.author = 'Martin Smola';
exports.readme = `# HTTP response
HTTP response will respond with data recieved using data-type set in Settings form or plain text if not set. Output is the message duration \`Number\` in seconds.`;
exports.html = `<div class="padding">
<div data-jc="dropdown" data-jc-path="datatype" data-jc-config="required:true;items:,Empty response|emptyresponse,JSON|json,HTML|html,Plain text|plain,XML|xml">@(Response data-type)</div>
<div class="help"><code>JSON</code> is by default.</div>
</div>`;
exports.install = function(instance) {
var dursum = 0;
var durcount = 0;
instance.on('data', function(flowdata) {
var ctrl = flowdata.repository.controller;
var data = flowdata.data;
if (!ctrl) {
instance.throw('No controller to use for response!');
return;
}
durcount++;
dursum += ((new Date() - flowdata.begin) / 1000).floor(2);
setTimeout2(instance.id, instance.custom.duration, 500, 10);
ctrl.$flowdata = flowdata;
var datatype = instance.options.datatype || 'json';
if (datatype === 'emptyresponse')
return ctrl.plain('');
if (datatype !== 'json' && typeof(data) !== 'string') {
instance.throw('Incorect type of data, expected string, got ' + typeof(data));
ctrl.plain(data == null ? '' : data.toString());
return;
}
switch(datatype) {
case 'html':
ctrl.content(data, 'text/html');
break;
case 'plain':
ctrl.plain(data);
break;
case 'xml':
ctrl.content(data, 'text/xml');
break;
default:
ctrl.json(data);
break;
}
});
instance.on('service', function() {
dursum = 0;
durcount = 0;
});
instance.custom.duration = function() {
var avg = (dursum / durcount).floor(2);
instance.status(avg + ' sec.');
instance.send2(0, avg);
};
};

326
flow/httproute.js Normal file
View file

@ -0,0 +1,326 @@
exports.id = 'httproute';
exports.title = 'HTTP Route';
exports.group = 'HTTP';
exports.color = '#5D9CEC';
exports.icon = 'globe';
exports.input = false;
exports.output = ['#6CAC5A', '#37BC9B'];
exports.version = '1.2.3';
exports.author = 'Martin Smola';
exports.cloning = false;
exports.options = { method: 'GET', url: '', size: 5, cacheexpire: '5 minutes', cachepolicy: 0, timeout: 5 };
exports.dateupdated = '2021-01-21 18:30d';
exports.readme = `# HTTP route
__Outputs__:
- first output: raw data (cache is empty or is disabled)
- second output: cached data
If one of the outputs is disabled then automatic responce with code "503 service unavailable" is sent.
When a request comes in bellow object is available at \`flowdata.data\`:
\`\`\`javascript
{
params: { id: '1' }, // params for dynamic routes, e.g. /test/{id}
query: { msg: 'Hello' }, // parsed query string, e.g. /test/1?msg=Hello
body: { test: 'OK' }, // object if json requests otherwise string
headers: {}, // headers data
session: {}, // session data
user: {}, // user data
files: [], // uploaded files
url: '/users/', // a relative URL address
referrer: '/', // referrer
mobile: false, // determines mobile device
robot: false, // determines search robots/crawlsers
language: 'en' // determines language
}
\`\`\`
See [documentation for flags](https://docs.totaljs.com/latest/en.html#api~HttpRouteOptionsFlags~unauthorize). These method flags are set automatically e.g. \`get, post, put, patch or delete\`
---
\`id:ROUTE_ID\` flag cannot be used since it's already used by this component internally`;
exports.html = `<div class="padding">
<section>
<label>@(Main settings)</label>
<div class="padding npb">
<div class="row">
<div class="col-md-3 m">
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE,PATCH,OPTIONS">@(HTTP method)</div>
</div>
<div class="col-md-9 m">
<div data-jc="textbox" data-jc-path="url" data-jc-config="required:true;maxlength:500;placeholder:/api/test;error:URL already in use">@(URL address)</div>
</div>
</div>
<div class="row">
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="flags" data-jc-config="placeholder:json">@(Additional flags)</div>
<div class="help m">@(Separate flags by comma e.g. <code>json, authorize</code>)</div>
</div>
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="size" data-jc-config="placeholder:@(in kB);increment:true;type:number;maxlength:10;align:center">@(Max. request size)</div>
<div class="help m">@(In <code>kB</code> kilobytes)</div>
</div>
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="timeout" data-jc-config="placeholder:@(in seconds);increment:true;type:number;maxlength:5;align:center">@(Timeout)</div>
<div class="help m">@(In seconds.)</div>
</div>
</div>
</div>
</section>
<br />
<div data-jc="checkbox" data-jc-path="emptyresponse" class="b black">@(Automatically respond with 200 OK?)</div>
<div class="help m">@(If not checked you need to use HTTP response component to respond to the request.)</div>
<hr />
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
</div>
<hr class="nmt" />
<div class="padding npt">
<div class="row">
<div class="col-md-9 m">
<div data-jc="dropdown" data-jc-path="cachepolicy" data-jc-config="type:number;items:@(no cache)|0,@(URL)|1,@(URL + query string)|2,@(URL + query string + user instance)|3">@(Cache policy)</div>
<div class="help">@(User instance must contain <code>id</code> property.)</div>
</div>
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="cacheexpire" data-jc-config="maxlength:20;align:center">@(Expiration)</div>
<div class="help">@(E.g. <code>5 minutes</code>)</div>
</div>
</div>
</div>
<script>
var httproute_currenturl = '';
var httproute_currentmethod = 'GET';
ON('open.httproute', function(component, options) {
if (options.flags instanceof Array) {
var method = options.method.toLowerCase();
options.flags = options.flags.remove(function(item) {
switch (typeof(item)) {
case 'string':
return item.substring(0, 3) === 'id:' || item === method;
case 'number': // timeout
return true;
}
}).join(', ');
}
if (component.isnew) {
options.url = '';
options.name = '';
} else {
httproute_currenturl = options.url;
httproute_currentmethod = options.method;
}
});
WATCH('settings.httproute.url', httproutecheckurl);
WATCH('settings.httproute.method', httproutecheckurl);
function httproutecheckurl() {
if (httproute_currenturl !== settings.httproute.url || httproute_currentmethod !== settings.httproute.method) {
TRIGGER('httproutecheckurl', { url: settings.httproute.url, method: settings.httproute.method }, function(e) {
var p = 'settings.httproute.url';
if (e) {
// invalid
INVALID(p);
} else {
if (!CAN(p))
RESET(p);
}
});
}
};
ON('save.httproute', function(component, options) {
!component.name && (component.name = options.method + ' ' + options.url);
var builder = [];
builder.push('### @(Configuration)');
builder.push('');
builder.push('- __' + options.method + ' ' + options.url + '__');
builder.push('- @(flags): ' + options.flags);
builder.push('- @(maximum request data length): __' + options.size + ' kB__');
builder.push('- @(empty response): __' + options.emptyresponse + '__');
if (options.headers) {
var headers = [];
Object.keys(options.headers).forEach(function(key){
headers.push(key + ': ' + options.headers[key]);
});
headers.length && builder.push('- @(headers):\\n\`\`\`' + headers.join('\\n') + '\`\`\`');
};
var cp = '@(no cache)';
if (options.cachepolicy === 1)
cp = '@(URL)';
if (options.cachepolicy === 2)
cp = '@(URL + query string)';
if (options.cachepolicy === 3)
cp = '@(URL + query string + user instance)';
builder.push('- @(cache policy): __' + cp + '__');
options.cacheexpire && builder.push('- @(cache expire): __' + options.cacheexpire + '__');
component.notes = builder.join('\\n');
});
</script>`;
exports.install = function(instance) {
var route;
var uninstall = function(id) {
if (F.is4) {
route && route.remove();
route = null;
} else
UNINSTALL('route', id);
};
instance.custom.emptyresponse = function(self) {
self.plain();
};
instance.reconfigure = function() {
var options = instance.options;
if (!options.url) {
instance.status('Not configured', 'red');
return;
}
if (typeof(options.flags) === 'string')
options.flags = options.flags.split(',').trim();
uninstall('id:' + instance.id);
var flags = options.flags || [];
flags.push('id:' + instance.id);
if (!F.is4)
flags.push(options.method.toLowerCase());
options.timeout && flags.push(options.timeout * 1000);
// Make unique values
flags = flags.filter(function(v, i, a) {
if(F.is4 && v.toString().toLowerCase() === options.method.toLowerCase())
return false; // remove method
return a.indexOf(v) === i;
});
options.flags = flags;
var handler = function() {
if (instance.paused || (instance.isDisabled && (instance.isDisabled('output', 0) || instance.isDisabled('output', 1)))) {
instance.status('503 Service Unavailable');
this.status = 503;
this.json();
return;
}
var key;
var self = this;
if (instance.options.emptyresponse) {
instance.status('200 OK');
setTimeout(instance.custom.emptyresponse, 100, self);
if (instance.hasConnection(0)) {
var data = instance.make({
query: self.query,
body: self.body,
session: self.session,
user: self.user,
files: self.files,
headers: self.req.headers,
url: self.url,
params: self.params
});
instance.send2(0, data);
}
return;
}
switch (instance.options.cachepolicy) {
case 1: // URL
key = 'rro' + instance.id + self.url.hash();
break;
case 2: // URL + query
case 3: // URL + query + user
key = self.url;
var keys = Object.keys(self.query);
keys.sort();
for (var i = 0, length = keys.length; i < length; i++)
key += keys[i] + self.query[keys[i]] + '&';
if (instance.options.cachepolicy === 3 && self.user)
key += 'iduser' + self.user.id;
key = 'rro' + instance.id + key.hash();
break;
}
if (key && F.cache.get2(key)) {
var data = instance.make(F.cache.get2(key));
data.repository.controller = self;
instance.send2(1, data);
} else {
var data = instance.make({
query: self.query,
body: self.body,
session: self.session,
user: self.user,
files: self.files,
headers: self.req.headers,
url: self.url,
params: self.params,
mobile: self.mobile,
robot: self.robot,
referrer: self.referrer,
language: self.language
});
data.repository.controller = self;
instance.send2(0, data);
key && FINISHED(self.res, () => F.cache.set(key, self.$flowdata.data, instance.options.cacheexpire));
}
};
if (F.is4)
route = ROUTE(options.method.toUpperCase() + ' ' + options.url, handler, flags, options.size || 5);
else
F.route(options.url, handler, flags, options.size || 5);
instance.status('Listening', 'green');
};
instance.reconfigure();
instance.on('options', instance.reconfigure);
instance.on('close', function(){
uninstall('id:' + instance.id);
});
};
// check url exists
FLOW.trigger('httproutecheckurl', function(next, data) {
var url = data.url;
var method = data.method;
if (url[url.length - 1] !== '/')
url += '/';
var exists = F.routes.web.findItem(r => r.urlraw === url && r.method === method);
next(exists != null);
});
exports.uninstall = function() {
FLOW.trigger('httproutecheckurl', null);
};

121
flow/infosender.js Normal file
View file

@ -0,0 +1,121 @@
exports.id = 'infosender';
exports.title = 'Info sender';
exports.version = '1.0.0';
exports.group = 'Worksys';
exports.color = '#2134B0';
exports.input = 1;
exports.output = 1
exports.click = false;
exports.author = 'oms-is';
exports.icon = 'bolt';
exports.options = { edge: "undefined" };
const { networkInterfaces } = require('os');
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">CSV Import</div>
</div>
</div>
</div>`;
exports.readme = `# send all data to projects.worksys.io, required to monitor status of controller(unipi)`;
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
const fs = require('fs');
var path = require('path');
exports.install = async function(instance) {
let id;
let allValues = {};
let dbSettings;
let sendAllValuesInterval;
let now = new Date();
console.log(exports.title, "INSTALLED", now.toLocaleString("sk-SK"));
const nets = networkInterfaces();
let ipAddresses = Object.create(null); // Or just '{}', an empty object
for (const name of Object.keys(nets)) {
for (const net of nets[name]) {
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) {
if (!ipAddresses[name]) {
ipAddresses[name] = [];
}
ipAddresses[name].push(net.address);
}
}
}
try {
let p = path.join(__dirname + "/../databases/", 'settings.table');
if (fs.existsSync(p)) {
dbSettings = TABLE("settings");
let responseSettings = await promisifyBuilder(dbSettings.find());
id = responseSettings[0]["projects_id"];
console.log(exports.title, responseSettings, id);
}
} catch(err) {
console.error(err);
}
function sendValues()
{
if(Object.keys(allValues).length > 0)
{
if(id !== undefined)
{
delete allValues.__force__;
let dataToSend = {...allValues};
dataToSend.id = id;
dataToSend.ipAddresses = ipAddresses;
//dataToSend.notify_date = new Date().toISOString().slice(0, 19).replace('T', ' ');
//console.log(exports.title, "------------>sendValues", dataToSend);
instance.send(0, dataToSend);
allValues = {};
}
else
{
console.log(exports.title, "unable to send data, id is undefined");
}
}
}
instance.on("close", () => {
clearInterval(sendAllValuesInterval);
})
instance.on("data", (flowdata) => {
allValues = { ...allValues, ...flowdata.data};
//console.log("DATA RECEIVED", flowdata.data);
//__force__
if(flowdata.data.hasOwnProperty("__force__"))
{
if(flowdata.data.__force__)
{
sendValues();
}
}
})
sendAllValuesInterval = setInterval(() => {
sendValues();
}, 60000*3);
}

156
flow/monitorconsumption.js Normal file
View file

@ -0,0 +1,156 @@
exports.id = 'monitorconsumption';
exports.title = 'Consumption';
exports.version = '1.0.0';
exports.author = 'Peter Širka';
exports.group = 'Monitoring';
exports.color = '#967ADC';
exports.input = 0;
exports.output = 1;
exports.icon = 'bug';
exports.options = { interval: 5000, enabled: true, monitorconsumption: true, monitorsize: true, monitorconnections: true, monitorfiles: true };
exports.click = true;
exports.readme = `# Consumption monitoring
This component measure CPU and memory consumption, open files and open connections of this application. It uses these Linux commands: \`ps\`, \`lsof\`, \`netstat\` and \`df\`.
__Data Example__:
\`\`\`javascript
{
cpu: 0, // percentage
memory: 4096, // in bytes
size: 34303, // directory size in bytes
files: 34, // count of open files
connections: 343, // count of connections
uptime: '1-12:34:00'
}
\`\`\``;
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
</div>
</div>
<hr />
<div data-jc="checkbox" data-jc-path="monitorconsumption">Monitor: Consumption + uptime</div>
<div data-jc="checkbox" data-jc-path="monitorfiles">Monitor: Count of open files</div>
<div data-jc="checkbox" data-jc-path="monitorconnections">Monitor: Count of open connections</div>
<div data-jc="checkbox" data-jc-path="monitorsize">Monitor: Directory size</div>
</div>`;
exports.install = function(instance) {
var current = { cpu: 0, memory: 0, files: 0, connections: 0, size: 0, uptime: '', counter: 0 };
var tproc = null;
var Exec = require('child_process').exec;
var reg_empty = /\s{2,}/g;
var reg_appdisksize = /^[\d.,]+/;
instance.custom.run = function() {
if (tproc) {
clearTimeout(tproc);
tproc = null;
}
var arr = [];
// Get CPU and Memory consumption
instance.options.monitorconsumption && arr.push(function(next) {
Exec('ps -p {0} -o %cpu,rss,etime'.format(process.pid), function(err, response) {
if (err) {
instance.throw(err);
} else {
var line = response.split('\n')[1];
line = line.trim().replace(reg_empty, ' ').split(' ');
var cpu = line[0].parseFloat();
current.cpu = cpu.floor(1);
current.memory = line[1].parseInt2() * 1024; // kB to bytes
current.uptime = line[2];
}
next();
});
});
// Get count of open files
instance.options.monitorfiles && arr.push(function(next) {
Exec('lsof -a -p {0} | wc -l'.format(process.pid), function(err, response) {
if (err)
instance.throw(err);
else
current.files = response.trim().parseInt2();
next();
});
});
// Get count of opened network connections
instance.options.monitorconnections && arr.push(function(next) {
Exec('netstat -an | grep :{0} | wc -l'.format(F.port), function(err, response) {
if (err) {
instance.throw(err);
} else {
current.connections = response.trim().parseInt2() - 1;
if (current.connections < 0)
current.connections = 0;
}
next();
});
});
// Get directory size
instance.options.monitorsize && current.counter % 5 !== 0 && arr.push(function(next) {
Exec('du -hsb ' + process.cwd(), function(err, response) {
if (err) {
instance.throw(err);
} else {
var match = response.trim().match(reg_appdisksize);
match && (current.size = match.toString().trim().parseInt2());
}
next();
});
});
arr.async(function() {
tproc && clearTimeout(tproc);
if (instance.options.enabled) {
tproc = setTimeout(instance.custom.run, instance.options.interval);
instance.send2(current);
}
instance.custom.status();
current.counter++;
});
};
instance.custom.status = function() {
if (instance.options.enabled)
instance.status('{0}% / {1}'.format(current.cpu, current.memory.filesize()));
else
instance.status('Disabled', 'red');
};
instance.on('click', function() {
instance.options.enabled = !instance.options.enabled;
instance.custom.status();
if (instance.options.enabled) {
current.counter = 0;
instance.custom.run();
}
});
instance.on('close', function() {
if (tproc) {
clearTimeout(tproc);
tproc = null;
}
});
setTimeout(instance.custom.run, 1000);
};

96
flow/monitordisk.js Normal file
View file

@ -0,0 +1,96 @@
exports.id = 'monitordisk';
exports.title = 'Disk';
exports.version = '1.0.0';
exports.author = 'Peter Širka';
exports.group = 'Monitoring';
exports.color = '#F6BB42';
exports.output = 1;
exports.icon = 'hdd-o';
exports.click = true;
exports.options = { interval: 8000, path: '/', enabled: true };
exports.readme = `# Disk monitoring
This component monitors disk \`bytes\` consumption in Linux systems. It uses \`df\` command.
__Data Example__:
\`\`\`javascript
{
total: 474549649408,
used: 39125245952,
free: 411294994432
}
\`\`\``;
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-3 m">
<div data---="textbox__interval__placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
</div>
<div class="col-md-3 m">
<div data---="textbox__path__placeholder:/;required:true">@(Path)</div>
</div>
</div>
</div>`;
exports.install = function(instance) {
var current = { total: 0, used: 0, free: 0, path: '', type: '', percentUsed: 0 };
var tproc = null;
instance.custom.run = function() {
if (tproc) {
clearTimeout(tproc);
tproc = null;
}
if (!instance.options.enabled)
return;
require('child_process').exec('df -hTB1 ' + instance.options.path, function(err, response) {
tproc = setTimeout(instance.custom.run, instance.options.interval);
if (err) {
instance.error(err);
return;
}
response.parseTerminal(function(line) {
if (line[0][0] !== '/')
return;
current.total = line[2].parseInt();
current.free = line[4].parseInt();
current.used = line[3].parseInt();
current.path = instance.options.path || '/';
current.type = line[1];
current.percentUsed = line[5];
instance.custom.status();
instance.send2(current);
});
});
};
instance.custom.status = function() {
if (instance.options.enabled)
instance.status(current.free.filesize() + ' / ' + current.total.filesize());
else
instance.status('Disabled', 'red');
};
instance.on('click', function() {
instance.options.enabled = !instance.options.enabled;
instance.custom.status();
instance.options.enabled && instance.custom.run();
});
instance.on('close', function() {
if (tproc) {
clearTimeout(tproc);
tproc = null;
}
});
setTimeout(instance.custom.run, 1000);
};

87
flow/monitormemory.js Normal file
View file

@ -0,0 +1,87 @@
exports.id = 'monitormemory';
exports.title = 'Memory';
exports.version = '1.0.0';
exports.author = 'Peter Širka';
exports.group = 'Monitoring';
exports.color = '#F6BB42';
exports.output = 1;
exports.click = true;
exports.icon = 'microchip';
exports.options = { interval: 8000, enabled: true };
exports.readme = `# Memory monitoring
This component monitors memory \`bytes\` consumption in Linux systems. It uses \`free\` command.
__Data Example__:
\`\`\`javascript
{
total: 33558769664,
used: 1998868480,
free: 2653708288
}
\`\`\``;
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
</div>
</div>
</div>`;
exports.install = function(instance) {
var current = { total: 0, used: 0, free: 0 };
var tproc = null;
instance.custom.run = function() {
if (tproc) {
clearTimeout(tproc);
tproc = null;
}
if (!instance.options.enabled)
return;
require('child_process').exec('free -b -t', function(err, response) {
tproc = setTimeout(instance.custom.run, instance.options.interval);
if (err) {
instance.error(err);
return;
}
var memory = response.split('\n')[1].match(/\d+/g);
current.total = memory[0].parseInt();
current.used = memory[1].parseInt() - memory[3].parseInt();
current.free = current.total - current.used;
instance.custom.status();
instance.send2(current);
});
};
instance.custom.status = function() {
if (instance.options.enabled)
instance.status(current.free.filesize() + ' / ' + current.total.filesize());
else
instance.status('Disabled', 'red');
};
instance.on('click', function() {
instance.options.enabled = !instance.options.enabled;
instance.custom.status();
instance.options.enabled && instance.custom.run();
});
instance.on('close', function() {
if (tproc) {
clearTimeout(tproc);
tproc = null;
}
});
setTimeout(instance.custom.run, 1000);
};

441
flow/mqtt.js Normal file
View file

@ -0,0 +1,441 @@
exports.id = 'mqtt';
exports.title = 'MQTT broker';
exports.group = 'MQTT';
exports.color = '#888600';
exports.version = '1.0.1';
exports.icon = 'exchange';
exports.input = true;
exports.output = 0;
exports.author = 'Martin Smola';
exports.variables = true;
exports.options = { host: '127.0.0.1', port: 1883 };
exports.traffic = false;
exports.npm = ['mqtt'];
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:true" class="m">Hostname or IP address</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 class="help m">@(Supports variables, example: \`client_{device-id}\`)</div>
<div data-jc="checkbox" data-jc-path="secure" class="m">@(Secure (ssl))</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-6">
<div data-jc="checkbox" data-jc-path="auth" class="m">@(Require Authorization)</div>
</div>
</div>
<div class="row" data-bind="?.auth__show:value">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="username" class="m">@(Username)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="password" data-jc-config="type:password" class="m">@(Password)</div>
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-6">
<div data-jc="checkbox" data-jc-path="lwt" class="m">@(LWT)</div>
</div>
</div>
<div class="row" data-bind="?.lwt__show:value">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="lwttopic">@(Last will topic)</div>
<div class="help m">@(Supports variables, example: \`lwt/{device-id}\`)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="lwtmessage">@(Last will message)</div>
<div class="help m">@(Supports variables, example: \`{device-id} is offline\`)</div>
</div>
</div>
</div>
<script>
ON('save.mqtt', function(component, options) {
!component.name && (component.name = '{0} @ {1}:{2}'.format(options.username || '', options.host, options.port || '1883'));
});
</script>`;
exports.readme = `
# MQTT Broker
## Input
Allows to change connection programaticaly
\`\`\`javascipt
{
host: '1.2.3.4',
port: '',
secure: true/false,
username: 'john',
password: 'X',
lwttopic: '',
lwtmessage: '',
clientid: ''
}
\`\`\`
`;
var MQTT_BROKERS = [];
var mqtt;
global.MQTT = {};
exports.install = function(instance) {
var broker;
mqtt = require('mqtt');
instance.on('data', function(flowdata){
var data= flowdata.data;
var options = instance.options;
if (data.host && data.port)
return instance.custom.reconfigure(data, options);
if (data.close === true)
instance.close(NOOP);
});
instance.custom.reconfigure = function(o, old_options) {
if (old_options)
MQTT_BROKERS = MQTT_BROKERS.remove(function(b){
return b.id === old_options.id;
});
var options = instance.options;
if (!options.host || !options.port) {
instance.status('Not configured', 'red');
return;
}
options.id = (options.username || '') + '@' + options.host + ':' + options.port;
if (broker) {
broker.close();
EMIT('mqtt.brokers.status', 'reconfigured', old_options.id, options.id);
}
instance.custom.createBroker();
};
instance.custom.createBroker = function() {
ON('mqtt.brokers.status', brokerstatus);
var o = instance.options;
var opts = {
host: o.host,
port: o.port,
id: o.id,
secure: o.secure,
rejectUnauthorized: false,
reconnectPeriod: 3000,
resubscribe: false
};
if (o.auth) {
opts.username = o.username;
opts.password = o.password;
}
if (o.lwt) {
opts.will = {
topic: instance.arg(o.lwttopic),
payload: instance.arg(o.lwtmessage)
}
}
if (o.clientid)
opts.clientId = instance.arg(o.clientid);
broker = new Broker(opts);
MQTT_BROKERS.push(broker);
instance.status('Ready');
};
instance.close = function(done) {
broker && broker.close(function() {
MQTT_BROKERS = MQTT_BROKERS.remove('id', instance.options.id);
EMIT('mqtt.brokers.status', 'removed', instance.options.id);
});
OFF('mqtt.brokers.status', brokerstatus);
done();
};
function brokerstatus(status, brokerid, err) {
if (brokerid !== instance.options.id)
return;
switch (status) {
case 'connecting':
instance.status('Connecting', '#a6c3ff');
break;
case 'connected':
instance.status('Connected', 'green');
break;
case 'disconnected':
instance.status('Disconnected', 'red');
break;
case 'connectionfailed':
instance.status('Connection failed', 'red');
break;
case 'error':
instance.error('MQTT Error, ID: ' + instance.id + '\n ' + err);
break;
}
}
instance.on('options', instance.custom.reconfigure);
instance.custom.reconfigure();
};
FLOW.trigger('mqtt.brokers', function(next) {
var brokers = [''];
MQTT_BROKERS.forEach(n => brokers.push(n.id));
next(brokers);
});
MQTT.add = function(brokerid, componentid) {
var broker = MQTT_BROKERS.findItem('id', brokerid);
if (broker)
broker.add(componentid);
};
MQTT.remove = function(brokerid, componentid) {
var broker = MQTT_BROKERS.findItem('id', brokerid);
broker && broker.remove(componentid);
};
MQTT.publish = function(brokerid, topic, data, options) {
var broker = MQTT_BROKERS.findItem('id', brokerid);
if (broker)
broker.publish(topic, data, options);
else
EMIT('mqtt.brokers.status', 'error', brokerid, 'No such broker');
};
MQTT.subscribe = function(brokerid, componentid, topic, qos) {
var broker = MQTT_BROKERS.findItem('id', brokerid);
if (!broker)
return;
broker.add(componentid);
broker.subscribe(componentid, topic, qos);
};
MQTT.unsubscribe = function(brokerid, componentid, topic, qos) {
var broker = MQTT_BROKERS.findItem('id', brokerid);
if (!broker)
return;
broker.unsubscribe(componentid, topic);
broker.remove(componentid);
};
MQTT.broker = function(brokerid) {
return MQTT_BROKERS.findItem('id', brokerid);
};
/*
https://github.com/mqttjs/MQTT.js/blob/master/examples/client/secure-client.js
*/
/*
TODO
- add `birth` and `last will and testament` messages
- add options to self.client.connect(broker [,options]); - credentials, certificate etc.
*/
function Broker(options) {
var self = this;
if (!options.host || !options.port)
return false;
self.connecting = false;
self.connected = false;
self.closing = false;
self.components = [];
self.subscribtions = {};
self.id = options.id;
self.options = options;
setTimeout(function() {
EMIT('mqtt.brokers.status', 'new', self.id);
}, 500);
return self;
}
Broker.prototype.connect = function() {
var self = this;
if (self.connected || self.connecting)
return EMIT('mqtt.brokers.status', self.connected ? 'connected' : 'connecting', self.id);
self.connecting = true;
var broker = self.options.secure ? 'mqtts://' : 'mqtt://' + self.options.host + ':' + self.options.port;
EMIT('mqtt.brokers.status', 'connecting', self.id);
self.client = mqtt.connect(broker, self.options);
self.client.on('connect', function() {
self.connecting = false;
self.connected = true;
if (self.reconnecting) {
EMIT('mqtt.brokers.status', 'reconnected', self.id);
self.reconnecting = false;
self.resubscribe();
}
EMIT('mqtt.brokers.status', 'connected', self.id);
});
self.client.on('reconnect', function() {
self.connecting = true;
self.connected = false;
self.reconnecting = true;
EMIT('mqtt.brokers.status', 'connecting', self.id);
});
self.client.on('message', function(topic, message) {
message = message.toString();
if (message[0] === '{') {
TRY(function() {
message = JSON.parse(message);
}, () => FLOW.debug('MQTT: Error parsing data', message));
}
EMIT('mqtt.brokers.message', self.id, topic, message);
});
self.client.on('close', function(err) {
if (err && err.toString().indexOf('Error')) {
self.connecting = false;
self.connected = false;
EMIT('mqtt.brokers.status', 'error', self.id, err.code);
}
if (self.connected || !self.connecting) {
self.connected = false;
EMIT('mqtt.brokers.status', 'disconnected', self.id);
} else if (self.connecting) {
self.connecting = false;
EMIT('mqtt.brokers.status', 'connectionfailed', self.id);
}
});
self.client.on('error', function(err) {
if (self.connecting) {
self.client.end();
self.connecting = false;
EMIT('mqtt.brokers.status', 'error', self.id, err);
}
});
};
Broker.prototype.disconnect = function(reconnect) {
var self = this;
if (!self.closing)
self.close(function(){
reconnect && self.connect();
});
};
Broker.prototype.close = function(callback) {
var self = this;
self.closing = true;
if ((self.connected || self.connecting) && self.client && self.client.end)
self.client.end(true, cb);
else
cb();
function cb() {
EMIT('mqtt.brokers.status', 'disconnected', self.id);
self.client && self.client.removeAllListeners();
self.components = [];
self.client = null;
callback && callback();
}
};
Broker.prototype.subscribe = function(componentid, topic) {
var self = this;
self.subscribtions[topic] = self.subscribtions[topic] || [];
if (self.subscribtions[topic].indexOf(componentid) > -1)
return;
self.client.subscribe(topic);
self.subscribtions[topic].push(componentid);
};
Broker.prototype.resubscribe = function() {
var self = this;
var topics = Object.keys(self.subscribtions);
for (var i = 0; i < topics.length; i++)
self.client.subscribe(topics[i]);
};
Broker.prototype.unsubscribe = function(componentid, topic) {
var self = this;
var sub = self.subscribtions[topic];
if (sub) {
self.subscribtions[topic] = sub.remove(componentid);
self.client.connected && !self.subscribtions[topic].length && self.client.unsubscribe(topic);
}
};
Broker.prototype.publish = function(topic, data, options) {
var self = this;
if (!self.connected)
return;
if (typeof(data) === 'object') {
options.qos = parseInt(data.qos || options.qos);
options.retain = data.retain || options.retain;
topic = data.topic || topic;
data.payload && (data = typeof(data.payload) === 'string' ? data.payload : JSON.stringify(data.payload));
}
if (options.qos !== 0 || options.qos !== 1 || options.qos !== 2)
options.qos = null;
if (typeof(data) !== 'string')
data = JSON.stringify(data);
self.client.publish(topic, data || '', options);
};
Broker.prototype.add = function(componentid) {
var self = this;
self.components.indexOf(componentid) === -1 && self.components.push(componentid);
self.connect();
};
Broker.prototype.remove = function(componentid) {
var self = this;
self.components = self.components.remove(componentid);
!self.components.length && self.disconnect();
};

View file

@ -0,0 +1,129 @@
exports.id = 'mqtt_subscribe_temperature';
exports.title = 'MQTT subscribe temperature';
exports.group = 'MQTT';
exports.color = '#888600';
exports.version = '1.0.2';
exports.icon = 'sign-out';
exports.output = 2;
exports.options = { host: 'tb-stage.worksys.io', port: 1883, tbname: "", username: "" };
exports.npm = ['mqtt'];
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="tbname">@(SBS thingsboard name)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="topic" class="m">@(topic)</div>
</div>
</div>
</div>`;
exports.install = function(instance) {
var mqtt = require('mqtt');
var client;
var opts;
//set opts according to db settings
instance.reconfigure = function() {
if (instance.options.host !== "") {
//override settings from database
var o = instance.options;
opts = {
host: o.host,
port: o.port,
tbname: o.tbname,
topic: o.topic,
rejectUnauthorized: false,
resubscribe: false
};
console.log("wsmqttpublich -> loadSettings from instance.options", instance.options);
}
connectToTbServer();
}
function connectToTbServer() {
var url = "mqtt://" + opts.host + ":" + opts.port;
console.log("MQTT URL: ", url);
client = mqtt.connect(url, opts);
client.on('connect', function() {
client.subscribe(`${opts.topic}`, (err) => {
if (!err) {
console.log("MQTT subscribed");
}
});
instance.status("Connected", "green");
});
client.on('reconnect', function() {
instance.status("Reconnecting", "yellow");
});
client.on('message', function(topic, message) {
// message is type of buffer
message = message.toString();
//console.log('messageeee', message, typeof message);
if (message[0] === '{') {
try {
message = JSON.parse(message);
instance.send(0, message);
sendToTb({ temperature_out: message["temperature"] }, opts.tbname);
//console.log("teplota sokolov z cloudu: ", message);
} catch (error) {
console.log("Mqtt_subscribe_temperature: unable to parse mqtt temperature message");
}
}
});
client.on('close', function(err) {
if (err) console.log('client na mqtt teplotu sa odpojil')
client.reconnect();
});
client.on('error', function(err) {
instance.status("Err: " + err.code, "red");
});
}
instance.close = function() {
client.end();
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
function sendToTb(values, tbName) {
const dataToTb = {
[tbName]: [
{
ts: Date.now(),
values: values
}
]
};
instance.send(1, dataToTb);
}
};

134
flow/mqttpublish.js Normal file
View file

@ -0,0 +1,134 @@
exports.id = 'mqttpublish';
exports.title = 'MQTT publish';
exports.group = 'MQTT';
exports.color = '#888600';
exports.version = '1.1.0';
exports.icon = 'sign-out';
exports.input = true;
exports.output = 1;
exports.author = 'Martin Smola';
exports.options = {};
exports.html = `<div class="padding">
<div data-jc="dropdown" data-jc-path="broker" data-jc-config="datasource:mqttconfig.brokers;required:true" class="m">@(Brokers)</div>
<div data-jc="textbox" data-jc-path="topic" data-jc-config="placeholder:hello/world">Topic</div>
<div class="help m">@(Supports variables, example: \`device/{device-id}\`)</div>
<div data-jc="textbox" data-jc-path="staticmessage" data-jc-config="placeholder:123">Static message(string)</div>
<div class="help m">@(Supports variables), @(If specified then incoming data are ignored and this message is sent instead. Topic is required if static message is defined.)</div>
<div data-jc="dropdown" data-jc-path="qos" data-jc-config="items:,0,1,2" class="m">@(QoS)</div>
<div data-jc="checkbox" data-jc-path="retain" class="m">@(Retain)</div>
</div>
<script>
var mqttconfig = { brokers: [] };
ON('open.mqttpublish', function(component, options) {
TRIGGER('mqtt.brokers', 'mqttconfig.brokers');
});
ON('save.mqttpublish', function(component, options) {
!component.name && (component.name = options.broker + (options.topic ? ' -> ' + options.topic : ''));
});
</script>`;
exports.readme = `# MQTT publish
If the topic field is left empty and the data object does not have a 'topic' property then nothing is send.
Also if data object has a valid topic property it is assumed the object also have data property which is send as a payload;
Example:
\`\`\`javacsript
{
topic: '/topic',
data: {
hello: 'world'
}
}
// in above case only { hello: 'world' } is published
\`\`\`
If the topic field is not empty then the entire incomming data object is passed to the output.`;
exports.install = function(instance) {
var PUBLISH_OPTIONS = {};
var ready = false;
instance.custom.reconfigure = function() {
ready = false;
if (!MQTT.broker(instance.options.broker))
return instance.status('No broker', 'red');
if (instance.options.broker) {
MQTT.add(instance.options.broker, instance.id);
ready = true;
PUBLISH_OPTIONS.retain = instance.options.retain || false;
PUBLISH_OPTIONS.qos = parseInt(instance.options.qos || 0);
return;
}
instance.status('Not configured', 'red');
};
instance.on('options', instance.custom.reconfigure);
instance.on('data', function(flowdata) {
if (!ready)
return;
var msg = instance.options.staticmessage ? instance.arg(instance.options.staticmessage) : flowdata.data;
var topic = instance.arg(instance.options.topic || msg.topic);
if (topic) {
if (msg.topic)
msg = msg.data;
MQTT.publish(instance.options.broker, topic, msg, PUBLISH_OPTIONS);
} else
instance.debug('MQTT publish no topic');
instance.send(flowdata);
});
instance.on('close', function() {
MQTT.remove(instance.options.broker, instance.id);
OFF('mqtt.brokers.status', instance.custom.brokerstatus);
});
instance.custom.brokerstatus = function(status, brokerid, msg) {
if (brokerid !== instance.options.broker)
return;
switch (status) {
case 'connecting':
instance.status('Connecting', '#a6c3ff');
break;
case 'connected':
instance.status('Connected', 'green');
break;
case 'disconnected':
instance.status('Disconnected', 'red');
break;
case 'connectionfailed':
instance.status('Connection failed', 'red');
break;
case 'new':
!ready && instance.custom.reconfigure();
break;
case 'removed':
instance.custom.reconfigure();
break;
case 'error':
instance.status(msg, 'red');
break;
case 'reconfigured':
instance.options.broker = msg;
instance.reconfig();
instance.custom.reconfigure();
break;
}
}
ON('mqtt.brokers.status', instance.custom.brokerstatus);
instance.custom.reconfigure();
};

168
flow/mqttsubscribe.js Normal file
View file

@ -0,0 +1,168 @@
exports.id = 'mqttsubscribe';
exports.title = 'MQTT subscribe';
exports.group = 'MQTT';
exports.color = '#888600';
exports.version = '1.1.0';
exports.icon = 'sign-in';
exports.output = 1;
exports.variables = true;
exports.author = 'Martin Smola';
exports.options = {};
exports.html = `<div class="padding">
<div data-jc="dropdown" data-jc-path="broker" data-jc-config="datasource:mqttconfig.brokers;required:true" class="m">@(Select a broker)</div>
<div data-jc="textbox" data-jc-path="topic" data-jc-config="placeholder:hello/world;required:true">Topic</div>
<div class="help m">@(Supports variables, example: \`device/{device-id}\`)</div>
<div data-jc="dropdown" data-jc-path="qos" data-jc-config="items:,0,1,2" class="m">@(QoS)</div>
</div>
<script>
var mqttconfig = { brokers: [] };
ON('open.mqttsubscribe', function(component, options) {
TRIGGER('mqtt.brokers', 'mqttconfig.brokers');
});
ON('save.mqttsubscribe', function(component, options) {
!component.name && (component.name = options.broker + (options.topic ? ' -> ' + options.topic : ''));
});
</script>`;
exports.readme = `
# MQTT subscribe
The data recieved are passed to the output as follows:
\`\`\`javascript
{
topic: '/lights/on',
data: 'kitchen'
}
\`\`\`
If the topic is wildcard then there's an array of matches in flowdata repository which can be used in Function component like so:
\`\`\`javascript
// wildcard -> /+/status
// topic -> /devicename/status
var match = flowdata.get('mqtt_wildcard');
// match === ['devicename']
\`\`\`
More on wildcard topics [here](https://mosquitto.org/man/mqtt-7.html)
`;
exports.install = function(instance) {
var old_topic;
var ready = false;
instance.custom.reconfigure = function(o, old_options) {
ready = false;
if (!MQTT.broker(instance.options.broker))
return instance.status('No broker', 'red');
if (instance.options.broker && instance.options.topic) {
if (old_topic)
MQTT.unsubscribe(instance.options.broker, instance.id, old_topic);
old_topic = instance.arg(instance.options.topic);
MQTT.subscribe(instance.options.broker, instance.id, old_topic);
ready = true;
return;
}
instance.status('Not configured', 'red');
};
instance.on('options', instance.custom.reconfigure);
instance.on('close', function() {
MQTT.unsubscribe(instance.options.broker, instance.id, instance.options.topic);
OFF('mqtt.brokers.message', instance.custom.message);
OFF('mqtt.brokers.status', instance.custom.brokerstatus);
});
instance.custom.brokerstatus = function(status, brokerid, msg) {
if (brokerid !== instance.options.broker)
return;
switch (status) {
case 'connecting':
instance.status('Connecting', '#a6c3ff');
break;
case 'connected':
instance.status('Connected', 'green');
break;
case 'disconnected':
instance.status('Disconnected', 'red');
break;
case 'connectionfailed':
instance.status('Connection failed', 'red');
break;
case 'new':
!ready && instance.custom.reconfigure();
break;
case 'removed':
instance.custom.reconfigure();
break;
case 'error':
instance.status(msg, 'red');
break;
case 'reconfigured':
instance.options.broker = msg;
instance.reconfig();
instance.custom.reconfigure();
break;
}
}
instance.custom.message = function(brokerid, topic, message) {
if (brokerid !== instance.options.broker)
return;
var match = mqttWildcard(topic, old_topic);
if (match) {
var flowdata = instance.make({ topic: topic, data: message })
flowdata.set('mqtt_wildcard', match);
instance.send2(flowdata);
}
}
ON('mqtt.brokers.message', instance.custom.message);
ON('mqtt.brokers.status', instance.custom.brokerstatus);
instance.custom.reconfigure();
};
// https://github.com/hobbyquaker/mqtt-wildcard
function mqttWildcard(topic, wildcard) {
if (topic === wildcard) {
return [];
} else if (wildcard === '#') {
return [topic];
}
var res = [];
var t = String(topic).split('/');
var w = String(wildcard).split('/');
var i = 0;
for (var lt = t.length; i < lt; i++) {
if (w[i] === '+') {
res.push(t[i]);
} else if (w[i] === '#') {
res.push(t.slice(i).join('/'));
return res;
} else if (w[i] !== t[i]) {
return null;
}
}
if (w[i] === '#') {
i += 1;
}
return (i === w.length) ? res : null;
}

267
flow/particulatesensor.js Normal file
View file

@ -0,0 +1,267 @@
exports.id = 'particulatesensor';
exports.title = 'Particulate sensor';
exports.group = 'Worksys';
exports.color = '#5CB36D';
exports.version = '1.0.0';
exports.output = ["red", "white"];
exports.author = 'Rastislav Kovac';
exports.icon = 'atom';
exports.readme = `# PM sensor`;
/*
tento command zapne senzor zo stavu idle do stavu Meranie
rsPort.write([0x7E, 0x00, 0x00, 0x02, 0x01, 0x03, 0xF9, 0x7E]
tento command vypne senzor do stavu idle
rsPort.write([0x7E, 0x00, 0x01, 0x00, 0xFE, 0x7E]
tento command cita namerane data zo senzora
rsPort.write([0x7E, 0x00, 0x03, 0x00, 0xFC, 0x7E]
*/
const conversionTable = {
1: "pm1_0",
2: "pm2_5",
3: "pm4_0",
4: "pm10"
// 1: "Mass concentration PM1.0",
// 2: "Mass concentration PM2.5",
// 3: "Mass concentration PM4.0",
// 4: "Mass concentration PM10",
// 5: "Number concentration PM0.5",
// 6: "Number concentration PM1.0",
// 7: "Number concentration PM2.5",
// 8: "Number concentration PM4.0",
// 9: "Number concentration PM10",
// 10: "Typical Particle size",
}
exports.install = function(instance) {
const fs = require("fs");
const SerialPort = require('serialport');
const { exec } = require('child_process');
let rsPort = null;
let startToGetData = null;
const tbName = "mp93b2nvd7OoqgBeEyE7N18kjlAV1Y4ZNXwW0zLG";
function writeToFile(data)
{
try {
if (fs.existsSync("err.txt")) {
let stats = fs.statSync("err.txt")
let fileSizeInBytes = stats.size;
if(fileSizeInBytes > 20000000)
{
fs.unlinkSync("err.txt");
// file deleted
}
}
} catch(err) {
console.error(err)
}
fs.writeFile("err.txt", data, {flag: "a"},function (err,data) {
if (err) {
return console.log(err);
}
console.log(data);
});
}
function startRsPort()
{
rsPort = new SerialPort("/dev/ttyUSB0", { autoOpen: false });
rsPort.on('open', function() {
exec("stty -F /dev/ttyUSB0 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke", (error, stdout, stderr) => {
//exec("stty -F /dev/ttyUSB0 115200 min 1 time 5 cs8 -cstopb ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke", (error, stdout, stderr) => {
instance.send(0,{"stdout":stdout,"stderr":stderr,"err":error});
instance.send(0, exports.title + " RS USB port is set");
});
setStateToMeasurement();
})
rsPort.on('error', function(err) {
instance.send(0, err.message);
let d = new Date();
writeToFile(`${d}, ${err}`);
})
rsPort.on("close", () => {
clearInterval(startToGetData);
rsPort.close();
instance.send(0, exports.title + " RS USB port is closed now ---> reopenning in 30 seconds");
writeToFile(`${d}, RS USB port is closed now ---> reopenning in 30 seconds`);
rsPort = null;
setTimeout(startRsPort, 30000);
})
/*
RECEIVED BYTES
Byte 1 0x7E start
Byte 2 0x00 adresa
Byte 3 0x03 cmd
Byte 4 0x00 state podľa obr.2
Byte 5 0x28 počet prijatých dátových bytov
Na konci 0xD4 CRC
Na konci 0x7E stop
STATE STATUS
0 - 0x00 No error
1 - 0x01 Wrong data length for this command (too much or little data)
2 - 0x02 Unknown command
3 - 0x03 No access right for command
4 - 0x04 Illegal command parameter or parameter out of allowed range
40 - 0x28 Internal function argument out of range
67 - 0x43 Command not allowed in current state
*/
let rsPortReceivedData = [];
let measuredValues;
rsPort.on("data", function(data) {
//console.log("rsPort data function called")
data = JSON.stringify(data);
try {
data = JSON.parse(data);
} catch(err) {
console.log("[Particulate Sensor] - unable to convert data to JSON");
return;
}
// array of received bytes
data = data.data;
rsPortReceivedData = [...rsPortReceivedData, ...data];
//console.log("----rsportALLDATA", rsPortReceivedData);
if (rsPortReceivedData[0] != 126) {
rsPortReceivedData = [];
return;
}
if (rsPortReceivedData[rsPortReceivedData.length - 1] != 126) return;
if (rsPortReceivedData.length === 7) {
if (rsPortReceivedData[2] === 0){
instance.send(0, "Particulate sensor is in Measurement-Mode now");
rsPortReceivedData = [];
return;
}
}
// convert special characters to single byte
rsPortReceivedData = rsPortReceivedData.toString();
rsPortReceivedData = rsPortReceivedData.replace(/125,94/g, "126");
rsPortReceivedData = rsPortReceivedData.replace(/125,93/g, "125");
rsPortReceivedData = rsPortReceivedData.replace(/125,51/g, "19");
rsPortReceivedData = rsPortReceivedData.replace(/125,49/g, "17");
rsPortReceivedData = rsPortReceivedData.split(",");
//we only take measured values from received data
measuredValues = rsPortReceivedData.slice(5, rsPortReceivedData.length-2);
//console.log(measuredValues);
let l = measuredValues.length;
//console.log("length----", l);
let i, j, temparray, counter = 0, chunk = 4;
for ( i = 0, j = l; i < j; i += chunk ) {
counter++;
temparray = measuredValues.slice(i, i + chunk);
convertDatabytesToFloat(temparray, conversionTable[counter]);
}
counter = 0;
rsPortReceivedData = [];
})
rsPort.open();
}
function convertDatabytesToFloat(array, type) {
//console.log("----", array);
if(array.length < 4 || type == undefined) return;
let result = Buffer.from(array).readFloatBE(0);
result = parseFloat(result.toFixed(4));
//console.log(result, typeof result)
let dataToTb = {
[tbName]: [
{
"ts": Date.now(),
"values": {
[type]: result
}
}
]
}
instance.send(1, dataToTb);
}
// get data from PM sensor
function getMeasurements() {
rsPort.write([0x7E, 0x00, 0x03, 0x00, 0xFC, 0x7E], function(err) {
if(err){
return console.log('[Particulate Sensor] - Error on write: ', err.message)
}
instance.send(0, "Getting data from PM sensor");
})
}
// we set the state of sensor to start to measure
function setStateToMeasurement() {
rsPort.write([0x7E, 0x00, 0x00, 0x02, 0x01, 0x03, 0xF9, 0x7E], function(err) {
if(err){
return console.log('[Particulate Sensor] - Error on write: ', err.message)
}
instance.send(0, "PM sensor state set to measure");
})
}
instance.on("close", function() {
clearInterval(startToGetData);
rsPort.close();
});
setTimeout(startRsPort, 10000);
startToGetData = setInterval(getMeasurements, 300000);
};

634
flow/rce_modul.js Normal file
View file

@ -0,0 +1,634 @@
//! IMPORTANT
/**
* THIS IS JUST STATIC CODE FOR TEST PURPOSES OF CAMERA IN THE LAB.
* IT IS USED TO SEND ACTUAL NUMBER OF PEOPLE IN THE ZONE. ZONE IS CREATED IN datafromsky SOFTWARE
* DYNAMIC CODE UNDERNEATH FOR CLIENT PURPOSE
* IN DYNAMIC CODE IT IS NECCESSARY TO ADD "SINK_ID CALCULATION" (IN OUR CASE IT IS SET TO "id": 3 AT THE MOMENT)
* FOR STATIC CODE, YOU NEED TO SET ACCESS_TOKEN, CUBES_ID, ANALYTICS_ID, SEQUENCE_NUMBER AND SINK_ID MANUALLY
* IN OUR CASE NOW IT IS "VTLMQILFQG, 3, 0, 38, 3"
*
* ENABLE ON LINE 55: process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
*/
// exports.id = 'rce_modul';
// exports.title = 'RCE Modul';
// exports.group = 'Worksys';
// exports.color = '#5D9CEC';
// exports.version = '1.0.1';
// exports.output = ['red', 'white'];
// exports.author = 'Rastislav Kovac';
// exports.icon = 'cloud-upload';
// exports.html = `
// <div class="padding">
// <div class="row">
// <div class="col-md-12">
// <div>RCE MODUL</div><br>
// </div>
// </div>
// <div class="row">
// <div class="col-md-6 m">
// <div data-jc="textbox" data-jc-path="username" class="m" data-jc-config="required:true">@(User)</div>
// </div>
// <div class="col-md-6 m">
// <div data-jc="textbox" data-jc-path="userpassword" data-jc-config="required:true">@(Password)</div>
// </div>
// </div>
// <div class="row">
// <div class="col-md-4 m">
// <div data-jc="textbox" data-jc-path="ip" data-jc-config="required:true">@(IP address)</div>
// </div>
// <div class="col-md-4 m">
// <div data-jc="textbox" data-jc-path="port" data-jc-config="required:true">@(Port)</div>
// </div>
// <div class="col-md-4 m">
// <div data-jc="textbox" data-jc-path="edge" data-jc-config="required:true">@(My edge)</div>
// </div>
// </div>
// </div>
// `;
// exports.readme = `RCE modul`;
// //disabling ssl certification
// //process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
// exports.install = function(instance) {
// let startRequests;
// let ip;
// let port;
// let counter;
// let previousValue;
// let actualDataBody =
// {
// "sequence_number": "38",
// "sinks": [
// {
// "id": 3
// }
// ]
// }
// actualDataBody = JSON.stringify(actualDataBody);
// let dataHeaders = {
// "Content-Type": "application/json",
// "Authorization": "Bearer VTLMQILFQG",
// "Accept-Version": 3.17,
// }
// const getActualData = function() {
// U.request(dataUrl, ["post"], actualDataBody , function(err, data, status, headers, host) {
// if (status !== 200) {
// counter++;
// }
// if (counter > 100 && counter < 102) {
// instance.reconfigure();
// }
// if (counter > 500){
// instance.send(0, "Error, data are not being updated");
// counter = 0;
// }
// let response = JSON.parse(data);
// let value = response.sinks[0].data.value
// let dataToTB = {
// 'tbName': [
// {
// "ts": Date.now(),
// "values": {
// "event_count": value,
// "event_description":"Pocet osob v miestnosti"
// }
// }
// ]
// };
// if (value !== previousValue){
// instance.send(1, dataToTB);
// console.log(dataToTB.tbName[0]);
// }
// previousValue = value;
// }, {}, dataHeaders);
// }
// instance.reconfigure = function() {
// clearInterval(startRequests);
// options = instance.options;
// can = options.username && options.userpassword && options.edge && options.ip? true : false;
// instance.status(can ? 'Gathering data' : 'Not configured', can ? 'green' : 'red');
// ip = options.ip;
// port = options.port;
// dataUrl = `https://${ip}:${port}/cubes/3/analytics/0/sinks/data`;
// if (!can)
// return;
// startRequests = setInterval(getActualData, 1000);
// }
// instance.on('options', instance.reconfigure);
// setTimeout(instance.reconfigure, 3000);
// }
//! DYNAMIC CODE, ALL AUTOMATIC
exports.id = 'rce_modul';
exports.title = 'RCE Modul';
exports.group = 'Worksys';
exports.color = '#5D9CEC';
exports.version = '1.0.2';
exports.output = ['red', 'white'];
exports.author = 'Rastislav Kovac';
exports.icon = 'cloud-upload';
exports.html = `
<div class="padding">
<div class="row">
<div class="col-md-12">
<div>RCE MODUL</div><br>
</div>
</div>
<div class="row">
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="username" class="m" data-jc-config="required:true">@(User)</div>
</div>
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="userpassword" data-jc-config="required:true">@(Password)</div>
</div>
</div>
<div class="row">
<div class="col-md-4 m">
<div data-jc="textbox" data-jc-path="ip" data-jc-config="required:true">@(IP address)</div>
</div>
<div class="col-md-4 m">
<div data-jc="textbox" data-jc-path="port" data-jc-config="required:true">@(Port)</div>
</div>
<div class="col-md-4 m">
<div data-jc="textbox" data-jc-path="edge" data-jc-config="required:true">@(My edge)</div>
</div>
</div>
</div>
`;
exports.readme = `RCE modul`;
//disabling ssl certification
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
exports.install = function(instance) {
let startRequests;
let ip, port, username, password;
let accessToken = "VTLMQILFQG";
let tokenBody;
let allCubes = [0,1,2]; // tree cameras
let cubeId;
let analyticsId;
let sequence_number;
let counter = 0;
let previousValue;
let previousValues = {};
let sinksUrlArray;
let dataUrlArray;
// all urls
let tokenUrl, cubesUrl, analyticsUrl, sinksUrl, dataUrl;
let actualDataBody;
let dataHeaders;
// all headers
// cubesHeaders === analyticsHeaders === dataHeaders
const tokenHeaders = {
"Content-Type": "application/json",
"Accept-Version": 3.17,
}
dataHeaders = {
"Content-Type": "application/json",
"Authorization": `Bearer ${accessToken}`,
"Accept-Version": 3.17,
}
// all body
// tokenBody === cubesBody === analyticsBody
// const getToken = function() {
// console.log("------ url",tokenUrl)
// U.request(tokenUrl, ["post"], tokenBody, function(err, data, status, headers, host) {
// console.log('tokendata',data, err, status, headers, host);
// if (status !== 200) {
// counter++;
// }
// if (counter > 50 && counter < 52) {
// instance.reconfigure();
// instance.debug(0, exports.title + " Somethimg is going wrong, reconfiguring ...");
// }
// if (counter > 100){
// instance.send(0, exports.title + " Error, data are not being updated");
// instance.debug(0, exports.title + " Error, data are not being updated");
// counter = 0;
// }
// const response = JSON.parse(data);
// console.log("ii--ii", response);
// //accessToken = response.access_tokens[0];
// console.log(accessToken);
// dataHeaders = {
// "Content-Type": "application/json",
// "Authorization": `Bearer ${accessToken}`,
// "Accept-Version": 3.17,
// }
// getCubes();
// }, {}, tokenHeaders);
// }
let cubes = {
"cubes": [
{
"id": 0,
"name": "camera 1"
},
{
"id": 1,
"name": "Camera 2"
},
{
"id": 2,
"name": "Camera 3"
}
]
}
// const getCubes = function() {
// U.request(cubesUrl, ["get"], tokenBody, function(err, data, status, headers, host) {
// //console.log("getcubes data",data, err, status, headers, host);
// if (status !== 200) {
// counter++;
// }
// const response = JSON.parse(data);
// console.log("getcubes parsed data",response);
// // cubesId = response.cubes[0].id;
// response.cubes.map( cube => {
// allCubes.push(cube.id);
// })
// allCubes.map(cubesId => {
// analyticsUrl = `https://${ip}:${port}/cubes/${cubesId}/analytics`;
// getAnalytics();
// })
// }, {}, dataHeaders);
// }
// let sinksUrlArray = [
// `https://${ip}:${port}/cubes/0/analytics/0/sinks`,
// `https://${ip}:${port}/cubes/1/analytics/0/sinks`,
// `https://${ip}:${port}/cubes/2/analytics/0/sinks`,
// ];
// const getAnalytics = function() {
// U.request(analyticsUrl, ["get"], tokenBody, function(err, data, status, headers, host) {
// //console.log("getAnalytics", data, err, status, headers, host);
// if (status !== 200) {
// counter++;
// }
// //console.log("rrrrr", err, data, status, headers, host);
// let response = JSON.parse(data);
// //console.log("response", response);
// //sequence_number = response.analytics[0].sequence_number;
// analyticsId = response.analytics[0].id;
// //console.log("analyticsid", analyticsId);
// sinksUrl = `https://${ip}:${port}/cubes/${cubesId}/analytics/${analyticsId}/sinks`;
// //getActualData();
// getSinks();
// }, {}, dataHeaders);
// }
// let dataUrlArray = [
// `https://${ip}:${port}/cubes/0/analytics/0/sinks/data`,
// `https://${ip}:${port}/cubes/1/analytics/0/sinks/data`,
// `https://${ip}:${port}/cubes/2/analytics/0/sinks/data`,
// ]
const mapSinks = function() {
sinksUrlArray.map((sinksUrl, index) => {
getSinks(sinksUrl, index);
})
}
const getSinks = function(sinksUrl, index) {
U.request(sinksUrl, ["get"], tokenBody, function(err, data, status, headers, host) {
console.log("getSinks", data, err, status, headers, host);
if (status !== 200) {
counter++;
}
//console.log("rrrrr", err, data, status, headers, host);
let response = JSON.parse(data);
//console.log("getSinks response", response);
sequence_number = response.sequence_number;
actualDataBody = {
"sequence_number": sequence_number,
"sinks": []
};
// dataUrl = `https://${ip}:${port}/cubes/${cubesId}/analytics/${analyticsId}/sinks/data`;
dataUrl = dataUrlArray[index];
response.sinks.map( item => {
actualDataBody.sinks.push({"id": item.id})
})
console.log("actualdatabody", index, actualDataBody)
actualDataBody = JSON.stringify(actualDataBody);
//console.log("actualDataBody", actualDataBody);
startRequests = setInterval(getActualData, 30000, dataUrl, actualDataBody);
}, {}, dataHeaders);
}
let lavyCrossWalk = "A90zgZKXj6nmEMNrpBkX4Ek48y1ReLva3Vbxwqod";
let outOfTrebicR = "oAVmg6jdlZ0bqKN8Ln70wLkM3WBvRyEeax91OY42"
const getActualData = function(dataUrl, actualDataBody) {
U.request(dataUrl, ["post"], actualDataBody , function(err, data, status, headers, host) {
//console.log(data, err, status, headers, host);
if (status !== 200) {
//counter++;
}
if(counter > 20)
{
clearInterval(startRequests);
instance.debug(0, exports.title + " 'getActualData function' Error, data are not being updated");
}
let response = JSON.parse(data);
let actualTime = Date.now();
let values = {};
if(dataUrl == `https://${ip}:${port}/cubes/0/analytics/0/sinks/data`)
{
console.log("prva kamera")
response.sinks.map(item => {
console.log("getActualData", item.id);
console.log("getActualData", item);
if(item.name == 'Prechod-positive')
{
values["people_passing_left"] = item.data.value;
}
if(item.name == 'Prechod-negative')
{
values["people_passing_right"] = item.data.value;
}
if(item.name == 'chodci-zona-l')
{
values["people_left"] = item.data.value;
}
if(item.name == 'chodci-zona-r')
{
values["people_right"] = item.data.value;
}
previousValues[item.name] = item.data.value;
})
let dataToTB = {
"Lb6wB2EDpqr1jJdx0R58g07ly9KPoAN3GO4egmvM": [
{
"ts": actualTime,
"values": values
}
]
}
// "people_left"
// "people_right"
// "aggregation_per_5minutes"
// "aggregation_per_15minutes"
}
if(dataUrl == `https://${ip}:${port}/cubes/1/analytics/0/sinks/data`)
{
// console.log("druha kamera")
// console.log()
// response.sinks.map(item => {
// console.log("getActualData", item.id);
// })
}
if(dataUrl == `https://${ip}:${port}/cubes/2/analytics/0/sinks/data`)
{
// console.log("tretia kamera")
// response.sinks.map(item => {
// console.log("getActualData", item.id);
// console.log("getActualData", item.data);
// })
}
// let value = response.sinks[0].data.value
// let dataToTB = {
// 'tbName': [
// {
// "ts": Date.now(),
// "values": {
// "event_count": value,
// "event_description":"Pocet osob v miestnosti"
// }
// }
// ]
// };
// if (value !== previousValue){
// instance.send(1, dataToTB);
// console.log(dataToTB.tbName[0]);
// }
// previousValue = value;
}, {}, dataHeaders);
}
instance.reconfigure = function() {
clearInterval(startRequests);
options = instance.options;
username = options.username;
password = options.userpassword;
ip = options.ip;
port = options.port;
edge = options.edge;
can = username && password && edge && ip && port? true : false;
instance.status(can ? 'Gathering data' : 'Not configured', can ? 'green' : 'red');
if (!can)
return;
tokenUrl = `https://${ip}:${port}/users/auth`;
cubesUrl = `https://${ip}:${port}/cubes`;
tokenBody = {
"username": username,
"password": password
}
tokenBody = JSON.stringify(tokenBody);
//console.log("tokenBody", tokenBody);
sinksUrlArray = [
`https://${ip}:${port}/cubes/0/analytics/0/sinks`,
`https://${ip}:${port}/cubes/1/analytics/0/sinks`,
`https://${ip}:${port}/cubes/2/analytics/0/sinks`,
];
dataUrlArray = [
`https://${ip}:${port}/cubes/0/analytics/0/sinks/data`,
`https://${ip}:${port}/cubes/1/analytics/0/sinks/data`,
`https://${ip}:${port}/cubes/2/analytics/0/sinks/data`,
]
//startRequests = setInterval(getToken, 10000);
setTimeout(mapSinks, 5000);
//getToken();
}
instance.on('options', instance.reconfigure);
setTimeout(instance.reconfigure, 5000);
}

79
flow/rce_peoplecount.js Normal file
View file

@ -0,0 +1,79 @@
exports.id = 'rce_peoplecount';
exports.title = 'RCE people count';
exports.group = 'Worksys';
exports.color = '#704cff';
exports.input = true;
exports.output = 1;
exports.author = 'Rastislav Kovac';
exports.icon = 'users';
exports.version = '1.0.0';
exports.readme = '# Rce people count';
exports.install = function(instance) {
instance.on('data', function(allData) {
let values = {};
allData = allData.data;
let body = allData.body;
// epoch timestamp
let actualTime = parseInt(body["data_start_timestamp"]);
let value = body.data.value;
values["people_count"] = value;
// values["status"] = "OK";
let tbName = "mp93b2nvd7OoqgBeEyE7N18kjlAV1Y4ZNXwW0zLG";
let dataToTB = {
[tbName]: [
{
"ts": actualTime,
"values": values
}
]
};
instance.send(0, dataToTB);
});
};
let a = {
"query": {},
"body": {
"analytic_id": 0,
"block_name": "sbs",
"cube_id": 2,
"data": {
"data_validity": "ok",
"object_count": 5,
"value": 5
},
"data_end_timestamp": "1632327078920",
"data_start_timestamp": "1632327078920",
"id": 1,
"name": "count",
"operator_attribute": "object_count",
"output_type": "widget",
"output_value_type": "value"
},
"session": null,
"user": null,
"files": [],
"headers": {
"host": "10.0.0.35:12345",
"content-type": "application/json",
"content-length": "393",
"connection": "Keep-Alive",
"accept-encoding": "gzip, deflate",
"accept-language": "en-US,*",
"user-agent": "Mozilla/5.0"
},
"url": "/ludia/",
"params": {}
};

84
flow/send_to_display.js Normal file
View file

@ -0,0 +1,84 @@
exports.id = 'sendtodisplay';
exports.title = 'Send to display';
exports.group = 'Worksys';
exports.color = '#5D9CEC';
exports.version = '0.0.1';
exports.output = 2;
exports.input = 2;
exports.author = 'Rastislav Kovac';
exports.icon = 'cloud-upload';
const { execSync } = require('child_process');
exports.install = function(instance) {
const getDepartures = setInterval(checkIfWeHaveDepartures, 60000);
let departures = [];
let firstDepartureTime = getCurrentTimeFormatted();
function checkIfWeHaveDepartures() {
if (departures.length === 0) instance.send(1, "reload");
}
instance.on('1', flowdata => {
departures = flowdata.data;
})
instance.on('close', function() {
clearInterval(getDepartures);
})
instance.on('0', function(_) {
// for some reason new Date() function does not set month and year in local timezone, so we use "timedatectl" command
let dateFromCommand = execSync("timedatectl", {}).toString();
let first = dateFromCommand.search("time:");
let last = dateFromCommand.search(" CE");
dateFromCommand = dateFromCommand.slice(first, last); //Thu 2022-04-07 13:38:03
const d = new Date(dateFromCommand);
let hour = d.getHours();
let minute = d.getMinutes();
console.log('******hour, minute', hour, minute)
if (minute < 10) minute = `0${minute}`;
if (hour < 10) hour = `0${hour}`;
const now = `${hour}:${minute}`;
console.log('******--------', firstDepartureTime, now);
console.log('******--------', firstDepartureTime < now);
if (firstDepartureTime < now) {
let departures_filtered = departures.filter(departure => {
const timeOfDeparture = departure[1];
if (now < timeOfDeparture) return true;
return false;
})
if (departures_filtered.length > 0) firstDepartureTime = departures_filtered[0][1];
else firstDepartureTime = "00:00";
instance.send(0, departures_filtered.slice(0, 10));
}
})
}
function getCurrentTimeFormatted() {
const now = new Date(); // Get the current date and time
const hours = now.getHours(); // Get the current hour (0-23)
const minutes = now.getMinutes(); // Get the current minute (0-59)
// Pad with leading zero if the number is less than 10
const formattedHours = hours < 10 ? '0' + hours : hours;
const formattedMinutes = minutes < 10 ? '0' + minutes : minutes;
return `${formattedHours}:${formattedMinutes}`;
}

86
flow/serialport_helper.js Normal file
View file

@ -0,0 +1,86 @@
const { exec } = require('child_process');
function openPort(port){
return new Promise((resolve, reject) => {
var callbackError = function(data) {
port.removeListener('error', callbackError);
port.removeListener('open', callbackError);
reject(err.message);
};
var callbackOpen = function(data) {
port.removeListener('error', callbackError);
port.removeListener('open', callbackOpen);
resolve("port open: ok");
};
port.on('error', callbackError);
port.on('open', callbackOpen);
port.open();
})
}
function runSyncExec(command){
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if(error == null) resolve(stdout);
reject(error);
});
})
}
async function writeData(port, data, readbytes, timeout){
return new Promise((resolve, reject) => {
if(readbytes == undefined) readbytes = 0;
if(timeout == undefined) timeout = 10000;
var callback = function(data) {
rsPortReceivedData.push(...data);
let l = rsPortReceivedData.length;
if(l >= readbytes)
{
port.removeListener('data', callback);
clearTimeout(t);
resolve(rsPortReceivedData);
}
};
let t = setTimeout(() => {
port.removeListener('data', callback);
reject("TIMEOUT READING");
}, timeout);
let rsPortReceivedData = [];
if(readbytes > 0) port.on('data', callback);
port.write(Buffer.from(data), function(err) {
if (err) {
port.removeListener('data', callback);
reject(err.message);
}
if(readbytes == 0)
{
resolve(rsPortReceivedData);
}
});
})
}
module.exports = {
openPort,
runSyncExec,
writeData
}

704
flow/sokolovodchody.js Normal file
View file

@ -0,0 +1,704 @@
exports.id = 'sokolovodchodyspojov';
exports.title = 'Sokolov odchody spojov';
exports.version = '1.0.0';
exports.group = 'Worksys';
exports.color = '#2134B0';
exports.input = 1;
exports.output = ["blue", "white", "yellow", "red"];
exports.click = false;
exports.author = 'Rastislav Kovac';
exports.options = { edge: "undefined" };
exports.readme = `Get bus departures from bus stop in Sokolov`;
const instanceSendTo = {
debug: 0,
departures: 1,
delays: 2
};
exports.install = function(instance) {
const parseString = require('xml2js').parseString;
const fetch = require('node-fetch');
const { execSync } = require('child_process');
class FetchRequest {
async getDepartures() {
// use simpleDate to get bus departures for current day, otherwise use date to get departures for next day
// it must be in format 2022-12-05
console.log(new Date())
//let date = new Date().toLocaleString().split('/'); //[ '12', '6', '2022, 11:47:32 PM' ]
// console.log(date)
// console.log(new Date().toLocaleString('zh', { hour12: false }))
// console.log(new Date().toLocaleString('sk', { hour12: false }))
const date = new Date(new Date().toLocaleString("en-US", {timeZone: "Europe/Bratislava" }))
const year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
console.log(year, month, day)
if(('' + month).length == 1) month = '0' + month;
if(('' + day).length == 1) day = '0' + day;
// if(date[0].length == 1) date[0] = '0' + date[0];
// if(date[1].length == 1) date[1] = '0' + date[1];
// const year = date[2].slice(0,4); // "2022"
// const month = date[0];
// const day = date[1];
const simpleDate = `${year}-${month}-${day}`;
console.log('simple date ~~~~~~', simpleDate)
const sr =
'<?xml version="1.0" encoding="utf-8"?>' +
'<soap12:Envelope ' +
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
'xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> ' +
'<soap12:Body>' +
'<GetDepartures xmlns="http://www.emtest.sk/cp/">' +
'<busstopID>411000469</busstopID>' +
'<nastupiste>0</nastupiste>' +
`<dateFrom>${simpleDate}</dateFrom>` +
// '<dateFrom>2022-12-05</dateFrom>' +
`<dateTo>${simpleDate}</dateTo>` +
// '<dateTo>2022-12-05</dateTo>' +
'<firmaID>411</firmaID>' +
'</GetDepartures>' +
'</soap12:Body>' +
'</soap12:Envelope>';
let response = await fetch('http://62.141.28.141:5533/CPWebSvc2/service.asmx', {
method : "POST",
body: sr,
headers: {'Content-Type':'application/soap+xml'}
})
//console.log('bus departures``````````', response)
console.log('response ok ^^^^^^ dep',response.ok);
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
}
response = await response.text();
//console.log('bus departures``````````', response)
const result = this.parseDepartures(response);
return result;
}
parseDepartures(data) {
const allRegex = [/tn="[a-z]*/g, /lnt="[a-z]*/g, /td="[a-z]*/g, /ebn="[a-z]*/g];
let index = 0;
//we receive all departures for current day
const allDepartures = [];
//we filter just departures which are in future
const futureDepartures = [];
let value = null;
//for (const regexp of allRegex)
for (let i = 0; i < allRegex.length; i++)
{
index = 0;
const matches = data.matchAll(allRegex[i]);
// console.log('-----', matches)
for (const match of matches) {
if(!allDepartures[index]) allDepartures[index] = [];
//console.log(`Found ${match[0]} start=${match.index} end=${match.index + match[0].length}.`);
const substr = match.input.slice(match.index + match[0].length, );
const endOfSearchedString = substr.indexOf('"');
if(i == 1) value = substr.slice(4, endOfSearchedString); // bus number e.g. '7'
else if (i == 3) value = substr.slice(13, capitalizeFirstLetter(endOfSearchedString)); //name of buss stop e.g. "Sidlisko Michal skola"
else if (i == 2) {
// in case date, or time is just 1 number long (5.7.22 4:5), we need to change it to (05.07.22 04:05)
value = substr.slice(0, endOfSearchedString); // "5.11.22 4:5"
let date = value.slice(0, value.indexOf(' ')); // "5.11.22"
date = date.split('.') // ['5', '11', '22']
if(date[0].length == 1) date[0] = '0' + date[0];
if(date[1].length == 1) date[1] = '0' + date[1];
date = date.join('.')
let time = value.slice(value.indexOf(' ') + 1, value.length); // "4:5" ==> '04:05'
time = time.split(':');
if(time[0].length == 1) time[0] = '0' + time[0];
if(time[1].length == 1) time[1] = '0' + time[1];
time = time.join(':')
value = date + ' ' + time;
}
else value = substr.slice(0, endOfSearchedString);
allDepartures[index].push(value);
index++;
}
}
//return allDepartures;
// console.log('---',allDepartures);
//instance.send(instanceSendTo.allDepartures, allDepartures);
//we filter just departures, that are in future
let date = new Date();
date = date.toISOString();
console.log('******date', date)
allDepartures.map(item => {
let busDeparture = item[2];
let temp = [...busDeparture.matchAll(/\d\d/g)].map(a => a[0]); //[ '24', '11', '22', '04', '05' ]
busDeparture = new Date(`20${temp[2]}-${temp[1]}-${temp[0]} ${temp[3]}:${temp[4]}`)
//console.log(busDeparture.toISOString(), date.toISOString())
//date je UTC time, preto sa zmenia allDepartures az po 01:00
//console.log('***********',busDeparture, date)
if(busDeparture.toISOString() > date)
{
item[2] = `${temp[3]}:${temp[4]}`
futureDepartures.push(item);
}
})
return futureDepartures;
}
async getDelays() {
const delays = [];
const d = new Date();
const date = d.toISOString();
const sr =
'<?xml version="1.0" encoding="utf-8"?>' +
'<soap12:Envelope ' +
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
'xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> ' +
'<soap12:Body>' +
'<FindAVLDepartureFromBusStopTable xmlns="http://www.emtest.sk/cp/">' +
'<busStopID>411000469</busStopID>' +
`<departureDateTime>${date}</departureDateTime>` +
'<count>10</count>' +
'<platformNumber>0</platformNumber>' +
'<firmId>411</firmId>' +
'</FindAVLDepartureFromBusStopTable>' +
'</soap12:Body>' +
'</soap12:Envelope>';
let response = await fetch('http://62.141.28.141:5533/CPWebSvc2/service.asmx', {
method : "POST",
body: sr,
headers: {'Content-Type':'application/soap+xml'}
})
//console.log('delays ------', response);
//console.log('response ok ^^^^^^ dep',response.ok);
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
}
response = await response.text();
//console.log('delays ~~~~~', response);
parseString(response, function (err, result) {
//console.log(result)
//console.log('asd',result['soap:Envelope']['soap:Body'][0].FindAVLDepartureFromBusStopTableResponse[0].departureCollection[0].Departure);
result = result['soap:Envelope']['soap:Body'][0].FindAVLDepartureFromBusStopTableResponse[0].departureCollection[0].Departure;
//console.log(checkNested(result,'soap:Envelope.soap:Body[0].FindAVLDepartureFromBusStopTableResponse[0].departureCollection[0].Departure' ));
//console.log(checkNested(result,'soapEnvelope' ));
// result = result['soap:Envelope']['soap:Body'][0].FindAVLDepartureFromBusStopTableResponse[0].departureCollection[0];
// console.log('====', result)
if(!result) return;
for (const item of result)
{
let a = null;
//console.log(item)
if(item.Delay[0].Departure[0].startsWith('0001-01')) a = [item.TripNumber[0], 0];
else a = [item.TripNumber[0], item.Delay[0].Departure[0], item.Delay[0].Arrival[0]];
delays.push(a);
}
});
return delays;
}
}
const departureRequest = new FetchRequest();
const delaysRequest = new FetchRequest();
const getDepartures = () => {
departureRequest.getDepartures()
.then(futureDepartures => {
let responseObj = {};
responseObj["departures"] = futureDepartures;
console.log('future +++++++', responseObj)
instance.send(instanceSendTo.departures, responseObj);
}).catch(error => {
console.log('Error fetching bus departures - repeating request in 1 minute', error);
setTimeout(() => getDepartures(), 60000)
});
}
function getDelays(){
delaysRequest.getDelays()
.then(delays => {
let responseObj = {};
responseObj["delays"] = delays;
console.log('delays -------', responseObj)
instance.send(instanceSendTo.delays, responseObj);
})
.catch(error => {
console.log('Error fetching bus delays data', error);
});
}
setTimeout(getDepartures, 30000);
setInterval(getDelays, 60000);
// let startDay = new Date().getDay();
let date = new Date().toLocaleString().split('/'); //[ '12', '6', '2022, 11:47:32 PM' ]
let startDay = date[1]; //'6'
function checkIfNewDay() {
let currentDay = new Date().toLocaleString().split('/')[1];
console.log('new day check -----',startDay, currentDay);
if(currentDay == startDay) return;
getDepartures();
startDay = currentDay;
}
//we check if day changed, if yes, we get bus departures. we check every 15 minutes
setInterval(checkIfNewDay, 900000);
function capitalizeFirstLetter(string) {
return string[0].toUpperCase() + string.slice(1);
}
};
const aodchody = [
[
"2",
"3",
"16.11.22 4:5",
"Závodu míru"
],
[
"290",
"3",
"16.11.22 4:25",
"Závodu míru"
],
[
"292",
"3",
"16.11.22 4:48",
"Závodu míru"
],
[
"296",
"3",
"16.11.22 4:58",
"Závodu míru"
],
[
"6",
"3",
"16.11.22 5:5",
"Závodu míru"
],
[
"300",
"3",
"16.11.22 5:18",
"Závodu míru"
],
[
"8",
"3",
"16.11.22 5:55",
"Závodu míru"
],
[
"16",
"3",
"16.11.22 6:30",
"Závodu míru"
],
[
"1",
"33",
"16.11.22 6:45",
"sídl. Michal škola"
],
[
"18",
"3",
"16.11.22 6:52",
"Hrušková"
],
[
"310",
"3",
"16.11.22 7:0",
"Závodu míru"
],
[
"22",
"3",
"16.11.22 7:10",
"Závodu míru"
],
[
"5",
"33",
"16.11.22 7:50",
"sídl. Michal škola"
],
[
"32",
"3",
"16.11.22 8:10",
"Závodu míru"
],
[
"7",
"33",
"16.11.22 8:25",
"sídl. Michal škola"
],
[
"100",
"3",
"16.11.22 8:30",
"Závodu míru"
],
[
"9",
"33",
"16.11.22 8:50",
"sídl. Michal škola"
],
[
"34",
"3",
"16.11.22 9:0",
"Závodu míru"
],
[
"38",
"3",
"16.11.22 9:20",
"Závodu míru"
],
[
"11",
"33",
"16.11.22 9:30",
"sídl. Michal škola"
],
[
"298",
"3",
"16.11.22 9:55",
"Závodu míru"
],
[
"2",
"7",
"16.11.22 10:10",
"Březová, aut. st."
],
[
"13",
"33",
"16.11.22 10:20",
"sídl. Michal škola"
],
[
"48",
"3",
"16.11.22 10:50",
"Závodu míru"
],
[
"50",
"3",
"16.11.22 11:13",
"Závodu míru"
],
[
"15",
"33",
"16.11.22 11:25",
"sídl. Michal škola"
],
[
"52",
"3",
"16.11.22 11:40",
"Závodu míru"
],
[
"17",
"33",
"16.11.22 11:52",
"sídl. Michal škola"
],
[
"56",
"3",
"16.11.22 12:20",
"Závodu míru"
],
[
"62",
"3",
"16.11.22 12:40",
"Závodu míru"
],
[
"64",
"3",
"16.11.22 12:55",
"Závodu míru"
],
[
"19",
"33",
"16.11.22 13:0",
"sídl. Michal škola"
],
[
"66",
"3",
"16.11.22 13:20",
"Stará ovčárna"
],
[
"21",
"33",
"16.11.22 13:35",
"sídl. Michal škola"
],
[
"23",
"33",
"16.11.22 14:0",
"sídl. Michal škola"
],
[
"72",
"3",
"16.11.22 14:5",
"Závodu míru"
],
[
"25",
"33",
"16.11.22 14:42",
"sídl. Michal škola"
],
[
"94",
"3",
"16.11.22 14:48",
"Závodu míru"
],
[
"27",
"33",
"16.11.22 15:5",
"sídl. Michal škola"
],
[
"106",
"3",
"16.11.22 15:9",
"Závodu míru"
],
[
"84",
"3",
"16.11.22 15:35",
"Závodu míru"
],
[
"29",
"33",
"16.11.22 15:35",
"sídl. Michal škola"
],
[
"6",
"7",
"16.11.22 15:50",
"Březová, aut. st."
],
[
"96",
"3",
"16.11.22 16:10",
"Závodu míru"
],
[
"31",
"33",
"16.11.22 16:10",
"sídl. Michal škola"
],
[
"4",
"7",
"16.11.22 16:25",
"Březová, aut. st."
],
[
"102",
"3",
"16.11.22 16:30",
"Závodu míru"
],
[
"302",
"3",
"16.11.22 16:44",
"Závodu míru"
],
[
"8",
"7",
"16.11.22 16:55",
"Březová, aut. st."
],
[
"108",
"3",
"16.11.22 17:5",
"Stará ovčárna"
],
[
"112",
"3",
"16.11.22 17:30",
"Závodu míru"
],
[
"114",
"3",
"16.11.22 17:53",
"Závodu míru"
],
[
"118",
"3",
"16.11.22 18:10",
"Závodu míru"
],
[
"120",
"3",
"16.11.22 18:30",
"Závodu míru"
],
[
"122",
"3",
"16.11.22 18:50",
"Závodu míru"
],
[
"124",
"3",
"16.11.22 19:15",
"Závodu míru"
],
[
"126",
"3",
"16.11.22 19:30",
"Závodu míru"
],
[
"130",
"3",
"16.11.22 19:50",
"Závodu míru"
],
[
"132",
"3",
"16.11.22 20:15",
"Závodu míru"
],
[
"134",
"3",
"16.11.22 20:45",
"Závodu míru"
],
[
"136",
"3",
"16.11.22 21:5",
"Závodu míru"
],
[
"256",
"3",
"16.11.22 21:20",
"Stará ovčárna"
],
[
"140",
"3",
"16.11.22 22:8",
"Stará ovčárna"
]
]

317
flow/suncalc.js Normal file
View file

@ -0,0 +1,317 @@
/*
(c) 2011-2015, Vladimir Agafonkin
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
https://github.com/mourner/suncalc
*/
(function () { 'use strict';
// shortcuts for easier to read formulas
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180;
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
// date/time constants and conversions
var dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588,
J2000 = 2451545;
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
function toDays(date) { return toJulian(date) - J2000; }
// general calculations for position
var e = rad * 23.4397; // obliquity of the Earth
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
function astroRefraction(h) {
if (h < 0) // the following formula works for positive altitudes only.
h = 0; // if h = -0.08901179 a div/0 would occur.
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
}
// general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
function eclipticLongitude(M) {
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
P = rad * 102.9372; // perihelion of the Earth
return M + C + P + PI;
}
function sunCoords(d) {
var M = solarMeanAnomaly(d),
L = eclipticLongitude(M);
return {
dec: declination(L, 0),
ra: rightAscension(L, 0)
};
}
var SunCalc = {};
// calculates sun position for a given date and latitude/longitude
SunCalc.getPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = sunCoords(d),
H = siderealTime(d, lw) - c.ra;
return {
azimuth: azimuth(H, phi, c.dec),
altitude: altitude(H, phi, c.dec)
};
};
// sun times configuration (angle, morning name, evening name)
var times = SunCalc.times = [
[-0.833, 'sunrise', 'sunset' ],
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
[ -6, 'dawn', 'dusk' ],
[ -12, 'nauticalDawn', 'nauticalDusk'],
[ -18, 'nightEnd', 'night' ],
[ 6, 'goldenHourEnd', 'goldenHour' ]
];
// adds a custom time to the times config
SunCalc.addTime = function (angle, riseName, setName) {
times.push([angle, riseName, setName]);
};
// calculations for sun times
var J0 = 0.0009;
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
// returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L) {
var w = hourAngle(h, phi, dec),
a = approxTransit(w, lw, n);
return solarTransitJ(a, M, L);
}
// calculates sun times for a given date, latitude/longitude, and, optionally,
// the observer height (in meters) relative to the horizon
SunCalc.getTimes = function (date, lat, lng, height) {
height = height || 0;
var lw = rad * -lng,
phi = rad * lat,
dh = observerAngle(height),
d = toDays(date),
n = julianCycle(d, lw),
ds = approxTransit(0, lw, n),
M = solarMeanAnomaly(ds),
L = eclipticLongitude(M),
dec = declination(L, 0),
Jnoon = solarTransitJ(ds, M, L),
i, len, time, h0, Jset, Jrise;
var result = {
solarNoon: fromJulian(Jnoon),
nadir: fromJulian(Jnoon - 0.5)
};
for (i = 0, len = times.length; i < len; i += 1) {
time = times[i];
h0 = (time[0] + dh) * rad;
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);
result[time[1]] = fromJulian(Jrise);
result[time[2]] = fromJulian(Jset);
}
return result;
};
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
M = rad * (134.963 + 13.064993 * d), // mean anomaly
F = rad * (93.272 + 13.229350 * d), // mean distance
l = L + rad * 6.289 * sin(M), // longitude
b = rad * 5.128 * sin(F), // latitude
dt = 385001 - 20905 * cos(M); // distance to the moon in km
return {
ra: rightAscension(l, b),
dec: declination(l, b),
dist: dt
};
}
SunCalc.getMoonPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = moonCoords(d),
H = siderealTime(d, lw) - c.ra,
h = altitude(H, phi, c.dec),
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
h = h + astroRefraction(h); // altitude correction for refraction
return {
azimuth: azimuth(H, phi, c.dec),
altitude: h,
distance: c.dist,
parallacticAngle: pa
};
};
// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
SunCalc.getMoonIllumination = function (date) {
var d = toDays(date || new Date()),
s = sunCoords(d),
m = moonCoords(d),
sdist = 149598000, // distance from Earth to Sun in km
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
return {
fraction: (1 + cos(inc)) / 2,
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
angle: angle
};
};
function hoursLater(date, h) {
return new Date(date.valueOf() + h * dayMs / 24);
}
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
var t = new Date(date);
if (inUTC) t.setUTCHours(0, 0, 0, 0);
else t.setHours(0, 0, 0, 0);
var hc = 0.133 * rad,
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
for (var i = 1; i <= 24; i += 2) {
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
a = (h0 + h2) / 2 - h1;
b = (h2 - h0) / 2;
xe = -b / (2 * a);
ye = (a * xe + b) * xe + h1;
d = b * b - 4 * a * h1;
roots = 0;
if (d >= 0) {
dx = Math.sqrt(d) / (Math.abs(a) * 2);
x1 = xe - dx;
x2 = xe + dx;
if (Math.abs(x1) <= 1) roots++;
if (Math.abs(x2) <= 1) roots++;
if (x1 < -1) x1 = x2;
}
if (roots === 1) {
if (h0 < 0) rise = i + x1;
else set = i + x1;
} else if (roots === 2) {
rise = i + (ye < 0 ? x2 : x1);
set = i + (ye < 0 ? x1 : x2);
}
if (rise && set) break;
h0 = h2;
}
var result = {};
if (rise) result.rise = hoursLater(t, rise);
if (set) result.set = hoursLater(t, set);
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
return result;
};
// export as Node module / AMD module / browser variable
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
else if (typeof define === 'function' && define.amd) define(SunCalc);
else window.SunCalc = SunCalc;
}());

96
flow/tcpipclient.js Normal file
View file

@ -0,0 +1,96 @@
exports.id = 'tcpipclient';
exports.title = 'TCP/IP Client';
exports.group = 'TCP/IP';
exports.color = '#888600';
exports.version = '1.0.0';
exports.icon = 'exchange';
exports.input = false;
exports.output = 0;
exports.author = 'Lukas Muransky';
exports.variables = true;
exports.options = { ip: '127.0.0.1', port: 9999 };
exports.traffic = false;
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="ip" data-jc-config="placeholder:127.0.0.1;required:true" class="m">Hostname or IP address</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:9999;required:true" class="m">Port</div>
</div>
</div>
<div data-jc="checkbox" data-jc-path="helvar">@(Helvar)</div>
</div>
<script>
ON('save.tcpip', function(component, options) {
!component.name && (component.name = '{0}:{1}'.format(options.ip, options.port));
});
</script>`;
exports.readme = `
# TCP/IP Clients`;
var CLIENTS = [];
var tcpip;
global.TCPIP_CLIENTS = [];
exports.install = function(instance) {
var client;
instance.custom.reconfigure = function(o, old_options) {
if (old_options)
CLIENTS = CLIENTS.remove(function(b){
return b.id === old_options.id;
});
var options = instance.options;
if (!options.ip || !options.port) {
instance.status('Not configured', 'red');
return;
}
options.id = instance.name;
instance.custom.createClient();
TCPIP_CLIENTS = [];
CLIENTS.forEach(n => TCPIP_CLIENTS.push(n));
};
instance.custom.createClient = function () {
var o = instance.options;
var opts = {
ip: o.ip,
port: o.port,
helvar: o.helvar,
id: o.id
};
client = new Client(opts);
CLIENTS.push(client);
instance.status('Ready');
};
instance.on('options', instance.custom.reconfigure);
instance.custom.reconfigure();
};
FLOW.trigger('tcpip.clients', function(next) {
var clients = [];
CLIENTS.forEach(n => clients.push(n.id));
next(clients);
});
function Client(options) {
var self = this;
self.id = options.id;
self.options = options;
return self;
}

96
flow/tcpipisend.js Normal file
View file

@ -0,0 +1,96 @@
exports.id = 'tcpipsend';
exports.title = 'TCP/IP Send';
exports.group = 'TCP/IP';
exports.color = '#888600';
exports.version = '1.0.2';
exports.icon = 'sign-out';
exports.input = true;
exports.output = 1;
exports.author = 'Lukas Muransky';
exports.options = {};
exports.html = `<div class="padding">
<div data---="dropdown__tcpclient__datasource:tcpipconfig;required:true" class="m">@(Clients)</div>
</div>
<script>
var tcpipconfig = [];
var clients = {};
ON('open.tcpipsend', function(component, options) {
/*TRIGGER('tcpip.clients', function(data) {
clients = data;
tcpipconfig = [];
data.forEach(function(client){
tcpipconfig.push(client.id);
});
console.log(tcpipconfig);
});*/
TRIGGER('tcpip.clients', 'tcpipconfig');
});
</script>`;
exports.readme = `# TCPIP Connect`;
exports.install = function(instance) {
var net = require('net');
instance.on('data', function(flowdata) {
connect(flowdata);
});
instance.reconfigure = function() {
if (!instance.options.tcpclient)
return instance.status('Not configured', 'red');
else
return instance.status('Configured');
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
function connect(response) {
//instance.debug('Input data: ' + data);
//let Summary = [];
var client = new net.Socket();
var tcpip = TCPIP_CLIENTS.find(obj => obj.id == instance.options.tcpclient);
var opt = {};
opt.ip = tcpip.options.ip;
opt.port = tcpip.options.port;
opt.helvar = tcpip.options.helvar;
client.connect(opt.port, opt.ip, function() {
instance.status('Connected');
});
//instance.debug('Response: ' + response.data);
client.on('close', function() {
//instance.debug('Connection closed (inside)');
instance.status('Closed');
});
client.on('data', function(answer) {
response.data = answer.toString();
instance.send2(response);
});
if(opt.helvar && response.data.slice(-1) == '*'){
response.data = response.data.slice(0, -1); //odstrani *
client.write(response.data);
response.data = response.data.slice(0, -1); //odstrani # lebo je prida ma koniec
response.data = '?'+response.data.slice(1) + "=Command sent#";
instance.send2(response);
//client.destroy();
} else {
client.write(response.data);
}
client.end();
}
};
///FLOW.find() najde komponentu instance.connect(data)

169
flow/tcpipserver.js Normal file
View file

@ -0,0 +1,169 @@
exports.id = 'tcpserver';
exports.title = 'TCP/IP Server';
exports.version = '1.0.4';
exports.group = 'TCP/IP';
exports.color = '#888600';
exports.output = ["red", "white"];
exports.click = false;
exports.author = 'Jakub Klena';
exports.icon = 'server';
exports.options = { ip: '0.0.0.0', port: 8421, edge: "M6ogKQW09bOXewAYvZyvkn5JrV1aRnPGE37p42Nx" };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="ip" data-jc-config="placeholder:0.0.0.0;required:true" class="m">IP</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:8421;required:true" class="m">Port</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:M6ogKQW09bOXewAYvZyvkn5JrV1aRnPGE37p42Nx;required:true" class="m">Edge TB Name</div>
</div>
</div>
</div>`;
exports.readme = `# TCP Server
## Outputs
- *Red* - ERROR output
- *White* - Incomming message output
`;
exports.install = function(instance) {
let net = require('net');
let server = null;
let myip = "0.0.0.0";
let myport = 8421;
let myedge = "M6ogKQW09bOXewAYvZyvkn5JrV1aRnPGE37p42Nx";
let interv = null;
let ERRWEIGHT = {
EMERGENCY: "emergency", // System unusable
ALERT: "alert", // Action must be taken immidiately
CRITICAL: "critical", // Component unable to function
ERROR: "error", // Error, but component able to recover from it
WARNING: "warning", // Possibility of error, system running futher
NOTICE: "notice", // Significant message but not an error, things user might want to know about
INFO: "informational", // Info
DEBUG: "debug" // Debug - only if CONFIG.debug is enabled
}
setTimeout(function(){
if (server !== null){
if (server.listening){
instance.status("Listening", "green");
} else {
instance.status("Not listening", "red");
}
}
}, 5000);
function resetServer(){
sendError(myedge, "resetServer", ERRWEIGHT.DEBUG, "resetServer called !", {});
if (server !== null){
sendError(myedge, "resetServer", ERRWEIGHT.DEBUG, "Server already exists", {});
server.close(function(){
sendError(myedge, "resetServer", ERRWEIGHT.DEBUG, "Server closed intentionally", {});
server = null;
resetServer();
});
} else {
sendError(myedge, "resetServer", ERRWEIGHT.DEBUG, "Server doesnt exist", {});
server = net.createServer((c) => {
sendError(myedge, "resetServer", ERRWEIGHT.INFO, "New client connected !", {"ip":c.localAddress, "port":c.localPort});
c.on("data", (d) => {
sendOutputMsg({
"ip":c.localAddress,
"port":c.localPort,
"data":d.toString()
});
});
c.on('end', () => {
sendError(myedge, "resetServer", ERRWEIGHT.INFO, "Client disconnected !", {"ip":c.localAddress, "port":c.localPort});
});
});
server.listen(myport, myip);
}
};
function isJson(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
instance.reconfigure = function() {
//code
myip = instance.options.ip;
myport = instance.options.port;
myedge = instance.options.edge;
setTimeout(resetServer, 5000);
};
instance.close = function() {
// close sockets and such
if (server !== null){
server.close(function(){});
}
};
function sendError(device, func, weight, str, extra){
let content = {
"type": weight,
"status": "new",
"source": {
"function":func,
"component":instance.id,
"component_name":instance.name
},
"message":str,
"message_data": extra
};
let error = {};
error[device] = [
{
"ts": Date.now(),
"values": {
"_event":content
}
}
];
instance.send(0, error);
}
function sendOutputMsg(str){
instance.send(1, str);
}
instance.on('options', instance.reconfigure);
instance.reconfigure();
function humanReadableTimeAndDate(){
let date_ob = new Date();
let date = ("0" + date_ob.getDate()).slice(-2);
let month = ("0" + (date_ob.getMonth() + 1)).slice(-2);
let year = date_ob.getFullYear();
let hours = ("0" + date_ob.getHours()).slice(-2);
let minutes = ("0" + date_ob.getMinutes()).slice(-2);
let seconds = ("0" + date_ob.getSeconds()).slice(-2);
return date+"."+month+"."+year+" "+hours+":"+minutes+":"+seconds;
}
};

87
flow/timer.js Normal file
View file

@ -0,0 +1,87 @@
exports.id = 'timer';
exports.title = 'Timer';
exports.version = '1.0.1';
exports.group = 'Time';
exports.color = '#F6BB42';
exports.output = 1;
exports.click = true;
exports.author = 'Peter Širka';
exports.icon = 'clock-o';
exports.options = { interval: 1000 };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:1000;increment:true;type:number;required:true;align:center">@(Interval in milliseconds)</div>
</div>
</div>
<section>
<label><i class="fa fa-random"></i>@(Output data)</label>
<div class="padding">
<div data-jc="dropdown" data-jc-path="datatype" data-jc-config="items:,String|string,Integer|integer,Float|float,Boolean|boolean,Date|date,Object|object,Base64 as Buffer|buffer" class="m">@(Data type (String by default))</div>
<div data-jc="textbox" data-jc-path="data" data-jc-config="placeholder:@(e.g. Hello world or { hello: 'world'} or ['hello', 'world'])">@(Data)</div>
</div>
</section>
</div>`;
exports.readme = `# Timer
Timer will trigger flow in the given interval (in milliseconds). You can optionally define a data-type of the output and the data.`;
exports.install = function(instance) {
var value;
var id;
instance.on('click', () => value && instance.send2(value));
instance.reconfigure = function() {
var options = instance.options;
if (!options.interval) {
instance.status('Not configured', 'red');
return;
}
value = null;
switch (options.datatype) {
case 'string':
value = options.data;
break;
case 'integer':
value = U.parseInt(options.data);
break;
case 'float':
value = U.parseFloat(options.data);
break;
case 'date':
var num = U.parseInt(options.data);
value = num ? new Date(num) : options.data.parseDate();
break;
case 'object':
try {
value = (new Function('return ' + options.data))();
} catch (e) {
instance.error(e);
}
break;
case 'boolean':
value = options.data.parseBoolean();
break;
case 'buffer':
try {
value = F.is4 ? Buffer.from(options.data) : U.createBuffer(options.data);
} catch (e) {
instance.error(e);
}
break;
}
clearInterval(id);
options.interval && (id = setInterval(() => instance.send2(value), options.interval));
instance.status('');
};
instance.on('close', () => clearInterval(id));
instance.on('options', instance.reconfigure);
instance.reconfigure();
};

129
flow/timesetter.js Normal file
View file

@ -0,0 +1,129 @@
exports.id = 'timesetter';
exports.title = 'Timesetter';
exports.group = 'Worksys';
exports.color = '#656D78';
exports.input = true;
exports.output = 1;
exports.author = 'Rastislav Kovac';
exports.icon = 'code';
exports.version = '1.0.0';
exports.readme = `
This component can be installed just on controllers without direct internet access!
'project_id' variable needs to be set for every project!!
Timesetter sends requests once a day to service-prod01.worksys.io to get
actual date and time. It sets unipi's system timedate
`;
//! SET project_id
const project_id = 39;
const { exec } = require('child_process');
exports.install = function(instance) {
instance.on('data', function(flowdata) {
RESTBuilder.make(function(builder) {
if(!builder) return;
builder.method('GET');
//FLOW.OMS_edge_fw_version
builder.url(`http://192.168.252.2:8004/gettime?projects_id=${project_id}`);
builder.callback(function(err, response, output) {
if (err) {
console.log(err);
return;
}
instance.send(0, "RESTBuilder timedatectl response");
const res = output.response;
try {
const obj = JSON.parse(res);
let d = new Date(obj.date);
const now = new Date();
//offset in minutes - convertUTCDateToLocalDate
let diffInMinutes = now.getTimezoneOffset();
//d.setMinutes( d.getMinutes() + diffInMinutes );
//let converted = convertUTCDateToLocalDate(d);
console.log("---->TimezoneOffset", diffInMinutes);
if(d instanceof Date)
{
console.log("current js date:", d, d.getHours());
let year = d.getFullYear();
let month = addZeroBefore(d.getMonth() + 1);
let day = addZeroBefore(d.getDate());
//-2 hodiny!!!!
let hours = addZeroBefore( d.getHours() );
let minutes = addZeroBefore(d.getMinutes() );
let seconds = addZeroBefore(d.getSeconds());
let timestamp = `${year}${month}${day} ${hours}:${minutes}:${seconds}`;
let dstr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
//TODO - poslat notifikaciu a nastav hw cas
//timedatectl set-timezone "Europe/Bratislava"
//hwclock --set --date="2021-08-24 15:02:00" --localtime
//https://www.tecmint.com/set-time-timezone-and-synchronize-time-using-timedatectl-command/
//timedatectl set-time "2022-04-27 09:13:00"
{
let year = d.getUTCFullYear();
let month = addZeroBefore(d.getUTCMonth() + 1);
let day = addZeroBefore(d.getUTCDate());
let hours = addZeroBefore( d.getUTCHours() );
let minutes = addZeroBefore(d.getUTCMinutes() );
let seconds = addZeroBefore(d.getUTCSeconds());
let UTCstr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
exec(`sudo timedatectl set-time "${UTCstr}"`, (err, stdout, stderr) => {
if (err || stderr) {
console.error(err);
console.log(stderr);
console.log(UTCstr);
}
else
{
console.log(`UTC: timedatectl set-time "${UTCstr}"`);
}
});
}
}
} catch(e) {
console.log(e)
}
})
})
})
function addZeroBefore(n) {
return (n < 10 ? '0' : '') + n;
}
};

79
flow/trigger.js Normal file
View file

@ -0,0 +1,79 @@
exports.id = 'trigger';
exports.title = 'Trigger';
exports.group = 'Inputs';
exports.color = '#F6BB42';
exports.click = true;
exports.output = 1;
exports.version = '1.1.1';
exports.author = 'Martin Smola';
exports.icon = 'play';
exports.html = `<div class="padding">
<div data-jc="dropdown__datatype__items:,String|string,Integer|integer,Float|float,Boolean|boolean,Date|date,Object|object,Base64 as Buffer|buffer" class="m">@(Data type (String by default))</div>
<div data-jc="textbox__data__placeholder:@(e.g. Hello world or { hello: 'world'} or ['hello', 'world']))" class="m">@(Data)</div>
<div data-jc="checkbox__restart">Trigger 5s after initialization.</div>
<div class="help">@(Useful when there's a need to run certain flow when the app restarts, etc.)</div>
</div>`;
exports.readme = `# Trigger
- Clicking on the component starts the chain
- Settings allows to set a data-type and a value`;
exports.install = function(instance) {
var value;
instance.on('click', () => instance.send2(value));
instance.reconfigure = function() {
var options = instance.options;
value = null;
switch (options.datatype) {
case 'integer':
value = options.data.parseInt2('error');
value = value === 'error' ? NaN : value;
break;
case 'float':
value = options.data.parseFloat2('error');
value = value === 'error' ? NaN : value;
break;
case 'date':
options.data = options.data.toString();
var num = options.data.parseInt('error');
num === 'error' && (num = options.data.parseDate('error'));
num === 'error' && (num = null);
value = num ? new Date(num).toUTCString() : num;
break;
case 'object':
try {
value = (new Function('return ' + options.data))();
} catch (e) {
instance.error(e);
}
break;
case 'boolean':
value = options.data.parseBoolean();
break;
case 'buffer':
try {
value = F.is4 ? Buffer.from(options.data) : U.createBuffer(options.data);
} catch (e) {
instance.error(e);
}
break;
case 'string':
default:
value = '' + (options.data || '');
break;
}
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
if (instance.options.restart)
setTimeout(function(){
instance.send2(value);
}, 5000);
};

0
flow/variables.txt Normal file
View file

43
flow/virtualwirein.js Normal file
View file

@ -0,0 +1,43 @@
exports.id = 'virtualwirein';
exports.title = 'Virtual wire in';
exports.version = '1.0.0';
exports.author = 'Martin Smola';
exports.color = '#303E4D';
exports.icon = 'sign-in';
exports.input = false;
exports.output = 1;
exports.options = {};
exports.readme = `# Virtual wire in
When the wires between the components are mess it's time to use Virtual wire.`;
exports.html = `<div class="padding">
<div data-jc="textbox" data-jc-path="wirename" data-jc-config="required:true;placeholder:@(some identifier)" class="m">@(Wire name)</div>
</div>
<script>
ON('save.virtualwirein', function(component, options) {
!component.name && (component.name = options.wirename);
});
WATCH('settings.virtualwirein.wirename', function(path, value, type){
if (type === 2)
SET('settings.virtualwirein.wirename', value.slug());
});
</script>`;
exports.install = function(instance) {
instance.custom.reconfigure = function(){
if (instance.options.wirename) {
instance.status(instance.options.wirename);
} else
instance.status('Not configured', 'red');
};
ON('virtualwire', function(wirename, flowdata){
if (instance.options.wirename && instance.options.wirename === wirename)
instance.send(flowdata);
});
instance.on('options', instance.custom.reconfigure);
instance.custom.reconfigure();
};

41
flow/virtualwireout.js Normal file
View file

@ -0,0 +1,41 @@
exports.id = 'virtualwireout';
exports.title = 'Virtual wire out';
exports.version = '1.0.0';
exports.author = 'Martin Smola';
exports.color = '#303E4D';
exports.icon = 'sign-out';
exports.input = true;
exports.options = {};
exports.readme = `# Virtual wire out
When the wires between the components are mess it's time to use Virtual wire.`;
exports.html = `<div class="padding">
<div data-jc="textbox" data-jc-path="wirename" class="m" data-jc-config="required:true;placeholder:@(some identifier)">@(Wire name)</div>
</div>
<script>
ON('save.virtualwireout', function(component, options) {
!component.name && (component.name = options.wirename);
});
WATCH('settings.virtualwireout.wirename', function(path, value, type){
if (type === 2)
SET('settings.virtualwireout.wirename', value.slug());
});
</script>`;
exports.install = function(instance) {
instance.custom.reconfigure = function(){
if (instance.options.wirename) {
instance.status(instance.options.wirename);
} else
instance.status('Not configured', 'red');
};
instance.on('data', function(flowdata) {
EMIT('virtualwire', instance.options.wirename, flowdata);
});
instance.on('options', instance.custom.reconfigure);
instance.custom.reconfigure();
};

554
flow/wsmqttpublish.js Normal file
View file

@ -0,0 +1,554 @@
exports.id = 'wsmqttpublish';
exports.title = 'WS MQTT publish';
exports.group = 'MQTT';
exports.color = '#888600';
exports.version = '1.0.2';
exports.icon = 'sign-out';
exports.input = 1;
exports.output = ["red", "white", "blue"];
exports.author = 'Daniel Segeš';
exports.options = { host: 'tb-stage.worksys.io', port: 1883, clientid: "", username: "" };
exports.npm = ['mqtt'];
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 = `
# WS MQTT Publish
Version 1.0.3.
Added:
- database collections,
- rpc response
`;
const instanceSendTo = {
debug: 0,
rpcCall: 1,
services: 2
}
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
//CONFIG
let useLog4js = true;
let createTelemetryBackup = true;
let saveTelemetryOnError = true;//backup_on_failure overrides this value
//------------------------
var fs = require('fs');
let rollers;
if(createTelemetryBackup) rollers = require('streamroller');
const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304
let insertNoSqlCounter = 0;
let insertBackupNoSqlCounter = 0;
let processingData = false;
let backup_on_failure = true;//== saveTelemetryOnError - create backup broker send failure
let restore_from_backup = 20; //how many rows process at once?
let restore_backup_wait = 5;//wait seconds
let lastRestoreTime = 0;
let errLogger;
let logger;
let monitor;
if(useLog4js)
{
var path = require('path');
var log4js = require("log4js");
log4js.configure({
appenders: {
errLogs: { type: 'file', filename: path.join(__dirname + "/../", 'err.txt') },
monitorLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'monitor.txt') },
console: { type: 'console' }
},
categories: {
errLogs: { appenders: ['console', 'errLogs'], level: 'error' },
monitorLogs: { appenders: ['console', 'monitorLogs'], level: 'trace' },
//another: { appenders: ['console'], level: 'trace' },
default: { appenders: ['console'], level: 'trace' }
}
});
errLogger = log4js.getLogger("errLogs");
logger = log4js.getLogger();
monitor = log4js.getLogger("monitorLogs");
//USAGE
//logger.debug("text");
//monitor.info('info');
//errLogger.error("some error");
}
process.on('uncaughtException', function (err) {
if(errLogger)
{
errLogger.error('uncaughtException:', err.message)
errLogger.error(err.stack);
}
//TODO
//send to service
//process.exit(1);
})
const nosql = NOSQL('tbdata');
const nosqlBackup = NOSQL('/backup/tbdata');
exports.install = function(instance) {
var broker;
var opts;
var brokerready = false;
instance.on('options', loadSettings);
mqtt = require('mqtt');
// wsmqtt status for notification purposes on projects.worksys.io database
let wsmqttName = null;
let sendWsStatusVar = null;
let wsmqtt_status = 'disconnected';
function getWsmqttName(host)
{
if(host == "tb-demo.worksys.io" || host == '192.168.252.4') return 'wsmqtt_demo';
else if(host == "tb-qas01.worksys.io" || host == '192.168.252.5') return 'wsmqtt_qas01';
else if(host == "tb-prod01.worksys.io" || host == '192.168.252.1') return 'wsmqtt_prod01';
}
function sendWsStatus()
{
instance.send(instanceSendTo.services, {[wsmqttName]: wsmqtt_status});
}
sendWsStatusVar = setInterval(sendWsStatus, 180000);
//set opts according to db settings
async function loadSettings()
{
if(instance.options.host !== "")
{
//override settings from database
var o = instance.options;
opts = {
host: o.host,
port: o.port,
clientId: o.clientid,
username: o.username,
rejectUnauthorized: false,
resubscribe: false
};
wsmqttName = getWsmqttName(o.host);
console.log("wsmqttpublich -> loadSettings from instance.options", instance.options);
}
else
{
const dbSettings = TABLE("settings");
let responseSettings = await promisifyBuilder(dbSettings.find());
backup_on_failure = responseSettings[0]["backup_on_failure"];
saveTelemetryOnError = backup_on_failure;
restore_from_backup = responseSettings[0]["restore_from_backup"];
restore_backup_wait = responseSettings[0]["restore_backup_wait"];
let mqtt_host = responseSettings[0]["mqtt_host"];
let mqtt_clientid = responseSettings[0]["mqtt_clientid"];
let mqtt_username = responseSettings[0]["mqtt_username"];
let mqtt_port = responseSettings[0]["mqtt_port"];
console.log("wsmqttpublich -> loadSettings from db", responseSettings[0]);
opts = {
host: mqtt_host,
port: mqtt_port,
keepalive: 10,
clientId: mqtt_clientid,
username: mqtt_username,
rejectUnauthorized: false,
resubscribe: false
};
wsmqttName = getWsmqttName(mqtt_host);
}
connectToTbServer();
}
function connectToTbServer()
{
var url = "mqtt://" + opts.host + ":" + opts.port;
console.log("MQTT URL: ", url);
broker = mqtt.connect(url, opts);
broker.on('connect', function() {
instance.status("Connected", "green");
brokerready = true;
FLOW.OMS_brokerready = brokerready;
wsmqtt_status = 'connected';
});
broker.on('reconnect', function() {
instance.status("Reconnecting", "yellow");
brokerready = false;
FLOW.OMS_brokerready = brokerready;
});
broker.on('message', function(topic, message) {
// message is type of buffer
message = message.toString();
if (message[0] === '{') {
try {
message = JSON.parse(message);
if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) {
broker.publish(topic, `{"device": ${message.device}, "id": ${message.data.id}, "data": {"success": true}}`, {qos:1});
instance.send(instanceSendTo.rpcCall, {"device": message.device, "id": message.data.id, "RPC response": {"success": true}});
}
} catch(error) {
instance.send(instanceSendTo.debug, "unable to parse RPC call");
}
}
instance.send(instanceSendTo.rpcCall, {"topic":topic, "content":message });
});
broker.on('close', function(err) {
brokerready = false;
FLOW.OMS_brokerready = brokerready;
wsmqtt_status = 'disconnected';
if (err && err.toString().indexOf('Error')) {
instance.status("Err: "+err.code, "red");
instance.send(instanceSendTo.debug, {"message":"Broker CLOSE signal received !", "error":err, "opt":opts });
} else {
instance.status("Disconnected", "red");
instance.send(instanceSendTo.debug, {"message":"Broker CLOSE signal received !", "error":err, "opt":opts });
}
broker.reconnect();
});
broker.on('error', function(err) {
instance.status("Err: "+ err.code, "red");
instance.send(instanceSendTo.debug, {"message":"Broker ERROR signal received !", "error":err, "opt":opts });
brokerready = false;
FLOW.OMS_brokerready = brokerready;
wsmqtt_status = 'disconnected';
});
//broker = new Broker(opts);
//MQTT_BROKERS.push(broker);
//instance.status('Ready');
}
//set opts accortding to options
/*
instance.reconfigure = function() {
var o = instance.options;
opts = {
host: o.host,
port: o.port,
keepalive: 10,
clientId: o.clientid,
username: o.username,
rejectUnauthorized: false,
resubscribe: false
};
//connectToTbServer();
};
*/
instance.on('data', function(data) {
if (brokerready)
{
//do we have some data in backup file?
//if any, process data from database
if(saveTelemetryOnError)
{
//read telemetry data and send back to server
if(!processingData) processDataFromDatabase();
}
}
if (brokerready)
{
let stringifiedJson = JSON.stringify(data.data);
broker.publish("v1/gateway/telemetry", stringifiedJson, {qos: 1});
//backup telemetry
if(createTelemetryBackup)
{
data.data.id = UID();
nosqlBackup.insert(data.data);
insertBackupNoSqlCounter++;
if(insertBackupNoSqlCounter > 150)
{
let options = {compress: true};
let path = __dirname + "/../databases/backup/tbdata.nosql";
var stream = new rollers.RollingFileStream(path, noSqlFileSizeLimit, 150, options);
stream.write("");
stream.end();
insertBackupNoSqlCounter = 0;
}
}
}
else
{
if(logger) logger.debug("Broker unavailable. Data not sent !", data.data);
instance.send(instanceSendTo.debug, {"message":"Broker unavailable. Data not sent !", "data": data.data });
if(saveTelemetryOnError)
{
//create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql
makeBackupFromDbFile();
//write to tb
data.data.id = UID();
nosql.insert(data.data);
}
}
});
instance.close = function(done) {
if (brokerready){
broker.end();
clearInterval(sendWsStatusVar);
}
};
function getDbBackupFileCounter(type)
{
var files = fs.readdirSync(__dirname + "/../databases");
let counter = 0;
for(var i = 0; i < files.length; i++)
{
if(files[i] == "tbdata.nosql") continue;
if(files[i].endsWith(".nosql"))
{
let pos = files[i].indexOf(".");
if(pos > -1)
{
let fileCounter = counter;
let firstDigit = files[i].slice(0, pos);
fileCounter = parseInt(firstDigit);
if (isNaN(fileCounter)) fileCounter = 0;
//console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type);
if(type == "max")
{
if(fileCounter > counter)
{
counter = fileCounter;
}
}
else if(type == "min")
{
if(counter == 0) counter = fileCounter;
if(fileCounter < counter)
{
counter = fileCounter;
}
}
}
}
}
if(type == "max") counter++;
return counter;
}
const makeBackupFromDbFile = async () => {
if(!saveTelemetryOnError) return;
//to avoid large file: tbdata.nosql
//init value is 0!
if(insertNoSqlCounter > 0)
{
--insertNoSqlCounter;
return;
}
insertNoSqlCounter = 100;
let source = __dirname + "/../databases/tbdata.nosql";
var stats = fs.statSync(source);
var fileSizeInBytes = stats.size;
if(fileSizeInBytes > noSqlFileSizeLimit)
{
let counter = 1;
counter = getDbBackupFileCounter("max");
let destination = __dirname + "/../databases/" + counter + "." + "tbdata.nosql";
//make backup file
fs.copyFileSync(source, destination);
//fs.renameSync(p, p + "." + counter);
//clear tbdata.nosql
fs.writeFileSync(source, "");
fs.truncateSync(source, 0);
}
}
const processDataFromDatabase = async () => {
if(restore_from_backup <= 0)
{
return;
}
//calculate diff
const now = new Date();
let currentTime = now.getTime();
let diff = currentTime - lastRestoreTime;
if( (diff / 1000) < restore_backup_wait)
{
//console.log("*********restore_backup_wait", diff, restore_backup_wait);
return;
}
processingData = true;
//get filename to process
let counter = getDbBackupFileCounter("min");
//we have some backup files
let dataBase = 'tbdata';
var nosql;
if(counter == 0) dataBase = 'tbdata';
else dataBase = counter + "." + 'tbdata';
nosql = NOSQL(dataBase);
//select all data - use limit restore_from_backup
let records = await promisifyBuilder(nosql.find().take(restore_from_backup));
for(let i = 0; i < records.length; i++)
{
if (brokerready) {
let item = records[i];
let id = item.id;
if(id !== undefined)
{
//console.log("------------processDataFromDatabase - remove", id, dataBase, i);
try{
let o = JSON.parse(JSON.stringify(item));
delete o.id;
let message = JSON.stringify(o);
broker.publish("v1/gateway/telemetry", message, {qos:1});
//remove from database
await promisifyBuilder(nosql.remove().where("id", id));
} catch (error) {
//process error
console.log("processDataFromDatabase", error);
}
}
}
else
{
processingData = false;
return;
}
}
if(records.length > 0)
{
//clean backup file
if(counter > 0) nosql.clean();
}
//no data in db, remove
if(records.length == 0)
{
if(counter > 0) nosql.drop();
}
const d = new Date();
lastRestoreTime = d.getTime();
processingData = false;
}
loadSettings();
//instance.on('options', instance.reconfigure);
//instance.reconfigure();
};

0
monitor.txt Normal file
View file

65
odchod.js Normal file
View file

@ -0,0 +1,65 @@
const fetch = require('node-fetch'); // Import node-fetch
async function getOdchodyData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const csvData = await response.text();
return parseOdchodyCSV(csvData);
} catch (error) {
console.error("Error fetching or processing data:", error);
return []; // Or handle the error as appropriate for your application
}
}
function parseOdchodyCSV(csvData) {
const lines = csvData.split('\n');
const odchod = [];
let dataStart = -1;
// Find the line where the actual data starts
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith("Linka;Spoj;Čas odjezdu;Nástupiště;Cílová obec spoje;Kód datumové masky")) {
dataStart = i + 1;
break;
}
}
if (dataStart === -1) {
console.warn("Data header not found in CSV.");
return []; // Or handle this error condition
}
for (let i = dataStart; i < lines.length; i++) {
const line = lines[i].trim();
if (line) { // Skip empty lines
const values = line.split(';');
if (values.length >= 6 && values[0] && values[2] && values[4]) {
odchod.push([
parseInt(values[0], 10), // Linka (parse to integer)
values[2], // Čas odjezdu
values[4] // Cílová obec spoje
]);
}
}
}
return odchod;
}
// --- Example Usage ---
const csvUrl = "https://bezpecne.sokolov.cz/zast/jedn_drogerie.csv";
getOdchodyData(csvUrl)
.then(result => {
console.log("Parsed Odchody Data:", result);
})
.catch(error => {
// Error already handled in getOdchodyData, but you can add more specific handling here if needed
});

30
package.json Normal file
View file

@ -0,0 +1,30 @@
{
"name": "totalproject",
"description": "Empty project",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"bitwise": "^2.1.0",
"easy-crc": "^0.0.2",
"jsmodbus": "^4.0.6",
"log4js": "^6.3.0",
"mosca": "^2.8.3",
"mqtt": "^4.2.8",
"node-fetch": "^2.6.7",
"node-schedule": "^2.0.0",
"nodemailer": "^6.7.0",
"serialport": "^9.2.4",
"total.js": "^3.4.10",
"total4": "^0.0.51",
"xml2js": "^0.4.23"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"empty",
"project"
],
"author": "Peter Širka",
"license": "MIT"
}

12
readme.md Normal file
View file

@ -0,0 +1,12 @@
# Empty project: Flow
- [Documentation](https://docs.totaljs.com)
- [Join Total.js Telegram](https://t.me/totaljs)
- [Support](https://www.totaljs.com/support/)
__Instructions__:
- install the latest version of the __Total.js framework 4__ from NPM `$ npm install total4`
- download example
- run `$ node index.js`
- open browser `http://127.0.0.1:8000`