Actual running flowserver on test panel LM 10.0.0.5

This commit is contained in:
rasta5man 2024-04-13 20:00:05 +02:00
commit 67c503d980
57 changed files with 16678 additions and 0 deletions

11
config Normal file
View 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

View file

48
databases/nodes.table Normal file
View 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|...........

View 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
View 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
View 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
View 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
View file

0
err.txt Normal file
View file

48
flow/audit_test_panel.csv Normal file
View 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
1 LumDimm NODE (HEX) NODE (DEC) Line TB name
2 1 299 665 3 gbv4nzqxW0XGAPKVNk8kr25ZQ2l3O6LRBprM97ew
3 2 28A 650 3 0XYElWeKBNJn1gdoMG8lYdDALkPvj4V3xra2q6mO
4 3 296 662 3 gbv4nzqxW0XGAPKVNk8kW48ZQ2l3O6LRBprM97ew
5 4 297 663 1 LpkVlmq4b3jMwJQxBZ8akayrXAP6o97Ke0aOYEg2
6 5 29C 668 3 lekrmdvO0BQG1ZW4AV8jeZ5M39xnN2wEbRgPjXLp
7 6 2B1 689 3 q0rElBPdL6kxMAjnzVDRl95emNZY7oOv2wK9gb31
8 7 2AB 683 3 XKQbz3WAwY21dGa0R453rWyJm9PZOjqlvpr6Nkeo
9 8 2B0 688 3 PaGbQ3wBAZWOmRvK9VDpvz5endLJYopEqlkzNMxX
10 9 2B9 697 3 joqRYBVL30k9eQWOlZ5qwpD2KJpNEmA6gPxXzwaM
11 10 293 659 3 Ymn9oleRxJ0vw17WzAyGwdyEBk4ObdMXj2VgpNLG
12 11 294 660 3 gj7zbKV46oQ1p2e0AJ8XqZDG3YNWaRrlOEXvBxmM
13 12 295 661 3 laYK7Pomn2bNZXEpedDxAqyOJkQ3WwV49gqxLrAR
14 13 2A0 672 2 0XYElWeKBNJn1gdoMG8lON5ALkPvj4V3xra2q6mO
15 14 2B4 692 2 l9YkRpoB2vVa0mKqEO8ZGGDjW43eXnJML6GxzbwQ
16 15 2B2 690 2 wGjQobgOK0n2YqBZmVDVR3DR9ep6EXA1ka3vzlP7
17 16 27C 636 2 M6ogKQW09bOXewAYvZyvJqyJrV1aRnPGE37p42Nx
18 17 27B 635 2 Vq2JaWpw1OdBKmNeoj8w605XE40l3kgL76Azb9QP
19 18 2B6 694 2 Jm32GR1qpwQxlZza0N5mE15AP96YbOKLogrXVW4e
20 19 2B5 693 2 KjbN4q7JPZmexgdnz2yKdn5YAWwO0Q3BMX6ERLoV
21 20 2B3 691 1 lekrmdvO0BQG1ZW4AV8jzq8M39xnN2wEbRgPjXLp
22 21 27F 639 3 BOjEzGRZ46bnp9wa2A8z76D0JkmW1QPNdrqevXVL
23 22 27E 638 3 9xgzG4Op1BrKZPmoQkDrmj8E73ndJNMjavAwX2Re
24 23 27D 637 3 koW06PeGrLlBp2YJQE5Ogw5RmMaXKzj3wOAZg9n7
25 24 28F 655 2 RMgnK93rkoAazbqdQ4yBYpDZ1YXGx6pmwBeVEP2O
26 25 288 648 2 gaMGN4x1e9JlZz0QPRDd9Rym6dVr3OpvqKnoWBbk
27 26 298 664 1 oGVzxNWP9lrjaQ7vKODQ7g51gqp62YZREmdw3XBM
28 27 29F 671 3 AvVdgzYJZaPx3oMqeED4Oj8NnmKkw716bRO90jLB
29 28 280 640 2 WjBL12pg63eX4N9P7zy0XYyEJKmlbkGwZMx0avQV
30 29 28B 651 2 qaAOzENGrvpbe0VoK7D6Ld519PZmdg3nl24JLQMk
31 30 27A 634 2 NGWamnYqlP1wbgrZQxDAWm5e2X7OVAK69koR04vL
32 31 29E 670 2 dlE1VQjYrNx9gZRmb38g1YyoLBO4qaAk2M6JPnG7
33 32 281 641 2 vnmG4kJxaXWNBgMQq0D7Mz5e9oZzOAlr6LdR3w2V
34 33 278 632 2 LpkVlmq4b3jMwJQxBZ8aM78rXAP6o97Ke0aOYEg2
35 34 29D 669 3 Y9aLW03wOZkABvKXbMyL0lyV1xdNj72r4egqGRzJ
36 35 2A8 680 1 KL2jNOVpdARa9XvoeJDPga8bkmPBxqn7Ww3gzGQ1
37 36 2BA 698 1 mYnBzbeGaAL62jowRv59M35Xq9QpZ0K7O1dg4xVl
38 37 29B 667 1 MzXBoWbEZjO0lrpqnRyoJ4DkmVeaNAGdL9g4QKxP
39 38 289 649 1 0p2rwdP7aGoOQLJNgAynJNy6xWXbmMe3nvZqlzkV
40 39 290 656 1 BrQx3NGKgVMRaXYAo9y1GE8ZzkWnj1le6bdOLE20
41 40 2AA 682 1 vnreBJ6PMqgz20pYEL82XQyG1jkWwdQxZVNAOlmK
42 41 285 645 1 jklN4JpQAx362o9XYZDN6wDgrWw1P7GEbdBM0vRV
43 42 283 643 1 oZmYXEbw9lVWRv1jLxDe9bDdgAMz4PKQnNJ6eB23
44 43 282 642 1 pEonaKBOGbj9034MgJ8W3G8qXvxNWVkAPQz21R6L
45 44 287 647 1 BLQal6Pn9oz1KmNgek5Yqd50vd2MAbqG3OV7Rp4j
46 45 286 646 1 4agVJ9dPQkmp1R2X3EDJKxyrK6ZlNoM0n7qxBOev
47 46 29A 666 1 9PpgLEnvk4WMV6RmOJybMGDaeAXzo2BQNG3K17Zw
48 47 28E 654 1 Mmp93b2nvd7OoqgBeEyEZq5kjlAV1Y4ZNXwW0zLG

4169
flow/cmd_manager.js Normal file

File diff suppressed because it is too large Load diff

90
flow/code.js Normal file
View file

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

11
flow/comment.js Normal file
View file

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

175
flow/csv_import.js Normal file
View 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
View 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
View file

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

2172
flow/designer.json Normal file

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

220
flow/gettemperature.js Normal file
View 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);
};

View 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;

View 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
View 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
}

View 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
}

View 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
View 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
}

View 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
View 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
}

View 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
View file

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

124
flow/helper/utils.js Normal file
View 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
View 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
View file

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

326
flow/httproute.js Normal file
View file

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

121
flow/infosender.js Normal file
View file

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

1127
flow/modbus_citysys.js Normal file

File diff suppressed because it is too large Load diff

156
flow/monitorconsumption.js Normal file
View file

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

107
flow/monitorcpu.js Normal file
View 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
View file

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

87
flow/monitormemory.js Normal file
View file

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

441
flow/mqtt.js Normal file
View file

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

171
flow/mqtt_listener.js Normal file
View 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
View file

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

168
flow/mqttsubscribe.js Normal file
View file

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

191
flow/nosql.js Normal file
View 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
View 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é
1 key weight en sk
2 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}
3 dusk_has_occured INFO Dusk has occured Nastal súmrak
4 dawn_has_occured INFO Dawn has occured Nastal úsvit
5 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}
6 master_node_is_responding_again NOTICE Master node is responding again Master node začal znovu odpovedať
7 command_was_sent_from_terminal_interface DEBUG A command was sent from terminal interface Z terminálu bol odoslaný príkaz
8 master_node_is_not_responding ALERT Master node is not responding Master node neodpovedá
9 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
10 circuit_breaker_was_turned_on_line NOTICE Circuit breaker was turned on - line no. ${line} Zapnutie ističa na línii č. ${line}
11 circuit_breaker_was_turned_off_line ERROR Circuit breaker was turned off - line no. ${line} Vypnutie ističa na línií č. ${line}
12 dimming_profile_was_processed_for_node INFO Dimming profile was processed for node no. ${node} Stmievací profil bol spracovaný pre node č. ${node}
13 switching_profile_was_processed_for_line INFO Switching profile was processed for line no. ${line} Spínací profil bol spracovaný pre líniu č. ${line}
14 thermometer_is_not_responding WARNING Thermometer is not responding Teplomer neodpovedá
15 thermometer_is_responding_again NOTICE Thermometer is responding again Teplomer znovu odpovedá
16 thermometer_sends_invalid_data WARNING Thermometer sends invalid data Teplomer posiela neplatné hodnoty
17 main_switch_has_been_turned_off CRITICAL Main switch has been turned off Hlavný vypínač bol vypnutý
18 main_switch_has_been_turned_on NOTICE Main switch has been turned on Hlavný vypínač bol zapnutý
19 power_supply_has_disconnected_input ALERT Power supply has disconnected input Napájací zdroj nemá napätie na vstupe
20 power_supply_works_correctly NOTICE Power supply works correctly Napájací zdroj pracuje správne
21 battery_level_is_low ERROR Battery level is low Batéria má nízku úroveň napätia
22 battery_level_is_ok NOTICE Battery level is OK Batéria má správnu úroveň napätia
23 door_has_been_open NOTICE Door has been open Dvere boli otvorené
24 door_has_been_closed NOTICE Door has been closed Dvere boli zatvorené
25 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
26 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}
27 local_database_is_corrupted CRITICAL
28 electrometer_is_not_responding ERROR Electrometer is not responding Elektromer neodpovedá
29 no_voltage_detected_on_phase CRITICAL No voltage detected on phase no. ${phase} Na fáze č. ${phase} nie je napätie
30 electrometer_is_responding_again NOTICE Electrometer is responding again Elektromer znovu odpovedá
31 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
View 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 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 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
View 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
View file

@ -0,0 +1,79 @@
exports.id = 'trigger';
exports.title = 'Trigger';
exports.group = 'Inputs';
exports.color = '#F6BB42';
exports.click = true;
exports.output = 1;
exports.version = '1.1.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
View file

43
flow/virtualwirein.js Normal file
View file

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

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

551
flow/wsmqttpublish.js Normal file
View 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
View file

27
package.json Normal file
View 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
View file

@ -0,0 +1,15 @@
[![Support](https://www.totaljs.com/img/button-support.png)](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
View 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
}
]
}]
}
}