commit 67c503d98068fbd648263a5ee3602d97bbbc5754 Author: rasta5man Date: Sat Apr 13 20:00:05 2024 +0200 Actual running flowserver on test panel LM 10.0.0.5 diff --git a/config b/config new file mode 100644 index 0000000..80f7778 --- /dev/null +++ b/config @@ -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 diff --git a/databases/backup/tbdata.nosql b/databases/backup/tbdata.nosql new file mode 100644 index 0000000..e69de29 diff --git a/databases/nodes.table b/databases/nodes.table new file mode 100644 index 0000000..38fe2d5 --- /dev/null +++ b/databases/nodes.table @@ -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|........... diff --git a/databases/notifications.table b/databases/notifications.table new file mode 100644 index 0000000..aa0552c --- /dev/null +++ b/databases/notifications.table @@ -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 |............... diff --git a/databases/pins.table b/databases/pins.table new file mode 100644 index 0000000..be299ec --- /dev/null +++ b/databases/pins.table @@ -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|........... diff --git a/databases/relays.table b/databases/relays.table new file mode 100644 index 0000000..c21f5b2 --- /dev/null +++ b/databases/relays.table @@ -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||................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ diff --git a/databases/settings.table b/databases/settings.table new file mode 100644 index 0000000..d87a74e --- /dev/null +++ b/databases/settings.table @@ -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|........................................... diff --git a/databases/tbdata.nosql b/databases/tbdata.nosql new file mode 100644 index 0000000..e69de29 diff --git a/err.txt b/err.txt new file mode 100644 index 0000000..e69de29 diff --git a/flow/audit_test_panel.csv b/flow/audit_test_panel.csv new file mode 100644 index 0000000..907a6a9 --- /dev/null +++ b/flow/audit_test_panel.csv @@ -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 diff --git a/flow/cmd_manager.js b/flow/cmd_manager.js new file mode 100644 index 0000000..1b708af --- /dev/null +++ b/flow/cmd_manager.js @@ -0,0 +1,4169 @@ +exports.id = 'cmd_manager'; +exports.title = 'CMD Manager'; +exports.group = 'Worksys'; +exports.color = '#5D9CEC'; +exports.version = '0.0.4'; +exports.output = ['red', 'blue', 'yellow', 'blue', 'white']; + +//blue - send message to relays + +exports.input = true; +exports.author = 'Daniel Segeš'; +exports.icon = 'cloud-upload'; +//exports.npm = ['serialport' , 'child_process']; + +exports.html = ` +
+
+
+
RPC - run RPC calls

+
+
+
@(User)
+
+
+
@(Password)
+
+
+
@(My edge)
+
+
+
+`; + +exports.readme = `Manager for CMD calls`; + +const SerialPort = require('serialport'); +const { exec } = require('child_process'); +const { crc8, crc16, crc32 } = require('easy-crc'); +const { openPort, runSyncExec, writeData } = require('./helper/serialport_helper.js'); +const { bytesToInt, longToByteArray, addZeroBefore, isEmptyObject, convertUTCDateToLocalDate } = require('./helper/utils'); +const bitwise = require('bitwise'); + +var SunCalc = require('./helper/suncalc.js'); +const DataToTbHandler = require('./helper/DataToTbHandler.js'); +const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler.js'); +const { promisifyBuilder } = require('./helper/db_helper.js'); +const { sendNotification, initNotifications, ERRWEIGHT } = require('./helper/notification_reporter.js'); + +//https://github.com/log4js-node/log4js-node/blob/master/examples/example.js +//file: { type: 'file', filename: path.join(__dirname, 'log/file.log') } + +var path = require('path'); +var log4js = require("log4js"); +const process = require('process'); + +log4js.configure({ + appenders: { + errLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, 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"); + +//USAGE +//logger.debug("text") +//monitor.info('info'); +//errLogger.error("some error"); + +//load from settings +let latitude = 48.70826502;//48.682255758; +let longitude = 17.28455203;//17.278910807; + +const gmtOffset = 0; + +//ak nie je nastaveny +//https://www.tecmint.com/set-time-timezone-and-synchronize-time-using-timedatectl-command/ +//https://stackoverflow.com/questions/16086962/how-to-get-a-time-zone-from-a-location-using-latitude-and-longitude-coordinates + +//set dimming - LUM1_13 - 647 je node linie 1 kt. dobre vidime + + +//priorities for registers +let priorities = []; + +let minutes = 1; +priorities["0"] = minutes; +priorities["1"] = minutes; + +minutes = 5; +priorities["74"] = minutes; +priorities["75"] = minutes; +priorities["76"] = minutes; +priorities["77"] = minutes; +priorities["78"] = minutes; +priorities["79"] = minutes; +priorities["84"] = minutes; + +minutes = 10; +priorities["87"] = minutes; +priorities["6"] = minutes; +priorities["7"] = minutes; +priorities["80"] = minutes; +priorities["8"] = minutes; +priorities["3"] = minutes; +priorities["89"] = minutes; + +//prikazy kt sa budu spustat na dany node - see config.js in terminal-oms.app +let listOfCommands = [0,1,3,6,7,8,74,75,76,77,78,79,80,84,87,89]; + +//1 - dimming + +const dbNodes = TABLE("nodes"); +const dbRelays = TABLE("relays"); +const dbSettings = TABLE("settings"); + +const errorHandler = new ErrorToServiceHandler(); + +let rotary_switch_state = "Off"; +let lux_sensor; +let state_of_breaker = {};//key is line, value is On/Off +let disconnectedReport = {};//key is tbname, value true/false + +let relaysData = {};//key is line, value is data from db +let nodesData = {};//key is node, value data from db + +//helper container for counting resolved group of commands (commands related to set profile) +let cmdCounter = {};//key is node, value is counter +let cmdNOKNodeCounter = {};//key is node, value is counter +function cmdCounterResolve(address) +{ + if(cmdCounter.hasOwnProperty(address)) + { + cmdCounter[address] = cmdCounter[address] - 1; + + let result = cmdCounter[address]; + if(result == 0) delete cmdCounter[address]; + + return result; + } + + return -1; +} + +function getParams(priority) +{ + let params = {}; + + //core rpc values + params.address = 0;//if(recipient === 0) address = 0; + params.byte1 = 0;//msb, podla dokumentacie data3 + params.byte2 = 0;//podla dokumentacie data2 + params.byte3 = 0;//podla dokumentacie data1 + params.byte4 = 0;//lsb, podla dokumentacie data0 + params.recipient = 0;//0: Master, 1: Slave, 2: Broadcast + params.register = -1;//register number + params.rw = 0;//0: read, 1: write + + //other values + //params.type = "cmd"; "relay" "cmd-terminal" + //params.tbname = tbname; + params.priority = priorityTypes.node_cmd;//default priority + params.timestamp = 0;//execution time + if(priority != undefined ) + { + params.timestamp = priority; + params.priority = priority; + } + + params.addMinutesToTimestamp = 0;//repeat if > 0, + + //params.isDusk = false; + //params.isDawn = false; + //params.info = ""; + + return params; +} + +async function loadSettings() +{ + let responseSettings = await promisifyBuilder(dbSettings.find()); + + latitude = responseSettings[0]["latitude"]; + longitude = responseSettings[0]["longitude"]; + + //globals + FLOW.OMS_language = responseSettings[0]["lang"]; + FLOW.OMS_rvo_name = responseSettings[0]["rvo_name"]; + FLOW.OMS_projects_id = responseSettings[0]["projects_id"]; + //FLOW.OMS_rvo_tbname = responseSettings[0]["tbname"]; + FLOW.OMS_temperature_adress = responseSettings[0]["temperature_adress"]; + FLOW.OMS_controller_type = responseSettings[0]["controller_type"]; + FLOW.OMS_serial_port = responseSettings[0]["serial_port"]; + + //logger.log("", "settings", responseSettings[0], "-------------------------------------"); + logger.debug('settings', responseSettings[0]); + + //FLOW.OMS_tem + //rvo_name:string|lang:string|temperature_adress:string|latitude:number|longitude:number + + initNotifications(); +} + +//nastav profil nodu +function processNodeProfile(node) +{ + if(rotary_switch_state != "Automatic") + { + logger.debug("unable to process profile for node", node, "rotary_switch_state != Automatic"); + return; + } + + let nodeObj = nodesData[node]; + let line = nodeObj.line; + + if(relaysData[line].contactor == 0) + { + logger.debug("line line is off", line, node); + return; + } + + if(nodeObj.processed == 1) + { + logger.debug("node was already processed", node); + return; + } + + let profile = nodeObj.profile; + + logger.debug("processNodeProfile: start - set profile for ", node, profile); + + let nodeProfile; + try{ + nodeProfile = JSON.parse( profile ); + if(Object.keys(nodeProfile).length === 0) throw ("profile is not defined"); + } catch (error) {} + + //test reset profilu + //nodeProfile = undefined; + + logger.debug("processNodeProfile", node, line, nodeObj, nodeProfile); + //return; + + //let timestamp = priorityTypes.node_cmd; + + //let now = new Date(); + //now.setSeconds(now.getSeconds() + 10); + //let timestamp = now.getTime(); + + let timestamp = priorityTypes.node_cmd; + + //nodeProfile = undefined; + removeTask({type: "set_node_profile", address: node}); + cmdNOKNodeCounter[node] = 0; + + //co ked sa prave spracovava? + //if(cmdNOKNodeCounter[params.address] < 5) saveToTb = false; + + + if(nodeProfile === undefined) + { + //vypneme profil nodu, posleme cmd + //Pokiaľ je hodnota rovná 1 – Profil sa zapne, ostatné bity sa nezmenia. + //Pokiaľ sa hodnota rovná 2 – profil sa vypne, ostatné bity sa nezmenia + + logger.debug("turn off profile"); + + let params = getParams(priorityTypes.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 32; + params.recipient = 1; + params.register = 8; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'turn off/reset node profile'; + + cmdCounter[node] = 1; + + + + tasks.push(params); + + //sendNotification("CMD Manager: process cmd", relaysData[0].tbname, ERRWEIGHT.NOTICE, "Master node is working again", "", instanceSendTo.tb, instance ); + } + else + { + let tasksProfile = []; + //cmdCounter[node] = tasksProfile.length; + //tasks.push(tasksProfile); + + //let timestamp = priorityTypes.node_cmd; + + //vypneme profil - Zapísať hodnotu 32 do registra Time Schedule Settings – reset profilu + let params = getParams(priorityTypes.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 32; + params.recipient = 1; + params.register = 8; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'turn off node profile'; + + tasksProfile.push(params); + + timestamp++; + + logger.debug("processNodeProfile: TS1 Time point a TS1 Time Point Levels ", node); + + //TS1 Time point a TS1 Time Point Levels + let register = 9; + for(let i = 0; i < nodeProfile.intervals.length; i++) + { + let obj = nodeProfile.intervals[i]; + //let timePoint = obj.time_point; + let dim_value = obj.value; + + + + //Reg 9 až Reg 40 + + /* + Samotný profil sa zapisuje do max. 16 párov – časový bod a úroveň. + Prázdny profil je vtedy keď časový bod obsahuje hodnotu 0xFFFFFFFF (táto hodnota sa zapíše do registrov keď sa aktivuje reset profilu do registru 8). + Páry sa prechádzajú časovo zoradené takže teoreticky je jedno v akom poradí sa zapisujú ale je lepšie ich zapisovať v chronologickom poradí od 13:00. + Časový bod má formát: + Byte 3: hodiny Byte 2: minúty Byte 1: sekundy Byte 0 – rezervované + Register úrovne má rovnaký formát ako dimming register (Reg 1). + */ + + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + //params.byte1 = 0;//msb, podla dokumentacie data3 + //params.byte2 = 0;//podla dokumentacie data2 + //params.byte3 = 0;//podla dokumentacie data1 + //params.byte4 = 0;//lsb, podla dokumentacie data0 + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + let start_time = obj.start_time; + let t = start_time.split(":"); + //if(timePoint != undefined) t = timePoint.split(":"); + //else t = [0,0]; + + logger.debug("processNodeProfile: TS1 Time point ", (i + 1), node); + + params = getParams(priorityTypes.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = parseInt(t[0]);//hh + params.byte2 = parseInt(t[1]);//mm + params.byte3 = 0;//ss + params.byte4 = 0;// + params.recipient = 1; + params.register = register; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'TS1 Time point ' + (i + 1); + + tasksProfile.push(params); + + register++; + timestamp++; + + params = getParams(priorityTypes.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0;//ss + params.byte4 = parseInt(dim_value) + 128;// + params.recipient = 1; + params.register = register; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = 'TS1 Time point Levels ' + (i + 1); + + tasksProfile.push(params); + + register++; + timestamp++; + } + + //Threshold lux level for DUSK/DAWN + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + //params.byte1 = 0;//msb, podla dokumentacie data3 + //params.byte2 = 0;//podla dokumentacie data2 + //params.byte3 = 0;//podla dokumentacie data1 + //params.byte4 = 0;//lsb, podla dokumentacie data0 + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + + //Time schedule settings na koniec + //if(nodeProfile.dusk_lux_sensor || nodeProfile.dawn_lux_sensor) + { + + logger.debug("processNodeProfile: Threshold lux level for DUSK/DAWN", node); + + let params = getParams(priorityTypes.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 96; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "Threshold lux level for DUSK/DAWN"; + + if(nodeProfile.dusk_lux_sensor) + { + let v = nodeProfile.dusk_lux_sensor_value; + let ba = longToByteArray(v); + + params.byte1 = ba[1];//msb + params.byte2 = ba[0]; + } + + if(nodeProfile.dawn_lux_sensor) + { + let v = nodeProfile.dawn_lux_sensor_value; + let ba = longToByteArray(v); + + params.byte3 = ba[1];//msb + params.byte4 = ba[0]; + } + + tasksProfile.push(params); + timestamp++; + + } + + //DUSK/DAWN max. adjust period + { + + logger.debug("processNodeProfile: DUSK/DAWN max. adjust period", node); + + let params = getParams(priorityTypes.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 97; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "DUSK/DAWN max. adjust period"; + + if(nodeProfile.astro_clock) + { + let v = nodeProfile.dusk_lux_sensor_time_window; + let ba = longToByteArray(v); + + params.byte1 = ba[1];//msb + params.byte2 = ba[0]; + } + + if(nodeProfile.astro_clock) + { + let v = nodeProfile.dawn_lux_sensor_time_window; + let ba = longToByteArray(v); + + params.byte3 = ba[1];//msb + params.byte4 = ba[0]; + } + + tasksProfile.push(params); + timestamp++; + + } + + //Static offset + { + + //Statický offset pre časy úsvitu a súmraku. Byte 1 je pre DUSK, Byte 0 je pre DAWN. Formát: + //Bity 0 – 6: hodnota v minútach + //Bit 7: znamienko (1 – mínus) + + logger.debug("processNodeProfile: Static offset", node); + + let params = getParams(priorityTypes.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 98; + params.recipient = 1; + params.rw = 1;//write + params.timestamp = timestamp; + params.addMinutesToTimestamp = 0; + params.info = "Static offset"; + + if(nodeProfile.astro_clock) + { + let dusk_astro_clock_offset = parseInt(nodeProfile.dusk_astro_clock_offset); + let dawn_astro_clock_offset = parseInt(nodeProfile.dawn_astro_clock_offset); + + if(dusk_astro_clock_offset < 0) + { + params.byte3 = (dusk_astro_clock_offset * -1) + 128; + } + else + { + params.byte3 = dusk_astro_clock_offset; + } + + if(dawn_astro_clock_offset < 0) + { + params.byte4 = (dawn_astro_clock_offset * -1) + 128; + } + else + { + params.byte4 = dawn_astro_clock_offset; + } + } + + tasksProfile.push(params); + timestamp++; + } + + logger.debug("Time schedule settings - turn on", node); + + params = getParams(priorityTypes.node_cmd); + params.type = "set_node_profile"; + params.address = node; + params.register = 8; + params.recipient = 1; + params.rw = 1;//write + + + + //Time schedule settings + let bits = []; + + //Byte 0 (LSB): + //Bit 0 (LSB) – zapnutie/vypnutie profilov ako takých (1 – zapnuté). + bits.push(1); + //Bit 1 – 3 - zatiaľ nepoužité (zapisovať 0) + bits.push(0); + bits.push(0); + bits.push(0); + if(nodeProfile.astro_clock == true) + { + //Bit 4 – ak je nastavený profil sa riadi podľa astrohodín, a je 0 tak profil je jednoduchý + bits.push(1); + } + else bits.push(0); + + //Bit 5 – zápis 1 spôsobí reset nastavení profilu (nastavenie prázdneho profilu) + bits.push(0); + + //Bity 6-7 - zatiaľ nepoužité + bits.push(0); + bits.push(0); + + params.byte4 = bitwise.byte.write(bits.reverse()); + + //Byte 2 – nastavenie pre lux senzor: + bits = []; + + //Bit 0 (LSB) – riadenie súmraku podľa lux senzoru (1 – zapnuté). Súmrak sa môže posúvať v rámci času v registri 97 podľa intenzity osvetlenia + if(nodeProfile.dusk_lux_sensor == true)//sumrak + { + bits.push(1); + } + else bits.push(0); + + //Bit 1 - riadenie úsvitu podľa lux senzoru (1 – zapnuté). Úsvit sa môže posúvať v rámci času v registri 97 podľa intenzity osvetlenia + if(profile.dawn_lux_sensor == true)//usvit + { + bits.push(1); + } + else bits.push(0); + + //Bit 2 – zdroj pre hodnotu luxov – 0 – RVO posiela hodnoty zo svojho luxmetra, 1 – node má pripojený svoj vlastný lux meter. + bits.push(0);//zatial neimplementovane + + //Bit 3 – 7 - nepoužité + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + + params.byte2 = bitwise.byte.write(bits.reverse()); + params.timestamp = timestamp; + params.info = "Time schedule settings - turn on"; + + tasksProfile.push(params); + + //zaver + cmdCounter[node] = tasksProfile.length; + + //tasks.push(tasksProfile); + tasks = tasks.concat(tasksProfile); + + } + + logger.debug("finished set profile for ", node); + +} + +const instanceSendTo = { + debug: 0, + tb: 1, + http_response: 2, + di_do_controller: 3, + infoSender: 4 +} + +const priorityTypes = { + terminal: 0, + fw_detection: 1,//reserved only for FW detection - FLOW.OMS_masterNodeIsResponding + high_priority: 2,//reserverd only for: read dimming / brightness (after set dimming from platform) + relay_profile: 3, + node_broadcast: 4, + node_profile: 5, + node_cmd: 6 +} + + +let interval = null;//timeout for procesing tasks +let refFlowdata = null;//holds reference to httprequest flowdata +let refFlowdataObj = {}; + +function cleanUpRefFlowdataObj() +{ + let now = new Date(); + let timestamp = now.getTime(); + + //clear old refFlowdata references + let keys = Object.keys(refFlowdataObj); + for(let i = 0; i < keys.length; i++) + { + let timestampKey = keys[i]; + + if((timestamp - timestampKey) > 60*1000 ) + { + console.log("cleanUpRefFlowdataObj delete", timestampKey); + delete refFlowdataObj[ timestampKey ]; + } + } +} + +let tasks = [];//list of command calls to process + +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; + + }); + +} + + +//TODO - to remove? +const shortIterval = 10; +const longInterval = 100; + +loadSettings(); + +exports.install = function(instance) { + + process.on('uncaughtException', function (err) { + + //TODO send to service + + errLogger.error('uncaughtException:', err.message) + errLogger.error(err.stack); + + errorHandler.sendMessageToService(err.message + "\n" + err.stack, 0, "js_error"); + //process.exit(1); + }) + + //te();//force error + + const tbHandler = new DataToTbHandler(instanceSendTo.tb); + tbHandler.setSender(exports.title); + + //FLOW.OMS_projects_id, name: FLOW.OMS_rvo_name + //const errorHandler = new ErrorToServiceHandler(instance, instanceSendTo.infoSender); + errorHandler.setProjectsId(FLOW.OMS_projects_id); + //const errorHandler = new ErrorToServiceHandler(instance); + //errorHandler.sendMessageToService("ahoj", 0); + + + async function loadRelaysData(line) + { + logger.debug("loadRelaysData", line); + + //ak zapiname liniu, mali by sme skontrolovat kde processed je false + //nodes.table: node:number|tbname:string|line:number|profile:string|processed:boolean + //vyselektujem vsetky nodes a spracujem profil + + return new Promise((resolve, reject) => { + + dbRelays.find().make(function(builder) { + builder.callback(function(err, response) { + + if(err != null) reject(err); + + let relaysDataTmp = {}; + for(let i = 0; i < response.length; i++) + { + let record = response[i]; + let line = record["line"]; + relaysDataTmp[ record["line"] ] = record; + + //porovname predchadzajuce hodnoty + //ak record.contactor == 1, a aktualna hodnota record.contactor == 0 + //to znamena, ze sa zmenil stav - linia bola vypnuta + + let prevData = relaysData[ record["line"] ]; + + //ugly but do not remove!!! + relaysData[ record["line"] ] = record; + + let state = "";//on, off or empty (no change) + if(prevData != undefined) + { + /* + if(prevData.contactor == 1 && record.contactor == 0) + { + state = "off"; + reportOfflineNodeStatus(line); + } + + if(prevData.contactor == 0 && record.contactor == 1) + { + state = "on"; + reportOnlineNodeStatus(line); + } + */ + + } + else + { + //start flowu + state = "start"; + } + + if(line != undefined) + { + //ak sa jedna o update profilu linie - pozor di_co_controller posiela command pre loadRelaysData + if(line != record["line"] ) continue; + } + + //je zapnuta linia? contactor = 1 a processed = false, spracujeme profil + if(record.contactor == 1) + { + + //nespracovany profil, zapisem do nodu + //rotary_switch_state = Automatic - profilu pre nody sa vykonavaju + //ak je spracovany, a automatic - tak ho zapnem + + if(rotary_switch_state == "Automatic") + { + //prejs nodes - nacitame vsetky nody z pre danu liniu + for (let k in nodesData) { + //node:number|tbname:string|line:number|profile:string|processed:boolean + + //potrebujem nody k danej linii + if(record.line == nodesData[k].line) + { + let node = nodesData[k].node; + let processed = nodesData[k].processed; + + if(!processed) + { + processNodeProfile(node); + } + else{ + //logger.debug( `node ${node} profile for line ${nodesData[k].line} was already processed`); + } + } + } + + } + else + { + logger.debug("unable to process profile - rotary_switch_state is", rotary_switch_state); + } + + } + } + + relaysData = {...relaysDataTmp}; + + resolve("OK"); + + }); + }); + //resolve(stdout); + //reject(error); + + }) + } + + function reportOnlineNodeStatus(line, newRotarySwitchState) + { + //broadcast cas, o 1-2 sek neskor - status, brightness + + //Po zapnutí línie broadcastovo aktualizovať predtým čas. + + logger.debug("--->reportOnlineNodeStatus for line", line); + + //return; + + //run broadcast //Actual time (3x reportujeme -> prvy krat hned, potom po 20 sekundach pre istotu) + addMinutesToTimestamp = 0; + + let params = {}; + + let recipient = 2;//2 broadcast, address = 0 + let address = 0;//0 + if(recipient === 2) + { + address = 0xffffffff;//Broadcast + } + + var d = new Date(); + let hours = d.getHours(); + let minutes = d.getMinutes(); + let seconds = d.getSeconds(); + + params.address = address;//broadcast + params.byte1 = hours;//h + params.byte2 = minutes;//m + params.byte3 = seconds;//s + params.byte4 = 0; + params.recipient = recipient; + params.register = 87;//Actual time + params.rw = 1;//write + + let timestampStart = priorityTypes.node_broadcast; + + //other values + params.type = "cmd"; + //params.tbname = tbname; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "run broadcast: Actual time"; + + tasks.push(params); + + params.timestamp = d.getTime() + 13000; + tasks.push(params); + + params.timestamp = d.getTime() + 14000; + tasks.push(params); + + if(newRotarySwitchState == 'Automatic') + { + + // we execute 3x the same command (1st after 30seconds, 2nd 31seconsd, 3rd 32seconds) + // cmd = WRITE: 255, 255, 255, 255, 255, 0, 8, 0, 0, 0, 17, 214, 34 + // 4bytes address, 1byte recipient, 2 register, 4bytes data, 2bytes crc + const timestamp = Date.now(); + + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + // params.tbname = ''; + params.address = 0xffffffff; //broadcast + params.register = 8; //register na casove profily pre nody + params.recipient = 2; //broadcast + params.rw = 1; //write + params.byte4 = 17; //decimal 17 + params.timestamp = timestamp + 13000; + params.info = 'znovuzapnutie profilov na svietidlach'; + tasks.push(params); + + params.timestamp = timestamp + 14000; + tasks.push(params); + + params.timestamp = timestamp + 15000; + tasks.push(params); + } + + if(newRotarySwitchState == 'Manual') + { + // we execute 3x the same command (1st after 30seconds, 2nd 31seconsd, 3rd 32seconds) + // cmd = WRITE: 255, 255, 255, 255, 255, 0, 8, 0, 0, 0, 0, 218, 226 + // 4bytes address, 1byte recipient, 2 register, 4bytes data, 2bytes crc + const timestamp = Date.now(); + + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + // params.tbname = ''; + params.address = 0xffffffff; //broadcast + params.register = 8; //register na casove profily pre nody + params.recipient = 2; //broadcast + params.rw = 1; //write + params.timestamp = timestamp + 13000; + params.info = 'vypnutie profilov na svietidlach'; + tasks.push(params); + + params.timestamp = timestamp + 14000; + tasks.push(params); + + params.timestamp = timestamp + 15000; + tasks.push(params); + + // after this 3 cmd, new cmds are executed: + // cmd = WRITE: 255, 255, 255, 255, 255, 0, 1, 0, 0, 0, 228, 144, 62 + + let newCmd = {...params}; + + newCmd.register = 1; + newCmd.byte4 = 228; // all nodes to 100% + newCmd.timestamp = timestamp + 17000; + tasks.push(newCmd); + + newCmd.timestamp = timestamp + 18000; + tasks.push(newCmd); + + newCmd.timestamp = timestamp + 19000; + tasks.push(newCmd); + } + + + let sec = 20; + setTimeout(function(){ + //Po zapnutí línie - spraviť hromadný refresh stavu práve zapnutých svietidiel + + for (let k in nodesData) { + + //potrebujem nody k danej linii + if(line == nodesData[k].line || line == undefined) + { + let tbname = nodesData[k].tbname; + let node = nodesData[k].node; + + //prud, vykon - current, input power pre liniu pre vsetky nody + + //a pridame aj vyreportovanie dimmingu + { + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 1;//dimming + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = priorityTypes.high_priority; + params.info = 'read dimming / brightness (after set dimming from platform)'; + //params.debug = true; + + tasks.push(params); + } + + //Prúd + { + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 75;//prud + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = priorityTypes.high_priority; + params.info = 'read current (after set dimming from platform)'; + //params.debug = true; + + tasks.push(params); + } + + //výkon + { + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 76;//výkon + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = priorityTypes.high_priority; + params.info = 'read power (after set dimming from platform)'; + //params.debug = true; + + tasks.push(params); + } + + } + } + + },sec*1000); + + } + + function reportOfflineNodeStatus(line) + { + + logger.debug("--->reportOfflineNodeStatus for line", line); + + values = {}; + values["dimming"] = 0;//brightness + values["power"] = 0;//výkon + values["current"] = 0;//prúd + values["status"] = "OFFLINE";//prúd + + for (let k in nodesData) { + + //potrebujem nody k danej linii + if(line == nodesData[k].line || line == undefined) + { + let tbname = nodesData[k].tbname; + + //logger.debug("node:", tbname); + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + } + } + + //report OFFLINE for line + //relaysData[line].tbname; + + //values = {}; + //values["status"] = "OFFLINE";//prúd + } + + let now = new Date(); + console.log("CMD Manager installed", now.toLocaleString("sk-SK")); + + //To disable NTP time synchronization, type the following command at the terminal. + exec(`timedatectl set-ntp false`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + + monitor.info("failed timedatectl set-ntp", err, stderr); + } + else + { + exec(`timedatectl set-timezone "Europe/Bratislava"`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + + monitor.info("failed timedatectl set-timezone", err, stderr); + } + else + { + console.log("timedatectl set-timezone Europe/Bratislava"); + } + }); + } + }); + + function turnOnLine(line, info) + { + let obj = { + line: line, + command: "turnOn", + info: info + }; + + logger.debug("linia", line, obj); + + if(rotary_switch_state == 'Automatic') reportOnlineNodeStatus(undefined, 'Automatic'); + + instance.send(instanceSendTo.di_do_controller, obj); + } + + function turnOffLine(line, info) + { + let obj = { + line: line, + command: "turnOff", + info: info + }; + + logger.debug("linia", line, obj); + + instance.send(instanceSendTo.di_do_controller, obj); + } + + function detectIfResponseIsValid(bytes) + { + +//ak sa odpoved zacina 0 - je to v poriadku, inak je NOK + + let type = "RESPONSE"; + if(bytes[4] == 0) type = "RESPONSE"; + else if(bytes[4] == 1) type = "ERROR"; + else if(bytes[4] == 2) type = "EVENT"; + else type = "UNKNOWN"; + + let crc = crc16('ARC', bytes.slice(0, 9)); + let c1 = (crc >> 8) & 0xFF; + let c2 = crc & 0xFF; + + let message = "OK"; + let error = ""; + if(c1 != bytes[9]) + { + //CRC_ERROR + message = "NOK"; + error = "CRC_ERROR c1"; + instance.send(instanceSendTo.debug, "CRC_ERROR c1"); + } + + if(c2 != bytes[10]) + { + //CRC_ERROR + message = "NOK"; + error = "CRC_ERROR c2"; + instance.send(instanceSendTo.debug, "CRC_ERROR c2"); + } + + //crc error + if(type != "RESPONSE") + { + instance.send(instanceSendTo.debug, bytes); + instance.send(instanceSendTo.debug, "RESPONSE " + type + " - " + bytes[4]); + + //logger.debug(instanceSendTo.debug, "RESPONSE " + type + " - " + bytes[4], bytes); + + error = "type is: " + type; + + message = "NOK"; + } + + return {message: message, type: type, error: error}; + } + + function buildTasks(params) + { + + //report FLOW.OMS_edge_fw_version as fw_version + //report date as startdate + + monitor.info("buildTasks - params", params); + //return; + + //https://service-prod01.worksys.io/gettime + + + let processLine;//defined line + + let init = false; + let processLineProfiles = true; + let processBroadcast = true; + let processNodes = true; + + if(params == undefined) + { + init = true; + tasks = []; + + logger.debug("-->buildTasks clear tasks"); + } + else + { + processLineProfiles = false; + processBroadcast = false; + processNodes = false; + + processLineProfiles = params.processLineProfiles; + processLine = params.line; + } + + //load profiles pre linie + //relaysData[ record["line"] ] + + let now = new Date(); + + if(processLineProfiles) + { + //process line profiles + let keys = Object.keys(relaysData); + for(let i = 0; i < keys.length; i++) + { + let line = keys[i];//line is turned off by default + let profilestr = relaysData[line].profile; + + //Reset linii + let resetLine = false; + if(FLOW.OMS_rvo_name == "Kovalov RVO 2" && line != '0' && init == true) resetLine = true; + + if(resetLine) + { + /* + + Takže v Koválove sú nastavené offesty pre dusk a dawn nasledovne: + + DUSK: offset +20 minút – teda napr. namiesto 17:00 bude 17:20 a reštart by sa robil v čase 17:19, teda o minútu skôr. Tak aby keď budeš robiť zapnutie o 17:20 tak na RVO1 sa svietidlá zapnú v rovnakom čase. Teda: vypnutie v čase DUSK_TIME + 19 minút, zapnutie v čase DUSK_TIME + 20 minút + DAWN: offset -30 minút – teda napr. namiesto 7:00 bude 6:30 a reštart by sa robil v čase 6:30, tak aby sa svietidlá zhasli rovnako s RVO1. Zapnutie by bolo 6:31. + + Teda: vypnutie v čase DAWN_TIME -30 minút, zapnutie v čase DAWN_TIME -29 minút + + Vždy po reštarte asi 30 sekúnd po zapnutí treba poslať aktuálny čas na nody. + */ + + //function calculateDuskDown(date, line, duskOffset = 0, dawnOffset = 0) + let duskOffset = 20; + let dawnOffset = -30; + let sunCalcResult = calculateDuskDown(new Date(), undefined, duskOffset, dawnOffset); + + console.log(sunCalcResult); + + //if(isDusk) time_points[t].value = 1;//sumrak - zapneme svetlo + //if(isDawn) time_points[t].value = 0;//vychod - vypneme svetlo + + //DUSK - sumrak + { + + //vypneme liniu a o minitu zapneme + { + let value = 0;//vypneme liniu + let isDusk = true; + let isDawn = false; + + let dusk_time = sunCalcResult.dusk_time; + if(dusk_time < now.getTime()) dusk_time = dusk_time + 24*60*60*1000;//1den + + let params = getParams(priorityTypes.relay_profile); + params.type = "relay"; + params.line = line; + params.value = value; + params.tbname = relaysData[line].tbname; + params.timestamp = dusk_time; + params.duskOffset = duskOffset; + params.useProfile = false; + + //once a day + params.addMinutesToTimestamp = 24*60; + + //this will be recalculated + params.isDusk = isDusk; + params.isDawn = isDawn; + + if(params.value == 0) params.info = "reset - KOVALOV - force turn off line: " + line; + else if(params.value == 1) params.info = "reset - KOVALOV - force turn on line: " + line; + + params.debug = true; + + //turn on/off line + tasks.push(params); + + console.log(params); + } + + //a o minutu zapneme + { + let value = 1;//zapneme liniu + let isDusk = true; + let isDawn = false; + + let dusk_time = sunCalcResult.dusk_time + 60*1000;//o minutu neskor po vypnuti zapneme + if(dusk_time < now.getTime()) dusk_time = dusk_time + 24*60*60*1000;//1den + + let params = getParams(priorityTypes.relay_profile); + params.type = "relay"; + params.line = line; + params.value = value; + params.tbname = relaysData[line].tbname; + params.timestamp = dusk_time; + params.duskOffset = duskOffset + 1; + params.useProfile = false; + + //once a day + params.addMinutesToTimestamp = 24*60; + + //this will be recalculated + params.isDusk = isDusk; + params.isDawn = isDawn; + + if(params.value == 0) params.info = "reset - KOVALOV - force turn off line: " + line; + else if(params.value == 1) params.info = "reset - KOVALOV - force turn on line: " + line; + + params.debug = true; + + //turn on/off line + tasks.push(params); + + console.log(params); + } + + + } + + //DAWN - vychod + { + //vypneme liniu a o minitu zapneme + { + let value = 0;//vypneme liniu + let isDusk = false; + let isDawn = true; + + let dawn_time = sunCalcResult.dawn_time; + if(dawn_time < now.getTime()) dawn_time = dawn_time + 24*60*60*1000;//1den + + let params = getParams(priorityTypes.relay_profile); + params.type = "relay"; + params.line = line; + params.value = value; + params.tbname = relaysData[line].tbname; + params.timestamp = dawn_time; + + params.dawnOffset = dawnOffset; + params.useProfile = false; + + //once a day + params.addMinutesToTimestamp = 24*60; + + //this will be recalculated + params.isDusk = isDusk; + params.isDawn = isDawn; + + if(params.value == 0) params.info = "reset - KOVALOV - force turn off line: " + line; + else if(params.value == 1) params.info = "reset - KOVALOV - force turn on line: " + line; + + params.debug = true; + + //turn on/off line + tasks.push(params); + + console.log(params); + } + + //a o minitu zapneme + { + let value = 1;//vypneme liniu + let isDusk = false; + let isDawn = true; + + let dawn_time = sunCalcResult.dawn_time + 1000*60;//o minutu neskor po vypnuti zapneme + if(dawn_time < now.getTime()) dawn_time = dawn_time + 24*60*60*1000;//1den + + let params = getParams(priorityTypes.relay_profile); + params.type = "relay"; + params.line = line; + params.value = value; + params.tbname = relaysData[line].tbname; + params.timestamp = dawn_time; + + params.dawnOffset = dawnOffset + 1; + params.useProfile = false; + + //once a day + params.addMinutesToTimestamp = 24*60; + + //this will be recalculated + params.isDusk = isDusk; + params.isDawn = isDawn; + + if(params.value == 0) params.info = "reset - KOVALOV - force turn off line: " + line; + else if(params.value == 1) params.info = "reset - KOVALOV - force turn on line: " + line; + + params.debug = true; + + //turn on/off line + tasks.push(params); + + console.log(params); + } + + + } + + //console.log("-------------------------Kovalov RVO 2----"); + } + + if(processLine != undefined) + { + if(processLine != line) continue; + } + + try{ + + if(profilestr === "") throw ("profile is not defined"); + let profile = JSON.parse(profilestr); + if(Object.keys(profile).length === 0) throw ("profile is not defined"); + + monitor.info("buildTasks: profile for line", line); + monitor.info("profile:", profile); + + let time_points = profile.time_points; + if(time_points == undefined) time_points = profile.intervals; + + monitor.info("buildTasks: time_points", time_points); + + let currentValue = 0; + if(time_points.length > 0) currentValue = time_points[ time_points.length - 1].value; + + //create task for tun on + turn off, calculate dusk/down + if(profile.astro_clock == true) + { + //let now = new Date().toLocaleString("en-US", {timeZone: "Europe/Bratislava"}); + let sunCalcResult = calculateDuskDown(new Date(), line); + + monitor.info("dusk and dawn sunCalcResult", line, sunCalcResult); + + //add to timpoints + if(profile.dawn_lux_sensor == false) time_points.push( {"start_time": sunCalcResult["dawn"], "value": 1, "isDawn": true} ); + if(profile.dusk_lux_sensor == false) time_points.push( {"start_time": sunCalcResult["dusk"], "value": 0, "isDusk": true} ); + + //aby nam to neostalo svietit + if(profile.dawn_lux_sensor == true) + { + //force to turn off after timestamp: dawn + dawn_lux_sensor_time_window + let [ahours, aminutes, aseconds] = sunCalcResult["dawn"].split(':'); + + let ad = new Date(); + ad.setHours( parseInt(ahours) ); + ad.setMinutes( parseInt(aminutes) + profile.dawn_lux_sensor_time_window ); + ad.setSeconds(0); + + let strDate = ad.getHours() + ":" + ad.getMinutes(); + + time_points.push( {"value": 0, "start_time": strDate} ); + } + + if(profile.dusk_lux_sensor == true) + { + //force to turn off after timestamp: dawn + dawn_lux_sensor_time_window + let [ahours, aminutes, aseconds] = sunCalcResult["dusk"].split(':'); + + let ad = new Date(); + ad.setHours( parseInt(ahours) ); + ad.setMinutes( parseInt(aminutes) + profile.dawn_lux_sensor_time_window ); + ad.setSeconds(0); + + let strDate = ad.getHours() + ":" + ad.getMinutes(); + + time_points.push( {"value": 1, "start_time": strDate} ); + } + } + + //sort time_points + 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(); + }); + + monitor.info("-->comming events turn on/off lines:"); + for(let t = 0; t < time_points.length; t++) + { + + let start_time = new Date(); + + let isDusk = false; + let isDawn = false; + if(time_points[t].hasOwnProperty("isDusk")) isDusk = time_points[t].isDusk; + if(time_points[t].hasOwnProperty("isDawn")) isDawn = time_points[t].isDawn; + + if(isDusk) time_points[t].value = 1;//sumrak - zapneme svetlo + if(isDawn) time_points[t].value = 0;//vychod - vypneme svetlo + + if(time_points[t].hasOwnProperty("start_time")) + { + let [hours, minutes, seconds] = time_points[t].start_time.split(':'); + + start_time.setHours( parseInt(hours) ); + start_time.setMinutes( parseInt(minutes) ); + start_time.setSeconds(0); + } + + //task is the past + if(now.getTime() > start_time.getTime()) + { + currentValue = time_points[t].value; + + //je v minulosti, pridame 24h + start_time.setDate(start_time.getDate() + 1); + } + + let params = getParams(priorityTypes.relay_profile); + params.type = "relay"; + params.line = line; + params.value = time_points[t].value; + params.tbname = relaysData[line].tbname; + params.timestamp = start_time.getTime(); + + params.addMinutesToTimestamp = 0; + + //once a day + if(!isDusk && !isDawn) params.addMinutesToTimestamp = 24*60; + + //inak sa cas vypocita dynamicky + + //this will be recalculated + params.isDusk = isDusk; + params.isDawn = isDawn; + + //if(profile.astro_clock == true && profile.dusk_lux_sensor == false && profile.dawn_lux_sensor == false) + + if(params.value == 0) + { + params.info = "turn off line: " + line; + if(isDusk) params.info = "dusk: turn off line: " + line; + if(isDawn) params.info = "dawn: turn off line: " + line; + } + else if(params.value == 1) + { + params.info = "turn on line: " + line; + if(isDusk) params.info = "dusk: turn on line: " + line; + if(isDawn) params.info = "dawn: turn on line: " + line; + } + + params.debug = true; + + //turn on/off line + tasks.push(params); + + monitor.info(params.info, start_time); + + } + + monitor.info("-->time_points final", line, time_points); + + //ensure to turn on/off according to calculated value + let params = getParams(priorityTypes.terminal); + params.type = "relay"; + params.line = parseInt(line); + params.tbname = relaysData[line].tbname; + params.value = currentValue; + params.isDusk = false; + params.isDawn = false; + + params.timestamp = priorityTypes.terminal; + params.addMinutesToTimestamp = 0; + params.debug = true; + + //logger.debug(now.toLocaleString("sk-SK")); + monitor.info("-->currentValue for relay", line, currentValue); + + //turn on/off line + if(params.value == 0) params.info = "turn off line on startup: " + line; + else if(params.value == 1) params.info = "turn on line on startup: " + line; + + tasks.push(params); + + + } catch (error) { + if(profilestr !=="" ) + { + //errLogger.error(profilestr, error); + errorHandler.sendMessageToService(profilestr + "-" + error, 0, "js_error"); + } + } + + } + + //logger.debug("tasks:"); + //logger.debug(tasks); + } + + + //PROCESS DEFAULT BROADCASTS + + //RPC pre nody / broadcast + //Time of dusk, Time of dawn + //Actual Time + + if(processBroadcast) + { + let addMinutesToTimestamp = 5; + + { + //run broadcast Time of dusk + addMinutesToTimestamp = 60*5; + + let params = getParams(priorityTypes.node_broadcast); + + let recipient = 2;//2 broadcast, address = 0 + let address = 0;//0 + if(recipient === 2) + { + address = 0xffffffff;//Broadcast + } + + let sunCalcResult = calculateDuskDown(); + let dusk_hours = sunCalcResult["dusk_hours"]; + let dusk_minutes = sunCalcResult["dusk_minutes"]; + + params.address = address;//broadcast + params.byte1 = dusk_hours;//h + params.byte2 = dusk_minutes;//m + params.byte3 = 0;//s + params.byte4 = 0; + params.recipient = recipient; + params.register = 6;//Time of dusk - Reg 6 + params.rw = 1;//write + + let timestampStart = priorityTypes.node_broadcast; + + //other values + params.type = "cmd"; + //params.tbname = tbname; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "run broadcast Time of dusk"; + + tasks.push(params); + + } + + { + + //run broadcast Time of dawn + addMinutesToTimestamp = 60*5; + + let params = getParams(priorityTypes.node_broadcast); + + let recipient = 2;//2 broadcast, address = 0 + let address = 0;//0 + if(recipient === 2) + { + address = 0xffffffff;//Broadcast + } + + let sunCalcResult = calculateDuskDown(); + let dawn_hours = sunCalcResult["dawn_hours"]; + let dawn_minutes = sunCalcResult["dawn_minutes"]; + + params.address = address;//broadcast + params.byte1 = dawn_hours;//h + params.byte2 = dawn_minutes;//m + params.byte3 = 0;//s + params.byte4 = 0; + params.recipient = recipient; + params.register = 7;//Time of dawn - Reg 6 + params.rw = 1;//write + + let timestampStart = priorityTypes.node_broadcast; + + //other values + params.type = "cmd"; + //params.tbname = tbname; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "run broadcast Time of dawn"; + + tasks.push(params); + } + + + { + //run broadcast //Actual time + addMinutesToTimestamp = 5; + + let params = getParams(priorityTypes.node_broadcast); + + let recipient = 2;//2 broadcast, address = 0 + let address = 0;//0 + if(recipient === 2) + { + address = 0xffffffff;//Broadcast + } + + var d = new Date(); + let hours = d.getHours(); + let minutes = d.getMinutes(); + let seconds = d.getSeconds(); + + params.address = address;//broadcast + params.byte1 = hours;//h + params.byte2 = minutes;//m + params.byte3 = seconds;//s + params.byte4 = 0; + params.recipient = recipient; + params.register = 87;//Actual time + params.rw = 1;//write + + let timestampStart = priorityTypes.node_broadcast; + + //other values + params.type = "cmd"; + //params.tbname = tbname; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "run broadcast: Actual time"; + + tasks.push(params); + + } + + { + //run broadcast Actual Lux level from cabinet + + //Do tohto registra posiela riadiaca jednotka hodnotu intenzity osvetlenia ktorú meria jej senzor pre potreby riadenia časov súmraku resp. úsvitu podľa intenzity osvetlenia. + //Byty 0 (LSB) a 1 obsahujú 16 bitový integer s luxami. + + let params = getParams(priorityTypes.node_broadcast); + + addMinutesToTimestamp = 15; + + let recipient = 2;//2 broadcast, address = 0 + let address = 0;//0 + if(recipient === 2) + { + address = 0xffffffff;//Broadcast + } + + //TODO + //16 bitový integer s luxami + params.byte3 = lux_sensor; + params.byte4 = lux_sensor; + params.timestamp = priorityTypes.node_broadcast; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "run broadcast: Actual Lux level from cabinet"; + params.register = 95;//Actual Lux level from cabinet + params.rw = 1;//write + + } + } + + //process nodes & tasks + //reportovanie pre platformu + if(processNodes) + { + for (let k in nodesData) { + let address = parseInt(k); + let tbname = nodesData[k].tbname; + let register = 0; + + //logger.debug("generated cmd - buildTasks for node:", address); + + //listOfCommands - READ + for(let i = 0; i < listOfCommands.length; i++) + { + register = listOfCommands[i]; + + let params = getParams(priorityTypes.node_cmd); + + //core rpc values + params.address = address; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 0; + params.recipient = 1; + params.register = register; + params.rw = 0; + + let addMinutesToTimestamp = priorities[register]; + + let timestampStart = priorityTypes.node_cmd; //run imediatelly in function runTasks + if(addMinutesToTimestamp > 1) + { + timestampStart = timestampStart + addMinutesToTimestamp * 60000; + } + + //other values + params.type = "cmd"; + params.tbname = tbname; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = addMinutesToTimestamp; + params.info = "generated cmd - buildTasks (node)"; + + //monitor last node && last command + /* + if(register == listOfCommands[ listOfCommands.length - 1 ]) + { + //if(k == 632) params.debug = true; + if(k == 698) params.debug = true; + } + */ + + tasks.push(params); + + } + } + } + + + + //niektore ulohy sa vygeneruju iba 1x pri starte!!! + if(!init) return; + + + //Priebežne (raz za cca 5 minút) je potrebné vyčítať z Master nodu verziu jeho FW. + //Jedná sa o register 10. Rovnaká interpretácia ako pri FW verzii nodu. + //Adresa mastera je 0. V prípade že kedykoľvek nastane situácia že Master Node neodpovedá (napríklad pri vyčítaní telemetrie z nodu nevráti žiadne dáta), + //tak treba vyreportovať string "NOK". + { + let params = getParams(priorityTypes.fw_detection); + params.type = "cmd"; + params.register = 4; + params.address = 0; + + let timestampStart = priorityTypes.fw_detection; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = 5; + params.tbname = FLOW.OMS_edgeName; + params.info = "Master node FW verzia"; + //params.debug = true; + + //this will set FLOW.OMS_masterNodeIsResponding + + tasks.push(params); + } + + //kazdu hodinu skontrolovat nastavenie profilov + { + //get exact datetime from services + //https://service-prod01.worksys.io/gettime + + let params = getParams(priorityTypes.fw_detection); + params.type = "process_profiles"; + + let timestampStart = priorityTypes.relay_profile; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = 60;//60 = every hour + params.info = "detekcia nespracovaných profilov linie a nodov"; + //params.debug = true; + + tasks.push(params); + } + + { + //get exact datetime from services + //https://service-prod01.worksys.io/gettime + //https://service-prod01.worksys.io/#main + //https://sa-prod01.worksys.io/ + //https://code-prod01.worksys.io/ + + let params = getParams(priorityTypes.fw_detection); + params.type = "ntp-gettime"; + + let timestampStart = priorityTypes.fw_detection; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = 60;//every hour + params.info = "https://service-prod01.worksys.io/gettime"; + //params.debug = true; + + //this will set FLOW.OMS_masterNodeIsResponding + + tasks.push(params); + } + + { + //edge_date_time + + let params = getParams(priorityTypes.node_cmd); + params.type = "edge_date_time"; + + let timestampStart = priorityTypes.node_cmd; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = 1; + params.tbname = FLOW.OMS_edgeName; + params.info = "reportovanie aktuálneho času na LM - EDGE-Date Time"; + //logger.debug("BUILD Master node FW verzia"); + tasks.push(params); + } + + { + //edge_date_time + + let params = getParams(priorityTypes.node_cmd); + params.type = "number_of_luminaires"; + + let timestampStart = priorityTypes.node_cmd + 1; + params.timestamp = timestampStart; + params.addMinutesToTimestamp = 1; + params.tbname = FLOW.OMS_edgeName; + params.info = "reportovanie number_of_luminaires"; + + tasks.push(params); + } + + monitor.info("tasks created:", tasks.length); + } + + function turnOnOffLinesAccordingToLuxSensor(lux_sensor_value) + { + //let dusk_hours = sunCalcResult["dusk_hours"]; + //let dusk_minutes = sunCalcResult["dusk_minutes"]; + + let duskTimeStamp; + let downTimeStamp; + + //prejedme si line s profilom, kde mame "astro_clock": true + + /* + "dawn_lux_sensor": true, + "dusk_lux_sensor": true, + "dawn_lux_sensor_value": 5, + "dusk_lux_sensor_value": 5, + "dawn_astro_clock_offset": 0, + "dusk_astro_clock_offset": 10, + "dawn_lux_sensor_time_window": 30, + "dusk_lux_sensor_time_window": 30, + "dawn_astro_clock_time_window": 60, + "dusk_astro_clock_time_window": 60 + */ + + //ak sme pred/po vychode a lux value <= lux_sensor_value, liniu zapneme + + //ak sme pred/po zapade a lux_value <= lux_sensor_value, liniu zapneme + + let now = new Date(); + let currentTimestamp = now.getTime(); + + let keys = Object.keys(relaysData); + for(let i = 0; i < keys.length; i++) + { + let line = keys[i];//line is turned off by default + let profilestr = relaysData[line].profile; + + try{ + + let profile = JSON.parse(profilestr); + if(Object.keys(profile).length === 0) throw ("profile is not defined"); + + if(profile.astro_clock == true) + { + let sunCalcResult = calculateDuskDown(date, line); + + //dawn: usvit/vychod - lux je nad hranicou - vypnem + //dusk: zapad pod hranicou - zapnem + + //"dawn_lux_sensor_time_window": 30, + //"dusk_lux_sensor_time_window": 30, + + //vychod + if(profile.dawn_lux_sensor == true) + { + let lux_sensor_time_window1 = sunCalcResult.dawn_time - parseInt( profile.dawn_lux_sensor_time_window ); + let lux_sensor_time_window2 = sunCalcResult.dawn_time + parseInt( profile.dawn_lux_sensor_time_window ); + + if(currentTimestamp >= lux_sensor_time_window1 && currentTimestamp <= lux_sensor_time_window2) + { + //dawn: usvit/vychod - lux je nad hranicou - vypnem + if(lux_sensor_value > profile.dawn_lux_sensor_value) + { + //vypnem + turnOffLine(line, "profile: dawn - turnOff line according to lux sensor"); + } + else + { + //zapnem + turnOnLine(line, "profile: dawn - turnOn line according to lux sensor"); + } + + } + + //ak sme po vychode + if(currentTimestamp > lux_sensor_time_window2) + { + //vypneme + //urobime jednorazovy prikaz + } + } + + //zapad + if(profile.dusk_lux_sensor == true) + { + let lux_sensor_time_window1 = sunCalcResult.dusk_time - parseInt( profile.dusk_lux_sensor_time_window ); + let lux_sensor_time_window2 = sunCalcResult.dusk_time + parseInt( profile.dusk_lux_sensor_time_window ); + + if(currentTimestamp >= lux_sensor_time_window1 && currentTimestamp <= lux_sensor_time_window2) + { + //dusk: zapad pod hranicou - zapnem + if(lux_sensor_value < profile.dusk_lux_sensor_value) + { + //zapnem + turnOnLine(line, "profile: dusk - turnOff line according to lux sensor"); + } + else + { + //vypnem + turnOffLine(line, "profile: dusk - turnOff line according to lux sensor"); + } + + } + } + + + } + + } catch (error) { + //if(profilestr !=="" ) logger.debug(profilestr, error); + } + } + } + + let sunCalcResult = calculateDuskDown(); + + let reportDuskDawn = { + dusk_time: sunCalcResult.dusk_time, + dawn_time: sunCalcResult.dawn_time, + dusk_time_reported: undefined, + dawn_time_reported: undefined + }; + + async function upateNodeStatus(node, status) + { + //MASTER + if(node == 0) return; + + let nodeObj = nodesData[node]; + if(nodeObj == undefined) return; + + if(status) + { + cmdNOKNodeCounter[node] = 0; + } + else cmdNOKNodeCounter[node]++; + + if(nodeObj.status !== status) + { + await dbNodes.modify({ status: status }).where("node", node).make(function(builder) { + builder.callback(function(err, response) { + if(err == null) nodesData[node].status = status; + }); + }); + } + } + + + async function runTasks() { + + clearInterval(interval); + + let currentTimestamp = Date.now(); + + //report dusk, dawn--------------------------------- + if(reportDuskDawn.dusk_time < currentTimestamp) + { + //vyreportuj iba ak nie je velky rozdiel napr. 60 sekund + if( (currentTimestamp - reportDuskDawn.dusk_time) < 60 * 1000) + { + //reportovali sme? + if(reportDuskDawn.dusk_time_reported != sunCalcResult.dusk_time) + { + sendNotification("CMD Manager: calculated Time of dusk", FLOW.OMS_edgeName, "dusk_has_occured", {value: sunCalcResult["dusk"]}, "", instanceSendTo.tb, instance); + reportDuskDawn.dusk_time_reported = sunCalcResult.dusk_time; + } + } + + var nextDay = new Date(); + nextDay.setDate(nextDay.getDate() + 1); + + sunCalcResult = calculateDuskDown(nextDay); + reportDuskDawn.dusk_time = sunCalcResult.dusk_time; + } + + if(reportDuskDawn.dawn_time < currentTimestamp) + { + //vyreportuj iba ak nie je velky rozdiel napr. 60 sekund + if( (currentTimestamp - reportDuskDawn.dusk_time) < 60 * 1000) + { + //reportovali sme? + if(reportDuskDawn.dawn_time_reported != sunCalcResult.dawn_time) + { + sendNotification("CMD Manager: calculated Time of dawn", FLOW.OMS_edgeName, "dawn_has_occured", {value: sunCalcResult["dawn"]}, "", instanceSendTo.tb, instance); + reportDuskDawn.dawn_time_reported = sunCalcResult.dawn_time; + } + } + + var nextDay = new Date(); + nextDay.setDate(nextDay.getDate() + 1); + + sunCalcResult = calculateDuskDown(nextDay); + reportDuskDawn.dawn_time = sunCalcResult.dawn_time; + + } + //-------------------------------------------------------- + + //sort tasks + //tasks.sort((a,b) => a.timestamp - b.timestamp ); + + tasks.sort(function (a, b) { + + if(a.timestamp <= currentTimestamp && b.timestamp <= currentTimestamp) + { + return a.priority - b.priority; + } + + return a.timestamp - b.timestamp; + }); + + if(tasks.length == 0 ) + { + instance.send(instanceSendTo.debug, "no tasks created"); + interval = setInterval(runTasks, longInterval); + + return; + } + + if(!rsPort.isOpen) + { + instance.send(instanceSendTo.debug, "!rsPort.isOpen"); + //await rsPort.open(); + + //continue + } + + let currentTask = tasks[0]; + + if(currentTask.debug) + { + //logger.debug("--->task to process", currentTask); + } + + if(currentTask.timestamp <= currentTimestamp) + { + let params = {...tasks[0]}; + + if(FLOW.OMS_maintenance_mode) + { + + //allow terminal commands + if(params.type == "cmd-terminal"); + else + { + interval = setInterval(runTasks, longInterval); + return; + } + } + + let type = params.type; + let tbname = params.tbname; + let nodeKey = params.address; + + let useProfile = params.useProfile; + if(useProfile === undefined) useProfile = true; + let duskOffset = params.duskOffset; + let dawnOffset = params.dawnOffset; + + let line = null; + //rpc related + if(nodesData[nodeKey] !== undefined) line = nodesData[nodeKey].line; + if(params.line !== undefined) line = params.line; + + let repeatTask = false; + if(params.addMinutesToTimestamp > 0) repeatTask = true; + if(params.isDawn || params.isDusk) repeatTask = true; + + if(repeatTask) + { + if(type == "cmd") + { + //set next start time automatically + tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + } + } + else + { + //terminal data... + tasks.shift(); + } + + //custom tasks + if(type == "number_of_luminaires") + { + tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + + //treba reportovat node status + { + //number_of_luminaires + //number_of_ok_luminaires + //number_of_nok_luminaires + + let keys = Object.keys(nodesData); + + let number_of_luminaires = keys.length; + let number_of_ok_luminaires = 0; + let number_of_nok_luminaires = 0; + + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + let nodeObj = nodesData[key]; + if(nodeObj.tbname == undefined) continue; + + if(nodeObj.status) number_of_ok_luminaires++; + else number_of_nok_luminaires++; + } + + let values = { + number_of_luminaires: number_of_luminaires, + number_of_ok_luminaires: number_of_ok_luminaires, + number_of_nok_luminaires: number_of_nok_luminaires + }; + + let dataToTb = { + [FLOW.OMS_edgeName]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + + interval = setInterval(runTasks, shortIterval); + + return; + } + } + + if(type == "ntp-gettime") + { + + RESTBuilder.make(function(builder) { + + if(!builder) return; + + builder.method('GET'); + //FLOW.OMS_edge_fw_version + builder.url('http://192.168.252.2:8004/gettime?projects_id=1'); + + builder.callback(function(err, response, output) { + + if (err) { + console.log(err); + return; + } + + instance.send(instanceSendTo.debug, "RESTBuilder response"); + const res = output.response; + + try{ + + const obj = JSON.parse(res); + let d = new Date(obj.date); + + const now = new Date(); + + //offset in minutes - convertUTCDateToLocalDate + let diffInMinutes = now.getTimezoneOffset(); + //d.setMinutes( d.getMinutes() + diffInMinutes ); + + //let converted = convertUTCDateToLocalDate(d); + + console.log("---->TimezoneOffset", diffInMinutes); + + if(d instanceof Date) + { + console.log("current js date:", d, d.getHours()); + + let year = d.getFullYear(); + let month = addZeroBefore(d.getMonth() + 1); + let day = addZeroBefore(d.getDate()); + + //-2 hodiny!!!! + let hours = addZeroBefore( d.getHours() ); + let minutes = addZeroBefore(d.getMinutes() ); + let seconds = addZeroBefore(d.getSeconds()); + + let timestamp = `${year}${month}${day} ${hours}:${minutes}:${seconds}`; + let dstr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + + logger.debug("--->RESTBuilder response", res, timestamp, dstr); + + //TODO - poslat notifikaciu a nastav hw cas + //timedatectl set-timezone "Europe/Bratislava" + //hwclock --set --date="2021-08-24 15:02:00" --localtime + + //https://www.tecmint.com/set-time-timezone-and-synchronize-time-using-timedatectl-command/ + + + //timedatectl set-time "2022-04-27 09:13:00" + { + + let year = d.getUTCFullYear(); + let month = addZeroBefore(d.getUTCMonth() + 1); + let day = addZeroBefore(d.getUTCDate()); + + let hours = addZeroBefore( d.getUTCHours() ); + let minutes = addZeroBefore(d.getUTCMinutes() ); + let seconds = addZeroBefore(d.getUTCSeconds()); + + let UTCstr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + + exec(`timedatectl set-time "${UTCstr}"`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + console.log(UTCstr); + + monitor.info("failed timedatectl set-time", err, stderr); + } + else + { + console.log(`UTC: timedatectl set-time "${UTCstr}"`); + } + + }); + } + + //RTC time - hardware time - ak je RTC in local TZ: yes - nastavime UTC preratany podla timezone + exec(`hwclock --set --date="${dstr}" --localtime`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + + monitor.info("failed to set date", dstr, res, err, stderr); + + } else { + console.log(stdout); + console.log(`Successfully set the system's datetime - ${dstr}`); + + const now = new Date(); + console.log(now); + + } + }); + + //detect Read-only file system + + ///dev/mmcblk0p2 on / type ext3 (rw,noatime,nodiratime,errors=remount-ro,commit=100,data=ordered) - (if ro = Read-only file system) + + //egrep " ro,|,ro " /proc/mounts + //mount + + exec(`egrep " ro,|,ro " /proc/mounts`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + + } else { + //console.log("Read-only", stdout); + + let lines = stdout + ""; + lines = lines.split("\n"); + + let readOnlyDetected = ""; + for(let i = 0; i < lines.length; i++) + { + if(lines[i].startsWith("/dev/mmcblk0p2")) + { + readOnlyDetected = lines[i]; + } + } + + if(readOnlyDetected !== "") + { + errorHandler.sendMessageToService("Detected: Read-only file system: " + readOnlyDetected); + } + + } + }); + + /* + //povodna verzia + exec(`sudo /bin/date --set="${timestamp}"`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + + monitor.info("failed to set date", d, res, err, stderr); + + } else { + console.log(stdout); + console.log(`Successfully set the system's datetime to ${stdout}`); + + const now = new Date(); + console.log(now); + + } + }); + */ + + } + + + } catch (error) { + logger.debug("--->ntp-gettime", error, res); + } + + + }); + }); + + tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + + interval = setInterval(runTasks, shortIterval); + + return; + } + + //kontrola nespracovanych profilov nodov + if(type == "process_profiles") + { + tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + + //select nespracovane nody + //node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean + + //buildTasks({processLineProfiles: true, line: line}); + + /* + let keys = Object.keys(nodesData); + for(let i = 0; i < keys.length; i++) + { + let node = keys[i]; + let line = node.line; + + if(node.processed) continue; + + if(relaysData[line] != undefined) + { + let relayStatus = relaysData[line].contactor; + if(relayStatus == 1) + { + //linia je zapnuta + //await loadRelaysData(flowdata.data.line); + } + } + + } + */ + + //vsetky linie kt. su zapnute, a spracuju sa nespracovane profily nodov + loadRelaysData(); + + interval = setInterval(runTasks, shortIterval); + return; + } + + if(type == "edge_date_time") + { + + //var d = new Date(); + //let hours = addZeroBefore(d.getHours()); + //let minutes = addZeroBefore(d.getMinutes()); + //let seconds = addZeroBefore(d.getSeconds()); + //let values = {"edge_date_time": `${hours}:${minutes}:${seconds}`}; + + let values = {"edge_date_time": Date.now()}; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + + interval = setInterval(runTasks, shortIterval); + + return; + } + + //relay + if(type == "relay") + { + + //ak je dusk, alebo dawn, vypocitame si dynamicky nove values + if(params.isDawn || params.isDusk) + { + let date = new Date(); + date.setDate(date.getDate() + 1);//next day + + let sunCalcResult; + if(useProfile) sunCalcResult = calculateDuskDown(date, params.line); + else + { + //do not use profile, line is there for undefined + sunCalcResult = calculateDuskDown(date, undefined, duskOffset, dawnOffset); + } + + if(params.isDawn) + { + tasks[0].timestamp = sunCalcResult.dawn_time; + } + + if(params.isDusk) + { + tasks[0].timestamp = sunCalcResult.dusk_time; + } + } + else + { + if(tasks[0].addMinutesToTimestamp == 0);// tasks.shift(); + else tasks[0].timestamp = currentTimestamp + tasks[0].addMinutesToTimestamp * 60000; + } + + let info; + if(useProfile) info = "aplikovaný bod profilu"; + else info = params.info; + + let message = ""; + if(params.value == 1) + { + turnOnLine(params.line, info); + message = "on"; + } + else if(params.value == 0) + { + turnOffLine(params.line, info); + message = "off"; + } + + //sendNotification("CMD Manager: process cmd", relaysData[0].tbname, ERRWEIGHT.INFO, "aplikovaný bod profilu línie " + params.line + " - stav: " + message, "", instanceSendTo.tb, instance, null ); + if(useProfile) sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "switching_profile_point_applied_to_line", {line: params.line, value: message}, "", instanceSendTo.tb, instance ); + + interval = setInterval(runTasks, shortIterval); + return; + } + + //zhodeny hlavny istic + let disconnected = false; + //if(rotary_switch_state == "Off") disconnected = true; + + //state_of_breaker[line] - alebo istic linie + if(state_of_breaker.hasOwnProperty(line)) + { + //if(state_of_breaker[line] == "Off") disconnected = true; + } + + //toto sa reportuje po prijati dat z di_do_controlera + if(disconnected) + { + + let values = {"status": "OFFLINE"}; + + logger.debug("disconnected", values); + logger.debug("rotary_switch_state", rotary_switch_state); + logger.debug("state_of_breaker", state_of_breaker[line]); + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //report only once! + if(!disconnectedReport.hasOwnProperty(tbname)) disconnectedReport[tbname] = false; + + if(!disconnectedReport[tbname]) + { + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + } + + interval = setInterval(runTasks, shortIterval); + + return; + } + + disconnectedReport[tbname] = false; + + //high_priority + if(!FLOW.OMS_masterNodeIsResponding) + { + //ak neodpoveda, nebudeme vykonavat ziadne commands, okrem cmd-terminal, a fw version + errorHandler.sendMessageToService("Master node is not responding"); + + let stop = true; + if(params.type == "cmd-terminal") stop = false; + + //fw version - register == 4 + if(params.type == "cmd" && params.register == 4 && params.address == 0) stop = false; + + if(stop) + { + interval = setInterval(runTasks, longInterval); + return; + } + + } + + let relayStatus = 1; + if(relaysData[line] != undefined) + { + relayStatus = relaysData[line].contactor; + } + + if(line == 0) relayStatus = 0; + if(params.type == "cmd-terminal") relayStatus = 1; + + //check if rotary_switch_state == "Off" + + if(relayStatus == 0) + { + //console.log("------------------------------------relayStatus", relayStatus, line); + let values = {"status": "OFFLINE"}; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + + interval = setInterval(runTasks, shortIterval); + + return; + } + + if(!rsPort.isOpen) + { + interval = setInterval(runTasks, longInterval); + return; + } + + //RE-CALCULATE VALUES + //set actual time for broadcast + if(params.register == 87 && params.recipient === 2) + { + var d = new Date(); + let hours = d.getHours(); + let minutes = d.getMinutes(); + let seconds = d.getSeconds(); + + params.byte1 = hours;//h + params.byte2 = minutes;//m + params.byte3 = seconds;//s + params.byte4 = 0; + } + + //set dusk/down for broadcast + + //Time of dusk + if(params.register == 6 && params.recipient === 2) + { + let sunCalcResult = calculateDuskDown(); + let dusk_hours = sunCalcResult["dusk_hours"]; + let dusk_minutes = sunCalcResult["dusk_minutes"]; + + params.byte1 = dusk_hours;//h + params.byte2 = dusk_minutes;//m + params.byte3 = 0;//s + params.byte4 = 0; + + //TODO astrohodiny + let dusk = "Time of dusk: " + sunCalcResult["dusk"]; + //sendNotification("CMD Manager: calculated Time of dusk", relaysData[0].tbname, ERRWEIGHT.INFO, dusk, "", instanceSendTo.tb, instance, null ); + } + + //Time of dawn + if(params.register == 7 && params.recipient === 2) + { + let sunCalcResult = calculateDuskDown(); + let dawn_hours = sunCalcResult["dawn_hours"]; + let dawn_minutes = sunCalcResult["dawn_minutes"]; + + params.byte1 = dawn_hours;//h + params.byte2 = dawn_minutes;//m + params.byte3 = 0;//s + params.byte4 = 0; + + //TODO astrohodiny + let dawn = "Time of dawn: " + sunCalcResult["dawn"]; + //sendNotification("CMD Manager: calculated Time of dusk", relaysData[0].tbname, ERRWEIGHT.INFO, dawn, "", instanceSendTo.tb, instance, null ); + } + //----------------------- + + + let register = params.register; + instance.send(instanceSendTo.debug, "address: " + params.address + " register:" + params.register + "type: " + params.type); + + var startTime, endTime; + startTime = new Date(); + + let resp = com_generic(params.address, params.recipient, params.rw, params.register, params.name, params.byte1, params.byte2, params.byte3, params.byte4); + + let readBytes = 11; + + //if broadcast WRITE - do not read + //if(params.recipient == 2) readBytes = 0; + + //WRITE + BROADCAST = readBytes = 0; + if(params.rw == 1 && params.recipient == 2) readBytes = 0; + + if(params.hasOwnProperty("debug")) + { + //console.log("--->readBytes", readBytes, params); + } + + await writeData(rsPort, resp, readBytes).then(function (data) { + + endTime = new Date(); + var timeDiff = endTime - startTime; + + //--1-4 adresa, 5 status ak je status 0 - ok, nasleduju 4 byty data + //let bytes = data.slice(0); + let bytes = data; + let dataBytes = data.slice(5,9); + + let result = detectIfResponseIsValid(bytes); + + let message = result.message; + let type = result.type; + let error = result.error; + + //ak sa odpoved zacina 0 - je to v poriadku, inak je NOK + + if(params.debug != "generated cmd") + { + //debug("writeData: done " + type + " duration: " + timeDiff + " type: " + params.debug, params); + } + + if(params.hasOwnProperty("debug")) + { + if(params.debug) + { + console.log("detected response:", result); + + logger.debug("writeData: done " + type + " duration: " + timeDiff + " type: " + params.debug, params, result); + } + } + + //debug("writeData: done " + type + " duration: " + timeDiff + " type: " + params.debug); + //debug("writeData done", type, "duration", timeDiff, "type", params.debug, result); + + let tbname = params.tbname; + + let saveToTb = true; + if(tbname == null || tbname == undefined || tbname == "") saveToTb = false; + //-- + + //CMD FINISHED + if(message == "OK") + { + + upateNodeStatus(params.address, true); + + //write + if(params.type == "set_node_profile") + { + let result = cmdCounterResolve(params.address); + if(result == 0) + { + + dbNodes.modify({ processed: true }).where("node", params.address).make(function(builder) { + + builder.callback(function(err, response) { + + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "dimming_profile_was_successfully_received_by_node", {node: params.address}, "", instanceSendTo.tb, instance ); + + logger.debug( "--> profil úspešne odoslaný na node č. " + params.address); + nodesData[params.address].processed = true; + + }); + }); + } + } + + //parse read response + let values = {}; + if(params.rw == 0) { + values = processResponse(register, dataBytes);//read + } + if(params.rw == 1) + { //write command + //set command dimming + if(params.register == 1) values = {"comm_status": message}; + } + + if(params.register == 0) values["status"] = message; + + //fw version - register == 4 + if(params.register == 4) values["edge_fw_version"] = FLOW.OMS_edge_fw_version; + + if(params.address == 0) + { + //sendNotification("CMD Manager: process cmd", relaysData[0].tbname, ERRWEIGHT.NOTICE, "Master node is working again", "", instanceSendTo.tb, instance, "rvo_status" ); + //sendNotification("CMD Manager: process cmd", relaysData[0].tbname, "master_node_is_responding_again", {}, "", instanceSendTo.tb, instance, "rvo_status" ); + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "master_node_is_responding_again", {}, "", instanceSendTo.tb, instance, "rvo_status" ); + FLOW.OMS_masterNodeIsResponding = true; + } + + //odoslanie príkazu z terminálu - dáta + if(params.type == "cmd-terminal") + { + //sendNotification("CMD Manager: process cmd", relaysData[0].tbname, ERRWEIGHT.DEBUG, "odoslanie príkazu z terminálu", params, instanceSendTo.tb, instance, null ); + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "command_was_sent_from_terminal_interface", {}, params, instanceSendTo.tb, instance ); + } + + if(params.debug) + { + logger.debug("saveToTb", saveToTb, tbname, values); + } + + if(saveToTb) + { + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + + } + else + { + + if(params.type == "cmd-terminal") + { + if(params.refFlowdataKey != undefined) + { + + logger.debug("cmd-terminal SUCCESS"); + logger.debug(currentTask); + + //make http response + let responseObj = {}; + responseObj["type"] = "SUCESS"; + responseObj["bytes"] = data; + + //params.refFlowdata.data = responseObj; + //instance.send(instanceSendTo.http_response, params.refFlowdata); + + let refFlowdata = refFlowdataObj[ params.refFlowdataKey ]; + refFlowdata.data = responseObj; + instance.send(instanceSendTo.http_response, refFlowdata); + + } + else + { + console.log("params.refFlowdataKey is undefined", params); + } + } + + } + + } + else + { + + upateNodeStatus(params.address, false); + + if(params.refFlowdataKey != undefined) + { + + logger.debug("cmd-terminal FAILED"); + logger.debug(currentTask); + + //make http response + let responseObj = {}; + responseObj["type"] = "ERROR"; + responseObj["bytes"] = data; + + //params.refFlowdata.data = responseObj; + //instance.send(instanceSendTo.http_response, params.refFlowdata); + + let refFlowdata = refFlowdataObj[ params.refFlowdataKey ]; + if(refFlowdata !== undefined) + { + refFlowdata.data = responseObj; + instance.send(instanceSendTo.http_response, refFlowdata); + } + + + } + + /* + if(params.type == "cmd-terminal") + { + if(params.refFlowdata != undefined) + { + + logger.debug("cmd-terminal FAILED"); + logger.debug(currentTask); + + //make http response + let responseObj = {}; + responseObj["type"] = "ERROR"; + responseObj["bytes"] = data; + + params.refFlowdata.data = responseObj; + instance.send(instanceSendTo.http_response, params.refFlowdata); + + } + } + */ + + if(params.address == 0) + { + //sendNotification("CMD Manager: process cmd", relaysData[0].tbname, ERRWEIGHT.ALERT, "Master node not responding", "", instanceSendTo.tb, instance, "rvo_status"); + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "master_node_is_not_responding", {}, "", instanceSendTo.tb, instance, "rvo_status"); + logger.debug("master_node_is_not_responding", params); + FLOW.OMS_masterNodeIsResponding = false; + } + + if(params.type == "set_node_profile") + { + delete cmdCounter[params.address]; + let tbname = nodesData[ params.address ].tbname; + + logger.debug( "profil nebol úspešne odoslaný na node č. ", params, result, resp); + + //sendNotification("CMD Manager: process cmd", tbname, ERRWEIGHT.ALERT, "profil nebol úspešne odoslaný na node č. " + params.address, "", instanceSendTo.tb, instance, null ); + sendNotification("CMD Manager: process cmd", tbname, "configuration_of_dimming_profile_to_node_failed", {node: params.address}, "", instanceSendTo.tb, instance ); + } + + //is it node? + if(nodesData.hasOwnProperty(params.address)) + { + if(cmdNOKNodeCounter[params.address] < 5) saveToTb = false; + } + + //Master node version + //if(params.register == 4 && saveToTb) + if(saveToTb) + { + let values = { + "status": "NOK" + }; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + } + + //instance.send(instanceSendTo.debug, result); + + if(params.hasOwnProperty("debug")) + { + if(params.debug) + { + logger.debug("writeData err: ", error, result, params); + } + } + + //logger.debug(error, result, params); + } + + }).catch(function (reason) { + + console.log("writeData catch exception", reason); + logger.debug(currentTask); + + if(params.refFlowdataKey != undefined) + { + + logger.debug("catch: cmd-terminal FAILED"); + logger.debug(currentTask); + + //make http response + let responseObj = {}; + responseObj["type"] = "ERROR";// + responseObj["message"] = "ERROR WRITE FAILED: " + reason;// + + //params.refFlowdata.data = responseObj; + //instance.send(instanceSendTo.http_response, params.refFlowdata); + + let refFlowdata = refFlowdataObj[ params.refFlowdataKey ]; + if(refFlowdata !== undefined) + { + refFlowdata.data = responseObj; + instance.send(instanceSendTo.http_response, refFlowdata); + } + + + } + /* + if(params.type == "cmd-terminal") + { + if(params.refFlowdata != undefined) + { + + logger.debug("cmd-terminal FAILED"); + logger.debug(currentTask); + + //make http response + let responseObj = {}; + responseObj["type"] = "ERROR WRITE FAILED: " + reason; + //responseObj["bytes"] = data; + + params.refFlowdata.data = responseObj; + instance.send(instanceSendTo.http_response, params.refFlowdata); + + //refFlowdata = undefined; + } + } + */ + + if(params.hasOwnProperty("debug")) + { + if(params.debug) + { + logger.debug("-->WRITE FAILED: " + reason, params.debug, params); + } + } + + upateNodeStatus(params.address, false); + + let tbname = params.tbname; + + let saveToTb = true; + if(tbname == null || tbname == undefined || tbname == "") saveToTb = false; + + if(params.address == 0) + { + //sendNotification("CMD Manager: process cmd", relaysData[0].tbname, ERRWEIGHT.ALERT, "Master node not responding", "", instanceSendTo.tb, instance, "rvo_status"); + sendNotification("CMD Manager: process cmd", FLOW.OMS_edgeName, "master_node_is_not_responding", {}, "", instanceSendTo.tb, instance, "rvo_status"); + logger.debug("master_node_is_not_responding", params); + + FLOW.OMS_masterNodeIsResponding = false; + } + + if(params.type == "set_node_profile") + { + delete cmdCounter[params.address]; + let tbname = nodesData[ params.address ].tbname; + + logger.debug( "profil nebol úspešne odoslaný na node č. ", params, resp); + + //sendNotification("CMD Manager: process cmd", tbname, ERRWEIGHT.ALERT, "odosielanie profilu na node č. " + params.address + " zlyhalo", "", instanceSendTo.tb, instance, null ); + sendNotification("CMD Manager: process cmd", tbname, "configuration_of_dimming_profile_to_node_failed", {node: params.address}, "", instanceSendTo.tb, instance ); + } + + //is it node? + if(nodesData.hasOwnProperty(params.address)) + { + if(cmdNOKNodeCounter[params.address] < 5) saveToTb = false; + } + + //Master node version + if(params.register == 4 && saveToTb) + { + let values = { + "status": "NOK", + "master_node_version": "NOK" + }; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + + FLOW.OMS_masterNodeIsResponding = false; + } + //treba? + /* + else if(saveToTb) + { + let values = { + "comm_status": "no_comm" + }; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + instance.send(instanceSendTo.tb, dataToTb); + } + */ + + instance.send(instanceSendTo.debug, reason); + }); + + } + else + { + if(currentTask.debug) + { + //currentTask.timestamp <= currentTimestamp + logger.debug("currentTask is not processed - task is in the future", currentTask); + } + + interval = setInterval(runTasks, longInterval); + return; + } + + //console.log("----->runTasks - setInterval", new Date()); + interval = setInterval(runTasks, shortIterval); + } + + //! rsPort LM = "/dev/ttymxc4", rsPort UNIPI = "/dev/ttyUSB0" + // const rsPort = new SerialPort("/dev/ttymxc4", { autoOpen: false }); //LM + // const rsPort = new SerialPort("/dev/ttyUSB0", { autoOpen: false }); // UNIPI + + if(FLOW.OMS_serial_port == "") FLOW.OMS_serial_port = "ttymxc4"; + if(FLOW.OMS_serial_port == undefined) FLOW.OMS_serial_port = "ttymxc4"; + if(FLOW.OMS_serial_port.length === 1) FLOW.OMS_serial_port = "ttymxc4"; + + const rsPort = new SerialPort(`/dev/${FLOW.OMS_serial_port}`, { autoOpen: false }); + //(node:16372) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 13 data listeners added to [SerialPort]. Use emitter.setMaxListeners() to increase limit + //rsPort.setMaxListeners(0); + + rsPort.on('open', async function() { + + logger.debug("CMD manager - rsPort opened sucess"); + + await loadRelaysData(); + + await runSyncExec(`stty -F /dev/${FLOW.OMS_serial_port} 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke`).then(function (status) { + instance.send(instanceSendTo.debug, "RPC runSyncExec - Promise Resolved:" + status); + + logger.debug(0, "RPC runSyncExec - Promise Resolved:" + status); + + //APP START + let dataToInfoSender = {id: FLOW.OMS_projects_id, name: FLOW.OMS_rvo_name}; + dataToInfoSender.fw_version = FLOW.OMS_edge_fw_version; + dataToInfoSender.startdate = new Date().toISOString().slice(0, 19).replace('T', ' '); + dataToInfoSender.__force__ = true; + + instance.send(instanceSendTo.infoSender, dataToInfoSender); + + logger.debug(0, "---------------------------->START message send to service", dataToInfoSender); + + //---- + + nodesData = {}; + + dbNodes.find().make(function(builder) { + builder.callback(function(err, response) { + + for(let i = 0; i < response.length; i++) + { + let node = response[i]; + let key = node["node"]; + + nodesData[ key ] = node; + } + + //buildTasks(); + //interval = setInterval(runTasks, longInterval); + + }); + }); + + }).catch(function (reason) { + instance.send(instanceSendTo.debug, "CMD manager - RPC runSyncExec - promise rejected:" + reason); + }); + + }); + + rsPort.on('error', function(err) { + + //TODO report to service!!! + //errLogger.error(exports.title, "unable to open port", FLOW.OMS_serial_port, err.message); + errorHandler.sendMessageToService([exports.title, "unable to open port", FLOW.OMS_serial_port, err.message], 0); + + instance.send(instanceSendTo.debug, err.message); + }); + + rsPort.on("close", () => { + rsPort.close(); + }); + + //loadRelaysData(); + rsPort.open(); + + instance.on("close", () => { + clearInterval(interval); + rsPort.close(); + }); + + //onData + instance.on("data", async function(flowdata) { + //instance.on("data", (data) => { + + //instance.send(instanceSendTo.debug, "on Data"); + //instance.send(instanceSendTo.debug, flowdata); + + //logger.debug(flowdata.data); + + //just testing functions + if(flowdata.data == "open") + { + if(!rsPort.isOpen) rsPort.open(); + return; + } + else if(flowdata.data == "close") + { + rsPort.close(); + return; + } + else if(flowdata.data == "clean") + { + tasks = []; + return; + } + else if(flowdata.data == "buildtasks") + { + //build & run + return; + } + else if(flowdata.data == "run") + { + //durations = []; + + if(tasks.length == 0) + { + + buildTasks(); + + if(rsPort.isOpen) + { + interval = setInterval(runTasks, 100); + } + else + { + instance.send(instanceSendTo.debug, "port is not opened!!!"); + } + } + } + else + { + //terminal data - object + //logger.debug("flowdata", flowdata.data); + + if(typeof flowdata.data === 'object') + { + //logger.debug("dido", flowdata.data); + if(flowdata.data.hasOwnProperty("sender")) + { + //data from di_do_controller + if(flowdata.data.sender == "di_do_controller") + { + + if(flowdata.data.hasOwnProperty("cmd")) + { + let cmd = flowdata.data.cmd; + + + if(cmd == "buildTasks") + { + clearInterval(interval); + + logger.debug("-->CMD MANAGER - BUILD TASKS"); + buildTasks(); + + //logger.debug("tasks:"); + //logger.debug(tasks); + + logger.debug("-->CMD MANAGER - RUN TASKS"); + interval = setInterval(runTasks, longInterval); + } + else if(cmd == "reload_relays") + { + await loadRelaysData(flowdata.data.line); + + if(flowdata.data.dataChanged) + { + if(!flowdata.data.value) + { + reportOfflineNodeStatus(flowdata.data.line); + } + else + { + reportOnlineNodeStatus(flowdata.data.line); + } + } + + } + else if(cmd == "rotary_switch_state") + { + //state was changed + if(rotary_switch_state != flowdata.data.value) + { + + if(flowdata.data.value == "Off") + { + //vyreportovat vsetky svietdla + reportOfflineNodeStatus(); + } + else reportOnlineNodeStatus(undefined, flowdata.data.value); + + } + + rotary_switch_state = flowdata.data.value; + + } + else if(cmd == "lux_sensor") + { + lux_sensor = parseInt(flowdata.data.value); + + //process profiles + turnOnOffLinesAccordingToLuxSensor(lux_sensor); + } + else if(cmd == "state_of_breaker") + { + //istic linie + let value = flowdata.data.value; + let line = parseInt(flowdata.data.line); + + let dataChanged = false; + if(state_of_breaker[line] != value) dataChanged = true; + + state_of_breaker[line] = value; + + let status = "OK"; + let weight = ERRWEIGHT.NOTICE; + let message = `zapnutý istič línie č. ${line}`; + if(value == "Off") + { + weight = ERRWEIGHT.ERROR; + message = `vypnutý istič línie č. ${line}`; + status = "NOK"; + } + + if(dataChanged) { + + if(relaysData.hasOwnProperty(line)) + { + let tbname = relaysData[line].tbname; + + if(value == "Off") sendNotification("CMD Manager: onData", tbname, "circuit_breaker_was_turned_off_line", {line: line}, "", instanceSendTo.tb, instance, "circuit_breaker"); + else sendNotification("CMD Manager: onData", tbname, "circuit_breaker_was_turned_on_line", {line: line}, "", instanceSendTo.tb, instance, "circuit_breaker"); + + //report status liniu + let values = { + "status": status + }; + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //instance.send(instanceSendTo.tb, dataToTb); + tbHandler.sendToTb(dataToTb, instance); + + //current value + if(value == "Off") + { + //vyreportovat vsetky svietdla na linii + reportOfflineNodeStatus(line); + } + else reportOnlineNodeStatus(line); + } + + } + } + else{ + logger.debug("undefined cmd", cmd); + } + } + } + + return; + } + + //data from worksys + if(flowdata.data.hasOwnProperty("topic")) + { + + let data = flowdata.data.content.data; + + let command = data.params.command; + let method = data.method; + let profile = data.params.payload; + if(profile == undefined) profile = ""; + let entity = data.params.entities[0]; + let entity_type = entity.entity_type; + let tbname = entity.tb_name; + + instance.send(instanceSendTo.debug, flowdata.data); + logger.debug("--->worksys", flowdata.data, data.params, entity, entity_type, command, method); + logger.debug("----------------------------"); + + if(entity_type == "street_luminaire") + { + if(method == "set_command") + { + + //let command = data.params.command; + let value = data.params.payload.value; + + if(command == "dimming") + { + + let nodeWasFound = false; + let keys = Object.keys(nodesData); + + //logger.debug("-----", keys); + + for(let i = 0; i < keys.length; i++) + { + let node = keys[i]; + //logger.debug( node, nodesData[node], tbname); + + if(tbname == nodesData[node].tbname.trim()) + { + let params = getParams(priorityTypes.high_priority); + + value = parseInt(value); + if(value > 0) value = value + 128; + + //set dimming - LUM1_13 - 647 je node linie 1 kt. dobre vidime + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 1;//dimming + params.recipient = 1;//slave + params.byte4 = value; + params.rw = 1;//write + params.timestamp = priorityTypes.high_priority; + params.info = 'set dimming from platform'; + //params.debug = true; + + //ak linia je + + //debug(params); + logger.debug("dimming", params); + + tasks.push(params); + + setTimeout(function(){ + + //spustime o 4 sekundy neskor, s prioritou priorityTypes.high_priority + //a pridame aj vyreportovanie dimmingu + { + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 1;//dimming + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = priorityTypes.high_priority; + params.info = 'read dimming (after set dimming from platform)'; + params.debug = true; + + tasks.push(params); + } + + //pridame aj vyreportovanie - vykon + { + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 76; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = priorityTypes.high_priority; + params.info = 'read Input Power (after set dimming from platform)'; + params.debug = true; + + tasks.push(params); + } + + //pridame aj vyreportovanie - prud svietidla + { + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 75; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = priorityTypes.high_priority; + params.info = 'read Input Current (after set dimming from platform)'; + params.debug = true; + + tasks.push(params); + } + + //pridame aj vyreportovanie - power faktor - ucinnik + { + let params = getParams(priorityTypes.high_priority); + + params.type = "cmd"; + params.tbname = tbname; + params.address = node; + params.register = 77; + params.recipient = 1;//slave + params.rw = 0;//read + params.timestamp = priorityTypes.high_priority; + params.info = 'read power factor - Cos phi (after set dimming from platform)'; + params.debug = true; + + tasks.push(params); + } + + },4000); + + + nodeWasFound = true; + + break; + } + } + + if(!nodeWasFound) + { + logger.debug("set dimming from platform", "unable to find tbname", tbname); + } + } + else + { + instance.send(instanceSendTo.debug, "undefined command " + command); + logger.debug("undefined command", command); + } + + return; + + } + else if(method == "set_profile") + { + //nastav profil nodu + logger.debug("-->set_profile for node", data.params); + logger.debug("------profile data", profile); + //instance.send(instanceSendTo.debug, "set_profile" + command); + + let keys = Object.keys(nodesData); + for(let i = 0; i < keys.length; i++) + { + let node = keys[i]; + if(tbname == nodesData[node].tbname.trim()) + { + + if(profile != "") profile = JSON.stringify(profile); + dbNodes.modify({ processed: false, profile: profile }).where("node", node).make(function(builder) { + + builder.callback(function(err, response) { + + logger.debug("worksys - update node profile done", profile); + if(profile === "") logger.debug("worksys - update node profile done - profile is empty"); + + //profil úspešne prijatý pre node č. xx + //sendNotification("CMD manager", tbname, ERRWEIGHT.INFO, `profil úspešne poslaný z platformy na RVO pre node č. ${node}`, profile, instanceSendTo.tb, instance, null ); + sendNotification("CMD manager", tbname, "dimming_profile_was_processed_for_node", {node: node}, profile, instanceSendTo.tb, instance ); + + nodesData[node].processed = false; + nodesData[node].profile = profile; + + let line = nodesData[node].line; + processNodeProfile(node); + + }); + }); + } + } + } + else + { + + instance.send(instanceSendTo.debug, "unknown method " + method); + logger.debug("unknown method", method); + + return; + } + + } + + //nastav profil linie z platformy + else if(entity_type == "edb_line" || entity_type == "edb") + { + //profil linie + //relays.table line:number|tbname:string|contactor:number|profile:string + //najdeme line relaysData + + if(method == "set_profile") + { + + logger.debug("-->set_profile for line", data.params); + logger.debug("profile data:", profile); + + let keys = Object.keys(relaysData); + for(let i = 0; i < keys.length; i++) + { + let line = keys[i]; + if(tbname == relaysData[line].tbname) + { + //zmazeme tasky + removeTask({type: "relay", line: line}); + + if(profile != "") profile = JSON.stringify(profile); + dbRelays.modify({ profile: profile }).where("line", line).make(function(builder) { + + builder.callback(function(err, response) { + + //update profile + logger.debug("worksys - update relay profile done:", profile); + instance.send(instanceSendTo.debug, "worksys - update relay profile done"); + + loadRelaysData(line).then(function (data) { + logger.debug("loadRelaysData DONE for line", line); + buildTasks({processLineProfiles: true, line: line}); + }); + + sendNotification("CMD manager - set profile from worksys", tbname, "switching_profile_was_processed_for_line", {line: line}, profile, instanceSendTo.tb, instance ); + + }); + }); + + break; + } + } + } + else if(method == "set_command") + { + let value = data.params.payload.value; + + if(command === "switch") + { + + let responseRelays = await promisifyBuilder(dbRelays.find().where("tbname", tbname)); + + let line = 0; + if(responseRelays.length == 1) line = responseRelays[0].line; + + if(value == false) turnOffLine(line, "command received form platform"); + else turnOnLine(line, "command received form platform"); + } + + } + else + { + instance.send(instanceSendTo.debug, "undefined method " + method); + logger.debug("undefined method", method); + } + + return; + + } + else{ + instance.send(instanceSendTo.debug, "UNKNOW entity_type " + entity_type); + logger.debug("UNKNOW entity_type", entity_type); + } + + return; + } + + //terminal + if(!rsPort.isOpen) await rsPort.open(); + + let params = flowdata.data.body; + if(params == undefined) + { + //logger.debug("CMD manager flowdata.data.body is undefined"); + return; + } + + params.priority = priorityTypes.terminal; + params.type = "cmd-terminal"; + params.tbname = ""; + params.timestamp = priorityTypes.terminal; + params.addMinutesToTimestamp = 0;// do not repeat task!!! + params.debug = true; + + let timestamp = Date.now(); + params.refFlowdataKey = timestamp; + //params.refFlowdata = flowdata; + //refFlowdata = flowdata; + + //console.log("flowdata", flowdata); + + cleanUpRefFlowdataObj(); + + refFlowdataObj[ timestamp ] = flowdata; + + //fix + //params.address = params.adress; + logger.debug("received from terminal", params); + logger.debug("date/time:", new Date()); + logger.debug("tasks length:", tasks.length); + + //tasks = []; + + //add to tasks + tasks.push(params); + + } + } + }) +} + + + + + + + + + + + + + + + + + + + + + + +///helper functions + +function calculateDuskDown(date, line, duskOffset = 0, dawnOffset = 0) +{ + + if(date === undefined) date = new Date(); + //if(duskOffset === undefined) duskOffset = 0; + //if(dawnOffset === undefined) dawnOffset = 0; + + //let line = keys[i]; + let profilestr = ""; + if(relaysData[line] != undefined) profilestr = relaysData[line].profile; + + let result = {}; + + var times = SunCalc.getTimes(date, latitude, longitude); + let dawn = new Date(times.sunrise);//usvit + let dusk = new Date(times.sunset);//sumrak + + + //http://suncalc.net/#/48.5598,18.169,11/2021.04.07/11:06 + //https://mapa.zoznam.sk/zisti-gps-suradnice-m6 + + + let dusk_astro_clock_offset = duskOffset;//minutes + let dawn_astro_clock_offset = dawnOffset;//minutes + + try{ + + let profile = JSON.parse(profilestr); + if(Object.keys(profile).length === 0) throw ("profile is not defined"); + + //Jednoduchý režim + if(profile.astro_clock == false && profile.dusk_lux_sensor == false && profile.dawn_lux_sensor == false) + { + + } + + //Režim astrohodín + if(profile.astro_clock == true) + { + //if(profile.dusk_lux_sensor == false) + { + if(profile.hasOwnProperty("dusk_astro_clock_offset")) dusk_astro_clock_offset = parseInt( profile.dusk_astro_clock_offset ); + } + + //if(profile.dawn_lux_sensor == false) + { + if(profile.hasOwnProperty("dawn_astro_clock_offset")) dawn_astro_clock_offset = parseInt( profile.dawn_astro_clock_offset ); + } + + } + + //dusk - súmrak + //down, sunrise - svitanie + + } catch (error) { + if(profilestr != "") + { + logger.debug(profilestr); + logger.debug(error); + } + } + + result.dusk_no_offset = addZeroBefore(dusk.getHours()) + ":" + addZeroBefore(dusk.getMinutes()); + result.dawn_no_offset = addZeroBefore(dawn.getHours()) + ":" + addZeroBefore(dawn.getMinutes()); + + dusk = new Date(dusk.getTime() + gmtOffset + dusk_astro_clock_offset*60000); + dawn = new Date(dawn.getTime() + gmtOffset + dawn_astro_clock_offset*60000); + + result.dusk = addZeroBefore(dusk.getHours()) + ":" + addZeroBefore(dusk.getMinutes()); + result.dusk_hours = dusk.getHours(); + result.dusk_minutes = dusk.getMinutes(); + + result.dawn = addZeroBefore(dawn.getHours()) + ":" + addZeroBefore(dawn.getMinutes()); + result.dawn_hours = dawn.getHours(); + result.dawn_minutes = dawn.getMinutes(); + + result.dusk_time = dusk.getTime(); + result.dawn_time = dawn.getTime(); + + result.dusk_astro_clock_offset = dusk_astro_clock_offset; + result.dawn_astro_clock_offset = dawn_astro_clock_offset; + + return result; +} + +function processResponse(register, bytes) +{ + + let values = {}; + + let byte3 = bytes[0]; + let byte2 = bytes[1]; + let byte1 = bytes[2]; + let byte0 = bytes[3]; + + //status + if(register == 0) + { + let statecode = bytesToInt(bytes); + values = {"statecode": statecode}; + return values; + } + + //Dimming, CCT + if(register == 1) + { + let brightness = 0; + let dimming = byte0; + if(dimming > 128) { + //dimming = -128; + brightness = dimming - 128; + } + + //cct + //Ak Byte3 == 1: CCT = (Byte2*256)+Byte1 + let cct; + if(byte3 == 1) cct = byte2*256 + byte1; + else cct = bytesToInt(bytes.slice(0, 3)); + + //cct podla auditu + + values["dimming"] = brightness; + return values; + } + + // + if(register == 4) + { + values["master_node_version"] = bytes[1] + "." + bytes[2]; + //logger.debug("FW Version", register, bytes); + } + + //Napätie + if(register == 74) + { + let voltage = (bytesToInt(bytes) * 0.1).toFixed(1); + values["voltage"] = Number(voltage); + } + + //Prúd + if(register == 75) + { + let current = bytesToInt(bytes); + values["current"] = current; + } + + //výkon + if(register == 76) + { + let power = (bytesToInt(bytes) * 0.1).toFixed(2); + values["power"] = Number(power); + } + + //účinník + if(register == 77) + { + let power_factor = Math.cos(bytesToInt(bytes) * 0.1).toFixed(2); + values["power_factor"] = Number(power_factor); + } + + //frekvencia + if(register == 78) + { + let frequency = (bytesToInt(bytes) * 0.1).toFixed(2); + values["frequency"] = Number(frequency); + } + + //energia + if(register == 79) + { + let energy = bytesToInt(bytes); + + //Energiu treba reportovať v kWh. Teda číslo, ktoré príde treba podeliť 1000. Toto som ti možno zle napísal. + + values["energy"] = energy / 1000; + } + + //doba života + if(register == 80) + { + let lifetime = ( bytesToInt(bytes) / 60).toFixed(2); + values["lifetime"] = Number(lifetime); + } + + //nastavenie profilu + if(register == 8) + { + let time_schedule_settings = bytesToInt(bytes); + values["time_schedule_settings"] = time_schedule_settings; + } + + //skupinová adresa 1 + if(register == 3) + { + let gr_add_1 = bytesToInt(byte0); + values["gr_add_1"] = gr_add_1; + + let gr_add_2 = bytesToInt(byte1); + values["gr_add_2"] = gr_add_2; + + let gr_add_3 = bytesToInt(byte2); + values["gr_add_3"] = gr_add_3; + + let gr_add_4 = bytesToInt(byte3); + values["gr_add_4"] = gr_add_4; + } + + //naklon + if(register == 84) + { + let temp; + if(byte3 >= 128) + { + temp = (byte3 - 128) * (-1); + } + else + { + temp = byte3; + } + + let inclination_x; + if(byte2 >= 128) + { + inclination_x = (byte2 - 128) * (-1); + } + else + { + inclination_x = byte2; + } + + let inclination_y; + if(byte1 >= 128) + { + inclination_y = (byte1 - 128) * (-1); + } + else + { + inclination_y = byte1; + } + + let inclination_z; + if(byte0 >= 128) + { + inclination_z = (byte0 - 128) * (-1); + } + else + { + inclination_z = byte0; + } + + values["temperature"] = temp; + + //náklon x + values["inclination_x"] = inclination_x; + + //náklon y + values["inclination_y"] = inclination_y; + + //náklon z + values["inclination_z"] = inclination_z; + } + + let h = byte3; + let m = byte2; + let s = byte1; + + let timestamp; + + if(register == 87 || register == 6 || register == 7 ) + { + //if(byte3 < 10) h = "0" + byte3; + //if(byte2 < 10) m = "0" + byte2; + //if(byte1 < 10) s = "0" + byte1; + + var d = new Date(); + d.setHours(h); + d.setMinutes(m); + d.setSeconds(s); + + timestamp = d.getTime(); + } + + + //aktuálny čas + if(register == 87) + { + //Byte3 - hodiny, Byte 2 - minúty, Byte 1 -sek. + //values["actual_time"] = h + ":" + m + ":" + s; + + values["actual_time"] = timestamp; + } + + //čas súmraku + if(register == 6) + { + //Byte3 - hodiny, Byte 2 - minúty, Byte 1 -sek. + //values["dusk_time"] = h + ":" + m + ":" + s; + + values["dusk_time"] = timestamp; + } + + //čas úsvitu + if(register == 7) + { + //Byte3 - hodiny, Byte 2 - minúty, Byte 1 -sek. + //values["dawn_time"] = h + ":" + m + ":" + s; + + values["dawn_time"] = timestamp; + } + + //FW verzia + if(register == 89) + { + //formát: "Byte3: Byte2.Byte1 (Byte0)" + + values["fw_version"] = byte3 + ":" + byte2 + "." + byte1 + "(" + byte0 + ")"; + } + + return values; +} + +//byte1 MSB = data3, byte2 = data2, byte3 = data1, byte4 = data0 LSB +function com_generic(adresa, rec, rw, register, name, byte1, byte2, byte3, byte4) { + let resp = []; + + let cmd = register; + + if (typeof adresa === 'string') adresa = parseInt(adresa); + if (typeof byte1 === 'string') byte1 = parseInt(byte1); + if (typeof byte2 === 'string') byte2 = parseInt(byte2); + if (typeof byte3 === 'string') byte3 = parseInt(byte3); + if (typeof byte4 === 'string') byte4 = parseInt(byte4); + + if (rw === 0) + { + cmd = cmd + 0x8000; + } + + //master + if(rec === 0) adresa = 0; + + if(rec === 2) + { + adresa = 0xffffffff;//Broadcast + } + + //recipient + if (rec === 3) + { + resp.push(0xFF); + resp.push(0xFF); + resp.push(0xFF); + resp.push(0xFF); + resp.push( adresa & 0xFF );//band + } + else + { + resp.push( (adresa >> 24) & 0xFF);//rshift + resp.push( (adresa >> 16) & 0xFF); + resp.push( (adresa >> 8) & 0xFF); + resp.push( adresa & 0xFF ); + + if (rec === 2) + { + resp.push(0xFF); + } + else resp.push(0); + } + + resp.push( (cmd >> 8) & 0xFF);//rshift + resp.push( cmd & 0xFF );//band + resp.push( byte1 & 0xFF );//band + resp.push( byte2 & 0xFF );//band + resp.push( byte3 & 0xFF );//band + resp.push( byte4 & 0xFF );//band + + //let data = '12345'; + let crc = crc16('ARC', resp); + let c1 = (crc >> 8) & 0xFF; + let c2 = crc & 0xFF; + + resp.push(c1); + resp.push(c2); + + //logger.debug("checksum", crc); + //logger.debug("resp", resp); + + return resp; + +} diff --git a/flow/code.js b/flow/code.js new file mode 100644 index 0000000..63b31bf --- /dev/null +++ b/flow/code.js @@ -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 = `
+
+
+
@(Number of outputs)
+
@(Minimum is 1)
+
+
+
@(Code)
+
@(Keep message instance)
+
+`; + +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(); +}; \ No newline at end of file diff --git a/flow/comment.js b/flow/comment.js new file mode 100644 index 0000000..1e0cd13 --- /dev/null +++ b/flow/comment.js @@ -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() {}; diff --git a/flow/csv_import.js b/flow/csv_import.js new file mode 100644 index 0000000..5ae1e68 --- /dev/null +++ b/flow/csv_import.js @@ -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 = `
+
+
+
CSV Import
+
+
+
`; + +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); + } + }) + +} + + diff --git a/flow/db_connector.js b/flow/db_connector.js new file mode 100644 index 0000000..65ee94b --- /dev/null +++ b/flow/db_connector.js @@ -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 = `
+
+
+
DbConnector
+
+
+
`; + +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); + } + } + }) +} + + diff --git a/flow/debug.js b/flow/debug.js new file mode 100644 index 0000000..00cb259 --- /dev/null +++ b/flow/debug.js @@ -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 = `
+
+
+
@(Output type)
+
@(Path to the property (leave empty to show the whole data object))
+
@(A group name)
+
@(Enabled)
+
+
+
`; + +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); + } +}; diff --git a/flow/designer.json b/flow/designer.json new file mode 100644 index 0000000..cc01fc6 --- /dev/null +++ b/flow/designer.json @@ -0,0 +1,2172 @@ +{ + "tabs": [ + { + "name": "MAIN PUSH", + "linker": "main-push", + "id": "1612772287426", + "index": 0 + }, + { + "name": "CMD manager", + "linker": "cmd-manager", + "id": "1615551125555", + "index": 1 + }, + { + "name": "Devices", + "linker": "devices", + "id": "1611921777196", + "index": 2 + } + ], + "components": [ + { + "id": "1611938185451", + "component": "modbus_citysys", + "tab": "1611921777196", + "name": "Modbus_citysys", + "x": 159.5, + "y": 164.5, + "connections": { + "0": [ + { + "index": "0", + "id": "1611951142547" + } + ], + "1": [ + { + "index": "0", + "id": "1611938192035" + } + ], + "2": [ + { + "index": "0", + "id": "1612772119611" + }, + { + "index": "0", + "id": "1611938192035" + } + ], + "3": [ + { + "index": "0", + "id": "1621340721628" + }, + { + "index": "0", + "id": "1611938192035" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Running", + "color": "green" + }, + "options": { + "edge": "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV" + }, + "color": "#2134B0", + "notes": "" + }, + { + "id": "1611938192035", + "component": "debug", + "tab": "1611921777196", + "name": "Debug", + "x": 566.5, + "y": 168.5, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1611951142547", + "component": "debug", + "tab": "1611921777196", + "name": "ERROR", + "x": 562, + "y": 54, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#DA4453", + "notes": "" + }, + { + "id": "1612772119611", + "component": "virtualwireout", + "tab": "1611921777196", + "name": "tb-demo-push", + "x": 625.75, + "y": 324.5, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1612776786008", + "component": "wsmqttpublish", + "tab": "1612772287426", + "name": "WS MQTT publish", + "x": 380.75, + "y": 264, + "connections": { + "0": [ + { + "index": "0", + "id": "1615551060773" + } + ], + "1": [ + { + "index": "0", + "id": "1618300858252" + }, + { + "index": "0", + "id": "1618558465485" + } + ], + "2": [ + { + "index": "0", + "id": "1618300863816" + }, + { + "index": "0", + "id": "1634303685503" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Connected", + "color": "green" + }, + "options": { + "username": "xmRd6RJxW53WZe4vMFLU", + "clientid": "showroom_test_panel_led", + "port": "1883", + "host": "" + }, + "color": "#888600", + "notes": "" + }, + { + "id": "1612778461252", + "component": "virtualwirein", + "tab": "1612772287426", + "name": "tb-demo-push", + "x": 68.75, + "y": 289, + "connections": { + "0": [ + { + "index": "0", + "id": "1612776786008" + }, + { + "index": "0", + "id": "1612783322136" + }, + { + "index": "0", + "id": "1656081515468" + }, + { + "index": "0", + "id": "1656420898232" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1612783322136", + "component": "debug", + "tab": "1612772287426", + "name": "to TB", + "x": 383.75, + "y": 167, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1613576617722", + "component": "comment", + "tab": "1612772287426", + "name": "In case broker is not ready, we save data to database, and when connected, we resend data", + "x": 72.88333129882812, + "y": 32, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#704cff", + "notes": "" + }, + { + "id": "1615551060773", + "component": "debug", + "tab": "1612772287426", + "name": "errors from MQTT Broker", + "x": 660, + "y": 126, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#DA4453", + "notes": "" + }, + { + "id": "1615563373927", + "component": "debug", + "tab": "1615551125555", + "name": "Debug", + "x": 806, + "y": 9, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#DA4453", + "notes": "" + }, + { + "id": "1615566865233", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "tb-demo-push", + "x": 802, + "y": 101, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1615798582262", + "component": "debug", + "tab": "1615551125555", + "name": "data to TB", + "x": 802, + "y": 190, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1615802995322", + "component": "debug", + "tab": "1611921777196", + "name": "Debug", + "x": 660.8833312988281, + "y": 527.3500061035156, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1615809128443", + "component": "debug", + "tab": "1611921777196", + "name": "Debug", + "x": 649.8833312988281, + "y": 633.3500061035156, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1615809595184", + "component": "virtualwireout", + "tab": "1611921777196", + "name": "tb-demo-push", + "x": 403.8833312988281, + "y": 663.25, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1615809105191", + "component": "gettemperature", + "tab": "1611921777196", + "name": "Get RVO temperature", + "x": 124.88333129882812, + "y": 488.3500061035156, + "connections": { + "0": [ + { + "index": "0", + "id": "1615802995322" + } + ], + "1": [ + { + "index": "0", + "id": "1615809128443" + }, + { + "index": "0", + "id": "1615809595184" + } + ], + "2": [ + { + "index": "0", + "id": "1621340721628" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#5CB36D", + "notes": "" + }, + { + "id": "1616165795916", + "component": "httproute", + "tab": "1615551125555", + "name": "POST /terminal", + "x": 72, + "y": 314, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Listening", + "color": "green" + }, + "options": { + "timeout": 10, + "cachepolicy": 0, + "cacheexpire": "5 minutes", + "size": 5, + "url": "/terminal", + "method": "POST", + "name": "", + "flags": [ + "id:1616165795916", + "post", + 10000 + ], + "emptyresponse": false + }, + "color": "#5D9CEC", + "notes": "### Configuration\n\n- __POST /terminal__\n- flags: \n- maximum request data length: __5 kB__\n- empty response: __false__\n- cache policy: __no cache__\n- cache expire: __5 minutes__", + "cloning": false + }, + { + "id": "1616165824813", + "component": "httpresponse", + "tab": "1615551125555", + "name": "HTTP Response", + "x": 800, + "y": 273, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "datatype": "json" + }, + "color": "#5D9CEC", + "notes": "" + }, + { + "id": "1617104731852", + "component": "debug", + "tab": "1615551125555", + "name": "Debug", + "x": 807, + "y": 545, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1617114651703", + "component": "trigger", + "tab": "1615551125555", + "name": "turnOff line", + "x": 74, + "y": 507, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "{line:2, command: \"turnOff\", force: true}", + "datatype": "object" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1617115013095", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "tb-demo-push", + "x": 794, + "y": 637, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1617178324650", + "component": "debug", + "tab": "1615551125555", + "name": "Debug", + "x": 703, + "y": 846, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1617179044099", + "component": "trigger", + "tab": "1615551125555", + "name": "start import", + "x": 312, + "y": 870, + "connections": { + "0": [ + { + "index": "0", + "id": "1617180390661" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "datatype": "object", + "data": "{table: \"konsberg_production_line_operations_error\", startFrom: 1, delimiter: \";\", uniqueColumn: \"\", path: \"flow/operations_error.csv\", mapImport: {0: \"production_line\",\t1: \"operation\", 2: \"error_type\", 3: \"error_code\", 4: \"error_text\", 5: \"error_text_user_defined\"}}" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1617180390661", + "component": "csv_import", + "tab": "1615551125555", + "name": "CsvImport", + "x": 508, + "y": 809, + "connections": { + "0": [ + { + "index": "0", + "id": "1617178324650" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "edge": "undefined" + }, + "color": "#2134B0", + "notes": "" + }, + { + "id": "1617197763128", + "component": "comment", + "tab": "1615551125555", + "name": "import data from csv", + "x": 485, + "y": 748, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#704cff", + "notes": "" + }, + { + "id": "1617284749681", + "component": "trigger", + "tab": "1615551125555", + "name": "update profile / node", + "x": 77, + "y": 57, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "datatype": "string", + "data": "profile_nodes" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1618232536546", + "component": "di_do_controller", + "tab": "1615551125555", + "name": "DI_DO_Controller", + "x": 450, + "y": 547, + "connections": { + "0": [ + { + "index": "0", + "id": "1617104731852" + } + ], + "1": [ + { + "index": "0", + "id": "1617115013095" + } + ], + "2": [ + { + "index": "0", + "id": "1618393583970" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "edge": "R3JjOWdylwgNLzxVab7NEBkZ2vG64rq8PEB5QmDo" + }, + "color": "#2134B0", + "notes": "" + }, + { + "id": "1618235171399", + "component": "trigger", + "tab": "1615551125555", + "name": "tun tasks", + "x": 80, + "y": 134, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "run" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1618300858252", + "component": "debug", + "tab": "1612772287426", + "name": "wsmqtt-exit1", + "x": 679.8833312988281, + "y": 218, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1618300863816", + "component": "debug", + "tab": "1612772287426", + "name": "wsmqtt-exit2", + "x": 753.8833312988281, + "y": 391, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1618393583970", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "platform-rpc-call", + "x": 796.8833312988281, + "y": 725, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "platform-rpc-call", + "color": "gray" + }, + "options": { + "wirename": "platform-rpc-call" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618393674428", + "component": "virtualwirein", + "tab": "1615551125555", + "name": "platform-rpc-call", + "x": 74.88333129882812, + "y": 222, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "platform-rpc-call", + "color": "gray" + }, + "options": { + "wirename": "platform-rpc-call" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618393759854", + "component": "virtualwirein", + "tab": "1615551125555", + "name": "di_do_controller-in", + "x": 72.88333129882812, + "y": 657, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + }, + { + "index": "0", + "id": "1683922207976" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "di_do_controller-in", + "color": "gray" + }, + "options": { + "wirename": "di_do_controller-in" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618393827655", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "di_do_controller-in", + "x": 798.8833312988281, + "y": 377, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "di_do_controller-in", + "color": "gray" + }, + "options": { + "wirename": "di_do_controller-in" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618558465485", + "component": "virtualwireout", + "tab": "1612772287426", + "name": "platform-rpc-call", + "x": 688.8833312988281, + "y": 307, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "platform-rpc-call", + "color": "gray" + }, + "options": { + "wirename": "platform-rpc-call" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618572059773", + "component": "trigger", + "tab": "1615551125555", + "name": "turnOn line", + "x": 75, + "y": 568, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "datatype": "object", + "data": "{line: 3, command: \"turnOn\", force: true}" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1619515097737", + "component": "cmd_manager", + "tab": "1615551125555", + "name": "CMD Manager", + "x": 431, + "y": 150, + "connections": { + "0": [ + { + "index": "0", + "id": "1615563373927" + } + ], + "1": [ + { + "index": "0", + "id": "1615566865233" + }, + { + "index": "0", + "id": "1615798582262" + } + ], + "2": [ + { + "index": "0", + "id": "1616165824813" + } + ], + "3": [ + { + "index": "0", + "id": "1618393827655" + } + ], + "4": [ + { + "index": "0", + "id": "1635936391935" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#5D9CEC", + "notes": "" + }, + { + "id": "1619605019281", + "component": "httproute", + "tab": "1615551125555", + "name": "GET db", + "x": 70, + "y": 420, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Listening", + "color": "green" + }, + "options": { + "timeout": 5, + "cachepolicy": 0, + "cacheexpire": "5 minutes", + "size": 100, + "url": "/db", + "method": "GET", + "name": "", + "flags": [ + "id:1619605019281", + "get", + 5000 + ] + }, + "color": "#5D9CEC", + "notes": "### Configuration\n\n- __GET /db__\n- flags: \n- maximum request data length: __100 kB__\n- empty response: __undefined__\n- cache policy: __no cache__\n- cache expire: __5 minutes__", + "cloning": false + }, + { + "id": "1619784672383", + "component": "trigger", + "tab": "1615551125555", + "name": "turnOnAlarm", + "x": 82, + "y": 755, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "{command: \"turnOnAlarm\"}", + "datatype": "object" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1619784812964", + "component": "trigger", + "tab": "1615551125555", + "name": "turnOffAlarm", + "x": 88, + "y": 819, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "{command: \"turnOffAlarm\"}", + "datatype": "object" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1621340721628", + "component": "virtualwireout", + "tab": "1611921777196", + "name": "di_do_controller-in", + "x": 630, + "y": 422, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "di_do_controller-in", + "color": "gray" + }, + "options": { + "wirename": "di_do_controller-in" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1622640022885", + "component": "httproute", + "tab": "1615551125555", + "name": "POST /db_connector", + "x": 1107, + "y": 338, + "connections": { + "0": [ + { + "index": "0", + "id": "1622640073521" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Listening", + "color": "green" + }, + "options": { + "timeout": 5, + "cachepolicy": 0, + "cacheexpire": "5 minutes", + "size": 500, + "url": "/db_connector", + "method": "POST", + "flags": [ + "id:1622640022885", + "post", + 5000 + ] + }, + "color": "#5D9CEC", + "notes": "### Configuration\n\n- __POST /db_connector__\n- flags: \n- maximum request data length: __500 kB__\n- empty response: __undefined__\n- cache policy: __no cache__\n- cache expire: __5 minutes__", + "cloning": false + }, + { + "id": "1622640073521", + "component": "db_connector", + "tab": "1615551125555", + "name": "DbConnector", + "x": 1363, + "y": 395, + "connections": { + "1": [ + { + "index": "0", + "id": "1622641420685" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "edge": "undefined" + }, + "color": "#2134B0", + "notes": "" + }, + { + "id": "1622641420685", + "component": "httpresponse", + "tab": "1615551125555", + "name": "HTTP Response", + "x": 1588, + "y": 454, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#5D9CEC", + "notes": "" + }, + { + "id": "1634303504177", + "component": "monitormemory", + "tab": "1612772287426", + "name": "RAM", + "x": 72.88333129882812, + "y": 674.5, + "connections": { + "0": [ + { + "index": "0", + "id": "1634465281992" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "203.08 MB / 249 MB", + "color": "gray" + }, + "options": { + "enabled": true, + "interval": 20000 + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1634303533779", + "component": "monitordisk", + "tab": "1612772287426", + "name": "disk", + "x": 78.88333129882812, + "y": 774.5, + "connections": { + "0": [ + { + "index": "0", + "id": "1634465821120" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "5.89 GB / 7.22 GB", + "color": "gray" + }, + "options": { + "enabled": true, + "path": "/", + "interval": 20000 + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1634303595494", + "component": "virtualwirein", + "tab": "1612772287426", + "name": "send-to-services", + "x": 5.883331298828125, + "y": 1089.5, + "connections": { + "0": [ + { + "index": "0", + "id": "1634463186563" + }, + { + "index": "0", + "id": "1634488120710" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "send-to-services", + "color": "gray" + }, + "options": { + "wirename": "send-to-services" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1634303602169", + "component": "virtualwireout", + "tab": "1612772287426", + "name": "send-to-services", + "x": 416.8833312988281, + "y": 690.5, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "send-to-services", + "color": "gray" + }, + "options": { + "wirename": "send-to-services" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1634303685503", + "component": "virtualwireout", + "tab": "1612772287426", + "name": "send-to-services", + "x": 776.8833312988281, + "y": 476.5, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "send-to-services", + "color": "gray" + }, + "options": { + "wirename": "send-to-services" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1634303743260", + "component": "httprequest", + "tab": "1612772287426", + "name": "http://192.168.252.2:8004/sentmessage", + "x": 633.8833312988281, + "y": 1047.7333374023438, + "connections": { + "0": [ + { + "index": "0", + "id": "1635327431236" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "stringify": "json", + "method": "POST", + "url": "http://192.168.252.2:8004/sentmessage" + }, + "color": "#5D9CEC", + "notes": "" + }, + { + "id": "1634463186563", + "component": "debug", + "tab": "1612772287426", + "name": "Debug", + "x": 350.75, + "y": 1021, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1634464580289", + "component": "code", + "tab": "1612772287426", + "name": "Code", + "x": 241, + "y": 592, + "connections": { + "0": [ + { + "index": "0", + "id": "1634465243324" + }, + { + "index": "0", + "id": "1634303602169" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "keepmessage": true, + "code": "let response = {};\nresponse.cpu = value.cpu;\nresponse.uptime = value.uptime;\n\nsend(0, response);", + "outputs": 1 + }, + "color": "#656D78", + "notes": "" + }, + { + "id": "1634465243324", + "component": "debug", + "tab": "1612772287426", + "name": "Debug", + "x": 436, + "y": 590, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1634465281992", + "component": "code", + "tab": "1612772287426", + "name": "Code", + "x": 232, + "y": 673, + "connections": { + "0": [ + { + "index": "0", + "id": "1634465338103" + }, + { + "index": "0", + "id": "1634303602169" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "keepmessage": true, + "code": "value.sender = \"ram\";\n//let total = value.total/1024/1024;\n//let free = value.free/1024/1024;\n//let used = value.used/1024/1024;\nlet response = {};\n//value.memory_total = (total).toFixed(0) + ' MB';\n//value.memory_free = (free).toFixed(0) + ' MB';\n//value.memory_used = (used).toFixed(0) + ' MB';\n\nresponse.memory_total = value.total;\nresponse.memory_free = value.free;\nresponse.memory_used = value.used;\n\nsend(0, response);", + "outputs": 1 + }, + "color": "#656D78", + "notes": "" + }, + { + "id": "1634465338103", + "component": "debug", + "tab": "1612772287426", + "name": "Debug", + "x": 413, + "y": 775, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1634465821120", + "component": "code", + "tab": "1612772287426", + "name": "Code", + "x": 234, + "y": 766, + "connections": { + "0": [ + { + "index": "0", + "id": "1634465892500" + }, + { + "index": "0", + "id": "1634303602169" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "keepmessage": true, + "code": "value.sender = \"hdd\";\n//let total = value.total/1024/1024;\n//let free = value.free/1024/1024;\n//let used = value.used/1024/1024;\nlet response = {};\n//value.hdd_total = (total).toFixed(0) + ' MB';\n//value.hdd_free = (free).toFixed(0) + ' MB';\n//value.used = (used).toFixed(0) + ' MB';\n\nresponse.hdd_total = value.total;\nresponse.hdd_free = value.free;\nresponse.hdd_used = value.used;\n\nsend(0, response);", + "outputs": 1 + }, + "color": "#656D78", + "notes": "" + }, + { + "id": "1634465892500", + "component": "debug", + "tab": "1612772287426", + "name": "Debug", + "x": 411, + "y": 862, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1634484067516", + "component": "debug", + "tab": "1612772287426", + "name": "Send info", + "x": 475, + "y": 1202, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1634488120710", + "component": "infosender", + "tab": "1612772287426", + "name": "Info sender", + "x": 233, + "y": 1148, + "connections": { + "0": [ + { + "index": "0", + "id": "1634484067516" + }, + { + "index": "0", + "id": "1634303743260" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "edge": "undefined" + }, + "color": "#2134B0", + "notes": "" + }, + { + "id": "1635327431236", + "component": "debug", + "tab": "1612772287426", + "name": "Debug", + "x": 1008.8833312988281, + "y": 1047.5, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1635936391935", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "send-to-services", + "x": 725, + "y": 456, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "send-to-services", + "color": "gray" + }, + "options": { + "wirename": "send-to-services" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1637069803394", + "component": "monitorconsumption", + "tab": "1612772287426", + "name": "CPU", + "x": 80, + "y": 588, + "connections": { + "0": [ + { + "index": "0", + "id": "1634464580289" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "9% / 57.78 MB", + "color": "gray" + }, + "options": { + "monitorfiles": true, + "monitorconnections": true, + "monitorsize": true, + "monitorconsumption": true, + "enabled": true, + "interval": 5000 + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1656081515468", + "component": "code", + "tab": "1612772287426", + "name": "send just example nodes data", + "x": 327.8833312988281, + "y": 432.8000030517578, + "connections": { + "0": [ + { + "index": "0", + "id": "1656420898232" + } + ] + }, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "keepmessage": true, + "code": "//send(0, value);\n\nconst nodes = ['oGVzxNWP9lrjaQ7vKODQ7g51gqp62YZREmdw3XBM','Ymn9oleRxJ0vw17WzAyGwdyEBk4ObdMXj2VgpNLG']\n\nif(nodes.includes(Object.keys(value)[0]))\n{\n\tif(value[Object.keys(value)[0]][0].values.hasOwnProperty('dimming') || value[Object.keys(value)[0]][0].values.hasOwnProperty('status'))\n\t{\n\t\tsend(0, value);\n\t}\n}\n\nconst example = {\n \"vnmG4kJxaXWNBgMQq0D7Aj5e9oZzOAlr6LdR3w2V\": [\n {\n \"ts\": 1656081519502,\n \"values\": {\n \"statecode\": 2\n }\n }\n ]\n}", + "outputs": 1 + }, + "color": "#656D78", + "notes": "" + }, + { + "id": "1656081823357", + "component": "debug", + "tab": "1612772287426", + "name": "1 mqtt processor exit 1", + "x": 1191.8833312988281, + "y": 509.8000030517578, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1656164263549", + "component": "trigger", + "tab": "1612772287426", + "name": "Trigger", + "x": 629.3500061035156, + "y": 625.3999938964844, + "connections": { + "0": [ + { + "index": "0", + "id": "1656420898232" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "{ \t\t \"vnmG4kJxaXWNBgMQq0D7Mz5e9oZzOAlr6LdR3w2V\": [ \t\t\t{ \t\t\t \"ts\": 1656420464663, \t\t\t \"values\": { \t\t\"status\":\"OK\"\t } \t\t\t} \t\t ] \t\t}", + "datatype": "object" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1656420898232", + "component": "mqttprocessor", + "tab": "1612772287426", + "name": "MQTT processor", + "x": 953.75, + "y": 578, + "connections": { + "0": [ + { + "index": "0", + "id": "1656081823357" + } + ], + "2": [ + { + "index": "0", + "id": "1656505814026" + }, + { + "index": "0", + "id": "1656586224071" + } + ] + }, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Connected", + "color": "green" + }, + "options": { + "username": "", + "clientid": "", + "port": 1883, + "host": "192.168.252.3" + }, + "color": "#888600", + "notes": "" + }, + { + "id": "1656505814026", + "component": "debug", + "tab": "1612772287426", + "name": "mqttprocessor exit 2", + "x": 1181, + "y": 687, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1656586224071", + "component": "virtualwireout", + "tab": "1612772287426", + "name": "3rd party system platform-rpc-call", + "x": 1181, + "y": 600.75, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "platform-rpc-call", + "color": "gray" + }, + "options": { + "wirename": "platform-rpc-call" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1683922207976", + "component": "debug", + "tab": "1615551125555", + "name": "to dido_controller", + "x": 450, + "y": 476, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1690972387040", + "component": "trigger", + "tab": "1612772287426", + "name": "Trigger", + "x": 73, + "y": 421, + "connections": { + "0": [ + { + "index": "0", + "id": "1612776786008" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "{ \"9YkRpoB2vVa0mKqEO8ZrOw8jW43eXnJML6GxzbwQ\": [ { \"ts\": 1690972374805, \"values\": { \"status\": \"OK\" } } ] }" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1691063647317", + "component": "trigger", + "tab": "1612772287426", + "name": "Trigger 650 ver4", + "x": 71, + "y": 497, + "connections": { + "0": [ + { + "index": "0", + "id": "1612776786008" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "{ \"GjQobgOK0n2YqBZmVDVzQGDR9ep6EXA1ka3vzlP7\": [ { \"ts\": 1690972374805, \"values\": { \"status\": \"OK\" } } ] }" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1702560071662", + "component": "mqttlistener", + "tab": "1612772287426", + "name": "MQTT listener", + "x": 244.88333129882812, + "y": 1397.433349609375, + "connections": { + "0": [ + { + "index": "0", + "id": "1702560098149" + } + ], + "1": [ + { + "index": "0", + "id": "1702560098149" + } + ], + "2": [ + { + "index": "0", + "id": "1702560098149" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Disconnected", + "color": "red" + }, + "options": { + "host": "192.168.252.2", + "port": "3883", + "clientid": "", + "username": "" + }, + "color": "#888600", + "notes": "" + }, + { + "id": "1702560098149", + "component": "debug", + "tab": "1612772287426", + "name": "Debug", + "x": 471.8833312988281, + "y": 1412.433349609375, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + } + ], + "version": 615 +} \ No newline at end of file diff --git a/flow/designer_deploy.json b/flow/designer_deploy.json new file mode 100644 index 0000000..e6242ce --- /dev/null +++ b/flow/designer_deploy.json @@ -0,0 +1,1516 @@ +{ + "tabs": [ + { + "name": "MAIN PUSH", + "linker": "main-push", + "id": "1612772287426", + "index": 0 + }, + { + "name": "CMD manager", + "linker": "cmd-manager", + "id": "1615551125555", + "index": 1 + }, + { + "name": "Devices", + "linker": "devices", + "id": "1611921777196", + "index": 2 + } + ], + "components": [ + { + "id": "1611938185451", + "component": "modbus_citysys", + "tab": "1611921777196", + "name": "Modbus_citysys", + "x": 159.5, + "y": 164.5, + "connections": { + "0": [ + { + "index": "0", + "id": "1611951142547" + } + ], + "1": [ + { + "index": "0", + "id": "1611938192035" + } + ], + "2": [ + { + "index": "0", + "id": "1612772119611" + }, + { + "index": "0", + "id": "1611938192035" + } + ], + "3": [ + { + "index": "0", + "id": "1621340721628" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Running", + "color": "green" + }, + "options": { + "edge": "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV" + }, + "color": "#2134B0", + "notes": "" + }, + { + "id": "1611938192035", + "component": "debug", + "tab": "1611921777196", + "name": "Debug", + "x": 566.5, + "y": 168.5, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1611951142547", + "component": "debug", + "tab": "1611921777196", + "name": "ERROR", + "x": 562, + "y": 54, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#DA4453", + "notes": "" + }, + { + "id": "1612772119611", + "component": "virtualwireout", + "tab": "1611921777196", + "name": "tb-demo-push", + "x": 625.75, + "y": 324.5, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1612776786008", + "component": "wsmqttpublish", + "tab": "1612772287426", + "name": "WS MQTT publish", + "x": 384.75, + "y": 247, + "connections": { + "0": [ + { + "index": "0", + "id": "1615551060773" + } + ], + "1": [ + { + "index": "0", + "id": "1618300858252" + }, + { + "index": "0", + "id": "1618558465485" + } + ], + "2": [ + { + "index": "0", + "id": "1613568918462" + }, + { + "index": "0", + "id": "1618300863816" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Connected", + "color": "green" + }, + "options": { + "username": "", + "clientid": "", + "port": "1883", + "host": "" + }, + "color": "#888600", + "notes": "" + }, + { + "id": "1612778461252", + "component": "virtualwirein", + "tab": "1612772287426", + "name": "tb-demo-push", + "x": 68.75, + "y": 269, + "connections": { + "0": [ + { + "index": "0", + "id": "1612776786008" + }, + { + "index": "0", + "id": "1612783322136" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1612783322136", + "component": "debug", + "tab": "1612772287426", + "name": "Debug", + "x": 426.75, + "y": 140, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1613568918462", + "component": "nosql", + "tab": "1612772287426", + "name": "Insert data to DB", + "x": 653.8833312988281, + "y": 460, + "connections": { + "0": [ + { + "index": "0", + "id": "1613568955702" + } + ], + "1": [ + { + "index": "0", + "id": "1613568983491" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "method": "insert", + "collection": "tbdata", + "addid": true + }, + "color": "#D770AD", + "notes": "" + }, + { + "id": "1613568955702", + "component": "debug", + "tab": "1612772287426", + "name": "Insert NOSQL", + "x": 906.8833312988281, + "y": 404, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1613568983491", + "component": "debug", + "tab": "1612772287426", + "name": "Inserted data NOSQL", + "x": 907.8833312988281, + "y": 501, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1613569046792", + "component": "httproute", + "tab": "1612772287426", + "name": "GET /tbdata", + "x": 36.883331298828125, + "y": 526, + "connections": { + "0": [ + { + "index": "0", + "id": "1613569068475" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Listening", + "color": "green" + }, + "options": { + "timeout": 5, + "cachepolicy": 0, + "cacheexpire": "5 minutes", + "size": 5, + "url": "/tbdata", + "method": "GET", + "name": "", + "flags": [ + "id:1613569046792", + "get", + 5000 + ] + }, + "color": "#5D9CEC", + "notes": "### Configuration\n\n- __GET /tbdata__\n- flags: undefined\n- maximum request data length: __5 kB__\n- empty response: __undefined__\n- cache policy: __no cache__\n- cache expire: __5 minutes__", + "cloning": false + }, + { + "id": "1613569068475", + "component": "nosql", + "tab": "1612772287426", + "name": "GET data from db", + "x": 248.88333129882812, + "y": 629, + "connections": { + "0": [ + { + "index": "0", + "id": "1613569129780" + }, + { + "index": "0", + "id": "1613574834147" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "method": "query", + "collection": "tbdata" + }, + "color": "#D770AD", + "notes": "" + }, + { + "id": "1613569129780", + "component": "code", + "tab": "1612772287426", + "name": "Code", + "x": 522.8833312988281, + "y": 702, + "connections": { + "0": [ + { + "index": "0", + "id": "1613569234477" + } + ], + "1": [ + { + "index": "0", + "id": "1613569202286" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "keepmessage": true, + "code": "//send(1, value);\n//value[\"response\"].map( item => {\n //if (typeof item.id !== \"undefined\" ){\n\t//send(0, {id:item.id});\n //}\n//})\n\nsend(1, value);\nlet reversed = value[\"response\"].reverse();\nreversed.map( item => {\n\tif (item.id !== undefined) {\n\t\tsend(0, {id: item.id});\n\t}\n})", + "outputs": 2 + }, + "color": "#656D78", + "notes": "", + "output": 2 + }, + { + "id": "1613569202286", + "component": "debug", + "tab": "1612772287426", + "name": "Data from DB", + "x": 717.8833312988281, + "y": 773, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1613569234477", + "component": "nosql", + "tab": "1612772287426", + "name": "Remove data from DB", + "x": 715.8833312988281, + "y": 650, + "connections": { + "0": [ + { + "index": "0", + "id": "1613569269806" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "method": "remove", + "collection": "tbdata" + }, + "color": "#D770AD", + "notes": "" + }, + { + "id": "1613569269806", + "component": "debug", + "tab": "1612772287426", + "name": "Removed data", + "x": 1006.8833312988281, + "y": 602, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1613574834147", + "component": "httpresponse", + "tab": "1612772287426", + "name": "HTTP Response", + "x": 498.8833312988281, + "y": 569, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "0.29 sec.", + "color": "gray" + }, + "options": { + "datatype": "json" + }, + "color": "#5D9CEC", + "notes": "" + }, + { + "id": "1613576617722", + "component": "comment", + "tab": "1612772287426", + "name": "In case broker is not ready, we save data to database, and when connected, we resend data", + "x": 72.88333129882812, + "y": 12, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#704cff", + "notes": "" + }, + { + "id": "1615551060773", + "component": "debug", + "tab": "1612772287426", + "name": "errors from MQTT Broker", + "x": 660, + "y": 106, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#DA4453", + "notes": "" + }, + { + "id": "1615563373927", + "component": "debug", + "tab": "1615551125555", + "name": "Debug", + "x": 803, + "y": 27, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#DA4453", + "notes": "" + }, + { + "id": "1615566865233", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "tb-demo-push", + "x": 802, + "y": 101, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1615798582262", + "component": "debug", + "tab": "1615551125555", + "name": "data to TB", + "x": 802, + "y": 190, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1615802995322", + "component": "debug", + "tab": "1611921777196", + "name": "Debug", + "x": 660.8833312988281, + "y": 527.3500061035156, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1615809128443", + "component": "debug", + "tab": "1611921777196", + "name": "Debug", + "x": 649.8833312988281, + "y": 633.3500061035156, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1615809595184", + "component": "virtualwireout", + "tab": "1611921777196", + "name": "tb-demo-push", + "x": 403.8833312988281, + "y": 663.25, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1615809105191", + "component": "gettemperature", + "tab": "1611921777196", + "name": "Get RVO temperature", + "x": 124.88333129882812, + "y": 488.3500061035156, + "connections": { + "0": [ + { + "index": "0", + "id": "1615802995322" + } + ], + "1": [ + { + "index": "0", + "id": "1615809128443" + }, + { + "index": "0", + "id": "1615809595184" + } + ], + "2": [ + { + "index": "0", + "id": "1621340721628" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#5CB36D", + "notes": "" + }, + { + "id": "1616165795916", + "component": "httproute", + "tab": "1615551125555", + "name": "POST /terminal", + "x": 72, + "y": 314, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Listening", + "color": "green" + }, + "options": { + "timeout": 5, + "cachepolicy": 0, + "cacheexpire": "5 minutes", + "size": 5, + "url": "/terminal", + "method": "POST", + "name": "", + "flags": [ + "id:1616165795916", + "post", + 5000 + ] + }, + "color": "#5D9CEC", + "notes": "### Configuration\n\n- __POST /terminal__\n- flags: \n- maximum request data length: __5 kB__\n- empty response: __undefined__\n- cache policy: __no cache__\n- cache expire: __5 minutes__", + "cloning": false + }, + { + "id": "1616165824813", + "component": "httpresponse", + "tab": "1615551125555", + "name": "HTTP Response", + "x": 800, + "y": 273, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "datatype": "json" + }, + "color": "#5D9CEC", + "notes": "" + }, + { + "id": "1617104731852", + "component": "debug", + "tab": "1615551125555", + "name": "Debug", + "x": 803, + "y": 499, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1617114651703", + "component": "trigger", + "tab": "1615551125555", + "name": "turnOff line", + "x": 74, + "y": 507, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "datatype": "object", + "data": "{line: 1, command: \"turnOff\"}" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1617115013095", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "tb-demo-push", + "x": 797, + "y": 596, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-demo-push", + "color": "gray" + }, + "options": { + "wirename": "tb-demo-push" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1617178324650", + "component": "debug", + "tab": "1615551125555", + "name": "Debug", + "x": 703, + "y": 846, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1617179044099", + "component": "trigger", + "tab": "1615551125555", + "name": "start import", + "x": 300, + "y": 791, + "connections": { + "0": [ + { + "index": "0", + "id": "1617180390661" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "{table: \"nodesx\", startFrom: 1, delimiter: \";\", uniqueColumn: \"node\", path: \"flow/audit_rvo14_lampy.csv\", mapImport: {1: \"node\",\t3: \"tbname\", 2: \"line\"}}", + "datatype": "object" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1617180390661", + "component": "csv_import", + "tab": "1615551125555", + "name": "CsvImport", + "x": 508, + "y": 809, + "connections": { + "0": [ + { + "index": "0", + "id": "1617178324650" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "edge": "undefined" + }, + "color": "#2134B0", + "notes": "" + }, + { + "id": "1617197763128", + "component": "comment", + "tab": "1615551125555", + "name": "import data from csv", + "x": 485, + "y": 748, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#704cff", + "notes": "" + }, + { + "id": "1617284749681", + "component": "trigger", + "tab": "1615551125555", + "name": "update profile / node", + "x": 77, + "y": 57, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "profile_nodes", + "datatype": "string" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1618232536546", + "component": "di_do_controller", + "tab": "1615551125555", + "name": "DI_DO_Controller", + "x": 450, + "y": 547, + "connections": { + "0": [ + { + "index": "0", + "id": "1617104731852" + } + ], + "1": [ + { + "index": "0", + "id": "1617115013095" + } + ], + "2": [ + { + "index": "0", + "id": "1618393583970" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "edge": "R3JjOWdylwgNLzxVab7NEBkZ2vG64rq8PEB5QmDo" + }, + "color": "#2134B0", + "notes": "" + }, + { + "id": "1618235171399", + "component": "trigger", + "tab": "1615551125555", + "name": "tun tasks", + "x": 80, + "y": 134, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "run" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1618300858252", + "component": "debug", + "tab": "1612772287426", + "name": "wsmqtt-exit1", + "x": 657.8833312988281, + "y": 198, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1618300863816", + "component": "debug", + "tab": "1612772287426", + "name": "wsmqtt-exit2", + "x": 654.8833312988281, + "y": 384, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "options": { + "type": "data", + "repository": false, + "enabled": true + }, + "color": "#967ADC", + "notes": "" + }, + { + "id": "1618393583970", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "platform-rpc-call", + "x": 802.8833312988281, + "y": 687, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "platform-rpc-call", + "color": "gray" + }, + "options": { + "wirename": "platform-rpc-call" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618393674428", + "component": "virtualwirein", + "tab": "1615551125555", + "name": "platform-rpc-call", + "x": 78.88333129882812, + "y": 221, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "platform-rpc-call", + "color": "gray" + }, + "options": { + "wirename": "platform-rpc-call" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618393759854", + "component": "virtualwirein", + "tab": "1615551125555", + "name": "di_do_controller-in", + "x": 72.88333129882812, + "y": 657, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "di_do_controller-in", + "color": "gray" + }, + "options": { + "wirename": "di_do_controller-in" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618393827655", + "component": "virtualwireout", + "tab": "1615551125555", + "name": "di_do_controller-in", + "x": 798.8833312988281, + "y": 377, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "di_do_controller-in", + "color": "gray" + }, + "options": { + "wirename": "di_do_controller-in" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618558465485", + "component": "virtualwireout", + "tab": "1612772287426", + "name": "platform-rpc-call", + "x": 652.8833312988281, + "y": 290, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "platform-rpc-call", + "color": "gray" + }, + "options": { + "wirename": "platform-rpc-call" + }, + "color": "#303E4D", + "notes": "" + }, + { + "id": "1618572059773", + "component": "trigger", + "tab": "1615551125555", + "name": "turnOn line", + "x": 75, + "y": 568, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "data": "{line: 1, command: \"turnOn\"}", + "datatype": "object" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1619515097737", + "component": "cmd_manager", + "tab": "1615551125555", + "name": "CMD Manager", + "x": 431, + "y": 150, + "connections": { + "0": [ + { + "index": "0", + "id": "1615563373927" + } + ], + "1": [ + { + "index": "0", + "id": "1615566865233" + }, + { + "index": "0", + "id": "1615798582262" + } + ], + "2": [ + { + "index": "0", + "id": "1616165824813" + } + ], + "3": [ + { + "index": "0", + "id": "1618393827655" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": {}, + "color": "#5D9CEC", + "notes": "" + }, + { + "id": "1619605019281", + "component": "httproute", + "tab": "1615551125555", + "name": "GET db", + "x": 70, + "y": 411, + "connections": { + "0": [ + { + "index": "0", + "id": "1619515097737" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Listening", + "color": "green" + }, + "options": { + "timeout": 5, + "cachepolicy": 0, + "cacheexpire": "5 minutes", + "size": 5, + "url": "/db", + "method": "GET", + "name": "", + "flags": [ + "id:1619605019281", + "get", + 5000 + ] + }, + "color": "#5D9CEC", + "notes": "### Configuration\n\n- __GET /db__\n- flags: undefined\n- maximum request data length: __5 kB__\n- empty response: __undefined__\n- cache policy: __no cache__\n- cache expire: __5 minutes__", + "cloning": false + }, + { + "id": "1619784672383", + "component": "trigger", + "tab": "1615551125555", + "name": "turnOnAlarm", + "x": 82, + "y": 755, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "datatype": "object", + "data": "{command: \"turnOnAlarm\"}" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1619784812964", + "component": "trigger", + "tab": "1615551125555", + "name": "turnOffAlarm", + "x": 88, + "y": 819, + "connections": { + "0": [ + { + "index": "0", + "id": "1618232536546" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "options": { + "datatype": "object", + "data": "{command: \"turnOffAlarm\"}" + }, + "color": "#F6BB42", + "notes": "" + }, + { + "id": "1621340721628", + "component": "virtualwireout", + "tab": "1611921777196", + "name": "di_do_controller-in", + "x": 630, + "y": 422, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "di_do_controller-in", + "color": "gray" + }, + "options": { + "wirename": "di_do_controller-in" + }, + "color": "#303E4D", + "notes": "" + } + ], + "version": 615 +} \ No newline at end of file diff --git a/flow/di_do_controller.js b/flow/di_do_controller.js new file mode 100644 index 0000000..a218aec --- /dev/null +++ b/flow/di_do_controller.js @@ -0,0 +1,1764 @@ +exports.id = 'di_do_controller'; +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 = `
+
+
+
Edge TB Name
+
+
+
`; + +exports.readme = `# Sets RS232 port and all digital pins on device. Then it starts to receive data from sensors. +It receives: + +rotary_switch_state, +rotary_switch_state, +door_condition, +state_of_breaker, +state_of_contactor, +twilight_sensor +`; + +/* +we open rsPort "/dev/ttymxc0" and set digital input and output pins with "setRSPortData" +Currently we are interested in pins no. 1,2,3,6,8,9,10,16 +pins number 11, 12, 13 (we receive 10,11,12 in rsPortReceivedData) are "stykace" +When port receives data, it must be exactly 4 bytes long. Second byte is pin, that changed its value, fourth byte is value itself. +After that, we set this value to "previousValues[allPins[whichpin]]" variable +*/ + + +/* +RVO objekt: +state_of_main_switch - sem sa bude reportovať stav hlavného ističa : 0-> off 1-> on (toto nie je na platforme, ale Rado to už do entity type doplnil) +rotary_switch_state - sem by sa mal reportovať stav vstupov manual a auto podľa nasledovnej logiky: + Manual = 1 a Auto = 0 -> vyreportuje Manual + Manual = 0 a Auto = 0 -> vyreportuje Off + Manual = 0 a Auto = 1 -> vyreportuje Automatic + +door_condition - tuto ide pin 6, dverový kontakt -> 1 -> vyreportuje Closed, 0 -> vyreportuje Open +twilight_sensor - hodnotu, ktorú vracia ten analógový vstup (17) treba poslať sem ako float number. Zrejme tu potom pridáme nejaký koeficient prevodu na luxy + +zjavne nám v jsone chýba stav hlavného ističa. Musíme to potom doplniť + +Na každú líniu: +state_of_breaker - podľa indexu ističa sa reportuje jeho stav, teda istič 1 na líniu 1: 0-> off 1-> on +state_of_contactor - podľa indexu stykača sa reportuje jeho stav, teda stykač 1 na líniu 1: 0-> off 1-> on + momentálne sa stav zmení len keď vo flow klikneš aby sa zmenil, ale tá zmena by sa mala ukázať aj na platforme +*/ + + +//globals +//FIRMWARE version +FLOW.OMS_edge_fw_version = "2022-05-12";//rok-mesiac-den +FLOW.OMS_edgeName = ""; +FLOW.OMS_maintenance_mode = false; + +//dynamic values +FLOW.OMS_masterNodeIsResponding = true; //cmd_manager +//FLOW.OMS_brokerready = false //wsmqttpublish +FLOW.OMS_no_voltage = new Set();//modbus_citysys - elektromer + +//see loadSettings() in cmd_manager +FLOW.OMS_language = "en";//cmd_manager +FLOW.OMS_rvo_name = "";//cmd_manager +FLOW.OMS_rvo_tbname = "";//relaysData +FLOW.OMS_temperature_adress = "";//cmd_manager +//----------------------------------------------- + +let alarmStatus = "OFF"; + +const instanceSendTo = { + debug: 0, + tb: 1, + cmd_manager: 2 +} + +var log4js = require("log4js"); +var path = require('path'); + +log4js.configure({ + appenders: { + errLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, 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"); + +//console.log(path.join(__dirname, 'err.txt', "-----------------------------")); + +/* +process.on('uncaughtException', function (err) { + + errLogger.error('uncaughtException:', err.message) + errLogger.error(err.stack); + + //process.exit(1); +}) +*/ + +//USAGE +//logger.debug("text") +//monitor.info('info'); +//errLogger.error("some error"); + +exports.install = function(instance) { + + process.on('uncaughtException', function (err) { + + //TODO send to service + + errLogger.error('uncaughtException:', err.message) + errLogger.error(err.stack); + + errorHandler.sendMessageToService(err.message + "\n" + err.stack, 0, "js_error"); + + //process.exit(1); + }) + + let previousValues = {temperature: 0}; + let rsPortReceivedData = []; + + let twilight_sensor_interval = 5;//minutes + let twilight_sensor = []; + const twilight_sensor_array = []; + let twighlightError = false; + + let edgeName = ""; + + monitor.info("DI_DO_Relay_Controller installed"); + + //key is PIN number , line: 0 = RVO + /* + let conversionTable = { + "1": {tbname: "", type: "state_of_main_switch", "line": 0}, //state_of_main_switch pin1 + "2": {tbname: "", type: "rotary_switch_state", "line": 0}, //rotary_switch_state - poloha manual = pin2 + "3": {tbname: "", type: "rotary_switch_state", "line": 0}, //rotary_switch_state - poloha auto = pin3 + "4": {tbname: "", type: "power_supply", "line": 0}, + "5": {tbname: "", type: "battery", "line": 0}, + "6": {tbname: "", type: "door_condition", "line": 0}, // door_condition = pin6, 1 -> vyreportuje Closed, 0 -> vyreportuje Open + "8": {tbname: "", type: "state_of_breaker", "line": 1}, // state_of_breaker linia 1 0=off, 1=on + "9": {tbname: "", type: "state_of_breaker", "line": 2}, // state_of_breaker linia 2 0=off, 1=on + "10": {tbname: "", type: "state_of_breaker", "line": 3}, // state_of_breaker linia 3 0=off, 1=on + "11": {tbname: "", type: "state_of_contactor", "line": 1}, // state_of_contactor linia 1 0=off, 1=on + "12": {tbname: "", type: "state_of_contactor", "line": 2}, // state_of_contactor linia 2 0=off, 1=on + "13": {tbname: "", type: "state_of_contactor", "line": 3}, // state_of_contactor linia 3 0=off, 1=on + "16": {tbname: "", type: "twilight_sensor", "line": 0}, // twilight_sensor = pin16 + }; + */ + + const dbPins = TABLE("pins"); + let pinsData = {};//key is pin + + const dbRelays = TABLE("relays"); + let relaysData = {};//key is line + + //status for calculating Statecodes + let deviceStatuses = {};//key is device name: temperature,.... + deviceStatuses["state_of_main_switch"] = "Off";//Hlavný istič + deviceStatuses["rotary_switch_state"] = "Off";//Prevádzkový mód + deviceStatuses["door_condition"] = "closed";//Dverový kontakt + deviceStatuses["rvo"] = {status: "OK"};//elektromer rvo + deviceStatuses["temperature"] = "OK";//templomer + deviceStatuses["battery"] = "OK";//Batéria + deviceStatuses["power_supply"] = "OK";//Zdroj + deviceStatuses["master_node"] = "OK";//MN - FLOW.OMS_masterNodeIsResponding + deviceStatuses["no_voltage"] = "OK";//FLOW.OMS_no_voltage - výpadok napätia na fáze + + deviceStatuses["state_of_breaker"] = {};//"Off";//Istič + deviceStatuses["state_of_contactor"] = {};//"Off";//Stykač + + /* + dbRelays.on('change', function(doc, old) { + console.log("'DI_DO_Controller - dbRelays.on('change'"); + instance.send(instanceSendTo.cmd_manager, "reload_relays"); + }); + */ + + const SerialPort = require('serialport'); + const WebSocket = require('ws'); + + let ws = null; + let rsPort = null; + + //const { exec } = require('child_process'); + const { openPort, runSyncExec, writeData } = require('./helper/serialport_helper.js'); + const { bytesToInt, resizeArray } = require('./helper/utils'); + const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); + const { sendNotification, ERRWEIGHT } = require('./helper/notification_reporter.js'); + const bitwise = require('bitwise'); + + const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler.js'); + const errorHandler = new ErrorToServiceHandler(); + + //let useTurnOffCounter = false; + //let turnOffCounter = 0; + let controller_type = FLOW.OMS_controller_type //"lm" or "unipi" //logicMachine + if(controller_type == "") controller_type = "lm"; + + console.log(exports.title, "controller type: ", controller_type); + + async function loadAllDb() + { + let responsePins = await promisifyBuilder(dbPins.find()); + pinsData = makeMapFromDbResult(responsePins, "pin"); + + let responseRelays = await promisifyBuilder(dbRelays.find()); + relaysData = makeMapFromDbResult(responseRelays, "line"); + + FLOW.OMS_rvo_tbname = relaysData[0].tbname; + + if(controller_type === "lm") + { + handleRsPort(); + } + else if(controller_type === "unipi") + { + handleWebSocket(); + } + else { + errLogger.debug("UNKNOWN controller_type:", controller_type); + } + } + + + function initialSetting() + { + //force turn off relays & set tbname + let keys = Object.keys(pinsData); + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + let line = pinsData[key].line; + + if(line != undefined) + { + if(relaysData[line] != undefined) + { + pinsData[key].tbname = relaysData[line].tbname; + + relaysData[line].contactor = 0; + } + else + { + errLogger.error("CRITICAL!!! undefined relay", relaysData[line], line); + + //sendNotification("set port ", edgeName, ERRWEIGHT.CRITICAL, "local database is corrupted", "", instanceSendTo.tb, instance, null ); + sendNotification("set port ", edgeName, "local_database_is_corrupted", {}, "", instanceSendTo.tb, instance ); + } + } + + if(pinsData[key].type == "state_of_contactor") + { + + let pin = key - 1; + if(controller_type === "unipi") pin = key; + + //this will modify database + let forceTurnOff = true; + turnOffLine(line, pin, forceTurnOff, "turn off on startup"); + } + } + + //report RVO version relaysData[0].tbname; + let values = {}; + values["edge_fw_version"] = FLOW.OMS_edge_fw_version; + values["maintenance_mode"] = FLOW.OMS_maintenance_mode; + values["status"] = "OK"; + + edgeName = relaysData[0].tbname; + FLOW.OMS_edgeName = edgeName; + + logger.debug("RVO tbname:", edgeName); + + dataToTb = { + [edgeName]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + instance.send(instanceSendTo.tb, dataToTb); + + let time = 3*1000; + setTimeout(function(){ + instance.send(instanceSendTo.cmd_manager, {sender: "di_do_controller", cmd: "buildTasks"}); + + sendNotification("rsPort.open()", edgeName, "flow_start", {}, "", instanceSendTo.tb, instance ); + monitor.info("-->FLOW bol spustený", edgeName, FLOW.OMS_edge_fw_version); + + }, time); + } + + + function handleRsPort() + { + //TODO build according to pins!!! + //! rsPort to open are the same for lm and unipi and electromer ("/dev/ttymxc0") + const setRSPortData = [0xAA,6,6,6,6,6,6,0,6,6,6,1,1,1,1,0,0,10,10,10,10,10,10,0,10,10,10,0,0,0,0,0,0,5,0,0,0,15,15,15,15,15,15,0,15,15,15,0,0,0,0,0,0,30,0,0,0]; + rsPort = new SerialPort("/dev/ttymxc0", { autoOpen: false }); + + rsPort.on('error', function(err) { + logger.debug("rsPort opened error - failed", err.message); + instance.send(instanceSendTo.debug, err.message); + + errorHandler.sendMessageToService( exports.title + " rsPort opened error - failed: " + err.message); + }) + + 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) { + + //set port + rsPort.write(Buffer.from(setRSPortData), function(err) { + monitor.info(exports.title + "--->Digital in_out has been set (runSyncExec was sucessfull)"); + + turnOffAlarm(); + + //useTurnOffCounter = true; + //turnOffCounter = relaysData.length - 1; + + initialSetting(); + }) + + }).catch(function (reason) { + //instance.send(instanceSendTo.debug, exports.title + " runSyncExec - promise rejected:" + reason); + errLogger.error( exports.title + " runSyncExec - promise rejected:" + reason); + + errorHandler.sendMessageToService( exports.title + " runSyncExec - promise rejected:" + reason); + }); + + }); + + + 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("close", () => { + rsPort.close(); + }) + + rsPort.open(); + + } + + + function handleWebSocket() { + + console.log("handleWebSocket function called"); + ws = new WebSocket('ws:/10.0.0.38:1234/ws') + + + ws.onopen = function open() { + + instance.send(0, exports.title + " running"); + turnOffAlarm(); + + // useTurnOffCounter = true; + // turnOffCounter = relaysData.length - 1; + initialSetting(); + ws.send(JSON.stringify({"cmd":"all"})); + + // startRequests(); + }; + +// SAMPLE DATA FROM WEBSOCKET +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_07', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// modes: [ 'Simple', 'DirectSwitch' ], +// value: 0, +// circuit: '1_08', +// debounce: 50, +// counter: 0, +// counter_mode: 'Enabled', +// dev: 'input', +// mode: 'Simple' +// }, + + ws.onmessage = function(data) { + + data = JSON.parse(data.data); + // console.log(data) + + // data comes in array except of "temperature" ==> it comes as an object + if(!Array.isArray(data)) + { + let value = data['value']; + + // temperature value comes very often. To handle it, we check if it change for more than 0.2 degrees, if yes, we send to TB + if(Math.abs(previousValues["temperature"] - value) > 0.21 ) + { + const dataToTb = { + [FLOW.OMS_rvo_tbname]: [ + { + "ts": Date.now(), + "values": {temperature: value} + } + ] + }; + + deviceStatuses["temperature"] = "OK"; + previousValues["temperature"] = value; + instance.send(instanceSendTo.tb, dataToTb); + } + return; + } + + data.map(item => { + + let value = item['value']; + let pin = item["dev"] + item["circuit"]; // for example "relay1_03" or "input1_01" + + if (pin == undefined) return; + switchLogic(pin, value); + }) + } + + + ws.on('error', (err) => { + instance.send(instanceSendTo.debug, err.message); + }) + + + ws.onclose = function(){ + // connection closed, discard old websocket and create a new one in 5s + // stopRequests(); + ws = null; + console.log("ws is null now, reconnecting in 5 seconds"); + setTimeout(handleWebSocket, 5000); + } + } + + //! do we need requests every minute ??? + // const startRequests = () => { + // console.log("startRequest function called"); + // start = setInterval(() => { + // // console.log("data from evok requested"); + // ws.send(JSON.stringify({"cmd":"all"})); + // // ws.send(JSON.stringify({"cmd":"filter", "devices":["input", "relay"]})); + // }, 60000) + // } + + + instance.on("close", () => { + if(rsPort) rsPort.close(); + if(ws) ws.close(); + }) + + loadAllDb(); + + function getPin(line) + { + //conversionTable + let keys = Object.keys(pinsData); + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + + if(pinsData[key].type == "state_of_contactor" && pinsData[key].line == line) + { + if(rsPort) return key - 1; + if(ws) return key; + } + } + + logger.debug("no pin detected"); + + return null; + } + + + function turnOnAlarm() + { + if(FLOW.OMS_maintenance_mode) return; + + alarmStatus = "ON"; + + if(rsPort) + { + let arr = [0x55]; + arr.push( 13 ); + arr.push( 0 ); + arr.push( 1 ); + + rsPort.write(Buffer.from(arr), function(err) { + logger.debug("sirena zapnuta"); + }); + } + else if(ws) + { + let cmd = {"cmd": "set", "dev": "relay", "circuit": "1_08", "value": 1}; + ws.send(JSON.stringify(cmd)); + logger.debug("sirena zapnuta"); + } + } + + + function turnOffAlarm() + { + alarmStatus = "OFF"; + + if(rsPort) + { + let arr = [0x55]; + arr.push( 13 ); + arr.push( 0 ); + arr.push( 0 ); + + rsPort.write(Buffer.from(arr), function(err) { + logger.debug("sirena vypnuta"); + }); + } + else if(ws) + { + let cmd = {"cmd": "set", "dev": "relay", "circuit": "1_08", "value": 0}; + ws.send(JSON.stringify(cmd)); + logger.debug("sirena vypnuta"); + } + } + + + function reportLineStatus(line) + { + //Tá hodnota by mala fungovať tak že LSB bit číslo je stav ističa (1 - On, 0 - Off) a druhý bit je stav stýkača (1 - true, 0 - false). + + let tbname = relaysData[line].tbname; + + let bits = []; + + if(deviceStatuses["state_of_breaker"][line] == "On") + { + bits.push(0); + } + else bits.push(1); + + if(deviceStatuses["state_of_contactor"][line] == "On") + { + bits.push(0); + } + else bits.push(1); + + resizeArray(bits, 8, 0); + + let byte = bitwise.byte.write(bits.reverse()); + + //console.log("line", line, bits, byte); + + let values = { + statecode: byte + } + + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + //console.log(values); + + instance.send(instanceSendTo.tb, dataToTb); + } + + + function turnOnLine(line, pin, force, info) + { + + instance.send(instanceSendTo.debug, "turn on line " + line ); + if(force == undefined) force = false; + + if(line == 0) + { + if(alarmStatus == "ON") turnOffAlarm(); + FLOW.OMS_maintenance_mode = true; + + let values = {}; + values["statecode"] = calculateStateCode(); + values["power_mode"] = "maintenance"; + let tbname = relaysData[line].tbname; + sendTelemetry(values, tbname); + + monitor.info("turnOnLine (line, FLOW.OMS_maintenance_mode)", line, FLOW.OMS_maintenance_mode, info); + + return; + } + + if( pin === undefined) pin = getPin(line); + + monitor.info("turnOnLine (line, pin, force)", line, pin, force, info); + + if( pin === undefined) + { + monitor.info("pin is undefined!", line); + return; + } + + + if(!force) + { + if(relaysData[line].contactor == 1) + { + instance.send(instanceSendTo.debug, "line is already on " + line ); + logger.debug("turnOnLine: line is already on: ", line); + + return; + } + } + + // if(!rsPort.isOpen && !ws) + if(!rsPort && !ws) + { + errLogger.error("di do controller - port or websocket is not opened"); + return; + } + + if(rsPort) + { + let arr = [0x55]; + arr.push( pin ); + arr.push( 0 ); + arr.push( 1 ); + + rsPort.write(Buffer.from(arr), function(err) { + if(err === undefined) + { + console.log("turnONLine zapisal do rsPortu", line, arr); + switchLogic(arr); + } + else + { + monitor.info("turnOnLine WRITE error", err); + } + + }); + + } + else if(ws) + { + console.log("turnONLine pin (relay)", pin); + //pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method + let cmd = {"cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": 1}; + ws.send(JSON.stringify(cmd)); + switchLogic(pin, 1) + } + } + + + function turnOffLine(line, pin, force, info) + { + if(force == undefined) force = false; + + if(line == 0) + { + FLOW.OMS_maintenance_mode = false; + + let values = {}; + values["statecode"] = calculateStateCode(); + values["power_mode"] = "Automatic"; + let tbname = relaysData[line].tbname; + sendTelemetry(values, tbname); + + return; + } + + if( pin === undefined) pin = getPin(line); + + monitor.info("turnOffLine (line, pin, force)", line, pin, force, info); + + if( pin === undefined) + { + errLogger.error("pin is undefined!", line); + return; + } + + if(!force) + { + if(relaysData[line].contactor == 0) + { + instance.send(instanceSendTo.debug, "line is already off " + line ); + logger.debug("turnOffLine: line already off:", line); + + return; + } + } + + // if(!rsPort.isOpen && !ws) + if(!rsPort && !ws) + { + errLogger.error("di do controller - port or websocket is not opened"); + return; + } + + if(rsPort) + { + let arr = [0x55]; + arr.push( pin ); + arr.push( 0 ); + arr.push( 0 ); + + rsPort.write(Buffer.from(arr), function(err) { + if(err === undefined) + { + console.log("turnOffLine zapisal do rsPort-u", line, arr); + switchLogic(arr); + } + else + { + monitor.info("turnOffLine WRITE error", err); + } + + }); + + } + else if(ws) + { + //pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method + monitor.info("turnOffLine pin (relay)", pin); + let cmd = {"cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": 0}; + ws.send(JSON.stringify(cmd)); + switchLogic(pin, 0) + } + + } + + + // we expect array as flowdata.data + instance.on("data", (flowdata) => { + + console.log(flowdata.data); + + if(flowdata.data instanceof Object) + { + + if(flowdata.data.hasOwnProperty("sender")) + { + //console.log("sender", flowdata.data); + + if(flowdata.data.sender == "gettemperature") + { + deviceStatuses["temperature"] = flowdata.data.status; + } + else if(flowdata.data.sender == "modbus_citysys") + { + //elektromer rvo + if(flowdata.data.tbdata.hasOwnProperty(edgeName)) + { + //rvo + deviceStatuses["rvo"] = {status: flowdata.data.tbdata[edgeName][0]["values"]["status"], tbdata: flowdata.data.tbdata}; + } + else{ + //posli do tb - to je vyriesene na urovni modbus_citysys + //instance.send(instanceSendTo.tb, flowdata.data.tbdata); + } + } + + instance.send(instanceSendTo.debug, flowdata.data ); + return; + } + + let obj = flowdata.data; + + let line = obj.line; + let force = obj.force; + let info = obj.info; + + if(obj.command == "turnOn") turnOnLine(line, undefined, force, info); + else if(obj.command == "turnOff") turnOffLine(line, undefined, force, info); + else if(obj.command == "turnOnAlarm") turnOnAlarm(); + else if(obj.command == "turnOffAlarm") turnOffAlarm(); + + return; + } + + //! ake data prichadzaju z cmd_manager.js ??? + //TODO transform to websocket + if (Array.isArray(flowdata.data)){ + + rsPort.write(Buffer.from(flowdata.data), function(err) { + switchLogic(flowdata.data); + + instance.send(instanceSendTo.debug, {"WRITE":flowdata.data} ); + }); + } + }) + + + function calculateStateCode() + { + + let bytes = []; + let bits = []; + + + //Hlavný istič - state_of_main_switch + if(deviceStatuses["state_of_main_switch"] == "On") bits.push(0); + else if(deviceStatuses["state_of_main_switch"] == "Off") bits.push(1); + + //Prevádzkový mód - Manual, Off, Automatic, maintenance_mode = true/false + if(!FLOW.OMS_maintenance_mode) + { + if(deviceStatuses["rotary_switch_state"] == "Manual") + { + bits.push(0); + bits.push(1); + } + + if(deviceStatuses["rotary_switch_state"] == "Automatic") + { + bits.push(0); + bits.push(0); + } + + if(deviceStatuses["rotary_switch_state"] == "Off") + { + bits.push(1); + bits.push(0); + } + } + else{ + bits.push(1); + bits.push(1); + } + + //Dverový kontakt + if(deviceStatuses["door_condition"] == "closed") + { + bits.push(0); + } + else bits.push(1); + + //EM + if(deviceStatuses["rvo"].status == "NOK") bits.push(1); + else bits.push(0); + + //Teplomer + if(deviceStatuses["temperature"] == "NOK") bits.push(1); + else bits.push(0); + + //Batéria + if(deviceStatuses["battery"] == "NOK") bits.push(1); + else bits.push(0); + + //Zdroj + if(deviceStatuses["power_supply"] == "NOK") bits.push(1); + else bits.push(0); + + //MN + if(deviceStatuses["master_node"] == "NOK") bits.push(1); + else bits.push(0); + + //výpadok napätia na fáze + if(deviceStatuses["no_voltage"] == "NOK") bits.push(1); + else bits.push(0); + + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + bits.push(0); + + //console.log("calculateStateCode - deviceStatuses", deviceStatuses); + //console.log("calculateStateCode", bits); + + let byte0 = bitwise.byte.write(bits.slice(0,8).reverse()); + let byte1 = bitwise.byte.write(bits.slice(8).reverse()); + + let byte = bytesToInt([byte1, byte0]); + + //console.log("calculateStateCode", byte); + + return byte; + } + + + function checkFinalRVOStatus() { + + // we check if any of these pins values are 0 --> it means status RVO is "NOK" + // pinIndex 6 is door_condition - if open in maintenance mode - status = OK + + //set RVO state + + let status = "OK"; + + if(deviceStatuses["rvo"].status == "NOK") + { + let writeToFile = errorHandler.processMessage("checkFinalRVOStatus: rvo status is NOK"); + if(writeToFile) errLogger.error("checkFinalRVOStatus: rvo status is NOK", deviceStatuses["rvo"].tbdata); + + status = "NOK"; + } + + //ak teplomer NOK, rvo nok + if(deviceStatuses["temperature"] == "NOK") + { + + let writeToFile = errorHandler.processMessage("checkFinalRVOStatus: temperature status is NOK"); + if(writeToFile) errLogger.error("checkFinalRVOStatus: temperature status is NOK"); + + status = "NOK"; + } + + if(status == "OK") + { + for (const pinIndex of [1, 4, 6]) { + if (previousValues[pinIndex] === 0) { + + if (pinIndex === 6 && FLOW.OMS_maintenance_mode) continue; + + let writeToFile = errorHandler.processMessage("checkFinalRVOStatus: value is 0"); + if(writeToFile) errLogger.error("checkFinalRVOStatus: value is 0", pinsData[pinIndex].type); + + status = "NOK"; + + break; + } + } + } + + // battery status. If value is 1 - battery is not ok + if (previousValues[5] === 1) + { + let writeToFile = errorHandler.processMessage("checkFinalRVOStatus: NOK status generated by battery"); + if(writeToFile) errLogger.error("checkFinalRVOStatus: NOK status generated by battery"); + + status = "NOK"; + } + + //ak mame telemetriu z elektromeru, posleme + if(deviceStatuses["rvo"].tbdata != undefined) + { + //deviceStatuses["rvo"] = {status: flowdata.data.tbdata[edgeName][0]["values"]["status"], tbdata: flowdata.data.tbdata}; + + deviceStatuses["rvo"].tbdata[edgeName][0]["values"]["status"] = status; + + + instance.send(instanceSendTo.tb, deviceStatuses["rvo"].tbdata); + delete deviceStatuses["rvo"].tbdata; + } + + //console.log("FLOW.OMS_masterNodeIsResponding", FLOW.OMS_masterNodeIsResponding); + + if(!FLOW.OMS_masterNodeIsResponding) + { + //errLogger.error("Master node is not responding"); + errorHandler.sendMessageToService("Master node is not responding"); + status = "NOK"; + + deviceStatuses["master_node"] = "NOK"; + } + else deviceStatuses["master_node"] = "OK"; + + //console.log("checkFinalRVOStatus", status); + if(FLOW.OMS_no_voltage.size > 0) + { + let writeToFile = errorHandler.processMessage("no voltage detected"); + if(writeToFile) errLogger.error("no voltage detected", FLOW.OMS_no_voltage); + + status = "NOK"; + + deviceStatuses["no_voltage"] = "NOK"; + } + else deviceStatuses["no_voltage"] = "OK"; + + if(status == "NOK") + { + + const dataToTb = { + [edgeName]: [ + { + "ts": Date.now(), + "values": {status: "NOK"} + } + ] + } + + instance.send(instanceSendTo.tb, dataToTb); + + return false; + } + + return true; + } + + + + // we pass array to function in case of rsPort [[55,3,0,1]] + // we pass two values in case of websocket [3,1] + const switchLogic = (...args) => { + + let values = {status: "OK"}; + let dataToTb, pinIndex, newPinValue, twighlight; + + if(args.length == 1) + { + pinIndex = args[0][1] + 1; + if (pinIndex === 17) pinIndex--; + newPinValue = args[0][3]; + twighlight = args[0][2]; + } + else + { + pinIndex = args[0]; + newPinValue = args[1]; + } + + let obj = pinsData[pinIndex]; + + if(obj == undefined) + { + previousValues[pinIndex] = newPinValue; + return; + } + + let type = obj.type; + let line = obj.line; + let tbname = obj.tbname; + + //default value + let value = "On"; + if(newPinValue === 0) value = "Off"; + + //Hlavný istič + if(type === "state_of_main_switch") + { + if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) + { + sendNotification("switchLogic", edgeName, "main_switch_has_been_turned_off", {}, "", instanceSendTo.tb, instance , "state_of_main_switch"); + values["status"] = "NOK"; + + deviceStatuses["state_of_main_switch"] = "Off"; + } + else if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) + { + sendNotification("switchLogic", edgeName, "main_switch_has_been_turned_on", {}, "", instanceSendTo.tb, instance , "state_of_main_switch"); + + deviceStatuses["state_of_main_switch"] = "On"; + } + } + + //Prevádzkový mód + if(type == "rotary_switch_state") + { + // combination of these two pins required to get result + let pin2, pin3; + if(pinIndex == 2 || pinIndex == "input1_02") + { + pin2 = newPinValue; + pin3 = previousValues[3] || previousValues["input1_03"]; + if (pin3 == undefined) pin3 = 0; + } + else if(pinIndex == 3 || pinIndex == "input1_03") + { + pin3 = newPinValue; + pin2 = previousValues[2] || previousValues["input1_02"]; + if (pin2 == undefined) pin2 = 0; + } + + value = "Off"; + if (pin2 == 1 && pin3 == 0) value = "Manual"; + if (pin2 == 0 && pin3 == 0) value = "Off"; + if (pin2 == 0 && pin3 == 1) value = "Automatic"; + + deviceStatuses["rotary_switch_state"] = value; + + //automatic - profilu pre nody sa vykonavaju + //ak je spracovany, a automatic - tak ho zapnem + //ak nie je spracovany, iba profil zapisem + + instance.send(instanceSendTo.cmd_manager, {sender: "di_do_controller", cmd: "rotary_switch_state", value: value}); + + //console.log("rotary_switch_state pin", pin2, pin3, value); + } + //Zdroj - pin 4 + else if (type === "power_supply") + { + if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) + { + //sendNotification("switchLogic", edgeName, ERRWEIGHT.ALERT, "Power supply is not OK", "", instanceSendTo.tb, instance); + sendNotification("switchLogic", edgeName, "power_supply_has_disconnected_input", {}, "", instanceSendTo.tb, instance, "power_supply"); + values["status"] = "NOK"; + + deviceStatuses["power_supply"] = "NOK"; + } + else if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) + { + //sendNotification("switchLogic", edgeName, ERRWEIGHT.NOTICE, "Power supply is is OK", "", instanceSendTo.tb, instance); + sendNotification("switchLogic", edgeName, "power_supply_works_correctly", {}, "", instanceSendTo.tb, instance, "power_supply"); + + deviceStatuses["power_supply"] = "OK"; + } + } + //Batéria - pin 5 + else if (type === "battery") + { + if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) + { + //sendNotification("switchLogic", edgeName, ERRWEIGHT.ERROR, "Battery is not OK", "", instanceSendTo.tb, instance); + sendNotification("switchLogic", edgeName, "battery_level_is_low", {}, "", instanceSendTo.tb, instance, "battery_level"); + values["status"] = "NOK"; + + deviceStatuses["battery"] = "NOK"; + } + else if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) + { + //sendNotification("switchLogic", edgeName, ERRWEIGHT.NOTICE, "Battery is OK", "", instanceSendTo.tb, instance); + sendNotification("switchLogic", edgeName, "battery_level_is_ok", {}, "", instanceSendTo.tb, instance, "battery_level"); + + deviceStatuses["battery"] = "OK"; + } + } + //Dverový kontakt - pin 6 + else if(type == "door_condition") + { + newPinValue === 0 ? value = "open" : value = "closed"; + + if (newPinValue != previousValues[pinIndex]) + { + //sendNotification("switchLogic", edgeName, ERRWEIGHT.NOTICE, `RVO door ${value}`, "", instanceSendTo.tb, instance, "rvo_door"); + //TODO ? sendNotification("switchLogic", edgeName, "door_value", {value: value}, "", instanceSendTo.tb, instance, "rvo_door"); + } + + if (value === "open" && FLOW.OMS_maintenance_mode) + { + sendNotification("switchLogic", edgeName, "door_has_been_open", {}, "", instanceSendTo.tb, instance, "rvo_door"); + } + + if (value === "open" && !FLOW.OMS_maintenance_mode) + { + //sendNotification("switchLogic", edgeName, ERRWEIGHT.WARNING, "RVO open door out of maintenance mode", "", instanceSendTo.tb, instance); + sendNotification("switchLogic", edgeName, "door_has_been_open_without_permision_alarm_is_on", {}, "", instanceSendTo.tb, instance, "rvo_door"); + values["status"] = "NOK"; + + //console.log(door_has_been_open_without_permision_alarm_is_on); + + //zapneme sirenu + turnOnAlarm(); + } + + if (value === "closed") + { + if(alarmStatus == "ON") turnOffAlarm(); + //turnOffAlarm(); + + sendNotification("switchLogic", edgeName, "door_has_been_closed", {}, "", instanceSendTo.tb, instance, "rvo_door"); + } + + deviceStatuses["door_condition"] = value; + + } + else if(type == "twilight_sensor") + { + //lux sensor + value = parseFloat(newPinValue + (256*twighlight)); + + let now = new Date(); + //new Date(dusk.getTime() + + let obj = {timestamp: now.getTime(), value: value}; + + //test + //twilight_sensor_interval = 1; + + twilight_sensor.push(obj); + twilight_sensor_array.push(value); + + //check if we receive just 1 constant value from lux sensor ==> error + if(twilight_sensor_array.length > 10) { + + let set = new Set(twilight_sensor_array); + if(set.size === 1 && !twighlightError) + { + twighlightError = true; + values["status"] = "NOK"; + let value = twilight_sensor_array.shift(); + //sendNotification("switchLogic", edgeName, ERRWEIGHT.ERROR, "Lux sensor error", {"Repeating value": value}, instanceSendTo.tb, instance ); + newPinValue = 0; + } + else if (set.size !== 1 && twighlightError) + { + //sendNotification("switchLogic", edgeName, ERRWEIGHT.NOTICE, "Lux sensor is working again", "", instanceSendTo.tb, instance ); + twighlightError = false; + twilight_sensor_array.shift(); + newPinValue = value; + } + else if (set.size === 1 && twighlightError) + { + values["status"] = "NOK"; + twilight_sensor_array.shift(); + newPinValue = 0; + } + } + + + let diff = twilight_sensor[ twilight_sensor.length - 1 ].timestamp - twilight_sensor[0].timestamp; + if(diff >= twilight_sensor_interval * 60 * 1000) + { + const average = twilight_sensor.reduce((acc, c) => acc + c.value, 0) / twilight_sensor.length; + instance.send(instanceSendTo.cmd_manager, {sender: "di_do_controller", cmd: "lux_sensor", value: average}); + + twilight_sensor = []; + + //console.log("lux_sensor send", average); + } + //else console.log("lux_sensor", value, diff); + + values["status"] = "OK"; + + // + } + else if(type == "state_of_contactor") + { + //sendNotification("switchLogic", edgeName, ERRWEIGHT.INFO, `State of contactor ${line} is now ${value}`, "", instanceSendTo.tb, instance ); + sendNotification("switchLogic", edgeName, "state_of_contactor_for_line", {line: line, value: value}, "", instanceSendTo.tb, instance ); + + deviceStatuses["state_of_contactor"][line] = value; + + //true, false + if(value === "On") value = true; + else if(value === "Off") value = false; + + //modify table relays + dbRelays.modify({ contactor: newPinValue }).where("line", line).make(function(builder) { + + builder.callback(function(err, response) { + + /* + if(useTurnOffCounter) + { + turnOffCounter--; + + if(turnOffCounter <= 0) + { + useTurnOffCounter = false; + } + } + */ + + if(err == undefined) + { + + let time = 0; + if(value) time = 1000 * 10;//10 sekund + + let dataChanged = false; + if(relaysData[line].contactor != value) dataChanged = true; + relaysData[line].contactor = value; + + //ak bola predchadzajuci stav off a novy stav je on, budu sa nastavovat nespracovane node profiles + //a budu sa odosielat commandy, tie vsak mozu zlyhat, a preto potrebujeme ich spusti trochu neskor + + setTimeout(function(){ + instance.send(instanceSendTo.cmd_manager, {sender: "di_do_controller", cmd: "reload_relays", line: line, time: time, value: value, dataChanged: dataChanged}); + }, time); + + reportLineStatus(line); + + + } + else + { + errLogger.error("modify table relays failed", err); + } + + + }); + }); + } + + if(type === "state_of_breaker") + { + + let valueChanged = false; + if(newPinValue != previousValues[pinIndex]) valueChanged = true; + + if(valueChanged) + { + instance.send(instanceSendTo.cmd_manager, {sender: "di_do_controller", cmd: "state_of_breaker", value: value, line: line}); + } + + if(value == "Off") values["status"] = "NOK"; + + deviceStatuses["state_of_breaker"][line] = value; + + reportLineStatus(line); + + } + + values[type] = value; + + let result = checkFinalRVOStatus(); + if(!result && line == 0) + { + values["status"] = "NOK"; + } + + //-- + + //if(FLOW.OMS_rvo_tbname == tbname) values["statecode"] = calculateStateCode(); + + if(pinsData.hasOwnProperty(pinIndex)) + { + let valueChanged = false; + if(newPinValue != previousValues[pinIndex]) valueChanged = true; + + if(type == "state_of_contactor") valueChanged = true; + if(type == "rotary_switch_state") valueChanged = true; + if(type === "state_of_breaker") + { + //console.log(type, values, valueChanged); + } + + if(FLOW.OMS_rvo_tbname == "") + { + console.log("FLOW.OMS_rvo_tbname is EMPTY"); + } + + if(FLOW.OMS_rvo_tbname == tbname) + { + values["statecode"] = calculateStateCode(); + //console.log(type, values, valueChanged, FLOW.OMS_rvo_tbname, tbname); + } + + + if(valueChanged) + { + sendTelemetry(values, tbname); + } + + if(type == "rotary_switch_state") + { + if(FLOW.OMS_maintenance_mode) value = "maintenance"; + value = value.toLowerCase(); + + let values = {}; + values["power_mode"] = value; + + sendTelemetry(values, tbname); + } + } + else + { + logger.debug("no pinIndex", pinsData[pinIndex], pinsData); + } + + //pin was changed + previousValues[pinIndex] = newPinValue; + + + } + + function sendTelemetry(values, tbname) + { + let dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + instance.send(instanceSendTo.tb, dataToTb); + } + + + +} + + + + +//! incomming data to websocket +// [ +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_08', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_01', +// alias: 'al_lights_kitchen', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_02', +// alias: 'al_lights_bedroom', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_03', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_04', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_05', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_06', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// glob_dev_id: 1, +// modes: [ 'Simple' ], +// value: 0, +// circuit: '1_07', +// pending: false, +// relay_type: 'physical', +// dev: 'relay', +// mode: 'Simple' +// }, +// { +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// modes: [ 'Simple', 'DirectSwitch' ], +// value: 0, +// circuit: '1_08', +// debounce: 50, +// counter: 0, +// counter_mode: 'Enabled', +// dev: 'input', +// mode: 'Simple' +// }, +// { +// counter_mode: 'Enabled', +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// dev: 'input', +// modes: [ 'Simple', 'DirectSwitch' ], +// debounce: 50, +// counter: 1, +// value: 1, +// alias: 'al_main_switch', +// mode: 'Simple', +// circuit: '1_01' +// }, +// { +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// modes: [ 'Simple', 'DirectSwitch' ], +// value: 1, +// circuit: '1_02', +// debounce: 50, +// counter: 2, +// counter_mode: 'Enabled', +// dev: 'input', +// mode: 'Simple' +// }, +// { +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// modes: [ 'Simple', 'DirectSwitch' ], +// value: 1, +// circuit: '1_03', +// debounce: 50, +// counter: 2, +// counter_mode: 'Enabled', +// dev: 'input', +// mode: 'Simple' +// }, +// { +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// modes: [ 'Simple', 'DirectSwitch' ], +// value: 0, +// circuit: '1_04', +// debounce: 50, +// counter: 1, +// counter_mode: 'Enabled', +// dev: 'input', +// mode: 'Simple' +// }, +// { +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// modes: [ 'Simple', 'DirectSwitch' ], +// value: 0, +// circuit: '1_05', +// debounce: 50, +// counter: 0, +// counter_mode: 'Enabled', +// dev: 'input', +// mode: 'Simple' +// }, +// { +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// modes: [ 'Simple', 'DirectSwitch' ], +// value: 0, +// circuit: '1_06', +// debounce: 50, +// counter: 0, +// counter_mode: 'Enabled', +// dev: 'input', +// mode: 'Simple' +// }, +// { +// counter_modes: [ 'Enabled', 'Disabled' ], +// glob_dev_id: 1, +// modes: [ 'Simple', 'DirectSwitch' ], +// value: 0, +// circuit: '1_07', +// debounce: 50, +// counter: 0, +// counter_mode: 'Enabled', +// dev: 'input', +// mode: 'Simple' +// }, +// { +// interval: 3, +// value: 24.5, +// circuit: '28744F7791180257', +// address: '28744F7791180257', +// time: 1631873896.48797, +// typ: 'DS18B20', +// lost: false, +// dev: 'temp' +// }, +// { +// bus: '/dev/i2c-2', +// interval: 3, +// dev: 'owbus', +// scan_interval: 300, +// circuit: '1', +// do_scan: false, +// do_reset: false +// }, +// { +// glob_dev_id: 1, +// last_comm: 0.014672994613647461, +// ver2: '0.1', +// sn: 42, +// circuit: '1', +// model: 'S207', +// dev: 'neuron', +// board_count: 1 +// }, +// { +// circuit: '1_01', +// value: 0, +// glob_dev_id: 1, +// dev: 'wd', +// timeout: 5000, +// was_wd_reset: 0, +// nv_save: 0 +// } +// ] + +//! loaded pins_data --> from LM +// { +// '1': { pin: 1, type: 'state_of_main_switch', line: 0 }, +// '2': { pin: 2, type: 'rotary_switch_state', line: 0 }, +// '3': { pin: 3, type: 'rotary_switch_state', line: 0 }, +// '4': { pin: 4, type: 'power_supply', line: 0 }, +// '5': { pin: 5, type: 'battery', line: 0 }, +// '6': { pin: 6, type: 'door_condition', line: 0 }, +// '8': { pin: 8, type: 'state_of_breaker', line: 1 }, +// '9': { pin: 9, type: 'state_of_breaker', line: 2 }, +// '10': { pin: 10, type: 'state_of_breaker', line: 3 }, +// '11': { pin: 11, type: 'state_of_contactor', line: 1 }, +// '12': { pin: 12, type: 'state_of_contactor', line: 2 }, +// '13': { pin: 13, type: 'state_of_contactor', line: 3 }, +// '16': { pin: 16, type: 'twilight_sensor', line: 0 } +// } + +//! pins.table --> from LM +// 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|........... + +//! pins.table --> from UNIPI +// pin:string|type:string|line:number +// *|al_mswitch|state_of_main_switch|0|........... +// *|al_rswitch1|rotary_switch_state|0|........... +// *|al_rswitch2|rotary_switch_state|0|........... +// *|al_power|power_supply|0|........... +// *|al_battery|battery|0|........... +// *|al_door|door_condition|0|........... +// *|al_breaker1|state_of_breaker|1|........... +// *|al_breaker2|state_of_breaker|2|........... +// *|al_breaker3|state_of_breaker|3|........... +// *|al_breaker4|state_of_breaker|4|........... +// *|al_relay_1|state_of_contactor|1|........... +// *|al_relay_2|state_of_contactor|2|........... +// *|al_relay_3|state_of_contactor|3|........... +// *|al_relay_4|state_of_contactor|4|........... +// *|16|twilight_sensor|0|........... +// *|28744F7791180257|temperature|0|........... + + +//! pins_data --> from UNIPI +// { +// '16': { pin: '16', type: 'twilight_sensor', line: 0 }, +// al_mswitch: { pin: 'al_mswitch', type: 'state_of_main_switch', line: 0 }, +// al_rswitch1: { pin: 'al_rswitch1', type: 'rotary_switch_state', line: 0 }, +// al_rswitch2: { pin: 'al_rswitch2', type: 'rotary_switch_state', line: 0 }, +// al_power: { pin: 'al_power', type: 'power_supply', line: 0 }, +// al_battery: { pin: 'al_battery', type: 'battery', line: 0 }, +// al_door: { pin: 'al_door', type: 'door_condition', line: 0 }, +// al_breaker1: { pin: 'al_breaker1', type: 'state_of_breaker', line: 1 }, +// al_breaker2: { pin: 'al_breaker2', type: 'state_of_breaker', line: 2 }, +// al_breaker3: { pin: 'al_breaker3', type: 'state_of_breaker', line: 3 }, +// al_breaker4: { pin: 'al_breaker4', type: 'state_of_breaker', line: 4 }, +// al_relay_1: { pin: 'al_relay_1', type: 'state_of_contactor', line: 1 }, +// al_relay_2: { pin: 'al_relay_2', type: 'state_of_contactor', line: 2 }, +// al_relay_3: { pin: 'al_relay_3', type: 'state_of_contactor', line: 3 }, +// al_relay_4: { pin: 'al_relay_4', type: 'state_of_contactor', line: 4 }, +// '28744F7791180257': { pin: '28744F7791180257', type: 'temperature', line: 0 } +// } + + +//! relays_data +// { +// '0': { +// line: 0, +// tbname: 'KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV', +// contactor: 1, +// profile: '' +// }, +// '1': { +// line: 1, +// tbname: 'RMgnK93rkoAazbqdQ4yBG95Z1YXGx6pmwBeVEP2O', +// contactor: 0, +// profile: '{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"10:00","start_time":"20:00"},{"value":0,"end_time":"10:20","start_time":"10:00"},{"value":1,"end_time":"10:40","start_time":"10:20"},{"value":0,"end_time":"11:00","start_time":"10:40"},{"value":1,"end_time":"11:30","start_time":"11:00"},{"value":0,"end_time":"11:50","start_time":"11:30"},{"value":1,"end_time":"13:00","start_time":"11: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}' +// }, +// '2': { +// line: 2, +// tbname: 'dlE1VQjYrNx9gZRmb38gG08oLBO4qaAk2M6JPnG7', +// contactor: 0, +// profile: '{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"10:00","start_time":"20:00"},{"value":0,"end_time":"10:20","start_time":"10:00"},{"value":1,"end_time":"10:40","start_time":"10:20"},{"value":0,"end_time":"11:00","start_time":"10:40"},{"value":1,"end_time":"11:30","start_time":"11:00"},{"value":0,"end_time":"11:50","start_time":"11:30"},{"value":1,"end_time":"13:00","start_time":"11: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}' +// }, +// '3': { +// line: 3, +// tbname: 'vnmG4kJxaXWNBgMQq0D7Aj5e9oZzOAlr6LdR3w2V', +// contactor: 0, +// profile: '{"intervals":[{"value":0,"end_time":"20:30","start_time":"13:00"},{"value":1,"end_time":"00:10","start_time":"20:30"},{"value":0,"end_time":"13:00","start_time":"05:40"},{"value":1,"end_time":"05:40","start_time":"00:10"}],"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}' +// } +// } + diff --git a/flow/gettemperature.js b/flow/gettemperature.js new file mode 100644 index 0000000..171cc96 --- /dev/null +++ b/flow/gettemperature.js @@ -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); + +}; \ No newline at end of file diff --git a/flow/helper/DataToTbHandler.js b/flow/helper/DataToTbHandler.js new file mode 100644 index 0000000..15b0c0a --- /dev/null +++ b/flow/helper/DataToTbHandler.js @@ -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; \ No newline at end of file diff --git a/flow/helper/ErrorToServiceHandler.js b/flow/helper/ErrorToServiceHandler.js new file mode 100644 index 0000000..5adc29a --- /dev/null +++ b/flow/helper/ErrorToServiceHandler.js @@ -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; \ No newline at end of file diff --git a/flow/helper/db_helper.js b/flow/helper/db_helper.js new file mode 100644 index 0000000..40e796c --- /dev/null +++ b/flow/helper/db_helper.js @@ -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 + } \ No newline at end of file diff --git a/flow/helper/error_reporter.js b/flow/helper/error_reporter.js new file mode 100644 index 0000000..2b84369 --- /dev/null +++ b/flow/helper/error_reporter.js @@ -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 +} \ No newline at end of file diff --git a/flow/helper/error_reporting.js b/flow/helper/error_reporting.js new file mode 100644 index 0000000..62dace5 --- /dev/null +++ b/flow/helper/error_reporting.js @@ -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, +} \ No newline at end of file diff --git a/flow/helper/md5.js b/flow/helper/md5.js new file mode 100644 index 0000000..d3b0480 --- /dev/null +++ b/flow/helper/md5.js @@ -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>>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))<>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>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_} + +module.exports = { + MD5 +} \ No newline at end of file diff --git a/flow/helper/notification_reporter.js b/flow/helper/notification_reporter.js new file mode 100644 index 0000000..566b93f --- /dev/null +++ b/flow/helper/notification_reporter.js @@ -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 +} \ No newline at end of file diff --git a/flow/helper/register.js b/flow/helper/register.js new file mode 100644 index 0000000..fc1d008 --- /dev/null +++ b/flow/helper/register.js @@ -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 +} \ No newline at end of file diff --git a/flow/helper/serialport_helper.js b/flow/helper/serialport_helper.js new file mode 100644 index 0000000..a84ab84 --- /dev/null +++ b/flow/helper/serialport_helper.js @@ -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 +} \ No newline at end of file diff --git a/flow/helper/suncalc.js b/flow/helper/suncalc.js new file mode 100644 index 0000000..c9ca56d --- /dev/null +++ b/flow/helper/suncalc.js @@ -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; + +}()); diff --git a/flow/helper/utils.js b/flow/helper/utils.js new file mode 100644 index 0000000..a16210c --- /dev/null +++ b/flow/helper/utils.js @@ -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 +} \ No newline at end of file diff --git a/flow/httprequest.js b/flow/httprequest.js new file mode 100644 index 0000000..469c8cb --- /dev/null +++ b/flow/httprequest.js @@ -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 = `
+
@(URL address)
+
+
+
@(HTTP method)
+
+
+
@(Serialization)
+
+
+
@(Download the content in chunks)
+
@(Keep persistent cookies)
+
@(Disable DNS cache)
+
@(Keep alive connection)
+
+
+
+
@(Custom headers)
+
@(Cookies)
+
+
+
+ +
+
+
+
@(User)
+
+
+
@(Password)
+
+
+
+
+
`; + +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(); +}; \ No newline at end of file diff --git a/flow/httpresponse.js b/flow/httpresponse.js new file mode 100644 index 0000000..160a3bb --- /dev/null +++ b/flow/httpresponse.js @@ -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 = `
+
@(Response data-type)
+
JSON is by default.
+
`; + +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); + }; +}; diff --git a/flow/httproute.js b/flow/httproute.js new file mode 100644 index 0000000..583a19d --- /dev/null +++ b/flow/httproute.js @@ -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 = `
+
+ +
+ +
+
+
@(HTTP method)
+
+
+
@(URL address)
+
+
+
+
+
@(Additional flags)
+
@(Separate flags by comma e.g. json, authorize)
+
+
+
@(Max. request size)
+
@(In kB kilobytes)
+
+
+
@(Timeout)
+
@(In seconds.)
+
+
+
+
+
+
@(Automatically respond with 200 OK?)
+
@(If not checked you need to use HTTP response component to respond to the request.)
+
+
@(Custom headers)
+
@(Cookies)
+
+
+
+
+
+
@(Cache policy)
+
@(User instance must contain id property.)
+
+
+
@(Expiration)
+
@(E.g. 5 minutes)
+
+
+
+`; + +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); +}; diff --git a/flow/infosender.js b/flow/infosender.js new file mode 100644 index 0000000..5043687 --- /dev/null +++ b/flow/infosender.js @@ -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 = `
+
+
+
CSV Import
+
+
+
`; + +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); + +} + diff --git a/flow/modbus_citysys.js b/flow/modbus_citysys.js new file mode 100644 index 0000000..d038b69 --- /dev/null +++ b/flow/modbus_citysys.js @@ -0,0 +1,1127 @@ +exports.id = 'modbus_citysys'; +exports.title = 'Modbus_citysys'; +exports.version = '1.0.0'; +exports.group = 'Worksys'; +exports.color = '#2134B0'; +exports.input = 1; +exports.output = ["red", "white", "blue", "orange"]; +exports.click = false; +exports.author = 'Jakub Klena'; +exports.icon = 'bolt'; +exports.options = { edge: "undefined" }; + +exports.html = `
+
+
+
Edge TB Name
+
+
+
`; + +exports.readme = `# Energomonitor +## Outputs + + - *Red* - ERROR output (can connect to filewriter or something) + - *White* - STATUS output (answers to your commands, ERRORS and WARNINGS from your commands go both to this and to their own outputs, so they get logged) + - *Blue* - TB output (pure data for TB) +`; + + +const instanceSendTo = { + error: 0, + debug: 1, + tb: 2, + di_do_controller: 3 +} + +const DataToTbHandler = require('./helper/DataToTbHandler.js'); +const { sendNotification } = require('./helper/notification_reporter.js'); +const dbRelays = TABLE("relays"); +const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); +let tbname; + +async function loadSettings() +{ + //todo global FLOW.OMS_edgeName is making problem, so we load it here as well, it should not be + let responseRelays = await promisifyBuilder(dbRelays.find()); + FLOW.OMS_edgeName = responseRelays[0]["tbname"]; + tbname = FLOW.OMS_edgeName; +} + +loadSettings(); + +exports.install = function(instance) { + const SerialPort = require('serialport'); + const { exec } = require('child_process'); + const fs = require("fs"); + const filepath = F.path.root("saved_data/modbus_settings"); + const backup_filepath = F.path.root("saved_data/modbus_settings_backup"); + + const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler.js'); + const errorHandler = new ErrorToServiceHandler(); + + let receivedDataArray = []; + + + instance.CONFIG = { + "isRunning": false, + "debug": true, + "timeoutTime": 10000, + "msgWaitTime": 1000, + "port": "/dev/ttymxc1", + //"port_options": "stty -F /dev/ttymxc1 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke" + "port_options": "stty -F /dev/ttymxc1 9600 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke" + }; + + let PRIVATEVARS = { + "errBuffer": [], // Buffer for error messages + "tbBuffer": [], // Buffer for TB push messages + "device_index": 0, + "cmd_index": -1, + "devices": [ + /*{ + "name": "Elektrometer 1", + "tb_name": "EOzNMgZ9n43qPbjXmy7zwdA2DKdYvW5e6pxGRrVa", + "type": "EM111", + "address": 1, + "data":[], + "cmd":[], + "timeoutcount":0, + "status":"virtual" + },*/ + // { + // "name": "Elektrometer 2", + // "tb_name": "pJX1ObgmqGZ54DMyYL7aDdkEVdve38WKRzwjNrQ9", + // "type": "EM111", + // "address": 2, + // "data":[], + // "cmd":[], + // "timeoutcount":0, + // "status":"virtual" + // }, + // { + // "name": "Elektrometer 3", + // "tb_name": "XRvmwNz8QPblKp41GD7lKVkJrLVYoBO92dMegn6W", + // "type": "EM111", + // "address": 3, + // "data":[], + // "cmd":[], + // "timeoutcount":0, + // "status":"virtual" + // }, + // { + // "name": "Elektrometer 4", + // "tb_name": "oRO8rjaBDy21qPQJzW7oD9ApK3xmNleVZg9Ed4Gw", + // "type": "EM111", + // "address": 4, + // "data":[], + // "cmd":[], + // "timeoutcount":0, + // "status":"virtual" + // }, + { + "name": "Elektrometer 1", + "tb_name": "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", + "type": "EM340", + "address": 1, + "data":[], + "cmd":[], + "timeoutcount":0, + "status":"virtual" + } + ], + "cmd_tables": [ + { + "type":"EM340", + "cmd":[ + { + "name": "Voltage L1", + "tb_name": "a", + "register": 0, + "size": 2, + "multiplier": 0.1 + }, + { + "name": "Voltage L2", + "tb_name": "b", + "register": 2, + "size": 2, + "multiplier": 0.1 + }, + { + "name": "Voltage L3", + "tb_name": "c", + "register": 4, + "size": 2, + "multiplier": 0.1 + }, + { + "name": "Current L1", + "tb_name": "d", + "register": 12, + "size": 2, + "multiplier": 0.001 + }, + { + "name": "Current L2", + "tb_name": "e", + "register": 14, + "size": 2, + "multiplier": 0.001 + }, + { + "name": "Current L3", + "tb_name": "f", + "register": 16, + "size": 2, + "multiplier": 0.001 + } + + + // { + // "name": "Power factor", + // "tb_name": "power_factor", + // "register": 14, + // "size": 1, + // "multiplier": 0.001 + // }, + // { + // "name": "Frequency", + // "tb_name": "frequency", + // "register": 15, + // "size": 1, + // "multiplier": 0.1 + // }, + // { + // "name": "Energy", + // "tb_name": "consumption", + // "register": 16, + // "size": 2, + // "multiplier": 0.1 + // } + ] + } + ] + }; + + 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 + }; + + instance.currentData = function(){ + let resp = []; + for (let f = 0; f < PRIVATEVARS.devices.length; f++){ + let dev = PRIVATEVARS.devices[f]; + for (let e = 0; e < dev.data.length; e++){ + let d = dev.data[e]; + resp.push({ + "name": dev.name+" - "+d.name, + "value": d.value + }); + } + } + return resp; + }; + + instance.configList = function(){ + let resp = []; + /*let data = PRIVATEVARS.feeds; + for (let a = 0; a < data.length; a++){ + for (let i = 0; i < instance.CONFIG.feeds.length; i++){ + let feed = instance.CONFIG.feeds[i]; + if (feed.name === data[a].id){ + for (let b = 0; b < data[a].streams.length; b++){ + for (let j = 0; j < feed.streams.length; j++){ + let stream = feed.streams[j]; + if (stream.name === data[a].streams[b].id){ + data[a].streams[b]["exists"] = true; + data[a].streams[b]["currently"] = stream; + } + } + } + } + } + } + resp.push({ + "name":"Device manager", + "icon":"tasks", + "_show":false, + "js_func":"energoDevManager", + "data": data + });*/ + + return resp; + } + + let timeoutInterval = null; + let msgWaitInterval = null; + let port = null; + let myEdge = "undefined"; + let starter = null; + instance.status("Loading...", "red"); + + + instance.availableCommands = [ + { + "name": "Status", + "cmd": "qStatus", + "icon": "stream", + "func": function(body){ + let a = true; + if (timeoutInterval === null){ + a = false; + } + let b = true; + if (msgWaitInterval === null){ + b = false; + } + let st = { + "isRunning":instance.CONFIG.isRunning, + "timeoutInterval":a, + "msgWaitInterval":b, + "CONFIG":instance.CONFIG + }; + return { + "type": "ok", + "timestamp": humanReadableTimeAndDate(), + "resp": st + }; + } + }, + { + "name": "Start Reading", + "cmd": "sStart", + "icon": "play", + "func": function(body){ + /*if (running === false){ + startCmdWaitInterval(); + running = true; + return "Reading started !"; + } else { + return "Reading already active !"; + }*/ + return { + "type": "ok", + "timestamp": humanReadableTimeAndDate(), + "resp": "WIP" + }; + } + }, + { + "name": "Stop Reading", + "cmd": "sStop", + "icon": "stop", + "func": function(body){ + /*if (running === true){ + stopCmdWaitInterval(); + stopTimeoutInterval(); + running = false; + return "Reading stopped !"; + } else { + return "Reading already inactive !"; + }*/ + return { + "type": "ok", + "timestamp": humanReadableTimeAndDate(), + "resp": "WIP" + }; + } + }, + { + "name": "Read current data", + "cmd": "qCurrentData", + "icon": "chart-bar", + "func": function(body){ + let resp = instance.currentData(); + return { + "type": "ok", + "timestamp": humanReadableTimeAndDate(), + "resp": resp + }; + } + }, + { + "name": "Save current config", + "cmd": "saveConfig", + "icon": "save", + "func": function(body){ + + instance.set("config", JSON.stringify(instance.CONFIG)); + instance.set("private", JSON.stringify(PRIVATEVARS)); + return { + "type": "ok", + "timestamp": humanReadableTimeAndDate(), + "resp": "done" + }; + } + }, + { + "name": "Toggle debug", + "cmd": "sDebug", + "icon": "comment-dots", + "func": function(body){ + + if (instance.CONFIG.debug){ + instance.CONFIG.debug = false; + instance.set("config", JSON.stringify(instance.CONFIG)); + + return { + "type": "ok", + "timestamp": humanReadableTimeAndDate(), + "resp": "debug OFF" + }; + } else { + instance.CONFIG.debug = true; + instance.set("config", JSON.stringify(instance.CONFIG)); + + return { + "type": "ok", + "timestamp": humanReadableTimeAndDate(), + "resp": "debug ON" + }; + } + + } + } + ]; + + + + function saveData(){ + if (checkFile(filepath)){ + let content = undefined; + try { + content = fs.readFileSync(filepath); + } catch (err){ + console.log("saveData", myEdge, ERRWEIGHT.ERROR, "Unable to read original configuration !", {"name":instance.name, "id":instance.id, "file":filepath, "err":err.message}); + + //sendError("saveData", myEdge, ERRWEIGHT.ERROR, "Unable to read original configuration !", {"name":instance.name, "id":instance.id, "file":filepath, "err":err.message}); + } + + if (content !== undefined){ + try { + fs.writeFileSync(backup_filepath, content, "utf8"); + } catch (err) { + //sendError("saveData", myEdge, ERRWEIGHT.ERROR, "Unable to save backup of configuration !", {"name":instance.name, "id":instance.id, "file":backup_filepath, "err":err.message}); + console.log("saveData", myEdge, ERRWEIGHT.ERROR, "Unable to save backup of configuration !", {"name":instance.name, "id":instance.id, "file":backup_filepath, "err":err.message}); + } + } + } + + let a = { + "config":instance.CONFIG, + "private":PRIVATEVARS + }; + + try { + fs.writeFileSync(filepath, JSON.stringify(a), "utf8"); + } catch (err) { + //sendError("saveData", myEdge, ERRWEIGHT.CRITICAL, "Unable to save configuration !", {"name":instance.name, "id":instance.id, "file":filepath, "err":err.message}); + console.log("saveData", myEdge, ERRWEIGHT.CRITICAL, "Unable to save configuration !", {"name":instance.name, "id":instance.id, "file":filepath, "err":err.message}); + } + } + + function loadData(){ + let content = undefined; + //console.log(filepath); + if (checkFile(filepath)){ + try { + content = fs.readFileSync(filepath); + } catch (err){ + //sendError("loadData", myEdge, ERRWEIGHT.ERROR, "Unable to read original configuration !", {"name":instance.name, "id":instance.id, "file":filepath, "err":err.message}); + console.log("loadData", myEdge, ERRWEIGHT.ERROR, "Unable to read original configuration !", {"name":instance.name, "id":instance.id, "file":filepath, "err":err.message}); + } + } else { + if (checkFile(backup_filepath)){ + try { + content = fs.readFileSync(backup_filepath); + } catch (err){ + //sendError("loadData", myEdge, ERRWEIGHT.CRITICAL, "Unable to read backup configuration !", {"name":instance.name, "id":instance.id, "file":backup_filepath, "err":err.message}); + console.log("loadData", myEdge, ERRWEIGHT.CRITICAL, "Unable to read backup configuration !", {"name":instance.name, "id":instance.id, "file":backup_filepath, "err":err.message}); + } + if (content !== undefined){ + //sendError("loadData", myEdge, ERRWEIGHT.WARNING, "No configuration, loading from backup !", {"name":instance.name, "id":instance.id}); + console.log("loadData", myEdge, ERRWEIGHT.WARNING, "No configuration, loading from backup !", {"name":instance.name, "id":instance.id}); + } + } else { + //sendError("loadData", myEdge, ERRWEIGHT.CRITICAL, "No configuration, not even backup !", {"name":instance.name, "id":instance.id}); + console.log("loadData", myEdge, ERRWEIGHT.CRITICAL, "No configuration, not even backup !", {"name":instance.name, "id":instance.id, "filepath": filepath}); + } + } + + + + if (content !== undefined){ + let a = JSON.parse(content); + instance.send(instanceSendTo.debug, a); + let c = a.config; + let p = a.private; + + + if (c === undefined){ + //sendError("loadData", myEdge, ERRWEIGHT.CRITICAL, "Configuration not found !", {"name":instance.name, "id":instance.id}); + console.log("loadData", myEdge, ERRWEIGHT.CRITICAL, "Configuration not found !", {"name":instance.name, "id":instance.id}); + instance.status("Error - no config", "red"); + } else if (p === undefined){ + //sendError("loadData", myEdge, ERRWEIGHT.CRITICAL, "Privatevars not found !", {"name":instance.name, "id":instance.id}); + console.log("loadData", myEdge, ERRWEIGHT.CRITICAL, "Privatevars not found !", {"name":instance.name, "id":instance.id}); + instance.status("Error - no private vars", "red"); + } else { + instance.CONFIG = c; + PRIVATEVARS = p; + + // Daj kazdemu device jeho tabulku prikazu + for (let i = 0; i < PRIVATEVARS.devices.length; i++){ + let device = PRIVATEVARS.devices[i]; + for (let j = 0; j < PRIVATEVARS.cmd_tables.length; j++){ + let table = PRIVATEVARS.cmd_tables[j]; + + if (device.type === table.type){ + PRIVATEVARS.devices[i].cmd = table.cmd; + } + } + } + + if (myEdge === "undefined"){ + instance.status("Unconfigured", "red"); + } else { + instance.status("Running", "green"); + startCmdWaitInterval(); + + instance.CONFIG.isRunning = true; + console.log("modbus loaded: ", PRIVATEVARS.devices); + } + } + } + } + + function checkFile(name){ + try { + fs.accessSync(name, fs.constants.F_OK | fs.constants.R_OK | fs.constants.W_OK); + return true; + } catch (err) { + return false; + } + } + + //Zapina slucku vycitavania dat + function readDeviceData(){ + stopCmdWaitInterval(); + + // let tbname = FLOW.OMS_edgeName; + + // Check port existance + if (port === null) + { + port = new SerialPort(instance.CONFIG.port); + + port.on('error', function(err) { + //logger.debug("rsPort opened error - failed", err.message); + //instance.send(instanceSendTo.debug, err.message); + + errorHandler.sendMessageToService( exports.title + " MODBUS RS485 open - failed: " + err.message); + }) + + port.on('open', function() { + + console.log("--->MODBUS RS485 READY - port opened"); + + exec("sudo halfduplex /dev/ttymxc1", (error, stdout, stderr) => { + instance.send(instanceSendTo.debug, {"err": error}); + + if (error) { + console.log("--->MODBUS RS485", error, stderr); + errorHandler.sendMessageToService( exports.title + " sudo halfduplex /dev/ttymxc1 - failed: " + error); + } + + }); + + exec(instance.CONFIG.port_options, (error, stdout, stderr) => { + instance.send(instanceSendTo.debug, {"stdout":stdout,"stderr":stderr,"err":error}); + + if (error) { + console.log("--->MODBUS RS485", error, stderr); + errorHandler.sendMessageToService( exports.title + " " + instance.CONFIG.port_options + " - failed: " + error); + } + + }); + + }); + + port.on('data', receivedData); + + //sendError("readDeviceData", myEdge, ERRWEIGHT.DEBUG, "Serial port open!", {}); + //console.log("-->MODBUS readDeviceData", myEdge, ERRWEIGHT.DEBUG, "Serial port open!", {}); + + startCmdWaitInterval(); + return; // Cakame na port aby sa spravne otvoril a rozbehol + } + + + // Skontroluj existenciu device listu + if (PRIVATEVARS.devices.length > 0){ + // Ponastavuj indexy + PRIVATEVARS.cmd_index++; + if (PRIVATEVARS.cmd_index >= PRIVATEVARS.devices[PRIVATEVARS.device_index].cmd.length){ + // Kedže všetky príkazy pre daný device sú vybavené, je treba odoslat vyčítané data do TB + updateDataInTB(); + + PRIVATEVARS.cmd_index = 0; + PRIVATEVARS.device_index++; + + if (PRIVATEVARS.device_index >= PRIVATEVARS.devices.length){ + PRIVATEVARS.device_index = 0; + } + } + + let device = PRIVATEVARS.devices[PRIVATEVARS.device_index]; + + // Skontroluj existenciu príkazú pre daný device type + if (device.cmd.length < 1){ + //sendError("readDeviceData", tbname, ERRWEIGHT.ERROR, "No commands for this device type !", {"type": device.type}); + console.log("readDeviceData", tbname, ERRWEIGHT.ERROR, "No commands for this device type !", {"type": device.type}); + startCmdWaitInterval(); + return; + } + + // Odešli nasledujúci príkaz + sendCommand(); + + } else { + instance.CONFIG.isRunning = false; + //sendError("readDeviceData", myEdge, ERRWEIGHT.CRITICAL, "Modbus has no devices registered!", {}); + console.log("modbus_citys: readDeviceData", myEdge, ERRWEIGHT.CRITICAL, "Modbus has no devices registered!", {}); + } + } + + function readingTimeouted(){ + stopCmdWaitInterval(); + stopTimeoutInterval(); + + // let tbname = FLOW.OMS_edgeName; + + let device = PRIVATEVARS.devices[PRIVATEVARS.device_index]; + let com = device.cmd[PRIVATEVARS.cmd_index]; + //sendError("readingTimeouted", tbame, ERRWEIGHT.WARNING, "Reading timeouted !", {"device": device.address, "cmd": com.register}); + console.log("modbus_citys: readingTimeouted", tbname, ERRWEIGHT.WARNING, "Reading timeouted !", {"device": device.address, "cmd": com.register}); + + device.timeoutcount++; + //console.log("device.timeoutcount", device.timeoutcount); + if (device.timeoutcount === 16) + { + + //sendError("modbus_citys: readingTimeouted", tbname, ERRWEIGHT.CRITICAL, "Electrometer is not responding - reading timeouted", ""); + sendNotification("modbus_citys: readingTimeouted", tbname, "electrometer_is_not_responding", {}, "", instanceSendTo.tb, instance ); + + if (device.status === "OK"){ + device.status === "NOK"; + } + } + + startCmdWaitInterval(); + } + + function receivedData(data){ + + //let array = [...data]; + //console.log("received data", array); + + // let tbname = FLOW.OMS_edgeName; + + //!if received data are less than 9 bytes, we store it in array variable and return. than we concatenate second incoming + // data and then length of array is 9 + receivedDataArray = [...receivedDataArray, ...data]; + //if (array.length < 9) return; + let l = receivedDataArray.length; + //console.log("received",receivedDataArray, l) + + if ( l < 7 || l > 9 || l == 8 ) return; + + let device = PRIVATEVARS.devices[PRIVATEVARS.device_index]; + let com = device.cmd[PRIVATEVARS.cmd_index]; + + if (device.timeoutcount > 16) { + //sendError("Modbus_citysys: receivedData", tbname, ERRWEIGHT.NOTICE, "Electrometer is responding again", ""); + sendNotification("modbus_citys: receivedData", tbname, "electrometer_is_responding_again", {}, "", instanceSendTo.tb, instance ); + } + + device.timeoutcount = 0; + + if ((l == 7 && com.size != 1) || ( l == 9 && com.size != 2)) return; + + stopTimeoutInterval(); + + //sendError("receivedData", tbname, ERRWEIGHT.DEBUG, "Received data !", {"cmd": receivedDataArray}); + //console.log("receivedData", tbname, ERRWEIGHT.DEBUG, "Received data !", {"cmd": receivedDataArray}); + + // Skontroluj či sedí počet bytú v správe + //console.log("com size", com.size*2, "array2", receivedDataArray[2]); + if (receivedDataArray[2] !== (com.size*2)){ + //sendError("receivedData", tbname, ERRWEIGHT.ERROR, "Received data of incorrect size !", {"expected": (com.size*2), "received": receivedDataArray[2], "cmd": com.register, "whole_msg": receivedDataArray}); + console.log("modbus_citys: receivedData", tbname, ERRWEIGHT.ERROR, "Received data of incorrect size !", {"expected": (com.size*2), "received": receivedDataArray[2], "cmd": com.register, "whole_msg": receivedDataArray}); + startTimeoutInterval(); + } else { + // Konvertuj raw data na human readable + + let v = (receivedDataArray[3] << 8) | receivedDataArray[4]; + if (com.size == 2){ + v = v | (receivedDataArray[5] << 24) | (receivedDataArray[6] << 16); + } + v = Math.round((v * com.multiplier) * 100) / 100; + + + // Pokad device nemá ešte žádné hodnoty vyčítané, pushni túto hodnotu do pola + if (device.data.length < 1){ + device.data.push({ // Vždy ked správne zakomunikuje obnov status na OK + "changed": true, + "name": "status", + "value": "OK" + }); + device.data.push({ + "changed": true, + "name": com.tb_name, + "value": v + }); + } else { + // Kedže už neco v poli má, kukni sa či je tam aj táto hodnota + let found = false; + for (let i = 0; i < device.data.length; i++){ + let d = device.data[i]; + if (d.name == "status"){ // Ked natrefíš na status (vždy tam musí byt) prepíš ho na OK lebo zakomunikoval správne + device.data[i].changed = true; + device.data[i].value = "OK"; + } + + if (d.name == com.tb_name){ + found = true; + device.data[i].changed = true; + device.data[i].value = v; + } + } + + // Pole existuje, ale táto hodnota tam neni, pridaj ju + if (found === false){ + device.data.push({ + "changed": true, + "name": com.tb_name, + "value": v + }); + } + } + + //Správne sme prijali odpoveď, je čas na další msg + startCmdWaitInterval(); + } + //console.log('received data array', receivedDataArray); + receivedDataArray = []; + } + + function updateDataInTB(){ + let device = PRIVATEVARS.devices[PRIVATEVARS.device_index]; + + // console.log("---- MB device", device); + // console.log("---MB device data", device.data); + + let values = ""; + for (let i = 0; i < device.data.length; i++){ + let data = device.data[i]; + if (data.changed){ + if (values !== ""){ + values += ", "; + } + + if (data.name === "status"){ + values += "\""+data.name+"\":\""+data.value+"\""; + // This makes sure, that if this device doesn’t respond even once in next reading cycle, it will be marked as NOK + device.data[i].changed = true; + device.data[i].value = "NOK"; + } else { + values += "\""+data.name+"\":"+data.value; + device.data[i].changed = false; + } + } + + } + + //console.log("values modbus", values); + + + if (values !== ""){ + + // let tbname = FLOW.OMS_edgeName; + // if(tbname == "" || tbname === undefined ) + // { + // console.log("!!!!!!FLOW.OMS_edgeName is empty - 1"); + // return; + // } + + let tbmsg = "{\"" + tbname + "\":[{\"ts\":"+Date.now()+", \"values\":{"+values+"}}] }"; + tbmsg = JSON.parse(tbmsg); + + values = tbmsg[tbname][0]["values"]; + + //console.log("modbus", Object.keys(values)); + + //sum Phase_1_power, Phase_2_power, Phase_3_power (if one of them is undefined, we handle it) + const numOr0 = n => isNaN(n) ? 0 : n; + let calculated_total_power = [values["Phase_1_power"], values["Phase_2_power"], values["Phase_3_power"]].reduce((a, b) => numOr0(a) + numOr0(b)); + values["total_power"] = parseFloat(calculated_total_power.toFixed(2)); + tbmsg[tbname][0]["values"] = values; + + Object.keys(values).map(singleValue => { + if (["Phase_1_voltage", "Phase_2_voltage", "Phase_3_voltage"].includes(singleValue)) + { + + let l = singleValue.split("_"); + let phase = parseInt(l[1]); + + if(FLOW.OMS_no_voltage == undefined) FLOW.OMS_no_voltage = new Set(); + + if(values[singleValue] == 0) + { + sendNotification("modbus_citys: updateDataInTB", tbname, "no_voltage_detected_on_phase", {phase: phase}, "", instanceSendTo.tb, instance, "voltage" + phase ); + + FLOW.OMS_no_voltage.add(phase); + } + else + { + FLOW.OMS_no_voltage.delete(phase); + sendNotification("modbus_citys: updateDataInTB", tbname, "voltage_on_phase_has_been_restored", {phase: phase}, "", instanceSendTo.tb, instance, "voltage" + phase); + } + + } + }) + + sendThingsBoard(tbmsg); + } + } + + + let electrometerNotResponding = 0; + function sendCommand(){ + let device = PRIVATEVARS.devices[PRIVATEVARS.device_index]; + let com = device.cmd[PRIVATEVARS.cmd_index]; + let array = [device.address, 3, ((com.register >> 8) & 0xFF), (com.register & 0xFF), ((com.size >> 8) & 0xFF), (com.size & 0xFF)]; + array = modbusCRC(array); + + //console.log('---device--', device); + //console.log('---device type--', device.type); + + //sendError("sendCommand", device.tb_name, ERRWEIGHT.DEBUG, "Sending command !", {"cmd": array}); + //console.log("sendCommand", device.tb_name, ERRWEIGHT.DEBUG, "Sending command !", {"cmd": array}); + + // let tbname = FLOW.OMS_edgeName; + // if(tbname == "" || tbname === undefined ) + // { + // console.log("!!!!!!FLOW.OMS_edgeName is empty - 2"); + // return; + // } + + startTimeoutInterval(); + port.write(Buffer.from(array), function(err) { + + //! poslany command + //console.log("poslany tento commant", array, err, device.type); + + if (err) { + stopTimeoutInterval(); + stopCmdWaitInterval(); + + + // elektromer neodpoveda viac ako 5 minut (15 commands za minutu sa posiela) + if (device.type === "EM111" || device.type === "EM340") + { + electrometerNotResponding++; + + if (electrometerNotResponding > 15 && electrometerNotResponding < 17) + { + + //sendError("Modbus_citys: sendCommand", tbname, ERRWEIGHT.CRITICAL, "Electrometer is not responding", {"err": err.message, "info": "No response more than 5 minutes"}); + sendNotification("modbus_citys: sendCommand", tbname, "electrometer_is_not_responding", {}, {"err": err.message, "info": "No response more than 5 minutes"}, instanceSendTo.tb, instance ); + + let tbmsg = { + [tbname]: [ + { + "ts": Date.now(), + "values": { + "status": "NOK" + } + } + ] + } + + sendThingsBoard(tbmsg) + } + } + + + return; + } + + if (device.type === "EM111") + { + if (electrometerNotResponding > 15) + { + //sendError("Modbus_citys: sendCommand", tbname, ERRWEIGHT.NOTICE, "Electrometer is responding again", ""); + sendNotification("modbus_citys: sendCommand", tbname, "electrometer_is_responding_again", {}, "", instanceSendTo.tb, instance ); + } + electrometerNotResponding = 0; + + } + }); + } + + function modbusCRC(array){ + let crc = 0xFFFF; + for (let i = 0; i < array.length; i++){ + let b = array[i]; + crc = crc ^ b; + + for (let j = 8; j>0; j--){ + if ((crc & 0x0001) != 0){ + crc = crc >> 1; + crc = crc ^ 0xA001; + } else { + crc = crc >> 1; + } + } + } + + array.push(crc & 0xFF); + array.push((crc >> 8) & 0xFF); + + return array; + } + + + instance.on('data', function(flowdata) { + + console.log("flowdata on data", flowdata); + sendStatus({"CONFIG": instance.CONFIG, "PRIVATEVARS":PRIVATEVARS}) + + }); + + instance.reconfigure = function() { + + //TODO remove ftom options + myEdge = instance.options.edge; + if (starter === null){ + starter = setInterval(function(){ + loadData(); + clearInterval(starter); + starter = null; + }, 5000); + } + }; + + instance.close = function() { + // close sockets and such + }; + + 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(instanceSendTo.tb, msg); // Even if error server is unavailable, send this message to output, for other possible component connections + + + + function sendBufferedErrors(){ + if (PRIVATEVARS.errBuffer === undefined){ + console.log("errBuffer undefined"); + console.log("private: ", PRIVATEVARS); + } + console.log("errBuffer size: ", PRIVATEVARS.errBuffer.length); + if (PRIVATEVARS.errBuffer.length > 0){ + for (let i = 0; i < PRIVATEVARS.errBuffer.length; i++){ + instance.send(instanceSendTo.error, PRIVATEVARS.errBuffer[i]); + } + PRIVATEVARS.errBuffer = []; //Clear the buffer + saveData(); + } + } + + function bufferError(msg){ + PRIVATEVARS.errBuffer.push(msg); + saveData(); + } + } + + function canSendErrData(){ + //if (FLOW.errServerAvailable) + return true; + //else + // return false; + } + + function sendStatus(str){ + instance.send(instanceSendTo.debug, str); + } + + function sendThingsBoard(obj){ + // Msg can be outputted from components only after configuration + /*if (canSendTbData()){ + sendBufferedTB(); + } else { + console.log("cant send data"); + bufferTB(str); + }*/ + //console.log("send thingsboard", str); + + //console.log("FLOW.OMS_edgeName", FLOW.OMS_edgeName, obj); + + if(obj.hasOwnProperty(FLOW.OMS_edgeName) && FLOW.OMS_edgeName != "") + { + //send it to di_do_controller + instance.send(instanceSendTo.di_do_controller, {sender: "modbus_citysys", tbdata: obj}); + } + // else + { + instance.send(instanceSendTo.tb, obj); // Even if TB server is unavailable, send this message to output, for other possible component connections + } + + //instance.send(2, str); // Even if TB server is unavailable, send this message to output, for other possible component connections + + + function sendBufferedTB(){ + if (PRIVATEVARS.tbBuffer.length > 0){ + console.log("sending buffered: ", PRIVATEVARS.tbBuffer.length ); + for (let i = 0; i < PRIVATEVARS.tbBuffer.length; i++){ + instance.send(instanceSendTo.tb, PRIVATEVARS.tbBuffer[i]); + } + PRIVATEVARS.tbBuffer = []; //Clear the buffer + saveData(); + } + } + + function bufferTB(str){ + PRIVATEVARS.tbBuffer.push(str); + saveData(); + } + } + + function canSendTbData(){ + //if (FLOW.tbAvailable) + return true; + //else + // return false; + } + + function startTimeoutInterval(){ + if (!timeoutInterval){ + timeoutInterval = setInterval(readingTimeouted, instance.CONFIG.timeoutTime); + } + } + + function stopTimeoutInterval(){ + if (timeoutInterval){ + clearInterval(timeoutInterval); + timeoutInterval = null; + } + } + + function startCmdWaitInterval(){ + if (!msgWaitInterval){ + msgWaitInterval = setInterval(readDeviceData, instance.CONFIG.msgWaitTime); + } + } + + function stopCmdWaitInterval(){ + if (msgWaitInterval){ + clearInterval(msgWaitInterval); + msgWaitInterval = null; + } + } + + instance.on('options', instance.reconfigure); + instance.reconfigure(); + + // LAST SECTION FOR COMMON FUNCTIONS + function humanReadableTimeAndDate(){ + let date_ob = new Date(); + + let date = ("0" + date_ob.getDate()).slice(-2); + let month = ("0" + (date_ob.getMonth() + 1)).slice(-2); + let year = date_ob.getFullYear(); + + let hours = ("0" + date_ob.getHours()).slice(-2); + let minutes = ("0" + date_ob.getMinutes()).slice(-2); + let seconds = ("0" + date_ob.getSeconds()).slice(-2); + + return date+"."+month+"."+year+" "+hours+":"+minutes+":"+seconds; + } + + if (starter === null){ + starter = setInterval(function(){ + loadData(); + clearInterval(starter); + starter = null; + }, 5000); + } + + //setTimeout(loadData, 5000); +}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flow/monitorconsumption.js b/flow/monitorconsumption.js new file mode 100644 index 0000000..64cc58e --- /dev/null +++ b/flow/monitorconsumption.js @@ -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 = `
+
+
+
@(Interval in milliseconds)
+
+
+
+
Monitor: Consumption + uptime
+
Monitor: Count of open files
+
Monitor: Count of open connections
+
Monitor: Directory size
+
`; + +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); +}; \ No newline at end of file diff --git a/flow/monitorcpu.js b/flow/monitorcpu.js new file mode 100644 index 0000000..0e906d1 --- /dev/null +++ b/flow/monitorcpu.js @@ -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 = `
+
+
+
@(Interval in milliseconds)
+
+
+
`; + +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); +}; \ No newline at end of file diff --git a/flow/monitordisk.js b/flow/monitordisk.js new file mode 100644 index 0000000..f4fdaa0 --- /dev/null +++ b/flow/monitordisk.js @@ -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 = `
+
+
+
@(Interval in milliseconds)
+
+
+
@(Path)
+
+
+
`; + +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); +}; \ No newline at end of file diff --git a/flow/monitormemory.js b/flow/monitormemory.js new file mode 100644 index 0000000..2decbc4 --- /dev/null +++ b/flow/monitormemory.js @@ -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 = `
+
+
+
@(Interval in milliseconds)
+
+
+
`; + +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); +}; \ No newline at end of file diff --git a/flow/mqtt.js b/flow/mqtt.js new file mode 100644 index 0000000..e34c940 --- /dev/null +++ b/flow/mqtt.js @@ -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 = `
+
+
+
Hostname or IP address
+
+
+
Port
+
+
+
+
+
@(Client id)
+
@(Supports variables, example: \`client_{device-id}\`)
+
@(Secure (ssl))
+
+
+
+
+
+
@(Require Authorization)
+
+
+
+
+
@(Username)
+
+
+
@(Password)
+
+
+
+
+
+
@(LWT)
+
+
+
+
+
@(Last will topic)
+
@(Supports variables, example: \`lwt/{device-id}\`)
+
+
+
@(Last will message)
+
@(Supports variables, example: \`{device-id} is offline\`)
+
+
+
+`; + +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(); +}; diff --git a/flow/mqtt_listener.js b/flow/mqtt_listener.js new file mode 100644 index 0000000..b9d778d --- /dev/null +++ b/flow/mqtt_listener.js @@ -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 = `
+
+
+
Hostname or IP address
+
+
+
Port
+
+
+
+
+
@(Client id)
+
+
+
@(Username)
+
+
+
`; + + +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(); +}; + + + diff --git a/flow/mqttpublish.js b/flow/mqttpublish.js new file mode 100644 index 0000000..3f6aef0 --- /dev/null +++ b/flow/mqttpublish.js @@ -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 = `
+
@(Brokers)
+
Topic
+
@(Supports variables, example: \`device/{device-id}\`)
+
Static message(string)
+
@(Supports variables), @(If specified then incoming data are ignored and this message is sent instead. Topic is required if static message is defined.)
+
@(QoS)
+
@(Retain)
+
+`; + +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(); +}; diff --git a/flow/mqttsubscribe.js b/flow/mqttsubscribe.js new file mode 100644 index 0000000..e866178 --- /dev/null +++ b/flow/mqttsubscribe.js @@ -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 = `
+
@(Select a broker)
+
Topic
+
@(Supports variables, example: \`device/{device-id}\`)
+
@(QoS)
+
+`; + +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; +} diff --git a/flow/nosql.js b/flow/nosql.js new file mode 100644 index 0000000..db6b7c4 --- /dev/null +++ b/flow/nosql.js @@ -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', '')\` 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 = ` +
+
DB collection name
+
@(Method)
+
+
Add unique ID to data before insert
+
+
+
Insert document if it doesn't exist
+
Add unique ID to data before insert (only if it doesn't exist)
+
+
`; + +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); + } + }); + } + + }); +}; diff --git a/flow/notifikacie.csv b/flow/notifikacie.csv new file mode 100644 index 0000000..520c997 --- /dev/null +++ b/flow/notifikacie.csv @@ -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é diff --git a/flow/relays.js b/flow/relays.js new file mode 100644 index 0000000..726abd6 --- /dev/null +++ b/flow/relays.js @@ -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 = `
+
+
+
Edge TB Name
+
+
+
`; + +exports.readme = `# Sets RS232 port and all digital pins on device. Then it starts to receive data from sensors. +It receives: + +rotary_switch_state, +rotary_switch_state, +door_condition, +state_of_breaker, +state_of_contactor, +twilight_sensor +`; + +/* +we open rsPort "/dev/ttymxc0" and set digital input and output pins with "setRSPortData" +Currently we are interested in pins no. 1,2,3,6,8,9,10,16 +pins number 11, 12, 13 (we receive 10,11,12 in rsPortReceivedData) are "stykace" +When port receives data, it must be exactly 4 bytes long. Second byte is pin, that changed its value, fourth byte is value itself. +After that, we set this value to "previousValues[allPins[whichpin]]" variable +*/ + + +/* +RVO objekt: +state_of_main_switch - sem sa bude reportovať stav hlavného ističa : 0-> off 1-> on (toto nie je na platforme, ale Rado to už do entity type doplnil) +rotary_switch_state - sem by sa mal reportovať stav vstupov manual a auto podľa nasledovnej logiky: + Manual = 1 a Auto = 0 -> vyreportuje Manual + Manual = 0 a Auto = 0 -> vyreportuje Off + Manual = 0 a Auto = 1 -> vyreportuje Automatic + +door_condition - tuto ide pin 6, dverový kontakt -> 1 -> vyreportuje Closed, 0 -> vyreportuje Open +twilight_sensor - hodnotu, ktorú vracia ten analógový vstup (17) treba poslať sem ako float number. Zrejme tu potom pridáme nejaký koeficient prevodu na luxy + +zjavne nám v jsone chýba stav hlavného ističa. Musíme to potom doplniť + +Na každú líniu: +state_of_breaker - podľa indexu ističa sa reportuje jeho stav, teda istič 1 na líniu 1: 0-> off 1-> on +state_of_contactor - podľa indexu stykača sa reportuje jeho stav, teda stykač 1 na líniu 1: 0-> off 1-> on + momentálne sa stav zmení len keď vo flow klikneš aby sa zmenil, ale tá zmena by sa mala ukázať aj na platforme +*/ + +exports.install = function(instance) { + + let previousValues = {}; + let rsPortReceivedData = []; + + console.log("DI_DO_Relay_Controller installed"); + + //key is PIN number + const conversionTable = { + "1": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "state_of_main_switch"}, //state_of_main_switch pin1 + "2": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "rotary_switch_state"}, //rotary_switch_state - poloha manual = pin2 + "3": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "rotary_switch_state"}, //rotary_switch_state - poloha auto = pin3 + "6": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "door_condition"}, // door_condition = pin6, 1 -> vyreportuje Closed, 0 -> vyreportuje Open + "8": {tbname: "RMgnK93rkoAazbqdQ4yBG95Z1YXGx6pmwBeVEP2O", type: "state_of_breaker", "line": 1}, // state_of_breaker linia 1 0=off, 1=on + "9": {tbname: "dlE1VQjYrNx9gZRmb38gG08oLBO4qaAk2M6JPnG7", type: "state_of_breaker", "line": 2}, // state_of_breaker linia 2 0=off, 1=on + "10": {tbname: "vnmG4kJxaXWNBgMQq0D7Aj5e9oZzOAlr6LdR3w2V", type: "state_of_breaker", "line": 3}, // state_of_breaker linia 3 0=off, 1=on + "11": {tbname: "RMgnK93rkoAazbqdQ4yBG95Z1YXGx6pmwBeVEP2O", type: "state_of_contactor", "line": 1}, // state_of_contactor linia 1 0=off, 1=on + "12": {tbname: "dlE1VQjYrNx9gZRmb38gG08oLBO4qaAk2M6JPnG7", type: "state_of_contactor", "line": 2}, // state_of_contactor linia 2 0=off, 1=on + "13": {tbname: "vnmG4kJxaXWNBgMQq0D7Aj5e9oZzOAlr6LdR3w2V", type: "state_of_contactor", "line": 3}, // state_of_contactor linia 3 0=off, 1=on + "16": {tbname: "KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV", type: "state_of_main_switch"}, // twilight_sensor = pin16 + }; + + const dbRelays = TABLE("relays"); + dbRelays.on('change', function(doc, old) { + instance.send(2, "reload_relays"); + }); + + //modify + + const SerialPort = require('serialport'); + //const { exec } = require('child_process'); + const { openPort, runSyncExec, writeData } = require('./serialport_helper.js'); + + const setRSPortData = [0xAA,6,6,6,6,0,6,0,6,6,6,1,1,1,1,0,0,10,10,10,10,0,10,0,10,10,10,0,0,0,0,0,0,5,0,0,0,15,15,15,15,0,15,0,15,15,15,0,0,0,0,0,0,30,0,0,0]; + const rsPort = new SerialPort("/dev/ttymxc0", { autoOpen: false }); + + rsPort.on('open', async function() { + + await runSyncExec("stty -F /dev/ttymxc0 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke").then(function (status) { + instance.send(0, exports.title + " runSyncExec - Promise Resolved:" + status); + + //set port + rsPort.write(Buffer.from(setRSPortData), function(err) { + instance.send(0, exports.title + " Digital in_out has been set"); + + //force turn off relays + let keys = Object.keys(conversionTable); + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + + if(conversionTable[key].type == "state_of_contactor") + { + let pin = key - 1; + let line = conversionTable[key].line; + + turnOff(line, pin); + } + } + + //dbRelays.modify({ contactor: 0 }); + }) + + }).catch(function (reason) { + instance.send(0, exports.title + " runSyncExec - promise rejected:" + reason); + }); + + }); + + rsPort.open(); + + rsPort.on('data', function (data){ + + rsPortReceivedData = [...rsPortReceivedData, ...data]; + + if (rsPortReceivedData[0] != 85) { + rsPortReceivedData = []; + return; + } + + let l = rsPortReceivedData.length; + + if (l < 4 ) return; + + if (l > 4 ) { + + // if array length is greater than 4, we take first 4 byte and do the logic, second 4 bytes, do the logic and so on + let i, j, temparray, chunk = 4; + for ( i = 0, j = l; i < j; i += chunk ) { + temparray = rsPortReceivedData.slice(i, i + chunk); + + if ( temparray.length < 4 ){ + rsPortReceivedData = [...temparray]; + return; + } + + switchLogic(temparray); + } + + rsPortReceivedData = []; + return; + } + + switchLogic(rsPortReceivedData); + + rsPortReceivedData = []; + + }); + + rsPort.on('error', function(err) { + instance.send(0, err.message); + }) + + rsPort.on("close", () => { + rsPort.close(); + }) + + instance.on("close", () => { + rsPort.close(); + }) + + function getPin(line) + { + //conversionTable + let keys = Object.keys(conversionTable); + for(let i = 0; i < keys.length; i++) + { + let key = keys[i]; + + if(conversionTable[key].type == "state_of_contactor" && conversionTable[key].line == line) + { + return key - 1; + } + } + + console.log("no pin detected"); + + return null; + } + + function turnOn(line, pin) + { + if( pin === undefined) pin = getPin(line); + if( pin === undefined) return; + + let arr = [0x55]; + arr.push( pin ); + arr.push( 0 ); + arr.push( 1 ); + + if(!rsPort.isOpen) + { + console.log("port is not opened"); + return; + } + + rsPort.write(Buffer.from(arr), function(err) { + switchLogic(arr); + }); + } + + function turnOff(line, pin) + { + if( pin === undefined) pin = getPin(line); + if( pin === undefined) return; + + let arr = [0x55]; + arr.push( pin ); + arr.push( 0 ); + arr.push( 0 ); + + if(!rsPort.isOpen) + { + console.log("port is not opened"); + return; + } + + rsPort.write(Buffer.from(arr), function(err) { + switchLogic(arr); + }); + } + + // we expect array as flowdata.data + instance.on("data", (flowdata) => { + + //console.log(flowdata.data); + + if(flowdata.data instanceof Object) + { + let obj = flowdata.data; + + let line = obj.line; + + if(obj.command == "turnOn") turnOn(line); + else if(obj.command == "turnOff") turnOff(line); + + return; + } + + if (Array.isArray(flowdata.data)){ + rsPort.write(Buffer.from(flowdata.data), function(err) { + switchLogic(flowdata.data); + + instance.send(0,{"WRITE":flowdata.data}); + }); + } + }) + + const switchLogic = (rsPortReceivedData) => { + + let dataToTb; + let values = {}; + + let pinIndex = rsPortReceivedData[1] + 1; + if (pinIndex === 17) pinIndex--; + + let newPinValue = rsPortReceivedData[3]; + + let obj = conversionTable[pinIndex]; + + if(obj == undefined) + { + //console.log("undefined pinIndex", pinIndex, rsPortReceivedData); + return; + } + + let type = obj.type; + let line = obj.line; + let tbname = obj.tbname; + + //default value + let value = "On"; + if(newPinValue === 0) value = "Off"; + + if(type == "rotary_switch_state") + { + // combination of these two pins required to get result + let pin2, pin3; + if(pinIndex == 2) + { + pin2 = newPinValue; + pin3 = previousValues[pinIndex]; + if (pin3 == undefined) pin3 = 0; + } + else if(pinIndex == 3) + { + pin3 = newPinValue; + pin2 = previousValues[1]; + if (pin2 == undefined) pin2 = 0; + } + + if (pin2 == 1 && pin3 == 0) value = "Manual"; + if (pin2 == 0 && pin3 == 0) value = "Off"; + if (pin2 == 0 && pin3 == 1) value = "Automatic"; + } + else if(type == "door_condition") + { + newPinValue === 0 ? value = "Open" : value = "Closed"; + } + else if(type == "twilight_sensor") + { + value = parseFloat(newPinValue + (256*rsPortReceivedData[2])); + } + else if(type == "state_of_contactor") + { + //modify table relays + dbRelays.modify({ contactor: newPinValue }).where("line", line); + } + + values[obj.type] = value; + + if(conversionTable.hasOwnProperty(pinIndex)) + { + let insertIntoTb = false; + if(newPinValue != previousValues[pinIndex]) insertIntoTb = true; + if(obj.hasOwnProperty("state_of_contactor")) insertIntoTb = true; + + if(insertIntoTb) + { + dataToTb = { + [tbname]: [ + { + "ts": Date.now(), + "values": values + } + ] + } + + instance.send(1, dataToTb); + } + } + + //pin was changed + previousValues[pinIndex] = newPinValue; + } +} + + diff --git a/flow/test.js b/flow/test.js new file mode 100644 index 0000000..f0ca813 --- /dev/null +++ b/flow/test.js @@ -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()); diff --git a/flow/trigger.js b/flow/trigger.js new file mode 100644 index 0000000..27545a8 --- /dev/null +++ b/flow/trigger.js @@ -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 = `
+
@(Data type (String by default))
+
@(Data)
+
Trigger 5s after initialization.
+
@(Useful when there's a need to run certain flow when the app restarts, etc.)
+
`; + +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); +}; diff --git a/flow/variables.txt b/flow/variables.txt new file mode 100644 index 0000000..e69de29 diff --git a/flow/virtualwirein.js b/flow/virtualwirein.js new file mode 100644 index 0000000..38a4e42 --- /dev/null +++ b/flow/virtualwirein.js @@ -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 = `
+
@(Wire name)
+
+`; + +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(); +}; diff --git a/flow/virtualwireout.js b/flow/virtualwireout.js new file mode 100644 index 0000000..94a1e4f --- /dev/null +++ b/flow/virtualwireout.js @@ -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 = `
+
@(Wire name)
+
+`; + +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(); +}; diff --git a/flow/wsmqttpublish.js b/flow/wsmqttpublish.js new file mode 100644 index 0000000..8d793ac --- /dev/null +++ b/flow/wsmqttpublish.js @@ -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 = `
+
+
+
Hostname or IP address (if not empty - setting will override db setting)
+
+
+
Port
+
+
+
+
+
@(Client id)
+
+
+
@(Username)
+
+
+
`; + + +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(); +}; diff --git a/monitor.txt b/monitor.txt new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..8a8344f --- /dev/null +++ b/package.json @@ -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" +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..f2e23d2 --- /dev/null +++ b/readme.md @@ -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` \ No newline at end of file diff --git a/saved_data/modbus_settings b/saved_data/modbus_settings new file mode 100644 index 0000000..61c37a3 --- /dev/null +++ b/saved_data/modbus_settings @@ -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 + } + ] + }] + } +}