Actual running flowserver on test panel LM 10.0.0.5
This commit is contained in:
commit
67c503d980
57 changed files with 16678 additions and 0 deletions
11
config
Normal file
11
config
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name : Total.js Flow
|
||||||
|
|
||||||
|
// Packages settings
|
||||||
|
package#flow (Object) : { url: '/' }
|
||||||
|
|
||||||
|
|
||||||
|
table.relays : line:number|tbname:string|contactor:number|profile:string
|
||||||
|
table.nodes : node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean
|
||||||
|
table.settings : rvo_name:string|lang:string|temperature_adress:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|projects_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number
|
||||||
|
table.pins : pin:number|type:string|line:number
|
||||||
|
table.notifications : key:string|weight:string|sk:string|en:string
|
||||||
0
databases/backup/tbdata.nosql
Normal file
0
databases/backup/tbdata.nosql
Normal file
48
databases/nodes.table
Normal file
48
databases/nodes.table
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean
|
||||||
|
+|697|joqRYBVL30k9eQWOlZ5qwpD2KJpNEmA6gPxXzwaM|3||1|1|...........
|
||||||
|
+|659|Ymn9oleRxJ0vw17WzAyGwdyEBk4ObdMXj2VgpNLG|3||1|1|...........
|
||||||
|
+|636|M6ogKQW09bOXewAYvZyvJqyJrV1aRnPGE37p42Nx|2||1|1|...........
|
||||||
|
+|648|gaMGN4x1e9JlZz0QPRDd9Rym6dVr3OpvqKnoWBbk|2||1|1|...........
|
||||||
|
+|664|oGVzxNWP9lrjaQ7vKODQ7g51gqp62YZREmdw3XBM|1||1|1|...........
|
||||||
|
+|634|NGWamnYqlP1wbgrZQxDAWm5e2X7OVAK69koR04vL|2||1|1|...........
|
||||||
|
+|670|dlE1VQjYrNx9gZRmb38g1YyoLBO4qaAk2M6JPnG7|2||1|1|...........
|
||||||
|
+|641|vnmG4kJxaXWNBgMQq0D7Mz5e9oZzOAlr6LdR3w2V|2||1|1|...........
|
||||||
|
+|632|LpkVlmq4b3jMwJQxBZ8aM78rXAP6o97Ke0aOYEg2|2||1|1|...........
|
||||||
|
+|667|MzXBoWbEZjO0lrpqnRyoJ4DkmVeaNAGdL9g4QKxP|1||1|1|...........
|
||||||
|
+|682|vnreBJ6PMqgz20pYEL82XQyG1jkWwdQxZVNAOlmK|1||1|1|...........
|
||||||
|
+|643|oZmYXEbw9lVWRv1jLxDe9bDdgAMz4PKQnNJ6eB23|1||1|1|...........
|
||||||
|
+|642|pEonaKBOGbj9034MgJ8W3G8qXvxNWVkAPQz21R6L|1||1|1|...........
|
||||||
|
+|647|BLQal6Pn9oz1KmNgek5Yqd50vd2MAbqG3OV7Rp4j|1||1|1|...........
|
||||||
|
+|646|4agVJ9dPQkmp1R2X3EDJKxyrK6ZlNoM0n7qxBOev|1||1|1|...........
|
||||||
|
+|666|9PpgLEnvk4WMV6RmOJybMGDaeAXzo2BQNG3K17Zw|1||1|1|...........
|
||||||
|
+|654|Mmp93b2nvd7OoqgBeEyEZq5kjlAV1Y4ZNXwW0zLG|1||1|1|...........
|
||||||
|
+|637|koW06PeGrLlBp2YJQE5Ogw5RmMaXKzj3wOAZg9n7|3||1|1|...........
|
||||||
|
+|680|KL2jNOVpdARa9XvoeJDPga8bkmPBxqn7Ww3gzGQ1|1|{"intervals":[{"cct":3000,"value":100,"end_time":"13:40","start_time":"13:00"},{"cct":3000,"value":-1,"end_time":"13:50","start_time":"13:40"},{"cct":3000,"value":100,"end_time":"13:00","start_time":"13:50"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|0|...........
|
||||||
|
+|645|jklN4JpQAx362o9XYZDN6wDgrWw1P7GEbdBM0vRV|1||1|1|...........
|
||||||
|
+|660|gj7zbKV46oQ1p2e0AJ8XqZDG3YNWaRrlOEXvBxmM|3||1|1|...........
|
||||||
|
+|669|Y9aLW03wOZkABvKXbMyL0lyV1xdNj72r4egqGRzJ|3|{"intervals":[{"cct":3000,"value":0,"end_time":"13:40","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"07:20","start_time":"13:50"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"07:20"},{"cct":3000,"value":0,"end_time":"13:50","start_time":"13:40"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|671|AvVdgzYJZaPx3oMqeED4Oj8NnmKkw716bRO90jLB|3||1|1|...........
|
||||||
|
+|638|9xgzG4Op1BrKZPmoQkDrmj8E73ndJNMjavAwX2Re|3||1|1|...........
|
||||||
|
+|639|BOjEzGRZ46bnp9wa2A8z76D0JkmW1QPNdrqevXVL|3||1|1|...........
|
||||||
|
+|693|KjbN4q7JPZmexgdnz2yKdn5YAWwO0Q3BMX6ERLoV|2|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"22:10","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"},{"cct":3000,"value":30,"end_time":"02:40","start_time":"22:10"},{"cct":3000,"value":90,"end_time":"05:30","start_time":"02:40"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|649|0p2rwdP7aGoOQLJNgAynJNy6xWXbmMe3nvZqlzkV|1|{"intervals":[{"cct":3000,"value":100,"end_time":"13:40","start_time":"13:00"},{"cct":3000,"value":-1,"end_time":"13:50","start_time":"13:40"},{"cct":3000,"value":100,"end_time":"13:00","start_time":"13:50"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|698|mYnBzbeGaAL62jowRv59M35Xq9QpZ0K7O1dg4xVl|1||1|1|...........
|
||||||
|
+|640|WjBL12pg63eX4N9P7zy0XYyEJKmlbkGwZMx0avQV|2||1|1|...........
|
||||||
|
+|656|BrQx3NGKgVMRaXYAo9y1GE8ZzkWnj1le6bdOLE20|1|{"intervals":[{"cct":3000,"value":100,"end_time":"13:40","start_time":"13:00"},{"cct":3000,"value":-1,"end_time":"13:50","start_time":"13:40"},{"cct":3000,"value":100,"end_time":"13:00","start_time":"13:50"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|651|qaAOzENGrvpbe0VoK7D6Ld519PZmdg3nl24JLQMk|2||1|1|...........
|
||||||
|
+|691|lekrmdvO0BQG1ZW4AV8jzq8M39xnN2wEbRgPjXLp|1||1|1|...........
|
||||||
|
+|661|laYK7Pomn2bNZXEpedDxAqyOJkQ3WwV49gqxLrAR|3|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":60,"end_time":"05:30","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|665|gbv4nzqxW0XGAPKVNk8kr25ZQ2l3O6LRBprM97ew|3||1|1|...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
|
+|662|gbv4nzqxW0XGAPKVNk8kW48ZQ2l3O6LRBprM97ew|3|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"21:20","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"},{"cct":3000,"value":80,"end_time":"22:20","start_time":"21:50"},{"cct":3000,"value":70,"end_time":"22:50","start_time":"22:20"},{"cct":3000,"value":60,"end_time":"23:20","start_time":"22:50"},{"cct":3000,"value":50,"end_time":"23:50","start_time":"23:20"},{"cct":3000,"value":40,"end_time":"00:20","start_time":"23:50"},{"cct":3000,"value":100,"end_time":"05:30","start_time":"03:20"},{"cct":3000,"value":30,"end_time":"00:50","start_time":"00:20"},{"cct":3000,"value":20,"end_time":"01:20","start_time":"00:50"},{"cct":3000,"value":90,"end_time":"21:50","start_time":"21:20"},{"cct":3000,"value":30,"end_time":"01:50","start_time":"01:20"},{"cct":3000,"value":40,"end_time":"02:20","start_time":"01:50"},{"cct":3000,"value":50,"end_time":"02:50","start_time":"02:20"},{"cct":3000,"value":60,"end_time":"03:20","start_time":"02:50"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|668|lekrmdvO0BQG1ZW4AV8jeZ5M39xnN2wEbRgPjXLp|3|{"intervals":[{"cct":3000,"value":0,"end_time":"13:10","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"12:50","start_time":"13:10"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"12:50"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
|
+|689|q0rElBPdL6kxMAjnzVDRl95emNZY7oOv2wK9gb31|3|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"21:20","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"},{"cct":3000,"value":80,"end_time":"22:20","start_time":"21:50"},{"cct":3000,"value":70,"end_time":"22:50","start_time":"22:20"},{"cct":3000,"value":60,"end_time":"23:20","start_time":"22:50"},{"cct":3000,"value":50,"end_time":"23:50","start_time":"23:20"},{"cct":3000,"value":40,"end_time":"00:20","start_time":"23:50"},{"cct":3000,"value":100,"end_time":"05:30","start_time":"03:20"},{"cct":3000,"value":30,"end_time":"00:50","start_time":"00:20"},{"cct":3000,"value":20,"end_time":"01:20","start_time":"00:50"},{"cct":3000,"value":90,"end_time":"21:50","start_time":"21:20"},{"cct":3000,"value":30,"end_time":"01:50","start_time":"01:20"},{"cct":3000,"value":40,"end_time":"02:20","start_time":"01:50"},{"cct":3000,"value":50,"end_time":"02:50","start_time":"02:20"},{"cct":3000,"value":60,"end_time":"03:20","start_time":"02:50"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|683|XKQbz3WAwY21dGa0R453rWyJm9PZOjqlvpr6Nkeo|3|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"21:20","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"},{"cct":3000,"value":80,"end_time":"22:20","start_time":"21:50"},{"cct":3000,"value":70,"end_time":"22:50","start_time":"22:20"},{"cct":3000,"value":60,"end_time":"23:20","start_time":"22:50"},{"cct":3000,"value":50,"end_time":"23:50","start_time":"23:20"},{"cct":3000,"value":40,"end_time":"00:20","start_time":"23:50"},{"cct":3000,"value":100,"end_time":"05:30","start_time":"03:20"},{"cct":3000,"value":30,"end_time":"00:50","start_time":"00:20"},{"cct":3000,"value":20,"end_time":"01:20","start_time":"00:50"},{"cct":3000,"value":90,"end_time":"21:50","start_time":"21:20"},{"cct":3000,"value":30,"end_time":"01:50","start_time":"01:20"},{"cct":3000,"value":40,"end_time":"02:20","start_time":"01:50"},{"cct":3000,"value":50,"end_time":"02:50","start_time":"02:20"},{"cct":3000,"value":60,"end_time":"03:20","start_time":"02:50"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|688|PaGbQ3wBAZWOmRvK9VDpvz5endLJYopEqlkzNMxX|3|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"21:20","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"},{"cct":3000,"value":80,"end_time":"22:20","start_time":"21:50"},{"cct":3000,"value":70,"end_time":"22:50","start_time":"22:20"},{"cct":3000,"value":60,"end_time":"23:20","start_time":"22:50"},{"cct":3000,"value":50,"end_time":"23:50","start_time":"23:20"},{"cct":3000,"value":40,"end_time":"00:20","start_time":"23:50"},{"cct":3000,"value":100,"end_time":"05:30","start_time":"03:20"},{"cct":3000,"value":30,"end_time":"00:50","start_time":"00:20"},{"cct":3000,"value":20,"end_time":"01:20","start_time":"00:50"},{"cct":3000,"value":90,"end_time":"21:50","start_time":"21:20"},{"cct":3000,"value":30,"end_time":"01:50","start_time":"01:20"},{"cct":3000,"value":40,"end_time":"02:20","start_time":"01:50"},{"cct":3000,"value":50,"end_time":"02:50","start_time":"02:20"},{"cct":3000,"value":60,"end_time":"03:20","start_time":"02:50"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|672|0XYElWeKBNJn1gdoMG8lON5ALkPvj4V3xra2q6mO|2||1|1|.................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
|
+|690|wGjQobgOK0n2YqBZmVDVR3DR9ep6EXA1ka3vzlP7|2||1|1|..............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
|
+|692|l9YkRpoB2vVa0mKqEO8ZGGDjW43eXnJML6GxzbwQ|2|{"intervals":[{"cct":3000,"value":0,"end_time":"13:10","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"12:50","start_time":"13:10"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"12:50"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
|
+|655|RMgnK93rkoAazbqdQ4yBYpDZ1YXGx6pmwBeVEP2O|2||1|1|..............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
|
+|694|Jm32GR1qpwQxlZza0N5mE15AP96YbOKLogrXVW4e|2|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"21:20","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"},{"cct":3000,"value":80,"end_time":"22:20","start_time":"21:50"},{"cct":3000,"value":70,"end_time":"22:50","start_time":"22:20"},{"cct":3000,"value":60,"end_time":"23:20","start_time":"22:50"},{"cct":3000,"value":50,"end_time":"23:50","start_time":"23:20"},{"cct":3000,"value":40,"end_time":"00:20","start_time":"23:50"},{"cct":3000,"value":100,"end_time":"05:30","start_time":"03:20"},{"cct":3000,"value":30,"end_time":"00:50","start_time":"00:20"},{"cct":3000,"value":20,"end_time":"01:20","start_time":"00:50"},{"cct":3000,"value":90,"end_time":"21:50","start_time":"21:20"},{"cct":3000,"value":30,"end_time":"01:50","start_time":"01:20"},{"cct":3000,"value":40,"end_time":"02:20","start_time":"01:50"},{"cct":3000,"value":50,"end_time":"02:50","start_time":"02:20"},{"cct":3000,"value":60,"end_time":"03:20","start_time":"02:50"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|635|Vq2JaWpw1OdBKmNeoj8w605XE40l3kgL76Azb9QP|2|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"21:20","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"},{"cct":3000,"value":80,"end_time":"22:20","start_time":"21:50"},{"cct":3000,"value":70,"end_time":"22:50","start_time":"22:20"},{"cct":3000,"value":60,"end_time":"23:20","start_time":"22:50"},{"cct":3000,"value":50,"end_time":"23:50","start_time":"23:20"},{"cct":3000,"value":40,"end_time":"00:20","start_time":"23:50"},{"cct":3000,"value":100,"end_time":"05:30","start_time":"03:20"},{"cct":3000,"value":30,"end_time":"00:50","start_time":"00:20"},{"cct":3000,"value":20,"end_time":"01:20","start_time":"00:50"},{"cct":3000,"value":90,"end_time":"21:50","start_time":"21:20"},{"cct":3000,"value":30,"end_time":"01:50","start_time":"01:20"},{"cct":3000,"value":40,"end_time":"02:20","start_time":"01:50"},{"cct":3000,"value":50,"end_time":"02:50","start_time":"02:20"},{"cct":3000,"value":60,"end_time":"03:20","start_time":"02:50"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|650|0XYElWeKBNJn1gdoMG8lYdDALkPvj4V3xra2q6mO|3|{"intervals":[{"cct":3000,"value":0,"end_time":"13:10","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"12:50","start_time":"13:10"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"12:50"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|...........
|
||||||
|
+|663|LpkVlmq4b3jMwJQxBZ8akayrXAP6o97Ke0aOYEg2|1|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":100,"end_time":"21:20","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"},{"cct":3000,"value":80,"end_time":"22:20","start_time":"21:50"},{"cct":3000,"value":70,"end_time":"22:50","start_time":"22:20"},{"cct":3000,"value":60,"end_time":"23:20","start_time":"22:50"},{"cct":3000,"value":50,"end_time":"23:50","start_time":"23:20"},{"cct":3000,"value":40,"end_time":"00:20","start_time":"23:50"},{"cct":3000,"value":100,"end_time":"05:30","start_time":"03:20"},{"cct":3000,"value":30,"end_time":"00:50","start_time":"00:20"},{"cct":3000,"value":20,"end_time":"01:20","start_time":"00:50"},{"cct":3000,"value":90,"end_time":"21:50","start_time":"21:20"},{"cct":3000,"value":30,"end_time":"01:50","start_time":"01:20"},{"cct":3000,"value":40,"end_time":"02:20","start_time":"01:50"},{"cct":3000,"value":50,"end_time":"02:50","start_time":"02:20"},{"cct":3000,"value":60,"end_time":"03:20","start_time":"02:50"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|0|1|...........
|
||||||
32
databases/notifications.table
Normal file
32
databases/notifications.table
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
key:string|weight:string|sk:string|en:string
|
||||||
|
+|switching_profile_point_applied_to_line|INFORMATIONAL|Aplikovaný bod spínacieho profilu na línií č. ${line} : ${value}|Switching profile point applied to line no. ${line} : ${value}|...............
|
||||||
|
+|dusk_has_occured|INFORMATIONAL|Nastal súmrak: ${value}|Dusk has occured: ${value}|...............
|
||||||
|
+|dawn_has_occured|INFORMATIONAL|Nastal úsvit: ${value}|Dawn has occured: ${value}|...............
|
||||||
|
+|dimming_profile_was_successfully_received_by_node|NOTICE|Stmievací profil bol úspešne prijatý nodom č. ${node}|Dimming profile was successfully received by node no. ${node}|...............
|
||||||
|
+|master_node_is_responding_again|NOTICE|Master node začal znovu odpovedať|Master node is responding again|...............
|
||||||
|
+|command_was_sent_from_terminal_interface|DEBUG|Z terminálu bol odoslaný príkaz|A command was sent from terminal interface|...............
|
||||||
|
+|master_node_is_not_responding|ALERT|Master node neodpovedá|Master node is not responding|...............
|
||||||
|
+|configuration_of_dimming_profile_to_node_failed|ALERT|Konfigurácia stmievacieho profilu pre node č. ${node} zlyhala|Configuration of dimming profile to node no. ${node} has failed|...............
|
||||||
|
+|circuit_breaker_was_turned_on_line|NOTICE|Zapnutie ističa na línii č. ${line}|Circuit breaker was turned on - line no. ${line}|...............
|
||||||
|
+|circuit_breaker_was_turned_off_line|ERROR|Vypnutie ističa na línií č. ${line}|Circuit breaker was turned off - line no. ${line}|...............
|
||||||
|
+|dimming_profile_was_processed_for_node|INFORMATIONAL|Stmievací profil bol spracovaný pre node č. ${node}|Dimming profile was processed for node no. ${node}|...............
|
||||||
|
+|switching_profile_was_processed_for_line|INFORMATIONAL|Spínací profil bol spracovaný pre líniu č. ${line}|Switching profile was processed for line no. ${line}|...............
|
||||||
|
+|thermometer_is_not_responding|WARNING|Teplomer neodpovedá|Thermometer is not responding|...............
|
||||||
|
+|thermometer_is_responding_again|NOTICE|Teplomer znovu odpovedá|Thermometer is responding again|...............
|
||||||
|
+|thermometer_sends_invalid_data|WARNING|Teplomer posiela neplatné hodnoty|Thermometer sends invalid data|...............
|
||||||
|
+|main_switch_has_been_turned_off|CRITICAL|Hlavný vypínač bol vypnutý|Main switch has been turned off|...............
|
||||||
|
+|main_switch_has_been_turned_on|NOTICE|Hlavný vypínač bol zapnutý|Main switch has been turned on|...............
|
||||||
|
+|power_supply_has_disconnected_input|ALERT|Napájací zdroj nemá napätie na vstupe|Power supply has disconnected input|...............
|
||||||
|
+|power_supply_works_correctly|NOTICE|Napájací zdroj pracuje správne|Power supply works correctly|...............
|
||||||
|
+|battery_level_is_low|ERROR|Batéria má nízku úroveň napätia|Battery level is low|...............
|
||||||
|
+|battery_level_is_ok|NOTICE|Batéria má správnu úroveň napätia|Battery level is OK|...............
|
||||||
|
+|door_has_been_open|NOTICE|Dvere boli otvorené|Door has been open|...............
|
||||||
|
+|door_has_been_closed|NOTICE|Dvere boli zatvorené|Door has been closed|...............
|
||||||
|
+|door_has_been_open_without_permision_alarm_is_on|WARNING|Dvere boli otvorené bez povolania - zapnutá siréna|Door has been open without permision - alarm is on|...............
|
||||||
|
+|state_of_contactor_for_line|INFORMATIONAL|Stav stýkača pre líniu č. ${line} je ${value}|State of contactor for line no. ${line} is ${value}|...............
|
||||||
|
+|local_database_is_corrupted|CRITICAL|||...............
|
||||||
|
+|electrometer_is_not_responding|ERROR|Elektromer neodpovedá|Electrometer is not responding|...............
|
||||||
|
+|no_voltage_detected_on_phase|CRITICAL|Na fáze č. ${phase} nie je napätie|No voltage detected on phase no. ${phase}|...............
|
||||||
|
+|electrometer_is_responding_again|NOTICE|Elektromer znovu odpovedá|Electrometer is responding again|...............
|
||||||
|
+|voltage_on_phase_has_been_restored|NOTICE|Napätie na fáze č. ${phase} bolo obnovené|Voltage on phase no. ${phase} has been restored|...............
|
||||||
|
+|flow_start|NOTICE|FLOW bol spustený|FLOW has been started |...............
|
||||||
14
databases/pins.table
Normal file
14
databases/pins.table
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
pin:number|type:string|line:number
|
||||||
|
*|1|state_of_main_switch|0|...........
|
||||||
|
*|2|rotary_switch_state|0|...........
|
||||||
|
*|3|rotary_switch_state|0|...........
|
||||||
|
*|4|power_supply|0|...........
|
||||||
|
*|5|battery|0|...........
|
||||||
|
*|6|door_condition|0|...........
|
||||||
|
*|8|state_of_breaker|1|...........
|
||||||
|
*|9|state_of_breaker|2|...........
|
||||||
|
*|10|state_of_breaker|3|...........
|
||||||
|
*|11|state_of_contactor|1|...........
|
||||||
|
*|12|state_of_contactor|2|...........
|
||||||
|
*|13|state_of_contactor|3|...........
|
||||||
|
*|16|twilight_sensor|0|...........
|
||||||
5
databases/relays.table
Normal file
5
databases/relays.table
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
line:number|tbname:string|contactor:number|profile:string
|
||||||
|
+|0|KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV|1||...........
|
||||||
|
+|3|vnmG4kJxaXWNBgMQq0D7Aj5e9oZzOAlr6LdR3w2V|0||....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
|
+|1|RMgnK93rkoAazbqdQ4yBG95Z1YXGx6pmwBeVEP2O|0|{"intervals":[{"value":0,"end_time":"13:00","start_time":"13:00"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|...........................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
|
+|2|dlE1VQjYrNx9gZRmb38gG08oLBO4qaAk2M6JPnG7|0||................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||||
2
databases/settings.table
Normal file
2
databases/settings.table
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
rvo_name:string|lang:string|temperature_adress:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|projects_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number
|
||||||
|
+|testpanel|en|28.427B45920702|48.70826502|17.28455203|192.168.252.4|showroom_test_panel_led|xmRd6RJxW53WZe4vMFLU|1883|0|1|lm|ttymxc4|1|20|5|...........................................
|
||||||
0
databases/tbdata.nosql
Normal file
0
databases/tbdata.nosql
Normal file
0
err.txt
Normal file
0
err.txt
Normal file
48
flow/audit_test_panel.csv
Normal file
48
flow/audit_test_panel.csv
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
LumDimm;NODE (HEX);NODE (DEC);Line;TB name
|
||||||
|
1;299;665;3;gbv4nzqxW0XGAPKVNk8kr25ZQ2l3O6LRBprM97ew
|
||||||
|
2;28A;650;3;0XYElWeKBNJn1gdoMG8lYdDALkPvj4V3xra2q6mO
|
||||||
|
3;296;662;3;gbv4nzqxW0XGAPKVNk8kW48ZQ2l3O6LRBprM97ew
|
||||||
|
4;297;663;1;LpkVlmq4b3jMwJQxBZ8akayrXAP6o97Ke0aOYEg2
|
||||||
|
5;29C;668;3;lekrmdvO0BQG1ZW4AV8jeZ5M39xnN2wEbRgPjXLp
|
||||||
|
6;2B1;689;3;q0rElBPdL6kxMAjnzVDRl95emNZY7oOv2wK9gb31
|
||||||
|
7;2AB;683;3;XKQbz3WAwY21dGa0R453rWyJm9PZOjqlvpr6Nkeo
|
||||||
|
8;2B0;688;3;PaGbQ3wBAZWOmRvK9VDpvz5endLJYopEqlkzNMxX
|
||||||
|
9;2B9;697;3;joqRYBVL30k9eQWOlZ5qwpD2KJpNEmA6gPxXzwaM
|
||||||
|
10;293;659;3;Ymn9oleRxJ0vw17WzAyGwdyEBk4ObdMXj2VgpNLG
|
||||||
|
11;294;660;3;gj7zbKV46oQ1p2e0AJ8XqZDG3YNWaRrlOEXvBxmM
|
||||||
|
12;295;661;3;laYK7Pomn2bNZXEpedDxAqyOJkQ3WwV49gqxLrAR
|
||||||
|
13;2A0;672;2;0XYElWeKBNJn1gdoMG8lON5ALkPvj4V3xra2q6mO
|
||||||
|
14;2B4;692;2;l9YkRpoB2vVa0mKqEO8ZGGDjW43eXnJML6GxzbwQ
|
||||||
|
15;2B2;690;2;wGjQobgOK0n2YqBZmVDVR3DR9ep6EXA1ka3vzlP7
|
||||||
|
16;27C;636;2;M6ogKQW09bOXewAYvZyvJqyJrV1aRnPGE37p42Nx
|
||||||
|
17;27B;635;2;Vq2JaWpw1OdBKmNeoj8w605XE40l3kgL76Azb9QP
|
||||||
|
18;2B6;694;2;Jm32GR1qpwQxlZza0N5mE15AP96YbOKLogrXVW4e
|
||||||
|
19;2B5;693;2;KjbN4q7JPZmexgdnz2yKdn5YAWwO0Q3BMX6ERLoV
|
||||||
|
20;2B3;691;1;lekrmdvO0BQG1ZW4AV8jzq8M39xnN2wEbRgPjXLp
|
||||||
|
21;27F;639;3;BOjEzGRZ46bnp9wa2A8z76D0JkmW1QPNdrqevXVL
|
||||||
|
22;27E;638;3;9xgzG4Op1BrKZPmoQkDrmj8E73ndJNMjavAwX2Re
|
||||||
|
23;27D;637;3;koW06PeGrLlBp2YJQE5Ogw5RmMaXKzj3wOAZg9n7
|
||||||
|
24;28F;655;2;RMgnK93rkoAazbqdQ4yBYpDZ1YXGx6pmwBeVEP2O
|
||||||
|
25;288;648;2;gaMGN4x1e9JlZz0QPRDd9Rym6dVr3OpvqKnoWBbk
|
||||||
|
26;298;664;1;oGVzxNWP9lrjaQ7vKODQ7g51gqp62YZREmdw3XBM
|
||||||
|
27;29F;671;3;AvVdgzYJZaPx3oMqeED4Oj8NnmKkw716bRO90jLB
|
||||||
|
28;280;640;2;WjBL12pg63eX4N9P7zy0XYyEJKmlbkGwZMx0avQV
|
||||||
|
29;28B;651;2;qaAOzENGrvpbe0VoK7D6Ld519PZmdg3nl24JLQMk
|
||||||
|
30;27A;634;2;NGWamnYqlP1wbgrZQxDAWm5e2X7OVAK69koR04vL
|
||||||
|
31;29E;670;2;dlE1VQjYrNx9gZRmb38g1YyoLBO4qaAk2M6JPnG7
|
||||||
|
32;281;641;2;vnmG4kJxaXWNBgMQq0D7Mz5e9oZzOAlr6LdR3w2V
|
||||||
|
33;278;632;2;LpkVlmq4b3jMwJQxBZ8aM78rXAP6o97Ke0aOYEg2
|
||||||
|
34;29D;669;3;Y9aLW03wOZkABvKXbMyL0lyV1xdNj72r4egqGRzJ
|
||||||
|
35;2A8;680;1;KL2jNOVpdARa9XvoeJDPga8bkmPBxqn7Ww3gzGQ1
|
||||||
|
36;2BA;698;1;mYnBzbeGaAL62jowRv59M35Xq9QpZ0K7O1dg4xVl
|
||||||
|
37;29B;667;1;MzXBoWbEZjO0lrpqnRyoJ4DkmVeaNAGdL9g4QKxP
|
||||||
|
38;289;649;1;0p2rwdP7aGoOQLJNgAynJNy6xWXbmMe3nvZqlzkV
|
||||||
|
39;290;656;1;BrQx3NGKgVMRaXYAo9y1GE8ZzkWnj1le6bdOLE20
|
||||||
|
40;2AA;682;1;vnreBJ6PMqgz20pYEL82XQyG1jkWwdQxZVNAOlmK
|
||||||
|
41;285;645;1;jklN4JpQAx362o9XYZDN6wDgrWw1P7GEbdBM0vRV
|
||||||
|
42;283;643;1;oZmYXEbw9lVWRv1jLxDe9bDdgAMz4PKQnNJ6eB23
|
||||||
|
43;282;642;1;pEonaKBOGbj9034MgJ8W3G8qXvxNWVkAPQz21R6L
|
||||||
|
44;287;647;1;BLQal6Pn9oz1KmNgek5Yqd50vd2MAbqG3OV7Rp4j
|
||||||
|
45;286;646;1;4agVJ9dPQkmp1R2X3EDJKxyrK6ZlNoM0n7qxBOev
|
||||||
|
46;29A;666;1;9PpgLEnvk4WMV6RmOJybMGDaeAXzo2BQNG3K17Zw
|
||||||
|
47;28E;654;1;Mmp93b2nvd7OoqgBeEyEZq5kjlAV1Y4ZNXwW0zLG
|
||||||
|
4169
flow/cmd_manager.js
Normal file
4169
flow/cmd_manager.js
Normal file
File diff suppressed because it is too large
Load diff
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() {};
|
||||||
175
flow/csv_import.js
Normal file
175
flow/csv_import.js
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
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"
|
||||||
|
};
|
||||||
|
|
||||||
|
//10.0.0.62
|
||||||
|
delimiter = ";";
|
||||||
|
uniqueColumn = "node";
|
||||||
|
path = "flow/audit_rvo14_lampy.csv";
|
||||||
|
startFrom = 1;
|
||||||
|
table = "nodes";
|
||||||
|
mapImport = {
|
||||||
|
1: "node",
|
||||||
|
3: "tbname",
|
||||||
|
2: "line"
|
||||||
|
};
|
||||||
|
|
||||||
|
//notification
|
||||||
|
delimiter = ";";
|
||||||
|
uniqueColumn = undefined;
|
||||||
|
path = "flow/notifikacie.csv";
|
||||||
|
startFrom = 1;
|
||||||
|
table = "notifications";
|
||||||
|
mapImport = {
|
||||||
|
0: "key",
|
||||||
|
1: "weight",
|
||||||
|
2: "en",
|
||||||
|
3: "sk"
|
||||||
|
};
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
exports.install = function(instance) {
|
||||||
|
|
||||||
|
//console.log("csv import installed");
|
||||||
|
|
||||||
|
instance.on("close", () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
instance.on("data", (flowdata) => {
|
||||||
|
|
||||||
|
instance.send(0, "start import");
|
||||||
|
console.log("csv import", flowdata.data);
|
||||||
|
|
||||||
|
//{table: "nodes", startFrom: 1, delimiter: ";", uniqueColumn: "node", path: "flow/audit_rvo14_lampy.csv", mapImport: {1: "node", 3: "tbname", 2: "line"}}
|
||||||
|
|
||||||
|
|
||||||
|
if(typeof flowdata.data === 'object')
|
||||||
|
{
|
||||||
|
console.log("*******************", flowdata.data);
|
||||||
|
|
||||||
|
if(!flowdata.data.hasOwnProperty("table"))
|
||||||
|
{
|
||||||
|
instance.send(0, "!!!!csv import - nedefinovana tabulka");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!flowdata.data.hasOwnProperty("uniqueColumn"))
|
||||||
|
{
|
||||||
|
//instance.send(0, "!!!!csv import - nedefinovane uniqueColumn");
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!flowdata.data.hasOwnProperty("path"))
|
||||||
|
{
|
||||||
|
instance.send(0, "!!!!csv import - nedefinovana cesta k suboru");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!flowdata.data.hasOwnProperty("mapImport"))
|
||||||
|
{
|
||||||
|
instance.send(0, "!!!!csv import - nedefinovany mapImport");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table = flowdata.data.table;
|
||||||
|
uniqueColumn = flowdata.data.uniqueColumn;
|
||||||
|
if(uniqueColumn === "") uniqueColumn = undefined;
|
||||||
|
|
||||||
|
path = flowdata.data.path;
|
||||||
|
mapImport = flowdata.data.mapImport;
|
||||||
|
|
||||||
|
if(flowdata.data.hasOwnProperty("delimiter")) delimiter = flowdata.data.delimiter;
|
||||||
|
if(flowdata.data.hasOwnProperty("startFrom")) startFrom = flowdata.data.startFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
//console.log("importineg", i, key, k);
|
||||||
|
|
||||||
|
if(data[key] != undefined) insertData[k] = data[key].trim();
|
||||||
|
else{
|
||||||
|
console.log("undefined", key, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("insertData", insertData);
|
||||||
|
|
||||||
|
if(uniqueColumn != undefined)
|
||||||
|
{
|
||||||
|
db.insert(insertData, true).where(uniqueColumn, insertData[uniqueColumn]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
db.insert(insertData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("csv import finished");
|
||||||
|
instance.send(0, "csv import finished");
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
instance.send(0, err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
286
flow/db_connector.js
Normal file
286
flow/db_connector.js
Normal file
|
|
@ -0,0 +1,286 @@
|
||||||
|
exports.id = 'db_connector';
|
||||||
|
exports.title = 'DbConnector';
|
||||||
|
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 = 'bolt';
|
||||||
|
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">DbConnector</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
exports.readme = `# read/write data to tables`;
|
||||||
|
|
||||||
|
const instanceSendTo = {
|
||||||
|
debug: 0,
|
||||||
|
http_response: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
|
||||||
|
|
||||||
|
function extractWhereParams(params)
|
||||||
|
{
|
||||||
|
let name = params[0];
|
||||||
|
let operator = '=';
|
||||||
|
let value = params[1];
|
||||||
|
|
||||||
|
if(params.length == 3)
|
||||||
|
{
|
||||||
|
operator = params[1];
|
||||||
|
value = params[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {name: name, operator: operator, value: value};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.install = function(instance) {
|
||||||
|
|
||||||
|
let refFlowdata = null;//holds reference to httprequest flowdata
|
||||||
|
|
||||||
|
instance.on("close", () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
instance.on("data", async function(flowdata) {
|
||||||
|
|
||||||
|
let params = flowdata.data.body;
|
||||||
|
console.log("DbConnector", params);
|
||||||
|
|
||||||
|
refFlowdata = flowdata;
|
||||||
|
|
||||||
|
if(refFlowdata != undefined)
|
||||||
|
{
|
||||||
|
//make http response
|
||||||
|
let responseObj = {};
|
||||||
|
responseObj["type"] = "SUCESS";
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
let table = params.table;
|
||||||
|
let action = params.action;
|
||||||
|
|
||||||
|
|
||||||
|
if(params.data != undefined)
|
||||||
|
{
|
||||||
|
let className = params.data.className;
|
||||||
|
|
||||||
|
if(className == "SqlQueryBuilder")
|
||||||
|
{
|
||||||
|
let type = params.data.type;
|
||||||
|
|
||||||
|
console.log("SqlQueryBuilder---->", params.data);
|
||||||
|
|
||||||
|
if(type == "SELECT")
|
||||||
|
{
|
||||||
|
let table = params.data.queryData.tables[0].table;
|
||||||
|
|
||||||
|
const db = TABLE(table);
|
||||||
|
var builder = db.find();
|
||||||
|
|
||||||
|
let result = await promisifyBuilder(builder);
|
||||||
|
|
||||||
|
let response = {};
|
||||||
|
response["result"] = result;
|
||||||
|
response["success"] = true;
|
||||||
|
|
||||||
|
responseObj["data"] = response;
|
||||||
|
|
||||||
|
//console.log(responseObj);
|
||||||
|
|
||||||
|
refFlowdata.data = responseObj;
|
||||||
|
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log("db_connector---->", table, action);
|
||||||
|
|
||||||
|
//actions: read, replace, insert, delete, update...
|
||||||
|
|
||||||
|
if(action == "read")
|
||||||
|
{
|
||||||
|
const db = TABLE(params.table);
|
||||||
|
|
||||||
|
//where builder.where('age', '<', 15);
|
||||||
|
//builder.where('id', 3403);
|
||||||
|
|
||||||
|
var builder = db.find();
|
||||||
|
|
||||||
|
//https://docs.totaljs.com/latest/en.html#api~DatabaseBuilder~builder.where
|
||||||
|
if(params.hasOwnProperty("where"))
|
||||||
|
{
|
||||||
|
//optionalCan contain "=", "<=", "<", ">=", ">".
|
||||||
|
//Default value: '='
|
||||||
|
|
||||||
|
//1.["production_line", 1]
|
||||||
|
//2. ["or", ["production_line", 1], ["production_line", 2], "end"]
|
||||||
|
|
||||||
|
if (Array.isArray(params.where)) {
|
||||||
|
|
||||||
|
let multipleConditions = false;
|
||||||
|
|
||||||
|
if(params.where[0] == "or") multipleConditions = true;
|
||||||
|
if (Array.isArray(params.where[0])) multipleConditions = true;
|
||||||
|
|
||||||
|
if(multipleConditions)
|
||||||
|
{
|
||||||
|
|
||||||
|
for(var i = 0; i < params.where.length; i++)
|
||||||
|
{
|
||||||
|
const item = params.where[i];
|
||||||
|
|
||||||
|
if(item === "or") builder.or();
|
||||||
|
|
||||||
|
if (Array.isArray(item))
|
||||||
|
{
|
||||||
|
const { name, operator, value } = extractWhereParams(item);
|
||||||
|
builder.where(name, operator, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(item === "end") builder.end();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const { name, operator, value } = extractWhereParams(params.where);
|
||||||
|
builder.where(name, operator, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if(params.where.length >=2 )
|
||||||
|
{
|
||||||
|
let name = params.where[0];
|
||||||
|
let operator = '=';
|
||||||
|
let value = params.where[1];
|
||||||
|
|
||||||
|
if(params.where.length == 3)
|
||||||
|
{
|
||||||
|
operator = params.where[1];
|
||||||
|
value = params.where[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.where(name, operator, value);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if(params.hasOwnProperty("between"))
|
||||||
|
{
|
||||||
|
builder.between(params.between[0], params.between[1], params.between[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await promisifyBuilder(builder);
|
||||||
|
responseObj["data"] = response;
|
||||||
|
|
||||||
|
//console.log(responseObj);
|
||||||
|
|
||||||
|
refFlowdata.data = responseObj;
|
||||||
|
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(action == "delete")
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(action == "update")
|
||||||
|
{
|
||||||
|
//! data receiving from terminal (params)
|
||||||
|
// {
|
||||||
|
// hostname: 'localhost',
|
||||||
|
// table: 'settings',
|
||||||
|
// action: 'update',
|
||||||
|
// body: {
|
||||||
|
// rvo_name: 'terrrr',
|
||||||
|
// lang: 'en',
|
||||||
|
// temperature_adress: '28.427B45920702',
|
||||||
|
// latitude: 48.70826502,
|
||||||
|
// longitude: 17.28455203,
|
||||||
|
// mqtt_host: '192.168.252.4',
|
||||||
|
// mqtt_clientid: 'showroom_test_panel_led',
|
||||||
|
// mqtt_username: 'xmRd6RJxW53WZe4vMFLU',
|
||||||
|
// mqtt_port: 1883,
|
||||||
|
// maintanace_mode: false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
const tableToModify = TABLE(params.table);
|
||||||
|
const newValues = params.body;
|
||||||
|
|
||||||
|
tableToModify.modify(newValues).make(function(builder) {
|
||||||
|
|
||||||
|
builder.callback(function(err, response) {
|
||||||
|
|
||||||
|
if(!err)
|
||||||
|
{
|
||||||
|
responseObj["data"] = response;
|
||||||
|
responseObj["tableUpdated"] = true;
|
||||||
|
refFlowdata.data = responseObj;
|
||||||
|
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(action == "replace")
|
||||||
|
{
|
||||||
|
//truncate table
|
||||||
|
const db = TABLE(params.table);
|
||||||
|
var builder = db.remove();
|
||||||
|
db.clean();
|
||||||
|
|
||||||
|
//insert data
|
||||||
|
let data = params.data;
|
||||||
|
|
||||||
|
for(let i = 0; i < data.length; i++)
|
||||||
|
{
|
||||||
|
//console.log(data[i]);
|
||||||
|
db.insert(data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("insert done");
|
||||||
|
|
||||||
|
let responseObj = {};
|
||||||
|
responseObj["type"] = "SUCESS";
|
||||||
|
|
||||||
|
refFlowdata.data = responseObj;
|
||||||
|
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
//console.log(error);
|
||||||
|
responseObj["type"] = "ERROR";
|
||||||
|
responseObj["message"] = error;
|
||||||
|
|
||||||
|
refFlowdata.data = responseObj;
|
||||||
|
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
2172
flow/designer.json
Normal file
2172
flow/designer.json
Normal file
File diff suppressed because it is too large
Load diff
1516
flow/designer_deploy.json
Normal file
1516
flow/designer_deploy.json
Normal file
File diff suppressed because it is too large
Load diff
1764
flow/di_do_controller.js
Normal file
1764
flow/di_do_controller.js
Normal file
File diff suppressed because it is too large
Load diff
220
flow/gettemperature.js
Normal file
220
flow/gettemperature.js
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
exports.id = 'gettemperature';
|
||||||
|
exports.title = 'Thermometer';
|
||||||
|
exports.group = 'Worksys';
|
||||||
|
exports.color = '#5CB36D';
|
||||||
|
exports.version = '1.0.2';
|
||||||
|
exports.output = ["red", "white", "blue"];
|
||||||
|
exports.author = 'Rastislav Kovac';
|
||||||
|
exports.icon = 'thermometer-three-quarters';
|
||||||
|
|
||||||
|
exports.readme = `# Getting temperature values from RVO`;
|
||||||
|
|
||||||
|
const instanceSendTo = {
|
||||||
|
debug: 0,
|
||||||
|
tb: 1,
|
||||||
|
di_do_controller: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
//read temperature - frequency
|
||||||
|
let timeoutMin = 5;//minutes
|
||||||
|
|
||||||
|
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' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const errLogger = log4js.getLogger("errLogs");
|
||||||
|
const logger = log4js.getLogger();
|
||||||
|
const monitor = log4js.getLogger("monitorLogs");
|
||||||
|
|
||||||
|
//logger.debug("text")
|
||||||
|
//monitor.info('info');
|
||||||
|
//errLogger.error("some error");
|
||||||
|
|
||||||
|
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
|
||||||
|
const dbSettings = TABLE("settings");
|
||||||
|
let temperatureAddress = "";
|
||||||
|
|
||||||
|
async function loadSettings()
|
||||||
|
{
|
||||||
|
//todo global FLOW.OMS_edgeName is making problem, so we load it here as well, it should not be
|
||||||
|
let responseSettings = await promisifyBuilder(dbSettings.find());
|
||||||
|
temperatureAddress = responseSettings[0]["temperature_adress"];
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
|
||||||
|
|
||||||
|
exports.install = function(instance) {
|
||||||
|
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const { sendNotification, ERRWEIGHT } = require('./helper/notification_reporter.js');
|
||||||
|
|
||||||
|
let startRead;
|
||||||
|
let dataToTb;
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
let edgeName = "";
|
||||||
|
|
||||||
|
|
||||||
|
logger.debug(exports.title, "installed");
|
||||||
|
|
||||||
|
instance.on("close", function(){
|
||||||
|
clearInterval(startRead);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const start = function(){
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
if(FLOW.OMS_controller_type === "unipi")
|
||||||
|
{
|
||||||
|
clearInterval(startRead);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(temperatureAddress === "") throw "gettemperature: temperatureAddress is not defined";
|
||||||
|
|
||||||
|
logger.debug("FLOW.OMS_temperature_adress", FLOW.OMS_temperature_adress);
|
||||||
|
|
||||||
|
exec(`owread -C ${temperatureAddress}/temperature`, (error, stdout, stderr) => {
|
||||||
|
|
||||||
|
edgeName = FLOW.OMS_edgeName;
|
||||||
|
|
||||||
|
if(edgeName !== "")
|
||||||
|
{
|
||||||
|
if(error)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(FLOW.OMS_brokerready == undefined)
|
||||||
|
{
|
||||||
|
logger.debug("gettemparature - FLOW.OMS_brokerready is undefined");
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
start();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(FLOW.OMS_brokerready)
|
||||||
|
{
|
||||||
|
//sendNotification("start", edgeName, ERRWEIGHT.WARNING, "Thermometer is not responding", {"Error": error}, instanceSendTo.tb, instance, "thermometer");
|
||||||
|
sendNotification("start", edgeName, "thermometer_is_not_responding", {}, {"Error": error}, instanceSendTo.tb, instance, "thermometer");
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = "NOK";
|
||||||
|
dataToTb = {
|
||||||
|
[edgeName]: [
|
||||||
|
{
|
||||||
|
"ts": Date.now(),
|
||||||
|
"values": {
|
||||||
|
"status": status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor.info("Thermometer is not responding", error, FLOW.OMS_brokerready);
|
||||||
|
|
||||||
|
instance.send(instanceSendTo.tb, dataToTb);
|
||||||
|
instance.send(instanceSendTo.di_do_controller, {sender: "gettemperature", status: status});
|
||||||
|
}
|
||||||
|
else parseData(stdout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
monitor.info("gettemperature: edgeName is not defined", FLOW.OMS_edgeName);
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
start();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//instance.send({"Temp":stdout,"stderr":stderr,"err":error});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
errLogger.error(exports.title, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseData = function(data) {
|
||||||
|
|
||||||
|
data = parseFloat(data);
|
||||||
|
|
||||||
|
logger.debug("gettemperature", data);
|
||||||
|
|
||||||
|
if (!isNaN(data)){
|
||||||
|
|
||||||
|
|
||||||
|
//if ( counter > 290 )
|
||||||
|
{
|
||||||
|
instance.send(instanceSendTo.debug, "[Get temperature component] - temperature data are comming again from RVO after more than 1 day break");
|
||||||
|
|
||||||
|
//sendNotification("parseData", edgeName, ERRWEIGHT.NOTICE, "Thermometer is working again", "", instanceSendTo.tb, instance, "thermometer");
|
||||||
|
if(FLOW.OMS_brokerready) sendNotification("parseData", edgeName, "thermometer_is_responding_again", {}, "", instanceSendTo.tb, instance, "thermometer");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("gettemperature", data);
|
||||||
|
|
||||||
|
dataToTb = {
|
||||||
|
[edgeName]: [
|
||||||
|
{
|
||||||
|
"ts": Date.now(),
|
||||||
|
"values": {
|
||||||
|
"temperature": Number(data.toFixed(2)),
|
||||||
|
"status": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.send(instanceSendTo.tb, dataToTb);
|
||||||
|
instance.send(instanceSendTo.di_do_controller, {sender: "gettemperature", status: "OK"});
|
||||||
|
|
||||||
|
counter = 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
monitor.info("gettemperature err", counter, data);
|
||||||
|
|
||||||
|
//ked je problem 1 den
|
||||||
|
let day = 24 * 60 / timeoutMin;
|
||||||
|
if ( counter > day && counter < day + 2 ) {
|
||||||
|
//sendNotification("parseData", edgeName, ERRWEIGHT.WARNING, "Thermometer receives invalid data", "", instanceSendTo.tb, instance, "thermometer");
|
||||||
|
sendNotification("parseData", edgeName, "thermometer_sends_invalid_data", {}, "", instanceSendTo.tb, instance, "thermometer");
|
||||||
|
|
||||||
|
instance.send(instanceSendTo.debug, "[Get temperature component] - no temperature data from RVO for more than 1 day");
|
||||||
|
instance.send(instanceSendTo.di_do_controller, {sender: "gettemperature", status: "NOK"});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
start();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
startRead = setInterval(start, timeoutMin * 1000 * 60);
|
||||||
|
|
||||||
|
};
|
||||||
163
flow/helper/DataToTbHandler.js
Normal file
163
flow/helper/DataToTbHandler.js
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
class DataToTbHandler
|
||||||
|
{
|
||||||
|
constructor(index) {
|
||||||
|
this.index = index;
|
||||||
|
|
||||||
|
this.previousValues = {};
|
||||||
|
this.debug = false;
|
||||||
|
this.messageCounter = 0;
|
||||||
|
|
||||||
|
this.sender = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
dump()
|
||||||
|
{
|
||||||
|
console.log("----------------------------");
|
||||||
|
console.log("previousValues", this.previousValues);
|
||||||
|
console.log("----------------------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
setSender(sender)
|
||||||
|
{
|
||||||
|
this.sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmptyObject( obj ) {
|
||||||
|
for ( var name in obj ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToTb(dataToTb, instance)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(!FLOW.OMS_brokerready)
|
||||||
|
{
|
||||||
|
return dataToTb;
|
||||||
|
}
|
||||||
|
|
||||||
|
let keys = Object.keys(dataToTb);
|
||||||
|
|
||||||
|
if(keys.length == 0)
|
||||||
|
{
|
||||||
|
if(this.debug) console.log("sendToTb received epty object", dataToTb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let tbname = keys[0];
|
||||||
|
let ts;
|
||||||
|
|
||||||
|
let arrayOfValues = dataToTb[tbname];
|
||||||
|
let arrayOfValuesToSend = [];
|
||||||
|
|
||||||
|
for(let i = 0; i < arrayOfValues.length; i++)
|
||||||
|
{
|
||||||
|
ts = arrayOfValues[i].ts;
|
||||||
|
|
||||||
|
//console.log("sendToTb------------>before", arrayOfValues[i].values, tbname);
|
||||||
|
|
||||||
|
let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values);
|
||||||
|
|
||||||
|
//console.log("sendToTb------------>after", values);
|
||||||
|
|
||||||
|
if(!this.isEmptyObject(values))
|
||||||
|
{
|
||||||
|
arrayOfValuesToSend.push({ts: ts, values: values});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(arrayOfValuesToSend.length == 0)
|
||||||
|
{
|
||||||
|
//if(this.debug) console.log("data not sent - empty array");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
let dataToTb = {
|
||||||
|
[tbname]: [
|
||||||
|
{
|
||||||
|
"ts": Date.now(),
|
||||||
|
"values": values
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.messageCounter++;
|
||||||
|
|
||||||
|
let dataToTbModified = {
|
||||||
|
[tbname]: arrayOfValuesToSend
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(this.sender + " DATA SEND TO TB ", tbname, this.messageCounter, new Date(ts), dataToTbModified[tbname][0].values, this.instance);
|
||||||
|
if(this.debug) console.log(this.sender + " DATA SEND TO TB ", this.index, tbname, arrayOfValuesToSend);
|
||||||
|
|
||||||
|
instance.send(this.index, dataToTbModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDiffTimestamp(key)
|
||||||
|
{
|
||||||
|
let seconds = 60*60;//1h
|
||||||
|
//seconds = 1;//for testing
|
||||||
|
|
||||||
|
//TODO set different value for given key!!!
|
||||||
|
//if(key == "status") seconds = 2*60*60;//2h
|
||||||
|
|
||||||
|
let timestampDiffToRemoveKey = seconds*1000;
|
||||||
|
|
||||||
|
return timestampDiffToRemoveKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareValuesForTb(tbname, timestamp, values)
|
||||||
|
{
|
||||||
|
let keys = Object.keys(values);
|
||||||
|
if(!this.previousValues.hasOwnProperty(tbname))
|
||||||
|
{
|
||||||
|
this.previousValues[tbname] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
//if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values);
|
||||||
|
|
||||||
|
for(let i = 0; i < keys.length; i++)
|
||||||
|
{
|
||||||
|
let key = keys[i];
|
||||||
|
let value = values[key];
|
||||||
|
|
||||||
|
if(!this.previousValues[tbname].hasOwnProperty(key))
|
||||||
|
{
|
||||||
|
this.previousValues[tbname][key] = {ts: timestamp, value: value};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.previousValues[tbname][key].value === value)
|
||||||
|
{
|
||||||
|
let diff = timestamp - this.previousValues[tbname][key].ts;
|
||||||
|
|
||||||
|
let timestampDiffToRemoveKey = this.getDiffTimestamp(key);
|
||||||
|
if(diff > timestampDiffToRemoveKey)
|
||||||
|
{
|
||||||
|
this.previousValues[tbname][key].ts = Date.now();
|
||||||
|
//if(this.debug) console.log(this.sender + ": update ts for key", key, "diff is", diff, "messageCounter", this.messageCounter);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete values[key];
|
||||||
|
//if(this.debug) console.log(this.sender + ": delete key", key, "diff is", diff, "messageCounter", this.messageCounter, timestampDiffToRemoveKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.previousValues[tbname][key].value = value;
|
||||||
|
this.previousValues[tbname][key].ts = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DataToTbHandler;
|
||||||
124
flow/helper/ErrorToServiceHandler.js
Normal file
124
flow/helper/ErrorToServiceHandler.js
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
const { MD5 } = require('./md5.js');
|
||||||
|
const { networkInterfaces } = require('os');
|
||||||
|
|
||||||
|
class ErrorToServiceHandler
|
||||||
|
{
|
||||||
|
constructor() {
|
||||||
|
this.previousValues = {};
|
||||||
|
|
||||||
|
this.projects_id = undefined;
|
||||||
|
this.message_type = "error_message";
|
||||||
|
|
||||||
|
const nets = networkInterfaces();
|
||||||
|
this.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 (!this.ipAddresses[name]) {
|
||||||
|
this.ipAddresses[name] = [];
|
||||||
|
}
|
||||||
|
this.ipAddresses[name].push(net.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(this.ipAddresses);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setProjectsId(projects_id)
|
||||||
|
{
|
||||||
|
this.projects_id = projects_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
processMessage(message, seconds, message_type)
|
||||||
|
{
|
||||||
|
if(Array.isArray(message)) message = message.join(', ');
|
||||||
|
//keep in memory - default value is 1h
|
||||||
|
if (seconds === undefined) seconds = 60*60;
|
||||||
|
if(message_type) this.message_type = message_type;
|
||||||
|
|
||||||
|
let key = MD5(message);
|
||||||
|
let timestamp = new Date().getTime();
|
||||||
|
|
||||||
|
if(!this.previousValues.hasOwnProperty(key))
|
||||||
|
{
|
||||||
|
this.previousValues[key] = {ts: timestamp, duration: seconds};
|
||||||
|
}
|
||||||
|
|
||||||
|
let diff = (timestamp - this.previousValues[key].ts);
|
||||||
|
if(diff < this.previousValues[key].duration*1000) return false;
|
||||||
|
|
||||||
|
this.previousValues[key].ts = timestamp;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessageToService(message, seconds, message_type)
|
||||||
|
{
|
||||||
|
|
||||||
|
let f = this.processMessage(message, seconds, message_type);
|
||||||
|
if(!f) return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
//-------------
|
||||||
|
if(message_type == undefined) message_type = "error_message";
|
||||||
|
if(Array.isArray(message)) message = message.join(', ');
|
||||||
|
|
||||||
|
let key = MD5(message);
|
||||||
|
let timestamp = new Date().getTime();
|
||||||
|
|
||||||
|
//keep in memory
|
||||||
|
if (seconds === undefined) seconds = 60*60;
|
||||||
|
|
||||||
|
if(!this.previousValues.hasOwnProperty(key))
|
||||||
|
{
|
||||||
|
this.previousValues[key] = {ts: timestamp, duration: seconds};
|
||||||
|
}
|
||||||
|
|
||||||
|
let diff = (timestamp - this.previousValues[key].ts);
|
||||||
|
if(diff < this.previousValues[key].duration*1000) return;
|
||||||
|
|
||||||
|
this.previousValues[key].ts = timestamp;
|
||||||
|
*/
|
||||||
|
|
||||||
|
//-------------------------
|
||||||
|
|
||||||
|
//send to service
|
||||||
|
|
||||||
|
let dataToInfoSender = {id: this.projects_id};
|
||||||
|
|
||||||
|
//js_error || error_message
|
||||||
|
dataToInfoSender[this.message_type] = message;
|
||||||
|
dataToInfoSender.ipAddresses = this.ipAddresses;
|
||||||
|
|
||||||
|
console.log("ErrorToServiceHandler------------------------>send to service", dataToInfoSender);
|
||||||
|
|
||||||
|
//TODO UGLY!!!
|
||||||
|
if(this.projects_id === undefined) this.projects_id = FLOW.OMS_projects_id;
|
||||||
|
|
||||||
|
/*
|
||||||
|
if(this.projects_id === undefined)
|
||||||
|
{
|
||||||
|
console.log("this.projects_id is undefined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
RESTBuilder.make(function(builder) {
|
||||||
|
builder.method('POST');
|
||||||
|
builder.post(dataToInfoSender);
|
||||||
|
builder.url('http://192.168.252.2:8004/sentmessage');
|
||||||
|
|
||||||
|
builder.callback(function(err, response, output) {
|
||||||
|
console.log("process.on error send", err, response, output, dataToInfoSender);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ErrorToServiceHandler;
|
||||||
44
flow/helper/db_helper.js
Normal file
44
flow/helper/db_helper.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
function promisifyBuilder(builder)
|
||||||
|
{
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
builder.callback(function(err, response) {
|
||||||
|
|
||||||
|
if(err != null) reject(err);
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMapFromDbResult(response, ...keys)
|
||||||
|
{
|
||||||
|
let s = "-";
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
for(let i = 0; i < response.length; i++)
|
||||||
|
{
|
||||||
|
let record = response[i];
|
||||||
|
|
||||||
|
let k = [];
|
||||||
|
for(let j = 0; j < keys.length; j++)
|
||||||
|
{
|
||||||
|
k.push( record[keys[j]] );
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = k.join(s);
|
||||||
|
data[ key ] = record;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
promisifyBuilder,
|
||||||
|
makeMapFromDbResult
|
||||||
|
}
|
||||||
72
flow/helper/error_reporter.js
Normal file
72
flow/helper/error_reporter.js
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
|
||||||
|
//key is device, value = str
|
||||||
|
let sentValues= {};
|
||||||
|
|
||||||
|
function sendError(func, device, weight, str, extra, tb_output, instance, type) {
|
||||||
|
// if ((weight === ERRWEIGHT.DEBUG) && (instance.CONFIG.debug === false)){
|
||||||
|
// return; // Allow debug messages only if CONFIG.debug is active
|
||||||
|
// }
|
||||||
|
|
||||||
|
let key = device;
|
||||||
|
if(type != undefined) key = type;
|
||||||
|
|
||||||
|
if(sentValues.hasOwnProperty(key))
|
||||||
|
{
|
||||||
|
if(sentValues[key] == str)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sentValues[key] = 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
|
||||||
|
}
|
||||||
56
flow/helper/error_reporting.js
Normal file
56
flow/helper/error_reporting.js
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
const 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
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function sendError(func, device, weight, str, extra){
|
||||||
|
if ((weight === ERRWEIGHT.DEBUG) && (instance.CONFIG.debug === false)){
|
||||||
|
return; // Allow debug messages only if CONFIG.debug is active
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = {
|
||||||
|
"type": weight,
|
||||||
|
"status": "new",
|
||||||
|
"source": {
|
||||||
|
"func":func,
|
||||||
|
"component":instance.id,
|
||||||
|
"component_name":instance.name,
|
||||||
|
"edge":myEdge
|
||||||
|
},
|
||||||
|
"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(0, msg); // Even if error server is unavailable, send this message to output, for other possible component connections
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sendError,
|
||||||
|
ERRWEIGHT,
|
||||||
|
}
|
||||||
5
flow/helper/md5.js
Normal file
5
flow/helper/md5.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
function MD5(d){var r = M(V(Y(X(d),8*d.length)));return r.toLowerCase()};function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MD5
|
||||||
|
}
|
||||||
135
flow/helper/notification_reporter.js
Normal file
135
flow/helper/notification_reporter.js
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
|
||||||
|
const { promisifyBuilder, makeMapFromDbResult } = require('./db_helper.js');
|
||||||
|
const dbNotifications = TABLE("notifications");
|
||||||
|
|
||||||
|
//key is device, value = str
|
||||||
|
let sentValues= {};
|
||||||
|
let notificationsData = {};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
function getKey(map, val) {
|
||||||
|
return Object.keys(map).findItem(key => map[key] === val);
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://stackoverflow.com/questions/41117799/string-interpolation-on-variable
|
||||||
|
var template = (tpl, args) => tpl.replace(/\${(\w+)}/g, (_, v) => args[v]);
|
||||||
|
|
||||||
|
async function initNotifications()
|
||||||
|
{
|
||||||
|
let response = await promisifyBuilder(dbNotifications.find());
|
||||||
|
notificationsData = makeMapFromDbResult(response, "key");
|
||||||
|
|
||||||
|
console.log("initNotifications done" );
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendNotification(func, device, key, params, extra, tb_output, instance, saveKey) {
|
||||||
|
|
||||||
|
let storeToSendValues = true;
|
||||||
|
if(saveKey == undefined) storeToSendValues = false;
|
||||||
|
|
||||||
|
let lang = FLOW.OMS_language;
|
||||||
|
if(lang != "en" || lang != "sk") lang = "en";
|
||||||
|
|
||||||
|
let tpl = key;
|
||||||
|
let weight = "";
|
||||||
|
|
||||||
|
if(notificationsData[key])
|
||||||
|
{
|
||||||
|
weight = notificationsData[key].weight;
|
||||||
|
weight = weight.toLowerCase();
|
||||||
|
|
||||||
|
tpl = notificationsData[key][lang];
|
||||||
|
tpl = template(tpl, params);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.error("sendNotification: Notifications: undefined key", key, func, notificationsData);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//detect invalid err weight
|
||||||
|
if(getKey(ERRWEIGHT, weight) == undefined)
|
||||||
|
{
|
||||||
|
console.error("sendNotification: Notifications: undefined weight", weight, key, func);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sentValues.hasOwnProperty(saveKey))
|
||||||
|
{
|
||||||
|
if(sentValues[saveKey] == tpl)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sentValues[saveKey] == undefined)
|
||||||
|
{
|
||||||
|
if(storeToSendValues)
|
||||||
|
{
|
||||||
|
//do not send - flow is was started
|
||||||
|
sentValues[saveKey] = tpl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(saveKey == "rvo_door")
|
||||||
|
{
|
||||||
|
//console.log("******", saveKey, sentValues[saveKey], tpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(storeToSendValues) sentValues[saveKey] = tpl;
|
||||||
|
|
||||||
|
let str = FLOW.OMS_rvo_name;
|
||||||
|
if(str != "") str = str + ": ";
|
||||||
|
str = str + tpl;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sendNotification,
|
||||||
|
initNotifications,
|
||||||
|
ERRWEIGHT
|
||||||
|
}
|
||||||
144
flow/helper/register.js
Normal file
144
flow/helper/register.js
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
0 - cislo registra / prikaz
|
||||||
|
1 - recepient - 0 = master, 1 = slave (slave je automaticky group a broadcast)
|
||||||
|
2 - r/rw - read/write
|
||||||
|
3- register name - nazov registra / prikazu (len pre info - zobrazenie v aplikacii)
|
||||||
|
|
||||||
|
4,5,6,7, - jednotlive byte-y - nazov byte-u
|
||||||
|
4-7 - RES. a prazdny string "" sa nezobrazia!!!
|
||||||
|
*/
|
||||||
|
|
||||||
|
//124 zaznamov
|
||||||
|
const registers = [
|
||||||
|
["0","1","R","Status","","",""],
|
||||||
|
["1","1","RW","Dimming","R-Channel ","G-Channel","B-Channel ","W - Channel"],
|
||||||
|
["2","1","R","Device types","","",".",""],
|
||||||
|
["3","1","RW","Group addresses 1-4","Groups Add. 4","Groups Add. 3","Groups Add. 2","Groups Add. 1"],
|
||||||
|
["4","1","RW","Group addresses 5-8","Groups Add. 8","Groups Add. 7","Groups Add. 6","Groups Add. 5"],
|
||||||
|
["5","1","RW","Serial number (MAC)","","","",""],
|
||||||
|
["6","1","RW","Time of dusk","HH","MM","SS","EXTRA"],
|
||||||
|
["7","1","RW","Time of dawn","HH","MM","SS","EXTRA"],
|
||||||
|
["8","1","RW","Time Schedule settings","TBD","TBD","Movement sensor","Time Schedule"],
|
||||||
|
["9","1","RW","TS1 Time point 1","HH","MM","SS","Ext. Device"],
|
||||||
|
["10","1","RW","TS1 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["11","1","RW","TS1 Time point 2","HH","MM","SS","Ext. Device"],
|
||||||
|
["12","1","RW","TS1 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["13","1","RW","TS1 Time point 3","HH","MM","SS","Ext. Device"],
|
||||||
|
["14","1","RW","TS1 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["15","1","RW","TS1 Time point 4","HH","MM","SS","Ext. Device"],
|
||||||
|
["16","1","RW","TS1 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["17","1","RW","TS1 Time point 5","HH","MM","SS","Ext. Device"],
|
||||||
|
["18","1","RW","TS1 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["19","1","RW","TS1 Time point 6","HH","MM","SS","Ext. Device"],
|
||||||
|
["20","1","RW","TS1 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["21","1","RW","TS1 Time point 7","HH","MM","SS","Ext. Device"],
|
||||||
|
["22","1","RW","TS1 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["23","1","RW","TS1 Time point 8","HH","MM","SS","Ext. Device"],
|
||||||
|
["24","1","RW","TS1 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["25","1","RW","TS1 Time point 9","HH","MM","SS","Ext. Device"],
|
||||||
|
["26","1","RW","TS1 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["27","1","RW","TS1 Time point 10","HH","MM","SS","Ext. Device"],
|
||||||
|
["28","1","RW","TS1 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["29","1","RW","TS1 Time point 11","HH","MM","SS","Ext. Device"],
|
||||||
|
["30","1","RW","TS1 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["31","1","RW","TS1 Time point 12","HH","MM","SS","Ext. Device"],
|
||||||
|
["32","1","RW","TS1 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["33","1","RW","TS1 Time point 13","HH","MM","SS","Ext. Device"],
|
||||||
|
["34","1","RW","TS1 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["35","1","RW","TS1 Time point 14","HH","MM","SS","Ext. Device"],
|
||||||
|
["36","1","RW","TS1 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["37","1","RW","TS1 Time point 15","HH","MM","SS","Ext. Device"],
|
||||||
|
["38","1","RW","TS1 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["39","1","RW","TS1 Time point 16","HH","MM","SS","Ext. Device"],
|
||||||
|
["40","1","RW","TS1 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["41","1","RW","TS2 Time point 1","HH","MM","SS","Ext. Device"],
|
||||||
|
["42","1","RW","TS2 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["43","1","RW","TS2 Time point 2","HH","MM","SS","Ext. Device"],
|
||||||
|
["44","1","RW","TS2 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["45","1","RW","TS2 Time point 3","HH","MM","SS","Ext. Device"],
|
||||||
|
["46","1","RW","TS2 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["47","1","RW","TS2 Time point 4","HH","MM","SS","Ext. Device"],
|
||||||
|
["48","1","RW","TS2 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["49","1","RW","TS2 Time point 5","HH","MM","SS","Ext. Device"],
|
||||||
|
["50","1","RW","TS2 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["51","1","RW","TS2 Time point 6","HH","MM","SS","Ext. Device"],
|
||||||
|
["52","1","RW","TS2 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["53","1","RW","TS2 Time point 7","HH","MM","SS","Ext. Device"],
|
||||||
|
["54","1","RW","TS2 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["55","1","RW","TS2 Time point 8","HH","MM","SS","Ext. Device"],
|
||||||
|
["56","1","RW","TS2 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["57","1","RW","TS2 Time point 9","HH","MM","SS","Ext. Device"],
|
||||||
|
["58","1","RW","TS2 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["59","1","RW","TS2 Time point 10","HH","MM","SS","Ext. Device"],
|
||||||
|
["60","1","RW","TS2 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["61","1","RW","TS2 Time point 11","HH","MM","SS","Ext. Device"],
|
||||||
|
["62","1","RW","TS2 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["63","1","RW","TS2 Time point 12","HH","MM","SS","Ext. Device"],
|
||||||
|
["64","1","RW","TS2 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["65","1","RW","TS2 Time point 13","HH","MM","SS","Ext. Device"],
|
||||||
|
["66","1","RW","TS2 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["67","1","RW","TS2 Time point 14","HH","MM","SS","Ext. Device"],
|
||||||
|
["68","1","RW","TS2 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["69","1","RW","TS2 Time point 15","HH","MM","SS","Ext. Device"],
|
||||||
|
["70","1","RW","TS2 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["71","1","RW","TS2 Time point 16","HH","MM","SS","Ext. Device"],
|
||||||
|
["72","1","RW","TS2 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||||
|
["73","1","RW","Power meter status","TBD","","",""],
|
||||||
|
["74","1","R","Input Voltage","","","",""],
|
||||||
|
["75","1","R","Input Current","","","",""],
|
||||||
|
["76","1","R","Input Power","","","",""],
|
||||||
|
["77","1","R","Cos phi","","","",""],
|
||||||
|
["78","1","R","Frequency","","","",""],
|
||||||
|
["79","1","RW","Energy","","","",""],
|
||||||
|
["80","1","RW","Lifetime","","","",""],
|
||||||
|
["81","1","RW","Power on cycles (input)","","","",""],
|
||||||
|
["82","1","RW","Power on cycles (relay)","","","",""],
|
||||||
|
["83","1","R","Time since last power on","","","",""],
|
||||||
|
["84","1","R","Accelerometer data","","","",""],
|
||||||
|
["85","1","RW","GPS latitude","pos/neg","deg","min ","sec"],
|
||||||
|
["86","1","RW","GPS longitude","pos/neg","deg","min ","sec"],
|
||||||
|
["87","1","RW","Actual time","HH","MM","SS","RES."],
|
||||||
|
["88","1","RW","Actual date","Day of week","Day","Month","Year"],
|
||||||
|
["89","1","R","Production data 1","","","",""],
|
||||||
|
["90","1","R","Production data 2","","","",""],
|
||||||
|
["91","1","RW","Network ID","NID3","NID2","NID1","NID0"],
|
||||||
|
["95","1","RW","Actual Lux level from cabinet","RES.","RES.","HB","LB"],
|
||||||
|
["96","1","RW","Threshold lux level","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
|
||||||
|
["97","1","RW","Adjust period","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
|
||||||
|
["98","1","RW","Offset","RES.","RES.","Dusk","Dawn"],
|
||||||
|
["99","1","RW","CCT min/max range","max-H","max-L","min-H","min-L"],
|
||||||
|
["100","1","RW","DALI interface","Cmd ID","Add","Cmd","Resp"],
|
||||||
|
["101","1","RW","Module FW ver","v1","v2","v3","v4"],
|
||||||
|
["102","1","RW","Module MAC-H","unused","unused","M1","M2"],
|
||||||
|
["103","1","RW","Module MAC-L","M3","M4","M5","M6"],
|
||||||
|
["122","1","R","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["123","1","R","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["124","1","R","FW update - data","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["0","0","R","Status","","","",""],
|
||||||
|
["1","0","RW","Control register","RES.","RES.","RES.","init mode enable"],
|
||||||
|
["2","0","R","Device types","","","",""],
|
||||||
|
["3","0","R","Serial number (MAC)","","","",""],
|
||||||
|
["4","0","R","Production data 1","","","",""],
|
||||||
|
["5","0","R","Production data 2","","","",""],
|
||||||
|
["6","0","RW","Network ID","NID3","NID2","NID1","NID0"],
|
||||||
|
["7","0","RW","RS232 settings","param.","param.","Baudrate H","Baudrate L"],
|
||||||
|
["8","0","R","Accelerometer data","","","",""],
|
||||||
|
["9","0","RW","Module FW ver","v1","v2","v3","v4"],
|
||||||
|
["10","0","RW","Module MAC-H","unused","unused","M1","M2"],
|
||||||
|
["11","0","RW","Module MAC-L","M3","M4","M5","M6"],
|
||||||
|
["32","0","RW","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["33","0","RW","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["34","0","RW","FW update - data","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["125","0","RW","Debug Register","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["126","0","RW","Network Control Register","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["127","0","R","Network Status Register","Byte3","Byte2","Byte1","Byte0"],
|
||||||
|
["128","0","RW","Node XX Serial Number Register","SER3","SER2","SER1","SER0"],
|
||||||
|
["256","0","R","Node XX Network Status Register","","","",""]
|
||||||
|
];
|
||||||
|
|
||||||
|
let register = {};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
registers,
|
||||||
|
register
|
||||||
|
}
|
||||||
94
flow/helper/serialport_helper.js
Normal file
94
flow/helper/serialport_helper.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
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) => {
|
||||||
|
|
||||||
|
//readbytes = 0 = broadcast
|
||||||
|
if(readbytes == undefined) readbytes = 0;
|
||||||
|
if(timeout == undefined) timeout = 10000;//10s, default timeout MASTERA je 3s
|
||||||
|
|
||||||
|
//cmd-manager mame http route POST / terminal a tomu sa tiez nastavuje timeout!!!
|
||||||
|
|
||||||
|
var callback = function(data) {
|
||||||
|
rsPortReceivedData.push(...data);
|
||||||
|
let l = rsPortReceivedData.length;
|
||||||
|
|
||||||
|
if(l >= readbytes)
|
||||||
|
{
|
||||||
|
port.removeListener('data', callback);
|
||||||
|
|
||||||
|
clearTimeout(t);
|
||||||
|
resolve(rsPortReceivedData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
port.removeListener('data', callback);
|
||||||
|
|
||||||
|
let t = setTimeout(() => {
|
||||||
|
port.removeListener('data', callback);
|
||||||
|
|
||||||
|
console.log("serialport helper: writeData TIMEOUT READING", rsPortReceivedData);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
}());
|
||||||
124
flow/helper/utils.js
Normal file
124
flow/helper/utils.js
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
function bytesToInt(bytes, numberOfBytes)
|
||||||
|
{
|
||||||
|
let buffer = [];
|
||||||
|
if(Array.isArray(bytes))
|
||||||
|
{
|
||||||
|
buffer = bytes.slice(0);
|
||||||
|
if(numberOfBytes != undefined)
|
||||||
|
{
|
||||||
|
buffer = bytes.slice(bytes.length - numberOfBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else buffer.push(bytes);
|
||||||
|
|
||||||
|
//var decimal = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
|
||||||
|
let l = (buffer.length - 1) * 8;
|
||||||
|
|
||||||
|
let decimal = 0;
|
||||||
|
for(let i = 0; i < buffer.length; i++)
|
||||||
|
{
|
||||||
|
var s = buffer[i] << l;
|
||||||
|
if(l < 8) s = buffer[i]
|
||||||
|
decimal = decimal + s;
|
||||||
|
|
||||||
|
l = l - 8;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeArray(arr, newSize, defaultValue) {
|
||||||
|
while(newSize > arr.length)
|
||||||
|
arr.push(defaultValue);
|
||||||
|
arr.length = newSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
longToByteArray = function(/*long*/long) {
|
||||||
|
// we want to represent the input as a 8-bytes array
|
||||||
|
var byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
|
||||||
|
for ( var index = 0; index < byteArray.length; index ++ ) {
|
||||||
|
var byte = long & 0xff;
|
||||||
|
byteArray [ index ] = byte;
|
||||||
|
long = (long - byte) / 256 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
function addDays(date, days) {
|
||||||
|
var result = new Date(date);
|
||||||
|
result.setDate(result.getDate() + days);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
sleep(2000).then(() => {
|
||||||
|
// Do something after the sleep!
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
function sleep (time) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, time));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyObject( obj ) {
|
||||||
|
for ( var name in obj ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertUTCDateToLocalDate(date) {
|
||||||
|
var newDate = new Date(date);
|
||||||
|
newDate.setMinutes(date.getMinutes() + date.getTimezoneOffset());
|
||||||
|
return newDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addZeroBefore(n) {
|
||||||
|
return (n < 10 ? '0' : '') + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
var convertBase = function () {
|
||||||
|
|
||||||
|
function convertBase(baseFrom, baseTo) {
|
||||||
|
return function (num) {
|
||||||
|
return parseInt(num, baseFrom).toString(baseTo);
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// binary to decimal
|
||||||
|
convertBase.bin2dec = convertBase(2, 10);
|
||||||
|
|
||||||
|
// binary to hexadecimal
|
||||||
|
convertBase.bin2hex = convertBase(2, 16);
|
||||||
|
|
||||||
|
// decimal to binary
|
||||||
|
convertBase.dec2bin = convertBase(10, 2);
|
||||||
|
|
||||||
|
// decimal to hexadecimal
|
||||||
|
convertBase.dec2hex = convertBase(10, 16);
|
||||||
|
|
||||||
|
// hexadecimal to binary
|
||||||
|
convertBase.hex2bin = convertBase(16, 2);
|
||||||
|
|
||||||
|
// hexadecimal to decimal
|
||||||
|
convertBase.hex2dec = convertBase(16, 10);
|
||||||
|
|
||||||
|
return convertBase;
|
||||||
|
}();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bytesToInt,
|
||||||
|
longToByteArray,
|
||||||
|
addDays,
|
||||||
|
addZeroBefore,
|
||||||
|
resizeArray,
|
||||||
|
isEmptyObject,
|
||||||
|
sleep,
|
||||||
|
convertUTCDateToLocalDate
|
||||||
|
}
|
||||||
137
flow/httprequest.js
Normal file
137
flow/httprequest.js
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
exports.id = 'httprequest';
|
||||||
|
exports.title = 'HTTP Request';
|
||||||
|
exports.group = 'HTTP';
|
||||||
|
exports.color = '#5D9CEC';
|
||||||
|
exports.input = true;
|
||||||
|
exports.version = '2.0.1';
|
||||||
|
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>
|
||||||
|
<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(response) {
|
||||||
|
var options = instance.options;
|
||||||
|
|
||||||
|
var headers = null;
|
||||||
|
var cookies = null;
|
||||||
|
|
||||||
|
options.headers && Object.keys(options.headers).forEach(function(key) {
|
||||||
|
!headers && (headers = {});
|
||||||
|
headers[key] = response.arg(options.headers[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.username && options.userpassword) {
|
||||||
|
!headers && (headers = {});
|
||||||
|
headers['Authorization'] = 'Basic ' + U.createBuffer(response.arg(options.username + ':' + options.userpassword)).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
options.cookies && Object.keys(options.cookies).forEach(function(key) {
|
||||||
|
!cookies && (cookies = {});
|
||||||
|
cookies[key] = response.arg(options.cookies[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.chunks) {
|
||||||
|
U.download(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, response) {
|
||||||
|
response.on('data', (chunks) => instance.send2(chunks));
|
||||||
|
}, cookies || cookies2, headers);
|
||||||
|
} else {
|
||||||
|
U.request(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, data, status, headers, host) {
|
||||||
|
if (response && !err) {
|
||||||
|
response.data = { data: data, status: status, headers: headers, host: host };
|
||||||
|
instance.send2(response);
|
||||||
|
} else if (err)
|
||||||
|
instance.error(err, response);
|
||||||
|
}, 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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
1127
flow/modbus_citysys.js
Normal file
1127
flow/modbus_citysys.js
Normal file
File diff suppressed because it is too large
Load diff
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);
|
||||||
|
};
|
||||||
107
flow/monitorcpu.js
Normal file
107
flow/monitorcpu.js
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
exports.id = 'monitorcpu';
|
||||||
|
exports.title = 'CPU';
|
||||||
|
exports.version = '1.0.0';
|
||||||
|
exports.author = 'Peter Širka';
|
||||||
|
exports.group = 'Monitoring';
|
||||||
|
exports.color = '#F6BB42';
|
||||||
|
exports.output = 1;
|
||||||
|
exports.icon = 'microchip';
|
||||||
|
exports.options = { enabled: true };
|
||||||
|
exports.click = true;
|
||||||
|
exports.readme = `# CPU monitoring
|
||||||
|
|
||||||
|
This component monitors CPU \`% percentage\` consumption in Linux systems. It uses \`mpstat\` command.
|
||||||
|
|
||||||
|
__Data Example__:
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
{
|
||||||
|
cpu: 30, // percentage
|
||||||
|
cores: [4, 60, 0], // percentage
|
||||||
|
count: 3 // count of cores
|
||||||
|
}
|
||||||
|
\`\`\``;
|
||||||
|
|
||||||
|
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>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
exports.install = function(instance) {
|
||||||
|
|
||||||
|
var fields = ['CPU', '%idle'];
|
||||||
|
var current = { cores: [], cpu: 0, count: 0 };
|
||||||
|
var proc = null;
|
||||||
|
var tproc = null;
|
||||||
|
|
||||||
|
instance.custom.kill = function() {
|
||||||
|
if (proc) {
|
||||||
|
proc.kill('SIGKILL');
|
||||||
|
proc = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.custom.run = function() {
|
||||||
|
|
||||||
|
if (tproc) {
|
||||||
|
clearTimeout(tproc);
|
||||||
|
tproc = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.custom.kill();
|
||||||
|
proc = require('child_process').spawn('mpstat', ['-P', 'ALL', 10]);
|
||||||
|
proc.stdout.on('data', U.streamer('\n\n', instance.custom.process));
|
||||||
|
proc.stdout.on('error', function(e) {
|
||||||
|
instance.error(e);
|
||||||
|
instance.custom.kill();
|
||||||
|
tproc = setTimeout(instance.custom.run, instance.options.interval || 5000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.custom.process = function(chunk) {
|
||||||
|
current.cpu = 0;
|
||||||
|
chunk.toString('utf8').parseTerminal(fields, instance.custom.parse);
|
||||||
|
current.count = current.cores.length;
|
||||||
|
if (current.count) {
|
||||||
|
instance.send2(current);
|
||||||
|
instance.custom.status();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.custom.parse = function(values) {
|
||||||
|
var val = 100 - values[1].parseFloat2();
|
||||||
|
if (values[0] === 'all')
|
||||||
|
current.cpu = val;
|
||||||
|
else
|
||||||
|
current.cores[+values[0]] = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.custom.status = function() {
|
||||||
|
if (instance.options.enabled)
|
||||||
|
instance.status(current.cpu.floor(1) + '%');
|
||||||
|
else
|
||||||
|
instance.status('Disabled', 'red');
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.on('click', function() {
|
||||||
|
instance.options.enabled = !instance.options.enabled;
|
||||||
|
instance.custom.status();
|
||||||
|
if (instance.options.enabled)
|
||||||
|
instance.custom.run();
|
||||||
|
else
|
||||||
|
instance.custom.kill();
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.on('close', function() {
|
||||||
|
instance.custom.kill();
|
||||||
|
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();
|
||||||
|
};
|
||||||
171
flow/mqtt_listener.js
Normal file
171
flow/mqtt_listener.js
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
exports.id = 'mqttlistener';
|
||||||
|
exports.title = 'MQTT listener';
|
||||||
|
exports.group = 'MQTT';
|
||||||
|
exports.color = '#888600';
|
||||||
|
exports.version = '1.0.0';
|
||||||
|
exports.icon = 'sign-out';
|
||||||
|
exports.input = 1;
|
||||||
|
exports.output = ["red", "white"];
|
||||||
|
exports.author = 'Rastislav Kovac';
|
||||||
|
exports.options = { host: "", port: 1883, clientid: "", username: "" };
|
||||||
|
//exports.npm = ['mqtt', 'streamroller'];
|
||||||
|
|
||||||
|
|
||||||
|
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</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 = `
|
||||||
|
# MQTT processor
|
||||||
|
|
||||||
|
Version 1.0.0
|
||||||
|
|
||||||
|
It serves as a client and subscribes to 'rpc' topic. It receives rpc calls from kovobel-prod01, which receives it from platform.
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const instanceSendTo = {
|
||||||
|
debug: 0,
|
||||||
|
rpcCall: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exports.install = function(instance) {
|
||||||
|
|
||||||
|
|
||||||
|
const mqtt = require("mqtt");
|
||||||
|
|
||||||
|
instance.on('options', loadNodes);
|
||||||
|
|
||||||
|
|
||||||
|
function loadNodes()
|
||||||
|
{
|
||||||
|
|
||||||
|
if(instance.options.host == "")
|
||||||
|
{
|
||||||
|
instance.status('No configuration', 'red');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var o = instance.options;
|
||||||
|
opts = {
|
||||||
|
host: o.host,
|
||||||
|
port: o.port,
|
||||||
|
clientId: o.clientid,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
resubscribe: true
|
||||||
|
};
|
||||||
|
|
||||||
|
connectToServer();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToServer()
|
||||||
|
{
|
||||||
|
var url = "mqtt://" + opts.host + ":" + opts.port;
|
||||||
|
console.log("MQTT URL: ", url);
|
||||||
|
|
||||||
|
client = mqtt.connect(url, opts);
|
||||||
|
|
||||||
|
client.on('connect', function() {
|
||||||
|
instance.status("Connected", "green");
|
||||||
|
client.subscribe('rpc', function (err) {
|
||||||
|
if (!err) {
|
||||||
|
client.publish('rpc', 'Hello mqtt');
|
||||||
|
console.log('message published');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('reconnect', function() {
|
||||||
|
instance.status("Reconnecting", "yellow");
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('message', function(topic, message) {
|
||||||
|
// message is type of buffer
|
||||||
|
message = message.toString();
|
||||||
|
|
||||||
|
// instance.send(1, message);
|
||||||
|
// return;
|
||||||
|
|
||||||
|
if (message[0] === '{') {
|
||||||
|
|
||||||
|
try {
|
||||||
|
message = JSON.parse(message);
|
||||||
|
|
||||||
|
if (Object.keys(message).length < 2 || !Object.keys(message).includes("message")) return;
|
||||||
|
|
||||||
|
message.message = JSON.parse(message.message);
|
||||||
|
instance.send(instanceSendTo.rpcCall, message);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
instance.debug(`MQTT: Error parsing data, ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('close', function(err) {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('error', function(err) {
|
||||||
|
instance.status("Err: "+ err.code, "red");
|
||||||
|
instance.send(instanceSendTo.debug, {"message":"Broker ERROR signal received !", "error":err, "opt":opts });
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//set opts accortding to options
|
||||||
|
instance.reconfigure = function() {
|
||||||
|
|
||||||
|
var o = instance.options;
|
||||||
|
opts = {
|
||||||
|
host: o.host,
|
||||||
|
port: o.port,
|
||||||
|
clientId: o.clientid,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
resubscribe: true
|
||||||
|
};
|
||||||
|
|
||||||
|
connectToServer();
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.close = function(done) {
|
||||||
|
client.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
loadNodes();
|
||||||
|
|
||||||
|
instance.on('options', instance.reconfigure);
|
||||||
|
instance.reconfigure();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
191
flow/nosql.js
Normal file
191
flow/nosql.js
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
exports.id = 'nosql';
|
||||||
|
exports.title = 'NoSQL';
|
||||||
|
exports.version = '1.2.1';
|
||||||
|
exports.group = 'Databases';
|
||||||
|
exports.author = 'Martin Smola';
|
||||||
|
exports.color = '#D770AD';
|
||||||
|
exports.icon = 'database';
|
||||||
|
exports.input = true;
|
||||||
|
exports.output = 2;
|
||||||
|
exports.options = {};
|
||||||
|
exports.readme = `# NoSQL embedded
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
First output is response from nosql engine and second is the data passed in.
|
||||||
|
|
||||||
|
## Collection
|
||||||
|
|
||||||
|
if the collection field is left empty, then we try to look at \`flowdata.get('collection')\`, to set this value you need to use \`flowdata.set('collection', '<collection-name>')\` in previous component (currently only \`function\` can be used)
|
||||||
|
|
||||||
|
## Insert
|
||||||
|
|
||||||
|
- will insert recieved data
|
||||||
|
- expects data to be an Object
|
||||||
|
- returns error, success, id
|
||||||
|
|
||||||
|
## Read
|
||||||
|
|
||||||
|
- will read a document by id
|
||||||
|
- expects data to be an Object with an \`id\` property
|
||||||
|
- returns error, response
|
||||||
|
|
||||||
|
## Update
|
||||||
|
|
||||||
|
- will update document by id
|
||||||
|
- expects data to be an Object with \`id\` property and all the props to be updated
|
||||||
|
- returns error, response
|
||||||
|
- if response is 0 then update failed
|
||||||
|
|
||||||
|
## Remove
|
||||||
|
|
||||||
|
- will remove document by id
|
||||||
|
- expects data to be an Object with an \`id\` property
|
||||||
|
- returns error, response
|
||||||
|
- if response is 0 then remove failed
|
||||||
|
|
||||||
|
## Query
|
||||||
|
|
||||||
|
- will query DB
|
||||||
|
- expects data to be an Array as shown bellow
|
||||||
|
- returns error, response
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
[
|
||||||
|
['where', 'sensor', 'temp'], // builder.where('sensor', 'temp');
|
||||||
|
['limit', 2] // builder.limit(2);
|
||||||
|
]
|
||||||
|
\`\`\``;
|
||||||
|
|
||||||
|
exports.html = `
|
||||||
|
<div class="padding">
|
||||||
|
<div data-jc="textbox" data-jc-path="collection" class="m mt10">DB collection name</div>
|
||||||
|
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:insert,update,read,query,remove" class="m">@(Method)</div>
|
||||||
|
<div data-jc="visible" data-jc-path="method" data-jc-config="if:value === 'insert'">
|
||||||
|
<div data-jc="checkbox" data-jc-path="addid">Add unique ID to data before insert</div>
|
||||||
|
</div>
|
||||||
|
<div data-jc="visible" data-jc-path="method" data-jc-config="if:value === 'update'">
|
||||||
|
<div data-jc="checkbox" data-jc-path="upsert">Insert document if it doesn't exist</div>
|
||||||
|
<div data-jc="checkbox" data-jc-path="upsertid">Add unique ID to data before insert (only if it doesn't exist)</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
exports.install = function(instance) {
|
||||||
|
|
||||||
|
instance.on('data', function(flowdata, next) {
|
||||||
|
|
||||||
|
instance.send2(1, flowdata.clone());
|
||||||
|
|
||||||
|
var options = instance.options;
|
||||||
|
var collection = options.collection || flowdata.get('collection');
|
||||||
|
if (!collection) {
|
||||||
|
flowdata.data = { err: '[DB] No collection specified' };
|
||||||
|
next(0, flowdata);
|
||||||
|
instance.error('[DB] No collection specified');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nosql = NOSQL(collection);
|
||||||
|
var builder;
|
||||||
|
|
||||||
|
if (options.method === 'read') {
|
||||||
|
|
||||||
|
if (!flowdata.data.id) {
|
||||||
|
flowdata.data = { err: '[DB] Cannot get record by id: `undefined`' };
|
||||||
|
next(0, flowdata);
|
||||||
|
instance.error('[DB] Cannot get record by id: `undefined`');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = nosql.find();
|
||||||
|
builder.where('id', flowdata.data.id);
|
||||||
|
builder.first();
|
||||||
|
builder.callback(function(err, response) {
|
||||||
|
if (err) {
|
||||||
|
instance.throw(err);
|
||||||
|
} else {
|
||||||
|
flowdata.data = { response: response };
|
||||||
|
next(0, flowdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (options.method === 'insert') {
|
||||||
|
|
||||||
|
options.addid && (flowdata.data.id = UID());
|
||||||
|
nosql.insert(flowdata.data).callback(function(err) {
|
||||||
|
if (err)
|
||||||
|
instance.throw(err);
|
||||||
|
else {
|
||||||
|
flowdata.data = { success: err ? false : true, id: flowdata.data.id };
|
||||||
|
next(0, flowdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (options.method === 'query') {
|
||||||
|
|
||||||
|
var query = flowdata.data;
|
||||||
|
builder = nosql.find();
|
||||||
|
|
||||||
|
query && query instanceof Array && query.forEach(function(q) {
|
||||||
|
if (q instanceof Array) {
|
||||||
|
var m = q[0];
|
||||||
|
var args = q.splice(1);
|
||||||
|
builder[m] && (builder[m].apply(builder, args));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.callback(function(err, response) {
|
||||||
|
if (err) {
|
||||||
|
instance.throw(err);
|
||||||
|
} else {
|
||||||
|
flowdata.data = { response: response || [] };
|
||||||
|
next(0, flowdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (options.method === 'update') {
|
||||||
|
|
||||||
|
if (!options.upsert && !flowdata.data.id) {
|
||||||
|
flowdata.data = { err: '[DB] Cannot update record by id: `undefined`' };
|
||||||
|
next(0, flowdata);
|
||||||
|
instance.error('[DB] Cannot update record by id: `undefined`');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.upsert && (options.upsertid && !flowdata.data.id)) {
|
||||||
|
flowdata.data.id = UID();
|
||||||
|
builder = nosql.modify(flowdata.data, options.upsert);
|
||||||
|
builder.where('id', flowdata.data.id);
|
||||||
|
builder.callback(function(err, count) {
|
||||||
|
if (err)
|
||||||
|
instance.throw(err);
|
||||||
|
else {
|
||||||
|
flowdata.data = { response: count || 0 };
|
||||||
|
next(0, flowdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (options.method === 'remove') {
|
||||||
|
|
||||||
|
if (!flowdata.data.id) {
|
||||||
|
flowdata.data = { err: '[DB] Cannot remove record by id: `undefined`' };
|
||||||
|
next(0, flowdata);
|
||||||
|
instance.error('[DB] Cannot remove record by id: `undefined`');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = nosql.remove();
|
||||||
|
builder.where('id', flowdata.data.id);
|
||||||
|
builder.callback(function(err, count) {
|
||||||
|
if (err)
|
||||||
|
instance.throw(err);
|
||||||
|
else {
|
||||||
|
flowdata.data = { response: count || 0 };
|
||||||
|
next(0, flowdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
31
flow/notifikacie.csv
Normal file
31
flow/notifikacie.csv
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
key;weight;en;sk
|
||||||
|
switching_profile_point_applied_to_line;INFO;Switching profile point applied to line no. ${line} : ${value};Aplikovaný bod spínacieho profilu na línií č. ${line} : ${value}
|
||||||
|
dusk_has_occured;INFO;Dusk has occured;Nastal súmrak
|
||||||
|
dawn_has_occured;INFO;Dawn has occured;Nastal úsvit
|
||||||
|
dimming_profile_was_successfully_received_by_node;NOTICE;Dimming profile was successfully received by node no. ${node};Stmievací profil bol úspešne prijatý nodom č. ${node}
|
||||||
|
master_node_is_responding_again;NOTICE;Master node is responding again;Master node začal znovu odpovedať
|
||||||
|
command_was_sent_from_terminal_interface;DEBUG;A command was sent from terminal interface;Z terminálu bol odoslaný príkaz
|
||||||
|
master_node_is_not_responding;ALERT;Master node is not responding;Master node neodpovedá
|
||||||
|
configuration_of_dimming_profile_to_node_failed;ALERT;Configuration of dimming profile to node no. ${node} has failed;Konfigurácia stmievacieho profilu pre node č. ${node} zlyhala
|
||||||
|
circuit_breaker_was_turned_on_line;NOTICE;Circuit breaker was turned on - line no. ${line};Zapnutie ističa na línii č. ${line}
|
||||||
|
circuit_breaker_was_turned_off_line;ERROR;Circuit breaker was turned off - line no. ${line};Vypnutie ističa na línií č. ${line}
|
||||||
|
dimming_profile_was_processed_for_node;INFO;Dimming profile was processed for node no. ${node};Stmievací profil bol spracovaný pre node č. ${node}
|
||||||
|
switching_profile_was_processed_for_line;INFO;Switching profile was processed for line no. ${line};Spínací profil bol spracovaný pre líniu č. ${line}
|
||||||
|
thermometer_is_not_responding;WARNING;Thermometer is not responding;Teplomer neodpovedá
|
||||||
|
thermometer_is_responding_again;NOTICE;Thermometer is responding again;Teplomer znovu odpovedá
|
||||||
|
thermometer_sends_invalid_data;WARNING;Thermometer sends invalid data;Teplomer posiela neplatné hodnoty
|
||||||
|
main_switch_has_been_turned_off;CRITICAL;Main switch has been turned off;Hlavný vypínač bol vypnutý
|
||||||
|
main_switch_has_been_turned_on;NOTICE;Main switch has been turned on;Hlavný vypínač bol zapnutý
|
||||||
|
power_supply_has_disconnected_input;ALERT;Power supply has disconnected input;Napájací zdroj nemá napätie na vstupe
|
||||||
|
power_supply_works_correctly;NOTICE;Power supply works correctly ;Napájací zdroj pracuje správne
|
||||||
|
battery_level_is_low;ERROR;Battery level is low;Batéria má nízku úroveň napätia
|
||||||
|
battery_level_is_ok;NOTICE;Battery level is OK;Batéria má správnu úroveň napätia
|
||||||
|
door_has_been_open;NOTICE;Door has been open;Dvere boli otvorené
|
||||||
|
door_has_been_closed;NOTICE;Door has been closed;Dvere boli zatvorené
|
||||||
|
door_has_been_open_without_permision_alarm_is_on;WARNING;Door has been open without permision - alarm is on;Dvere boli otvorené bez povolania - zapnutá siréna
|
||||||
|
state_of_contactor_for_line;INFO;State of contactor for line no. ${line} is ${value};Stav stýkača pre líniu č. ${line} je ${value}
|
||||||
|
local_database_is_corrupted;CRITICAL;;
|
||||||
|
electrometer_is_not_responding;ERROR;Electrometer is not responding;Elektromer neodpovedá
|
||||||
|
no_voltage_detected_on_phase;CRITICAL;No voltage detected on phase no. ${phase};Na fáze č. ${phase} nie je napätie
|
||||||
|
electrometer_is_responding_again;NOTICE;Electrometer is responding again;Elektromer znovu odpovedá
|
||||||
|
voltaga_on_phase_has_been_restored;NOTICE;Voltaga on phase no. ${phase} has been restored;Napätie na fáze č. ${phase} bolo obnovené
|
||||||
|
357
flow/relays.js
Normal file
357
flow/relays.js
Normal file
|
|
@ -0,0 +1,357 @@
|
||||||
|
exports.id = 'relay';
|
||||||
|
exports.title = 'DI_DO_Controller';
|
||||||
|
exports.version = '1.0.0';
|
||||||
|
exports.group = 'Worksys';
|
||||||
|
exports.color = '#2134B0';
|
||||||
|
exports.input = 1;
|
||||||
|
exports.output = ["red", "white", "yellow"];
|
||||||
|
exports.click = false;
|
||||||
|
exports.author = 'Daniel Segeš';
|
||||||
|
exports.icon = 'bolt';
|
||||||
|
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">Edge TB Name</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
exports.readme = `# Sets RS232 port and all digital pins on device. Then it starts to receive data from sensors.
|
||||||
|
It receives:
|
||||||
|
|
||||||
|
rotary_switch_state,
|
||||||
|
rotary_switch_state,
|
||||||
|
door_condition,
|
||||||
|
state_of_breaker,
|
||||||
|
state_of_contactor,
|
||||||
|
twilight_sensor
|
||||||
|
`;
|
||||||
|
|
||||||
|
/*
|
||||||
|
we open rsPort "/dev/ttymxc0" and set digital input and output pins with "setRSPortData"
|
||||||
|
Currently we are interested in pins no. 1,2,3,6,8,9,10,16
|
||||||
|
pins number 11, 12, 13 (we receive 10,11,12 in rsPortReceivedData) are "stykace"
|
||||||
|
When port receives data, it must be exactly 4 bytes long. Second byte is pin, that changed its value, fourth byte is value itself.
|
||||||
|
After that, we set this value to "previousValues[allPins[whichpin]]" variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
RVO objekt:
|
||||||
|
state_of_main_switch - sem sa bude reportovať stav hlavného ističa : 0-> off 1-> on (toto nie je na platforme, ale Rado to už do entity type doplnil)
|
||||||
|
rotary_switch_state - sem by sa mal reportovať stav vstupov manual a auto podľa nasledovnej logiky:
|
||||||
|
Manual = 1 a Auto = 0 -> vyreportuje Manual
|
||||||
|
Manual = 0 a Auto = 0 -> vyreportuje Off
|
||||||
|
Manual = 0 a Auto = 1 -> vyreportuje Automatic
|
||||||
|
|
||||||
|
door_condition - tuto ide pin 6, dverový kontakt -> 1 -> vyreportuje Closed, 0 -> vyreportuje Open
|
||||||
|
twilight_sensor - hodnotu, ktorú vracia ten analógový vstup (17) treba poslať sem ako float number. Zrejme tu potom pridáme nejaký koeficient prevodu na luxy
|
||||||
|
|
||||||
|
zjavne nám v jsone chýba stav hlavného ističa. Musíme to potom doplniť
|
||||||
|
|
||||||
|
Na každú líniu:
|
||||||
|
state_of_breaker - podľa indexu ističa sa reportuje jeho stav, teda istič 1 na líniu 1: 0-> off 1-> on
|
||||||
|
state_of_contactor - podľa indexu stykača sa reportuje jeho stav, teda stykač 1 na líniu 1: 0-> off 1-> on
|
||||||
|
momentálne sa stav zmení len keď vo flow klikneš aby sa zmenil, ale tá zmena by sa mala ukázať aj na platforme
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.install = function(instance) {
|
||||||
|
|
||||||
|
let previousValues = {};
|
||||||
|
let rsPortReceivedData = [];
|
||||||
|
|
||||||
|
console.log("DI_DO_Relay_Controller installed");
|
||||||
|
|
||||||
|
//key is PIN number
|
||||||
|
const conversionTable = {
|
||||||
|
"1": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "state_of_main_switch"}, //state_of_main_switch pin1
|
||||||
|
"2": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "rotary_switch_state"}, //rotary_switch_state - poloha manual = pin2
|
||||||
|
"3": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "rotary_switch_state"}, //rotary_switch_state - poloha auto = pin3
|
||||||
|
"6": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "door_condition"}, // door_condition = pin6, 1 -> vyreportuje Closed, 0 -> vyreportuje Open
|
||||||
|
"8": {tbname: "RMgnK93rkoAazbqdQ4yBG95Z1YXGx6pmwBeVEP2O", type: "state_of_breaker", "line": 1}, // state_of_breaker linia 1 0=off, 1=on
|
||||||
|
"9": {tbname: "dlE1VQjYrNx9gZRmb38gG08oLBO4qaAk2M6JPnG7", type: "state_of_breaker", "line": 2}, // state_of_breaker linia 2 0=off, 1=on
|
||||||
|
"10": {tbname: "vnmG4kJxaXWNBgMQq0D7Aj5e9oZzOAlr6LdR3w2V", type: "state_of_breaker", "line": 3}, // state_of_breaker linia 3 0=off, 1=on
|
||||||
|
"11": {tbname: "RMgnK93rkoAazbqdQ4yBG95Z1YXGx6pmwBeVEP2O", type: "state_of_contactor", "line": 1}, // state_of_contactor linia 1 0=off, 1=on
|
||||||
|
"12": {tbname: "dlE1VQjYrNx9gZRmb38gG08oLBO4qaAk2M6JPnG7", type: "state_of_contactor", "line": 2}, // state_of_contactor linia 2 0=off, 1=on
|
||||||
|
"13": {tbname: "vnmG4kJxaXWNBgMQq0D7Aj5e9oZzOAlr6LdR3w2V", type: "state_of_contactor", "line": 3}, // state_of_contactor linia 3 0=off, 1=on
|
||||||
|
"16": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "state_of_main_switch"}, // twilight_sensor = pin16
|
||||||
|
};
|
||||||
|
|
||||||
|
const dbRelays = TABLE("relays");
|
||||||
|
dbRelays.on('change', function(doc, old) {
|
||||||
|
instance.send(2, "reload_relays");
|
||||||
|
});
|
||||||
|
|
||||||
|
//modify
|
||||||
|
|
||||||
|
const SerialPort = require('serialport');
|
||||||
|
//const { exec } = require('child_process');
|
||||||
|
const { openPort, runSyncExec, writeData } = require('./serialport_helper.js');
|
||||||
|
|
||||||
|
const setRSPortData = [0xAA,6,6,6,6,0,6,0,6,6,6,1,1,1,1,0,0,10,10,10,10,0,10,0,10,10,10,0,0,0,0,0,0,5,0,0,0,15,15,15,15,0,15,0,15,15,15,0,0,0,0,0,0,30,0,0,0];
|
||||||
|
const rsPort = new SerialPort("/dev/ttymxc0", { autoOpen: false });
|
||||||
|
|
||||||
|
rsPort.on('open', async function() {
|
||||||
|
|
||||||
|
await runSyncExec("stty -F /dev/ttymxc0 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke").then(function (status) {
|
||||||
|
instance.send(0, exports.title + " runSyncExec - Promise Resolved:" + status);
|
||||||
|
|
||||||
|
//set port
|
||||||
|
rsPort.write(Buffer.from(setRSPortData), function(err) {
|
||||||
|
instance.send(0, exports.title + " Digital in_out has been set");
|
||||||
|
|
||||||
|
//force turn off relays
|
||||||
|
let keys = Object.keys(conversionTable);
|
||||||
|
for(let i = 0; i < keys.length; i++)
|
||||||
|
{
|
||||||
|
let key = keys[i];
|
||||||
|
|
||||||
|
if(conversionTable[key].type == "state_of_contactor")
|
||||||
|
{
|
||||||
|
let pin = key - 1;
|
||||||
|
let line = conversionTable[key].line;
|
||||||
|
|
||||||
|
turnOff(line, pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//dbRelays.modify({ contactor: 0 });
|
||||||
|
})
|
||||||
|
|
||||||
|
}).catch(function (reason) {
|
||||||
|
instance.send(0, exports.title + " runSyncExec - promise rejected:" + reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
rsPort.open();
|
||||||
|
|
||||||
|
rsPort.on('data', function (data){
|
||||||
|
|
||||||
|
rsPortReceivedData = [...rsPortReceivedData, ...data];
|
||||||
|
|
||||||
|
if (rsPortReceivedData[0] != 85) {
|
||||||
|
rsPortReceivedData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let l = rsPortReceivedData.length;
|
||||||
|
|
||||||
|
if (l < 4 ) return;
|
||||||
|
|
||||||
|
if (l > 4 ) {
|
||||||
|
|
||||||
|
// if array length is greater than 4, we take first 4 byte and do the logic, second 4 bytes, do the logic and so on
|
||||||
|
let i, j, temparray, chunk = 4;
|
||||||
|
for ( i = 0, j = l; i < j; i += chunk ) {
|
||||||
|
temparray = rsPortReceivedData.slice(i, i + chunk);
|
||||||
|
|
||||||
|
if ( temparray.length < 4 ){
|
||||||
|
rsPortReceivedData = [...temparray];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchLogic(temparray);
|
||||||
|
}
|
||||||
|
|
||||||
|
rsPortReceivedData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchLogic(rsPortReceivedData);
|
||||||
|
|
||||||
|
rsPortReceivedData = [];
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
rsPort.on('error', function(err) {
|
||||||
|
instance.send(0, err.message);
|
||||||
|
})
|
||||||
|
|
||||||
|
rsPort.on("close", () => {
|
||||||
|
rsPort.close();
|
||||||
|
})
|
||||||
|
|
||||||
|
instance.on("close", () => {
|
||||||
|
rsPort.close();
|
||||||
|
})
|
||||||
|
|
||||||
|
function getPin(line)
|
||||||
|
{
|
||||||
|
//conversionTable
|
||||||
|
let keys = Object.keys(conversionTable);
|
||||||
|
for(let i = 0; i < keys.length; i++)
|
||||||
|
{
|
||||||
|
let key = keys[i];
|
||||||
|
|
||||||
|
if(conversionTable[key].type == "state_of_contactor" && conversionTable[key].line == line)
|
||||||
|
{
|
||||||
|
return key - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("no pin detected");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function turnOn(line, pin)
|
||||||
|
{
|
||||||
|
if( pin === undefined) pin = getPin(line);
|
||||||
|
if( pin === undefined) return;
|
||||||
|
|
||||||
|
let arr = [0x55];
|
||||||
|
arr.push( pin );
|
||||||
|
arr.push( 0 );
|
||||||
|
arr.push( 1 );
|
||||||
|
|
||||||
|
if(!rsPort.isOpen)
|
||||||
|
{
|
||||||
|
console.log("port is not opened");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rsPort.write(Buffer.from(arr), function(err) {
|
||||||
|
switchLogic(arr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function turnOff(line, pin)
|
||||||
|
{
|
||||||
|
if( pin === undefined) pin = getPin(line);
|
||||||
|
if( pin === undefined) return;
|
||||||
|
|
||||||
|
let arr = [0x55];
|
||||||
|
arr.push( pin );
|
||||||
|
arr.push( 0 );
|
||||||
|
arr.push( 0 );
|
||||||
|
|
||||||
|
if(!rsPort.isOpen)
|
||||||
|
{
|
||||||
|
console.log("port is not opened");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rsPort.write(Buffer.from(arr), function(err) {
|
||||||
|
switchLogic(arr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// we expect array as flowdata.data
|
||||||
|
instance.on("data", (flowdata) => {
|
||||||
|
|
||||||
|
//console.log(flowdata.data);
|
||||||
|
|
||||||
|
if(flowdata.data instanceof Object)
|
||||||
|
{
|
||||||
|
let obj = flowdata.data;
|
||||||
|
|
||||||
|
let line = obj.line;
|
||||||
|
|
||||||
|
if(obj.command == "turnOn") turnOn(line);
|
||||||
|
else if(obj.command == "turnOff") turnOff(line);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(flowdata.data)){
|
||||||
|
rsPort.write(Buffer.from(flowdata.data), function(err) {
|
||||||
|
switchLogic(flowdata.data);
|
||||||
|
|
||||||
|
instance.send(0,{"WRITE":flowdata.data});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const switchLogic = (rsPortReceivedData) => {
|
||||||
|
|
||||||
|
let dataToTb;
|
||||||
|
let values = {};
|
||||||
|
|
||||||
|
let pinIndex = rsPortReceivedData[1] + 1;
|
||||||
|
if (pinIndex === 17) pinIndex--;
|
||||||
|
|
||||||
|
let newPinValue = rsPortReceivedData[3];
|
||||||
|
|
||||||
|
let obj = conversionTable[pinIndex];
|
||||||
|
|
||||||
|
if(obj == undefined)
|
||||||
|
{
|
||||||
|
//console.log("undefined pinIndex", pinIndex, rsPortReceivedData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = obj.type;
|
||||||
|
let line = obj.line;
|
||||||
|
let tbname = obj.tbname;
|
||||||
|
|
||||||
|
//default value
|
||||||
|
let value = "On";
|
||||||
|
if(newPinValue === 0) value = "Off";
|
||||||
|
|
||||||
|
if(type == "rotary_switch_state")
|
||||||
|
{
|
||||||
|
// combination of these two pins required to get result
|
||||||
|
let pin2, pin3;
|
||||||
|
if(pinIndex == 2)
|
||||||
|
{
|
||||||
|
pin2 = newPinValue;
|
||||||
|
pin3 = previousValues[pinIndex];
|
||||||
|
if (pin3 == undefined) pin3 = 0;
|
||||||
|
}
|
||||||
|
else if(pinIndex == 3)
|
||||||
|
{
|
||||||
|
pin3 = newPinValue;
|
||||||
|
pin2 = previousValues[1];
|
||||||
|
if (pin2 == undefined) pin2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin2 == 1 && pin3 == 0) value = "Manual";
|
||||||
|
if (pin2 == 0 && pin3 == 0) value = "Off";
|
||||||
|
if (pin2 == 0 && pin3 == 1) value = "Automatic";
|
||||||
|
}
|
||||||
|
else if(type == "door_condition")
|
||||||
|
{
|
||||||
|
newPinValue === 0 ? value = "Open" : value = "Closed";
|
||||||
|
}
|
||||||
|
else if(type == "twilight_sensor")
|
||||||
|
{
|
||||||
|
value = parseFloat(newPinValue + (256*rsPortReceivedData[2]));
|
||||||
|
}
|
||||||
|
else if(type == "state_of_contactor")
|
||||||
|
{
|
||||||
|
//modify table relays
|
||||||
|
dbRelays.modify({ contactor: newPinValue }).where("line", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
values[obj.type] = value;
|
||||||
|
|
||||||
|
if(conversionTable.hasOwnProperty(pinIndex))
|
||||||
|
{
|
||||||
|
let insertIntoTb = false;
|
||||||
|
if(newPinValue != previousValues[pinIndex]) insertIntoTb = true;
|
||||||
|
if(obj.hasOwnProperty("state_of_contactor")) insertIntoTb = true;
|
||||||
|
|
||||||
|
if(insertIntoTb)
|
||||||
|
{
|
||||||
|
dataToTb = {
|
||||||
|
[tbname]: [
|
||||||
|
{
|
||||||
|
"ts": Date.now(),
|
||||||
|
"values": values
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.send(1, dataToTb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//pin was changed
|
||||||
|
previousValues[pinIndex] = newPinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
120
flow/test.js
Normal file
120
flow/test.js
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
const getTimezoneOffset = (timeZone, date = new Date()) => {
|
||||||
|
const tz = date.toLocaleString("en", {timeZone, timeStyle: "long"}).split(" ").slice(-1)[0];
|
||||||
|
const dateString = date.toString();
|
||||||
|
const offset = Date.parse(`${dateString} UTC`) - Date.parse(`${dateString} ${tz}`);
|
||||||
|
|
||||||
|
// return UTC offset in millis
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
let profile = {
|
||||||
|
"time_points": [
|
||||||
|
{
|
||||||
|
"start_time": "13:00",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_time": "16:00",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"astro_clock": true,
|
||||||
|
"dusk_astro_clock_offset": 0,
|
||||||
|
"dawn_astro_clock_offset": 0,
|
||||||
|
"dusk_lux_sensor": false,
|
||||||
|
"dawn_lux_sensor": false,
|
||||||
|
"dusk_lux_sensor_value": 5,
|
||||||
|
"dawn_lux_sensor_value": 5,
|
||||||
|
"dusk_lux_sensor_time_window": 30,
|
||||||
|
"dawn_lux_sensor_time_window": 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = new Date(); // Creates a Date Object using the clients current time
|
||||||
|
console.log(now, now.getTime());
|
||||||
|
|
||||||
|
//let [hours, minutes, seconds] = "18:19:02".split(':');
|
||||||
|
//d.setHours(+hours); // Set the hours, using implicit type coercion
|
||||||
|
//d.setMinutes(minutes); // You can pass Number or String. It doesn't really matter
|
||||||
|
//d.setSeconds(seconds);
|
||||||
|
|
||||||
|
let time_points = profile.time_points;
|
||||||
|
time_points.push( {"start_time": "1:00", "value": 1} );
|
||||||
|
|
||||||
|
time_points.sort(function (a, b) {
|
||||||
|
|
||||||
|
let [ahours, aminutes, aseconds] = a.start_time.split(':');
|
||||||
|
let [bhours, bminutes, bseconds] = b.start_time.split(':');
|
||||||
|
|
||||||
|
let ad = new Date();
|
||||||
|
ad.setHours( parseInt(ahours) );
|
||||||
|
ad.setMinutes( parseInt(aminutes) );
|
||||||
|
ad.setSeconds(0);
|
||||||
|
|
||||||
|
let bd = new Date();
|
||||||
|
bd.setHours( parseInt(bhours) );
|
||||||
|
bd.setMinutes( parseInt(bminutes) );
|
||||||
|
ad.setSeconds(0);
|
||||||
|
|
||||||
|
return ad.getTime() - bd.getTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(time_points);
|
||||||
|
|
||||||
|
let value = time_points[ time_points.length - 1].value;
|
||||||
|
|
||||||
|
for(let i = 0; i < time_points.length; i++)
|
||||||
|
{
|
||||||
|
let [hours, minutes, seconds] = time_points[i].start_time.split(':');
|
||||||
|
|
||||||
|
let start_time = new Date();
|
||||||
|
start_time.setHours( parseInt(hours) );
|
||||||
|
start_time.setMinutes( parseInt(minutes) );
|
||||||
|
start_time.setSeconds(0);
|
||||||
|
|
||||||
|
if(now.getTime() > start_time.getTime())
|
||||||
|
{
|
||||||
|
value = time_points[i].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(start_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(value);
|
||||||
|
|
||||||
|
const offset = getTimezoneOffset("Europe/Bratislava");
|
||||||
|
console.log(offset);
|
||||||
|
|
||||||
|
|
||||||
|
function removeTask(obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
let keys = Object.keys(obj);
|
||||||
|
tasks = tasks.filter((task) => {
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
for(let i = 0; i < keys.length; i++)
|
||||||
|
{
|
||||||
|
let key = keys[i];
|
||||||
|
if(task.hasOwnProperty(key) && obj.hasOwnProperty(key))
|
||||||
|
{
|
||||||
|
if(task[key] == obj[key]) counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(counter == keys.length) return false;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertUTCDateToLocalDate(date) {
|
||||||
|
var newDate = new Date(date);
|
||||||
|
newDate.setMinutes(date.getMinutes() - date.getTimezoneOffset());
|
||||||
|
return newDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
let d = convertUTCDateToLocalDate(new Date("2022-04-26T06:56:54.000Z"));
|
||||||
|
|
||||||
|
console.log(d, d.getHours());
|
||||||
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.0';
|
||||||
|
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 = 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(options){
|
||||||
|
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();
|
||||||
|
};
|
||||||
551
flow/wsmqttpublish.js
Normal file
551
flow/wsmqttpublish.js
Normal file
|
|
@ -0,0 +1,551 @@
|
||||||
|
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 = false;//== saveTelemetryOnError - create backup broker send failure
|
||||||
|
let restore_from_backup = 0; //how many rows process at once?
|
||||||
|
let restore_backup_wait = 0;//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(function() {
|
||||||
|
|
||||||
|
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}});
|
||||||
|
}
|
||||||
|
|
||||||
|
}, () => instance.debug('MQTT: Error parsing data', message));
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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
27
package.json
Normal file
27
package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "emptyproject",
|
||||||
|
"description": "Total.js empty project.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "debug.js",
|
||||||
|
"dependencies": {
|
||||||
|
"easy-crc": "0.0.2",
|
||||||
|
"mqtt": "^4.2.6",
|
||||||
|
"serialport": "^9.0.0",
|
||||||
|
"total.js": "^3.4.5",
|
||||||
|
"bitwise": "^2.1.0",
|
||||||
|
"log4js": "^6.3.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/totaljs/emptyproject-flow"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"empty",
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"author": "Peter Širka",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
15
readme.md
Normal file
15
readme.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[](https://www.totaljs.com/support/)
|
||||||
|
|
||||||
|
- [__Live chat with professional support__](https://messenger.totaljs.com)
|
||||||
|
- [__HelpDesk with professional support__](https://helpdesk.totaljs.com)
|
||||||
|
- [Documentation](https://docs.totaljs.com)
|
||||||
|
- [Wiki](https://wiki.totaljs.com)
|
||||||
|
|
||||||
|
# Total.js: Empty Project - Flow
|
||||||
|
|
||||||
|
- download example
|
||||||
|
- open terminal / command-line
|
||||||
|
- open app directory
|
||||||
|
- install latest version of Total.js from NPM `$ npm install total.js`
|
||||||
|
- run `$ node debug.js`
|
||||||
|
- open browser `http://127.0.0.1:8000`
|
||||||
135
saved_data/modbus_settings
Normal file
135
saved_data/modbus_settings
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"isRunning": false,
|
||||||
|
"debug": true,
|
||||||
|
"timeoutTime": 4000,
|
||||||
|
"msgWaitTime": 20000,
|
||||||
|
"port": "/dev/ttymxc1",
|
||||||
|
"port_options": "stty -F /dev/ttymxc1 9600 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke"
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"errBuffer": [],
|
||||||
|
"tbBuffer": [],
|
||||||
|
"device_index": 0,
|
||||||
|
"cmd_index": 0,
|
||||||
|
"devices": [{
|
||||||
|
"name": "Elektrometer 1",
|
||||||
|
"tb_name": "",
|
||||||
|
"type": "EM340",
|
||||||
|
"address": 1,
|
||||||
|
"data": [],
|
||||||
|
"cmd": [],
|
||||||
|
"timeoutcount": 0,
|
||||||
|
"status": "virtual"
|
||||||
|
}],
|
||||||
|
"cmd_tables": [{
|
||||||
|
"type": "EM340",
|
||||||
|
"cmd": [{
|
||||||
|
"name": "Voltage L1",
|
||||||
|
"tb_name": "Phase_1_voltage",
|
||||||
|
"register": 0,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Voltage L2",
|
||||||
|
"tb_name": "Phase_2_voltage",
|
||||||
|
"register": 2,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Voltage L3",
|
||||||
|
"tb_name": "Phase_3_voltage",
|
||||||
|
"register": 4,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Current L1",
|
||||||
|
"tb_name": "Phase_1_current",
|
||||||
|
"register": 12,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Current L2",
|
||||||
|
"tb_name": "Phase_2_current",
|
||||||
|
"register": 14,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Current L3",
|
||||||
|
"tb_name": "Phase_3_current",
|
||||||
|
"register": 16,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Power L1",
|
||||||
|
"tb_name": "Phase_1_power",
|
||||||
|
"register": 18,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Power L2",
|
||||||
|
"tb_name": "Phase_2_power",
|
||||||
|
"register": 20,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Power L3",
|
||||||
|
"tb_name": "Phase_3_power",
|
||||||
|
"register": 22,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Power tot",
|
||||||
|
"tb_name": "total_power",
|
||||||
|
"register": 40,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Energy in",
|
||||||
|
"tb_name": "total_energy",
|
||||||
|
"register": 52,
|
||||||
|
"size": 2,
|
||||||
|
"multiplier": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PowF L1",
|
||||||
|
"tb_name": "Phase_1_pow_factor",
|
||||||
|
"register": 46,
|
||||||
|
"size": 1,
|
||||||
|
"multiplier": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PowF L2",
|
||||||
|
"tb_name": "Phase_2_pow_factor",
|
||||||
|
"register": 47,
|
||||||
|
"size": 1,
|
||||||
|
"multiplier": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PowF L3",
|
||||||
|
"tb_name": "Phase_3_pow_factor",
|
||||||
|
"register": 48,
|
||||||
|
"size": 1,
|
||||||
|
"multiplier": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PowF",
|
||||||
|
"tb_name": "power_factor",
|
||||||
|
"register": 49,
|
||||||
|
"size": 1,
|
||||||
|
"multiplier": 0.001
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue