Initial commit
This commit is contained in:
commit
c53c7085bc
60 changed files with 23885 additions and 0 deletions
10
config
Normal file
10
config
Normal 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
|
||||||
0
databases/backup/tbdata.nosql
Normal file
0
databases/backup/tbdata.nosql
Normal file
190
databases/bus_departures.js
Normal file
190
databases/bus_departures.js
Normal 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"]],
|
||||||
|
];
|
||||||
2
databases/departures.table
Normal file
2
databases/departures.table
Normal file
File diff suppressed because one or more lines are too long
14
databases/pins.table
Normal file
14
databases/pins.table
Normal 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
2
databases/settings.table
Normal 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
0
databases/tbdata.nosql
Normal file
2
databases/zenitel.table
Normal file
2
databases/zenitel.table
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
allCalls:string
|
||||||
|
+|{"no_of_incoming":12016,"no_of_outgoing":40451}|...................
|
||||||
10092
dayDepartures.txt
Normal file
10092
dayDepartures.txt
Normal file
File diff suppressed because it is too large
Load diff
7
err.txt
Normal file
7
err.txt
Normal 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
99
flow/bussdepartures.js
Normal 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
54
flow/check_if_new_day.js
Normal 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
90
flow/code.js
Normal 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
11
flow/comment.js
Normal 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
87
flow/csv_import.js
Normal 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
216
flow/davkovac.js
Normal 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 doesn’t 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
100
flow/debug.js
Normal 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
1931
flow/designer.json
Normal file
File diff suppressed because it is too large
Load diff
1905
flow/designer.json_oorig.txt
Normal file
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
951
flow/dido_controller_sbs.js
Normal 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
137
flow/function.js
Normal 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
613
flow/get_departures.js
Normal file
File diff suppressed because one or more lines are too long
68
flow/gettemperature.js
Normal file
68
flow/gettemperature.js
Normal 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
1261
flow/handledepartures.js
Normal file
File diff suppressed because it is too large
Load diff
29
flow/helper/db_helper.js
Normal file
29
flow/helper/db_helper.js
Normal 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
|
||||||
|
}
|
||||||
67
flow/helper/error_reporter.js
Normal file
67
flow/helper/error_reporter.js
Normal 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
|
||||||
|
}
|
||||||
86
flow/helper/serialport_helper.js
Normal file
86
flow/helper/serialport_helper.js
Normal 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
317
flow/helper/suncalc.js
Normal 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
238
flow/httprequest.js
Normal 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
76
flow/httpresponse.js
Normal 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
326
flow/httproute.js
Normal 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
121
flow/infosender.js
Normal 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
156
flow/monitorconsumption.js
Normal 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
96
flow/monitordisk.js
Normal 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
87
flow/monitormemory.js
Normal 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
441
flow/mqtt.js
Normal 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();
|
||||||
|
};
|
||||||
129
flow/mqtt_subscribe_temperature.js
Normal file
129
flow/mqtt_subscribe_temperature.js
Normal 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
134
flow/mqttpublish.js
Normal 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
168
flow/mqttsubscribe.js
Normal 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
267
flow/particulatesensor.js
Normal 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
634
flow/rce_modul.js
Normal 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
79
flow/rce_peoplecount.js
Normal 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
84
flow/send_to_display.js
Normal 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
86
flow/serialport_helper.js
Normal 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
704
flow/sokolovodchody.js
Normal 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
317
flow/suncalc.js
Normal 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
96
flow/tcpipclient.js
Normal 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
96
flow/tcpipisend.js
Normal 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
169
flow/tcpipserver.js
Normal 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 doesn’t 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
87
flow/timer.js
Normal 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
129
flow/timesetter.js
Normal 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
79
flow/trigger.js
Normal 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
0
flow/variables.txt
Normal file
43
flow/virtualwirein.js
Normal file
43
flow/virtualwirein.js
Normal 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
41
flow/virtualwireout.js
Normal 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
554
flow/wsmqttpublish.js
Normal 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
0
monitor.txt
Normal file
65
odchod.js
Normal file
65
odchod.js
Normal 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
30
package.json
Normal 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
12
readme.md
Normal 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`
|
||||||
Loading…
Add table
Add a link
Reference in a new issue