Compare commits

..

No commits in common. "total4-citysys-flowserver-with-debian12" and "master" have entirely different histories.

39 changed files with 7188 additions and 12568 deletions

View file

@ -1,36 +0,0 @@
import os
def process_set_file():
"""
Checks if /root/flowserver exists, reads set.txt, and modifies the second line.
"""
default_folder = "/root/flowserver" if os.path.exists("/root/flowserver") else "/home/unipi/flowserver"
flag = 1 if default_folder == "/root/flowserver" else 0
try:
with open("/home/unipi/flowserver/databases/settings.table", "r") as f:
lines = f.readlines()
if len(lines) >= 2:
lines[0] = lines[0].rstrip('\n') + "|has_main_switch:boolean\n"
second_line = lines[1].strip() # remove trailing newline
last_pipe_index = second_line.rfind("|")
if last_pipe_index != -1:
modified_line = second_line[:last_pipe_index + 1] + str(flag) + "|" + second_line[last_pipe_index + 1:]
lines[1] = modified_line
else:
print("Warning: No '|' character found in the second line of set.txt")
with open("/home/unipi/flowserver/databases/settings.table", "w") as f:
f.writelines(lines)
else:
print("Warning: settings.table has less than two lines.")
except FileNotFoundError:
print("Error: settings.table not found.")
except Exception as e:
print(e)
# if __name__ == "__main__":
process_set_file()

View file

@ -1,76 +0,0 @@
# import os
#
# def modify_file(file_path):
# """
# Modifies the given file by:
# 1. Appending "|cloud_topic" to the first line.
# 2. Inserting the text from the third "." to the first "|" on the second line after the last "|" character.
#
# Args:
# file_path (str): The path to the file to be modified.
# """
#
# with open(file_path, 'r+') as f:
# lines = f.readlines()
#
# # Modify the first line
# lines[0] += "|cloud_topic:string"
#
# # Modify the second line
# second_line = lines[1].strip() # Remove leading/trailing whitespace
# first_pipe_index = second_line.find('|')
# third_dot_index = second_line.find('.', second_line.find('.', second_line.find('.') + 1) + 1)
# text_to_insert = second_line[third_dot_index:first_pipe_index]
#
# last_pipe_index = second_line.rfind('|')
# lines[1] = second_line[:last_pipe_index + 1] + text_to_insert + "|" + second_line[last_pipe_index + 1:]
#
# print(first_pipe_index, third_dot_index, text_to_insert, last_pipe_index)
# # Write the modified lines back to the file
# # f.seek(0)
# # f.writelines(lines)
# # f.truncate()
#
# # Example usage:
# file_path = "settings.table" # Replace with the actual file path
# modify_file(file_path)
#
def modify_file(file_path):
"""
Modifies the given file by:
1. Appending "|cloud_topic" to the first line.
2. Inserting the text between the third "." and the second "|" on the second line after the last "|" character.
Args:
file_path (str): The path to the file to be modified.
"""
with open(file_path, 'r+') as f:
lines = f.readlines()
first_line = lines[0].strip()
first_line += "|cloud_topic:string\n"
# Modify the first line
lines[0] = first_line
# Modify the second line
second_line = lines[1].strip() # Remove leading/trailing whitespace
first_pipe_index = second_line.find('|')
second_pipe_index = second_line.find('|', first_pipe_index + 1)
third_dot_index = second_line.find('.', second_line.find('.', second_line.find('.') + 1) + 1)
text_to_insert = "u" + second_line[third_dot_index+1:second_pipe_index]
last_pipe_index = second_line.rfind('|')
lines[1] = second_line[:last_pipe_index + 1] + text_to_insert + "|" + second_line[last_pipe_index + 1:]
print(first_pipe_index, third_dot_index, text_to_insert, last_pipe_index)
# Write the modified lines back to the file
f.seek(0)
f.writelines(lines)
f.truncate()
# Example usage:
file_path = "/home/unipi/flowserver/databases/settings.table" # Replace with the actual file path
modify_file(file_path)

4
config
View file

@ -6,7 +6,7 @@ package#flow (Object) : { url: '/' }
table.relays : line:number|tbname:string|contactor:number|profile:string table.relays : line:number|tbname:string|contactor:number|profile:string
table.nodes : node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number table.nodes : node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean
table.settings : rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string|has_main_switch: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:string|type:string|line:number table.pins : pin:string|type:string|line:number
table.notifications : key:string|weight:string|sk:string|en:string table.notifications : key:string|weight:string|sk:string|en:string

View file

@ -1,43 +0,0 @@
print("zaciname")
import re, json
search_str = '|'
final = []
counter = 1
with open("/home/unipi/flowserver/databases/nodes.table", 'r') as file:
# with open("/home/rasta5man/dev/oms/flowserver/databases/nodes.table", 'r') as file:
# Read each line in the file
for line in file:
# Print each line
line = line.strip()
print(line)
if counter != 1:
i = [m.start() for m in re.finditer(re.escape(search_str), line)]
node = line[ i[0] + 1 : i[1] ]
tbname = line[ i[1] + 1 : i[2] ]
final.append({node:tbname})
counter += 1
print(json.dumps(final))
f = open("/home/unipi/flowserver/databases/nodes_original/nodes_original.table", "w")
f.write(json.dumps(final))
f.close()
#
# # ``d`` has to be replaced with a different character
# old_character = "'"
#
# # ``t`` will replace ``d`
# new_character = '"'
# resultant_string = 0;
# with open("/home/unipi/flowserver/databases/nodes_original/nodes_original.table", 'r') as file:
# for line in file:
# resultant_string = re.sub("'", '"', line)
#
# resultant_string = re.sub(" ", "", resultant_string)
# print(resultant_string)
#
# f = open("/home/unipi/flowserver/databases/nodes_original/nodes_original.table", "w")
# f.write(str(resultant_string))
# f.close()
#

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,31 @@
node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean
+|638|rDbQ84xzwgdqEoPm3kbJQWk9anOZY1RXyBv2LVM6|3|{"intervals":[{"cct":3000,"value":20,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"05:30","start_time":"20:00"},{"cct":3000,"value":20,"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":-20,"dusk_astro_clock_offset":20,"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|1725885127396|............................................................................................................................................................................................................................................................ +|3522|RO8rjaBDy21qPQJzW7oD96ApK3xmNleVZg9Ed4Gw|1||1|1|...........
+|4018|3JjOWdylwgNLzxVab7NEznkZ2vG64rq8PEB5QmDo|1||1|1|...........
+|4019|Z5KyJe9nEg1QNbWlX0wWRB0oDjBLdqzR83VGv624|1||1|1|...........
+|4154|1JMYvnx2RzKEo4aWQ7DmN5AL8yZV3m9NBePXbrdj|1||1|0|...........
+|3907|PjLblDgRBO6WQqnxmkJ59r0Jv3ewZN4p5a89yKdY|1||1|1|...........
+|4148|dz4ojlpP85JMgDLZWkQOoGAaKYqQexEr62GXRV1y|1||1|1|...........
+|4153|d5xjWYMwEJon6rLlK7yBYmAqgV4DaOeNB9ZX3Gzb|1||1|1|...........
+|3938|gRoJEyXVx4qD9er287LP1v7wBzGldaPjLWQKm3Mv|1||1|1|...........
+|3802|K94XLav1glVRnyQ6r01BNzkme3YJwBxM5oOzdP2j|1||1|1|...........
+|4015|d9x2V5LGYBzXp4mMRAOBDj7PloaqJwnQj6DgrNe3|1||1|0|...........
+|3929|B5EoxeMVp4zwr8nqW0GjDpARjvD1PNamOGbLg63Z|1||1|1|...........
+|3946|aw4eELG2DlPMdn1JW0B1DnAqQXOZRN3xB5yp8VKr|1||1|1|...........
+|4014|ZmRwd93QL4gaezxEbAxW5971prn2XjlPvGyqJ6BO|1||1|1|...........
+|4155|eod9aRWLVl34Gx1Dn7VoaaA2rz6qjgmpEXwQJN5Z|1||1|1|...........
+|4149|3a5oqJN1bgnx4Ol9dk86NBAByE6jQ8mKDWMpGrLV|1||1|1|...........
+|3642|EjgWGnXaLy9opPOz20n694086BlYM3w1deVQvbKr|1||1|1|...........
+|3636|wvKJdZML6mXP4DzWBAXWNW7jxNloa5g23Ve9Y1ry|1||1|1|...........
+|3991|Nzp2OoJlqn6r1ZgvdA3GWdAabBwP5G4eE3RQmyxD|1||1|1|...........
+|3994|PLBJzmK1r3Gynd6OW0gGdM0e5wV4vx9bDEqNgYR8|1||1|1|...........
+|3990|52dD6ZlV1QaOpRBmbAqKZgkKnGzWMLj4eJq38Pgo|1||1|1|...........
+|3967|rDbQ84xzwgdqEoPm3kbJw3k9anOZY1RXyBv2LVM6|1||1|1|...........
+|3977|E6Kg9oDnLWyzPRMva7vrwa7Jxp4VG58qO2w1lZYe|1||1|1|...........
+|3757|roKgWqY95V3mXMRzyAjm8D7bLjexpJPvaGDBw826|1||1|1|...........
+|3633|nJL5lPMwBx23YpqRe0rlKV7damXvWVbOrD4gNzy8|1||1|1|...........
+|3744|ZmRwd93QL4gaezxEbAxW5O71prn2XjlPvGyqJ6BO|1||1|1|...........
+|4023|eod9aRWLVl34Gx1Dn7VoaMA2rz6qjgmpEXwQJN5Z|1||1|1|...........
+|3720|3a5oqJN1bgnx4Ol9dk86NZAByE6jQ8mKDWMpGrLV|1||1|1|...........
+|3734|EjgWGnXaLy9opPOz20n69V086BlYM3w1deVQvbKr|1||1|1|...........
+|3741|wvKJdZML6mXP4DzWBAXWN17jxNloa5g23Ve9Y1ry|1||1|1|...........
+|3721|Nzp2OoJlqn6r1ZgvdA3GWKAabBwP5G4eE3RQmyxD|1||0|0|...........

View file

@ -1 +0,0 @@
[{"3815": "B5EoxeMVp4zwr8nqW0GjjoARjvD1PNamOGbLg63Z"}, {"3799": "roKgWqY95V3mXMRzyAjmmj7bLjexpJPvaGDBw826"}]

View file

@ -1,5 +1,5 @@
key:string|weight:string|sk:string|en:string 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}|............... +|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}|............... +|dusk_has_occured|INFORMATIONAL|Nastal súmrak: ${value}|Dusk has occured: ${value}|...............
+|dawn_has_occured|INFORMATIONAL|Nastal úsvit: ${value}|Dawn 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}|............... +|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}|...............
@ -20,12 +20,9 @@ key:string|weight:string|sk:string|en:string
+|power_supply_works_correctly|NOTICE|Napájací zdroj pracuje správne|Power supply works correctly|............... +|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_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|............... +|battery_level_is_ok|NOTICE|Batéria má správnu úroveň napätia|Battery level is OK|...............
+|door_main_open|NOTICE|Hlavné dvere boli otvorené|Main door has been opened|............... +|door_has_been_open|NOTICE|Dvere boli otvorené|Door has been open|...............
+|door_em_open|NOTICE|Dvere silovej časti boli otvorené|Power door has been opened|............... +|door_has_been_closed|NOTICE|Dvere boli zatvorené|Door has been closed|...............
+|door_main_open_without_permission|WARNING|Hlavné dvere boli otvorené bez povolenia - zapnutá siréna|Main door has been opened without permission - alarm is on|............... +|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|...............
+|door_em_open_without_permission|WARNING|Dvere silovej časti boli otvorené bez povolenia|Power door has been opened without permission|...............
+|door_main_close|NOTICE|Hlavné dvere boli zatvorené|Main door has been closed|...............
+|door_em_close|NOTICE|Dvere silovej časti boli zatvorené|Power door has been closed|...............
+|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}|............... +|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|||............... +|local_database_is_corrupted|CRITICAL|||...............
+|electrometer_nok|ERROR|Elektromer neodpovedá|Electrometer is not responding|............... +|electrometer_nok|ERROR|Elektromer neodpovedá|Electrometer is not responding|...............
@ -37,5 +34,4 @@ key:string|weight:string|sk:string|en:string
+|twilight_sensor_ok|NOTICE|Sensor súmraku znovu odpovedá|Twilight sensor is responding again|............... +|twilight_sensor_ok|NOTICE|Sensor súmraku znovu odpovedá|Twilight sensor is responding again|...............
+|lamps_have_turned_on|NOTICE|Lampy sa zapli|Lamps have turned on|............... +|lamps_have_turned_on|NOTICE|Lampy sa zapli|Lamps have turned on|...............
+|lamps_have_turned_off|NOTICE|Lampy sa vypli|Lamps have turned off|............... +|lamps_have_turned_off|NOTICE|Lampy sa vypli|Lamps have turned off|...............
+|flow_restart|NOTICE|FLOW bol reštartovaný|FLOW has been restarted|............... +|flow_restart|NOTICE|Restart flowu|Flow has been restarted|...............
+|nodes_db_changed|NOTICE|Zmena v node databáze|Node db has changed|...............

View file

@ -1,5 +1,5 @@
line:number|tbname:string|contactor:number|profile:string line:number|tbname:string|contactor:number|profile:string
+|0|6lQGaY9RDywdVzObj0PadOkPg4NBn3exEK51LWZq|1||........... +|0|6lQGaY9RDywdVzObj0PadOkPg4NBn3exEK51LWZq|1||...........
+|1|JzwxZXOvDj1bVrN4nkWw9Qk8qdyBl3MRKLpGPgaQ|9|{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"08:00","start_time":"20:00"},{"value":0,"end_time":"13:00","start_time":"08:00"}],"astro_clock":true,"dawn_lux_sensor":true,"dusk_lux_sensor":true,"dawn_lux_sensor_value":15,"dusk_lux_sensor_value":15,"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|JzwxZXOvDj1bVrN4nkWw9Qk8qdyBl3MRKLpGPgaQ|1|{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"08:00","start_time":"20:00"},{"value":0,"end_time":"13:00","start_time":"08:00"}],"astro_clock":true,"dawn_lux_sensor":true,"dusk_lux_sensor":true,"dawn_lux_sensor_value":15,"dusk_lux_sensor_value":15,"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|g9OxBZ5KRwNznlY6pAp6mxkWXvjdEL4eGQobMDy2|9|{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"08:00","start_time":"20:00"},{"value":0,"end_time":"13:00","start_time":"08:00"}],"astro_clock":true,"dawn_lux_sensor":true,"dusk_lux_sensor":true,"dawn_lux_sensor_value":15,"dusk_lux_sensor_value":15,"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|g9OxBZ5KRwNznlY6pAp6mxkWXvjdEL4eGQobMDy2|1|{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"08:00","start_time":"20:00"},{"value":0,"end_time":"13:00","start_time":"08:00"}],"astro_clock":true,"dawn_lux_sensor":true,"dusk_lux_sensor":true,"dawn_lux_sensor_value":15,"dusk_lux_sensor_value":15,"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|OzNMgZ9n43qPbjXmy7zWMJA2DKdYvW5e6pxGRrVa|9|{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"08:00","start_time":"20:00"},{"value":0,"end_time":"13:00","start_time":"08:00"}],"astro_clock":true,"dawn_lux_sensor":true,"dusk_lux_sensor":true,"dawn_lux_sensor_value":15,"dusk_lux_sensor_value":15,"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|OzNMgZ9n43qPbjXmy7zWMJA2DKdYvW5e6pxGRrVa|1|{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"08:00","start_time":"20:00"},{"value":0,"end_time":"13:00","start_time":"08:00"}],"astro_clock":true,"dawn_lux_sensor":true,"dusk_lux_sensor":true,"dawn_lux_sensor_value":15,"dusk_lux_sensor_value":15,"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}|...........

View file

@ -1,2 +1,2 @@
rvo_name:string|lang:string|temperature_address:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|project_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number|node_status_nok_time:number|phases:number|cloud_topic:string|has_main_switch:boolean 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
+|rvo_senica_22_ip10.0.0.109|en|28.F46E9D0E0000|48.70826502|17.28455203|192.168.252.1|rvo_senica_22_ip10.0.0.109|9excvr7yBcF3gl3kYZGY|1883|0|48|unipi|ttyUSB0|1|20|5|6|3|u109|0|........................................... +|rvo_senica_39_10.0.0.132|en|28.427B45920702|48.70826502|17.28455203|192.168.252.1|rvo_senica_39_10.0.0.132|qzSNuCNrLP4OL1v47YEe|1883|0|68|unipi|ttyUSB0|1|20|5|...........................................

View file

@ -1,37 +0,0 @@
//key is rvo_number, value is max energy when lamps are on
const total_energy = {
1: 580,
2: 1100,
3: 3700,
4: 4100,
7: 360,
12: 1700,
13: 5400,
14: 440,
15: 6100,
16: 4800,
20: 1600,
21: 1000,
22: 2600,
23: 1000,
25: 2600,
33: 240,
34: 4000,
35: 2700,
36: 820,
37: 1400,
35: 3500,
39: 1170,
41: 740,
42: 660,
43: 4900,
45: 930,
46: 700,
47: 1100,
48: 1500,
50: 3200,
55: 1000,
56: 5500
}
module.exports = total_energy;

48
flow/audit_test_panel.csv Normal file
View file

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

View file

@ -1,348 +0,0 @@
exports.id = 'cloudmqttconnect';
exports.title = 'Cloud connect mqtt';
exports.group = 'MQTT';
exports.color = '#888600';
exports.version = '1.0.2';
exports.icon = 'sign-out';
exports.input = 2;
exports.output = 2;
exports.options = { host: 'tb-stage.worksys.io', port: 1883, clientid: "", username: "" };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="host" data-jc-config="placeholder:test.mosquitto.org;required:false" class="m">Hostname or IP address (if not empty - setting will override db setting)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:1883;required:true" class="m">Port</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="clientid">@(Client id)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="topic" class="m">topic</div>
</div>
</div>
</div>`;
const { promisifyBuilder } = require('./helper/db_helper');
const fs = require('fs');
const mqtt = require('mqtt');
const nosql = NOSQL('tbdatacloud');
const SEND_TO = {
debug: 0,
rpcCall: 1,
}
//CONFIG
let saveTelemetryOnError = true;//backup_on_failure overrides this value
//------------------------
const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304
let insertNoSqlCounter = 0;
let insertBackupNoSqlCounter = 0;
let processingData = false;
let backup_on_failure = true;//== saveTelemetryOnError - create backup client send failure
let restore_from_backup = 50; //how many rows process at once?
let restore_backup_wait = 3;//wait seconds
let lastRestoreTime = 0;
// if there is an error in client connection, flow logs to monitor.txt. Not to log messages every second, we use sendClientError variable
let sendClientError = true;
exports.install = function(instance) {
var client;
var opts;
var clientReady = false;
let o = null; //options
function main() {
loadSettings();
}
//set opts according to db settings
function loadSettings() {
o = instance.options;
if (!o.topic) o.topic = FLOW.GLOBALS.settings.cloud_topic;
opts = {
host: o.host,
port: o.port,
clientId: o.clientid,
username: o.username,
rejectUnauthorized: false,
resubscribe: false
};
console.log("wsmqttpublich -> loadSettings from instance.options", o);
connectToTbServer();
}
function connectToTbServer() {
var url = "mqtt://" + opts.host + ":" + opts.port;
console.log("MQTT URL: ", url);
client = mqtt.connect(url, opts);
client.on('connect', function() {
client.subscribe(`${o.topic}_backward`, (err) => {
if (!err) {
console.log("MQTT subscribed");
}
});
instance.status("Connected", "green");
clientReady = true;
sendClientError = true;
});
client.on('reconnect', function() {
instance.status("Reconnecting", "yellow");
clientReady = false;
});
client.on('message', function(topic, message) {
// message is type of buffer
message = message.toString();
if (message[0] === '{') {
try {
message = JSON.parse(message);
if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) {
client.publish(`${o.topic}_forward`, `{"device": "${message.device}", "id": ${message.data.id}, "data": {"success": true}}`, { qos: 1 });
instance.send(SEND_TO.rpcCall, { "device": message.device, "id": message.data.id, "RPC response": { "success": true } });
}
} catch (e) { instance.debug('MQTT: Error parsing data', message) }
instance.send(SEND_TO.rpcCall, { "topic": o.topic, "content": message });
}
});
client.on('close', function() {
clientReady = false;
instance.status("Disconnected", "red");
instance.send(SEND_TO.debug, { "message": "Client CLOSE signal received !" });
});
client.on('error', function(err) {
instance.status("Err: " + err.code, "red");
instance.send(SEND_TO.debug, { "message": "Client ERROR signal received !", "error": err, "opt": opts });
if (sendClientError) {
console.log('MQTT client error', err);
sendClientError = false;
}
clientReady = false;
});
}
instance.on('0', function(data) {
if (clientReady) {
//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();
}
let stringifiedJson = JSON.stringify(data.data)
client.publish(`${o.topic}_forward`, stringifiedJson, { qos: 1 });
}
else {
//logger.debug("Client unavailable. Data not sent !", JSON.stringify(data.data));
instance.send(SEND_TO.debug, { "message": "Client 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.on("1", _ => {
main();
})
instance.close = function(done) {
if (clientReady) {
client.end();
}
};
function getDbBackupFileCounter(type) {
var files = fs.readdirSync(__dirname + "/../databases");
let counter = 0;
for (var i = 0; i < files.length; i++) {
if (files[i] == "tbdatacloud.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/tbdatacloud.nosql";
var stats = fs.statSync(source);
var fileSizeInBytes = stats.size;
if (fileSizeInBytes > noSqlFileSizeLimit) {
let counter = 1;
counter = getDbBackupFileCounter("max");
let destination = __dirname + "/../databases/" + counter + "." + "tbdatacloud.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 = 'tbdatacloud';
else dataBase = counter + "." + 'tbdatacloud';
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 (clientReady) {
let item = records[i];
let id = item.id;
if (id !== undefined) {
//console.log("------------processDataFromDatabase - remove", id, dataBase, i);
try {
let message = JSON.parse(JSON.stringify(item));
delete message.id;
client.publish(`${o.topic}_forward`, JSON.stringify(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;
}
instance.on('options', main);
};

File diff suppressed because it is too large Load diff

View file

@ -1,60 +0,0 @@
exports.id = 'count';
exports.title = 'Count';
exports.version = '1.0.1';
exports.author = 'John Graves';
exports.color = '#656D78';
exports.icon = 'plus-square';
exports.input = 2;
exports.output = 1;
exports.options = { increment: 1, initialvalue: 1 };
exports.readme = `# Counter
Counter Number of times called.`;
exports.html = `<div class="padding">
<div data-jc="textbox" data-jc-path="initialvalue" data-jc-config="placeholder:1;increment:true;type:number;align:center">@(Initial Value)</div>
<div data-jc="textbox" data-jc-path="increment" data-jc-config="placeholder:1;increment:true;type:number;align:center">@(Increment)</div>
<p><a href="https://youtu.be/NuUbTm1oRE0" target="_blank">Example Video</a></p>
</div>`;
exports.readme = `# Count
This component counts the number of messages received.
__Response:__
Integer value based on the initial value and increment settings.
__Arguments:__
- Initial Value: What number should be output on the receipt of the first message.
- Increment: What should the increment be for each following message received.`;
exports.install = function(instance) {
var count = 0;
var initialCall = true;
instance.on('data', function(flowdata) {
var index = flowdata.index;
if (index) {
instance.debug('Reset Count.');
count = instance.options.initialvalue;
initialCall = true;
} else {
// If this is the first time, set the value to 'initial value'
if(initialCall) {
initialCall = false;
count = instance.options.initialvalue;
} else
count = count+instance.options.increment;
instance.status('Count:' + count);
instance.send2(count);
}
});
instance.on('options', function() {
count = instance.options.initialvalue;
initialCall = true;
});
};

175
flow/csv_import.js Normal file
View file

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

View file

@ -1,124 +0,0 @@
exports.id = 'db_init';
exports.title = 'DB Initialization';
exports.group = 'Worksys';
exports.color = '#888600';
exports.version = '1.0.2';
exports.icon = 'sign-out';
exports.output = 2;
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="host" data-jc-config="placeholder:test.mosquitto.org;required:false" class="m">Hostname or IP address (if not empty - setting will override db setting)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:1883;required:true" class="m">Port</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="clientid">@(Client id)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="username" class="m">@(Username)</div>
</div>
</div>
</div>`;
exports.readme = `
# DB initialization
`;
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
const { initNotification } = require('./helper/notification_reporter');
const errorHandler = require('./helper/ErrorToServiceHandler');
const SEND_TO = {
db_init: 0,
infoSender: 1
};
exports.install = async function(instance) {
const dbNodes = TABLE("nodes");
const dbRelays = TABLE("relays");
const dbSettings = TABLE("settings");
const dbPins = TABLE("pins");
const dbNotifications = TABLE("notifications");
FLOW.GLOBALS = {};
const dbs = FLOW.GLOBALS;
const responseSettings = await promisifyBuilder(dbSettings.find());
const responseNodes = await promisifyBuilder(dbNodes.find());
const responsePins = await promisifyBuilder(dbPins.find());
const responseRelays = await promisifyBuilder(dbRelays.find());
const response = await promisifyBuilder(dbNotifications.find());
dbs.pinsData = makeMapFromDbResult(responsePins, "pin");
dbs.relaysData = makeMapFromDbResult(responseRelays, "line");
dbs.nodesData = makeMapFromDbResult(responseNodes, "node");
dbs.notificationsData = makeMapFromDbResult(response, "key");
//+|354|nodesdata.....+|482|nodesdata....
//for some reason, if last line in nodes.table is not empty, flow wrote more nodes data in one row,
//so we have to add empty line at the bottom of nodes table to avoid this.
//now, remove empty lines from nodesData database:
if (dbs.nodesData.hasOwnProperty("0")) delete dbs.nodesData["0"];
Object.keys(dbs.nodesData).forEach(node => dbs.nodesData[node].readout = {})
dbs.settings = {
edge_fw_version: "2025-04-24", //rok-mesiac-den
language: responseSettings[0]["lang"],
rvo_name: responseSettings[0]["rvo_name"],
project_id: responseSettings[0]["project_id"],
rvoTbName: dbs.relaysData[0]["tbname"],
temperature_address: responseSettings[0]["temperature_address"],
controller_type: responseSettings[0]["controller_type"],
serial_port: responseSettings[0]["serial_port"],
node_status_nok_time: responseSettings[0]["node_status_nok_time"] * 60 * 60 * 1000,// hour * minutes *
latitude: responseSettings[0]["latitude"],
longitude: responseSettings[0]["longitude"],
no_voltage: new Set(),//modbus_citysys - elektromer
backup_on_failure: responseSettings[0]["backup_on_failure"],
restore_from_backup: responseSettings[0]["restore_from_backup"],
restore_backup_wait: responseSettings[0]["restore_backup_wait"],
mqtt_host: responseSettings[0]["mqtt_host"],
mqtt_clientid: responseSettings[0]["mqtt_clientid"],
mqtt_username: responseSettings[0]["mqtt_username"],
mqtt_port: responseSettings[0]["mqtt_port"],
phases: responseSettings[0]["phases"],
cloud_topic: responseSettings[0]["cloud_topic"],
has_main_switch: responseSettings[0]["has_main_switch"],
//dynamic values
masterNodeIsResponding: true, //cmd_manager
maintenance_mode: false,
}
FLOW.dbLoaded = true;
errorHandler.setProjectId(dbs.settings.project_id);
initNotification();
//APP START - send to data services
const toService = {
id: dbs.settings.project_id,
name: dbs.settings.rvo_name,
fw_version: dbs.settings.edge_fw_version,
startdate: new Date().toISOString().slice(0, 19).replace('T', ' '),
js_error: "",
error_message: ""
};
instance.send(SEND_TO.infoSender, toService);
console.log("----------------> START - message send to service", toService);
setTimeout(() => {
console.log("DB_INIT - data loaded");
instance.send(SEND_TO.db_init, "_")
}, 5000)
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,187 +1,163 @@
class DataToTbHandler { class DataToTbHandler
{
constructor(index) {
this.index = index;
constructor(index) { this.previousValues = {};
this.index = index; this.debug = false;
this.messageCounter = 0;
// time, after new value for the given key will be resend to tb (e.g. {status: "OK"}) this.sender = "";
this.timeToHoldTbValue = 30 * 60; //30 minutes }
this.previousValues = {};
this.debug = false;
this.messageCounter = 0;
this.itIsNodeReadout = false;
this.sender = "";
// if attribute change difference is less than limit value, we do not send to tb. dump()
this.attributeChangeLimit = { {
temperature: 0.5, console.log("----------------------------");
Phase_1_voltage: 2, console.log("previousValues", this.previousValues);
Phase_2_voltage: 2, console.log("----------------------------");
Phase_3_voltage: 2, }
Phase_1_current: 0.1,
Phase_2_current: 0.1,
Phase_3_current: 0.1,
Phase_1_power: 2,
Phase_2_power: 2,
Phase_3_power: 2,
total_power: 2,
total_energy: 1,
Phase_1_pow_factor: 0.1,
Phase_2_pow_factor: 0.1,
Phase_3_pow_factor: 0.1,
power_factor: 0.1,
lifetime: 2,
voltage: 2,
power: 2,
frequency: 3,
energy: 0.1,
current: 2,
inclination_x: 10,
inclination_y: 10,
inclination_z: 10
};
} setSender(sender)
{
this.sender = sender;
}
dump() { isEmptyObject( obj ) {
console.log("----------------------------"); for ( var name in obj ) {
console.log("previousValues", this.previousValues); return false;
console.log("----------------------------"); }
} return true;
}
setSender(sender) { sendToTb(dataToTb, instance)
this.sender = sender; {
}
isEmptyObject(obj) { if(!FLOW.OMS_brokerready)
for (var _ in obj) { {
return false; return dataToTb;
} }
return true;
} let keys = Object.keys(dataToTb);
if(keys.length == 0)
{
if(this.debug) console.log("sendToTb received epty object", dataToTb);
return;
}
sendToTb(data, instance) { let tbname = keys[0];
let ts;
//not to modify data object, we do deep copy: let arrayOfValues = dataToTb[tbname];
let dataCopy = JSON.parse(JSON.stringify(data)); let arrayOfValuesToSend = [];
let keys = Object.keys(dataCopy); for(let i = 0; i < arrayOfValues.length; i++)
{
ts = arrayOfValues[i].ts;
if (keys.length == 0) { //console.log("sendToTb------------>before", arrayOfValues[i].values, tbname);
if (this.debug) console.log("sendToTb received empty object", dataCopy);
return;
}
let tbname = keys[0]; let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values);
let ts;
let arrayOfValues = dataCopy[tbname]; //console.log("sendToTb------------>after", values);
let arrayOfValuesToSend = [];
for (let i = 0; i < arrayOfValues.length; i++) { if(!this.isEmptyObject(values))
{
arrayOfValuesToSend.push({ts: ts, values: values});
}
}
ts = arrayOfValues[i].ts; if(arrayOfValuesToSend.length == 0)
let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values); {
//if(this.debug) console.log("data not sent - empty array");
return;
}
if (!this.isEmptyObject(values)) { /*
arrayOfValuesToSend.push({ ts: ts, values: values }); let dataToTb = {
} [tbname]: [
{
"ts": Date.now(),
"values": values
}
]
}
*/
} this.messageCounter++;
if (arrayOfValuesToSend.length == 0) { let dataToTbModified = {
//if(this.debug) console.log("data not sent - empty array"); [tbname]: arrayOfValuesToSend
return; }
}
this.messageCounter++; //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);
let dataToTbModified = { instance.send(this.index, dataToTbModified);
[tbname]: arrayOfValuesToSend }
}
//console.log(this.sender + " DATA SEND TO TB ", tbname, this.messageCounter, new Date(ts), dataToTbModified[tbname][0].values, this.instance); getDiffTimestamp(key)
//if(this.debug) console.log(this.sender + " DATA SEND TO TB ", this.index, tbname, arrayOfValuesToSend); {
instance.send(this.index, dataToTbModified); let seconds = 60*60;//1h
} //seconds = 1;//for testing
//TODO set different value for given key!!!
//if(key == "status") seconds = 2*60*60;//2h
getDiffTimestamp(key) { let timestampDiffToRemoveKey = seconds*1000;
//TODO set different value for given key!!!
//if(key == "status") this.timeToHoldTbValue = 2*60*60;//2h
return this.timeToHoldTbValue * 1000;
}
return timestampDiffToRemoveKey;
}
prepareValuesForTb(tbname, timestamp, values) { prepareValuesForTb(tbname, timestamp, values)
{
let keys = Object.keys(values);
if(!this.previousValues.hasOwnProperty(tbname))
{
this.previousValues[tbname] = {};
}
let keys = Object.keys(values); //if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values);
if (keys.includes("lifetime")) this.itIsNodeReadout = true; for(let i = 0; i < keys.length; i++)
{
let key = keys[i];
let value = values[key];
if (!this.previousValues.hasOwnProperty(tbname)) { if(!this.previousValues[tbname].hasOwnProperty(key))
this.previousValues[tbname] = {}; {
} this.previousValues[tbname][key] = {ts: timestamp, value: value};
continue;
}
//if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values); if(this.previousValues[tbname][key].value === value)
{
let diff = timestamp - this.previousValues[tbname][key].ts;
for (let i = 0; i < keys.length; i++) { 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);
let key = keys[i]; }
let value = values[key]; 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;
}
if (!this.previousValues[tbname].hasOwnProperty(key)) { }
this.previousValues[tbname][key] = { ts: timestamp, value: value };
continue;
}
// attributeData ==> {voltage: {ts:333333, value:5}} return values;
let attributeData = this.previousValues[tbname][key]; }
let attributeToChange = false;
if (key in this.attributeChangeLimit) attributeToChange = true;
let limit = this.attributeChangeLimit[key];
let timestampDiffToRemoveKey;
//this will ensure "node statecode" will be sent just once an hour
if (this.itIsNodeReadout && key === "statecode") {
attributeData.value = value;
this.itIsNodeReadout = false;
timestampDiffToRemoveKey = 1 * 60 * 60 * 1000; // 1 hour
}
if (key === "twilight_sensor" && value > 100) {
attributeData.value = value;
}
//if edge, master or node version do not change, send just once a day:
if (["edge_fw_version", "master_node_version", "fw_version"].includes(key)) {
timestampDiffToRemoveKey = 24 * 60 * 60 * 1000;
}
if (attributeData.value === value || attributeToChange && Math.abs(attributeData.value - value) < limit) {
let diff = timestamp - attributeData.ts;
if (!timestampDiffToRemoveKey) timestampDiffToRemoveKey = this.getDiffTimestamp(key);
if (diff > timestampDiffToRemoveKey) {
attributeData.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 {
attributeData.value = value;
attributeData.ts = timestamp;
}
}
return values;
}
} }
module.exports = DataToTbHandler; module.exports = DataToTbHandler;

View file

@ -1,91 +1,124 @@
const { MD5 } = require('./md5.js'); const { MD5 } = require('./md5.js');
const { networkInterfaces } = require('os'); const { networkInterfaces } = require('os');
class ErrorToServiceHandler { class ErrorToServiceHandler
{
constructor() { constructor() {
this.previousValues = {}; this.previousValues = {};
this.project_id = undefined; this.projects_id = undefined;
const nets = networkInterfaces(); const nets = networkInterfaces();
this.ipAddresses = {}; this.ipAddresses = Object.create(null); // Or just '{}', an empty object
for (const name of Object.keys(nets)) { for (const name of Object.keys(nets)) {
for (const net of nets[name]) { for (const net of nets[name]) {
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) { if (net.family === 'IPv4' && !net.internal) {
if (!this.ipAddresses[name]) { if (!this.ipAddresses[name]) {
this.ipAddresses[name] = []; this.ipAddresses[name] = [];
} }
this.ipAddresses[name].push(net.address); this.ipAddresses[name].push(net.address);
} }
} }
} }
//console.log(this.ipAddresses);
} }
setProjectId(project_id) { setProjectsId(projects_id)
this.project_id = project_id; {
this.projects_id = projects_id;
} }
processMessage(message, seconds) { processMessage(message, seconds, message_type)
if (Array.isArray(message)) message = message.join(', '); {
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 - default value is 1h
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 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 key = MD5(message);
let ts = Date.now(); let timestamp = new Date().getTime();
//keep in memory - default value is 1h //keep in memory
if (seconds === undefined) seconds = 60 * 60; if (seconds === undefined) seconds = 60*60;
if (!this.previousValues.hasOwnProperty(key)) { if(!this.previousValues.hasOwnProperty(key))
this.previousValues[key] = { ts: ts, duration: seconds }; {
this.previousValues[key] = {ts: timestamp, duration: seconds};
} }
let diff = (ts - this.previousValues[key].ts); let diff = (timestamp - this.previousValues[key].ts);
if (diff < this.previousValues[key].duration * 1000) return false; if(diff < this.previousValues[key].duration*1000) return;
this.previousValues[key].ts = ts; this.previousValues[key].ts = timestamp;
*/
return message; //-------------------------
}
sendMessageToService(message, seconds, message_type) { //send to service
// if error occures too early FLOW.GLOBALS.settings.project_id is still undefined let dataToInfoSender = {id: this.projects_id};
if (this.project_id === undefined) {
console.log("ErrorToServiceHandler.js: no project_id");
return;
}
let f = this.processMessage(message, seconds);
if (f === false) return;
if (message_type === undefined) message_type = "error_message";
let toService = {
id: this.project_id,
ipAddresses: this.ipAddresses
};
//js_error || error_message //js_error || error_message
toService[message_type] = message; dataToInfoSender[message_type] = message;
dataToInfoSender.ipAddresses = this.ipAddresses;
console.log("ErrorToServiceHandler------------------------>send to service", toService); 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) { RESTBuilder.make(function(builder) {
builder.method('POST'); builder.method('POST');
builder.post(toService); builder.post(dataToInfoSender);
builder.url('http://192.168.252.2:8004/sentmessage'); builder.url('http://192.168.252.2:8004/sentmessage');
builder.callback(function(err, response, output) { builder.callback(function(err, response, output) {
console.log("process.on error send", err, response, output, toService); console.log("process.on error send", err, response, output, dataToInfoSender);
}); });
}); });
} }
} }
const errorHandler = new ErrorToServiceHandler(); module.exports = ErrorToServiceHandler;
module.exports = errorHandler;
//module.exports = ErrorToServiceHandler;

View file

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

View file

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

View file

@ -1,30 +0,0 @@
//https://github.com/log4js-node/log4js-node/blob/master/examples/example.js
//file: { type: 'file', filename: path.join(__dirname, 'log/file.log') }
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");
//USAGE
//logger.debug("text")
//monitor.info('info');
//errLogger.error("some error");
module.exports = { errLogger, logger, monitor };

View file

@ -1,9 +1,10 @@
//key is device, value = message {}
let sentValues = {};
let notificationsData = null;
let rvoName;
//sendNotification("CMD Manager: process cmd", SETTINGS.rvoTbName, "dimming_profile_was_successfully_received_by_node", { node: node }, "", SEND_TO.tb, instance); const { promisifyBuilder, makeMapFromDbResult } = require('./db_helper.js');
const dbNotifications = TABLE("notifications");
//key is device, value = str
let sentValues= {};
let notificationsData = {};
let ERRWEIGHT = { let ERRWEIGHT = {
EMERGENCY: "emergency", // System unusable EMERGENCY: "emergency", // System unusable
@ -23,70 +24,87 @@ function getKey(map, val) {
//https://stackoverflow.com/questions/41117799/string-interpolation-on-variable //https://stackoverflow.com/questions/41117799/string-interpolation-on-variable
var template = (tpl, args) => tpl.replace(/\${(\w+)}/g, (_, v) => args[v]); var template = (tpl, args) => tpl.replace(/\${(\w+)}/g, (_, v) => args[v]);
async function initNotifications()
{
let response = await promisifyBuilder(dbNotifications.find());
notificationsData = makeMapFromDbResult(response, "key");
function initNotification() { console.log("initNotifications done" );
notificationsData = FLOW.GLOBALS.notificationsData;
rvoName = FLOW.GLOBALS.settings.rvo_name;
} }
function sendNotification(func, device, key, params, extra, tb_output, instance, saveKey) { function sendNotification(func, device, key, params, extra, tb_output, instance, saveKey) {
// return;
let storeToSendValues = true; let storeToSendValues = true;
if (saveKey == undefined) storeToSendValues = false; if(saveKey == undefined) storeToSendValues = false;
let lang = FLOW.OMS_language;
if(lang != "en" || lang != "sk") lang = "en";
let tpl = key;
let weight = ""; let weight = "";
let message = {};
let notification = notificationsData[key]; if(notificationsData[key])
{
weight = notificationsData[key].weight;
weight = weight.toLowerCase();
if (notification) { tpl = notificationsData[key][lang];
weight = notification.weight.toLowerCase(); tpl = template(tpl, params);
Object.keys(notification).forEach(item => {
if (["en", "sk", "de", "cz", "it", "es"].includes(item)) {
message[item] = rvoName + ": " + template(notification[item], params);
}
})
} }
else { else
//console.error("sendNotification: Notifications: undefined key", key, func, notificationsData); {
console.error("sendNotification: Notifications: undefined key", key, func); console.error("sendNotification: Notifications: undefined key", key, func, notificationsData);
return false; return false;
} }
//detect invalid err weight //detect invalid err weight
if (getKey(ERRWEIGHT, weight) == undefined) { if(getKey(ERRWEIGHT, weight) == undefined)
{
console.error("sendNotification: Notifications: undefined weight", weight, key, func); console.error("sendNotification: Notifications: undefined weight", weight, key, func);
return false; return false;
} }
if (sentValues.hasOwnProperty(saveKey)) { if(sentValues.hasOwnProperty(saveKey))
if (JSON.stringify(sentValues[saveKey]) == JSON.stringify(message)) { {
if(sentValues[saveKey] == tpl)
{
return false; return false;
} }
} }
if (sentValues[saveKey] == undefined) { if(sentValues[saveKey] == undefined)
if (storeToSendValues) { {
if(storeToSendValues)
{
//do not send - flow is was started //do not send - flow is was started
sentValues[saveKey] = message; sentValues[saveKey] = tpl;
return false; return false;
} }
} }
if (storeToSendValues) sentValues[saveKey] = message; 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 = { let content = {
"type": weight, "type": weight,
"status": "new", "status": "new",
"source": { "source": {
"func": func, "func":func,
"component": instance.id, "component":instance.id,
"component_name": instance.name, "component_name":instance.name,
"edge": device "edge":device
}, },
"message": message, "message":str,
"message_data": extra "message_data": extra
}; };
@ -95,7 +113,7 @@ function sendNotification(func, device, key, params, extra, tb_output, instance,
{ {
"ts": Date.now(), "ts": Date.now(),
"values": { "values": {
"_event": content "_event":content
} }
} }
]; ];
@ -106,7 +124,6 @@ function sendNotification(func, device, key, params, extra, tb_output, instance,
} else { } else {
bufferError(msg); bufferError(msg);
}*/ }*/
instance.send(tb_output, msg); // Even if error server is unavailable, send this message to output, for other possible component connections instance.send(tb_output, msg); // Even if error server is unavailable, send this message to output, for other possible component connections
return true; return true;
@ -115,7 +132,6 @@ function sendNotification(func, device, key, params, extra, tb_output, instance,
module.exports = { module.exports = {
sendNotification, sendNotification,
ERRWEIGHT, initNotifications,
initNotification ERRWEIGHT
} }

View file

@ -1,95 +1,90 @@
const { exec } = require('child_process'); const { exec } = require('child_process');
function openPort(port) { function openPort(port){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var callbackError = function(err) { var callbackError = function(err) {
port.removeListener('error', callbackError); port.removeListener('error', callbackError);
port.removeListener('open', callbackError); port.removeListener('open', callbackError);
reject(err.message); reject(err.message);
}; };
var callbackOpen = function(data) { var callbackOpen = function(data) {
port.removeListener('error', callbackError); port.removeListener('error', callbackError);
port.removeListener('open', callbackOpen); port.removeListener('open', callbackOpen);
resolve("port open: ok"); resolve("port open: ok");
}; };
port.on('error', callbackError); port.on('error', callbackError);
port.on('open', callbackOpen); port.on('open', callbackOpen);
port.open(); port.open();
}) })
} }
function runSyncExec(command) { function runSyncExec(command){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => { exec(command, (error, stdout, stderr) => {
if (error == null) resolve(stdout); if(error == null) resolve(stdout);
reject(error); reject(error);
}); });
}) })
} }
async function writeData(port, data, readbytes, timeout) { async function writeData(port, data, readbytes, timeout){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// If first item in data array is 255, we just write broadcast command to rsPort //readbytes = 0 = broadcast
// We wait 3 seconds and resolve(["broadcast"]) if(readbytes == undefined) readbytes = 0;
// It is important to resolve with array if(timeout == undefined) timeout = 10000;//10s, default timeout MASTERA je 3s
if (data[0] == 255) {
//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) { port.write(Buffer.from(data), function(err) {
if (err) { if (err) {
port.removeListener('data', callback);
reject(err.message); reject(err.message);
} }
if(readbytes == 0)
{
resolve(rsPortReceivedData);
}
}); });
})
setTimeout(resolve, 3000, ["broadcast"]);
return;
}
//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 = [];
port.on('data', callback);
port.write(Buffer.from(data), function(err) {
if (err) {
port.removeListener('data', callback);
reject(err.message);
}
});
})
} }
module.exports = { module.exports = {

View file

@ -1,102 +1,56 @@
function bytesToInt_orig(bytes, numberOfBytes) { 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);
let buffer = []; //var decimal = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
if (Array.isArray(bytes)) { let l = (buffer.length - 1) * 8;
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;
let decimal = 0; for(let i = 0; i < buffer.length; i++)
for (let i = 0; i < buffer.length; i++) { {
var s = buffer[i] << l; var s = buffer[i] << l;
if (l < 8) s = buffer[i] if(l < 8) s = buffer[i]
decimal = decimal + s; decimal = decimal + s;
l = l - 8;
}
// console.log("decimal utils.js: ", decimal);
let decimal1 = 0n; l = l - 8;
for (let i = 0; i < buffer.length; i++) {
decimal1 += BigInt(buffer[i]) * (2n ** BigInt((buffer.length - 1 - i) * 8)); }
}
// console.log("decimal biging utils.js: ", decimal1); return decimal;
return decimal;
} }
//bytestouintBE
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);
console.log(bytes, buffer);
let result = 0;
for (let i = 0; i < buffer.length; i++) {
result = (result << 8) | bytes[i];
}
// console.log("decimal biging utils.js: ", decimal1);
console.log("originall: ", bytesToInt_orig(buffer));
console.log("uint little endian: ", bytesToUintLE(buffer));
console.log('neww: ', result >>> 0);
return result >>> 0;
}
function bytesToUintLE(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 result = 0;
for (let i = buffer.length - 1; i <= 0; i--) {
result = (result << 8) | bytes[i];
}
return result >>> 0;
}
function resizeArray(arr, newSize, defaultValue) { function resizeArray(arr, newSize, defaultValue) {
while (newSize > arr.length) while(newSize > arr.length)
arr.push(defaultValue); arr.push(defaultValue);
arr.length = newSize; arr.length = newSize;
} }
longToByteArray = function(/*long*/long) { longToByteArray = function(/*long*/long) {
// we want to represent the input as a 8-bytes array // we want to represent the input as a 8-bytes array
var byteArray = [0, 0, 0, 0, 0, 0, 0, 0]; var byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
for (var index = 0; index < byteArray.length; index++) { for ( var index = 0; index < byteArray.length; index ++ ) {
var byte = long & 0xff; var byte = long & 0xff;
byteArray[index] = byte; byteArray [ index ] = byte;
long = (long - byte) / 256; long = (long - byte) / 256 ;
} }
return byteArray; return byteArray;
}; };
function addDays(date, days) { function addDays(date, days) {
var result = new Date(date); var result = new Date(date);
result.setDate(result.getDate() + days); result.setDate(result.getDate() + days);
return result; return result;
} }
/* /*
@ -107,64 +61,64 @@ sleep(2000).then(() => {
}); });
*/ */
function sleep(time) { function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time)); return new Promise((resolve) => setTimeout(resolve, time));
} }
function isEmptyObject(obj) { function isEmptyObject( obj ) {
for (var name in obj) { for ( var name in obj ) {
return false; return false;
} }
return true; return true;
} }
function convertUTCDateToLocalDate(date) { function convertUTCDateToLocalDate(date) {
var newDate = new Date(date); var newDate = new Date(date);
newDate.setMinutes(date.getMinutes() + date.getTimezoneOffset()); newDate.setMinutes(date.getMinutes() + date.getTimezoneOffset());
return newDate; return newDate;
} }
function addZeroBefore(n) { function addZeroBefore(n) {
return (n < 10 ? '0' : '') + n; return (n < 10 ? '0' : '') + n;
} }
var convertBase = function() { var convertBase = function () {
function convertBase(baseFrom, baseTo) { function convertBase(baseFrom, baseTo) {
return function(num) { return function (num) {
return parseInt(num, baseFrom).toString(baseTo); return parseInt(num, baseFrom).toString(baseTo);
}; };
} }
// binary to decimal // binary to decimal
convertBase.bin2dec = convertBase(2, 10); convertBase.bin2dec = convertBase(2, 10);
// binary to hexadecimal // binary to hexadecimal
convertBase.bin2hex = convertBase(2, 16); convertBase.bin2hex = convertBase(2, 16);
// decimal to binary // decimal to binary
convertBase.dec2bin = convertBase(10, 2); convertBase.dec2bin = convertBase(10, 2);
// decimal to hexadecimal // decimal to hexadecimal
convertBase.dec2hex = convertBase(10, 16); convertBase.dec2hex = convertBase(10, 16);
// hexadecimal to binary // hexadecimal to binary
convertBase.hex2bin = convertBase(16, 2); convertBase.hex2bin = convertBase(16, 2);
// hexadecimal to decimal // hexadecimal to decimal
convertBase.hex2dec = convertBase(16, 10); convertBase.hex2dec = convertBase(16, 10);
return convertBase; return convertBase;
}(); }();
module.exports = { module.exports = {
bytesToInt, bytesToInt,
longToByteArray, longToByteArray,
addDays, addDays,
addZeroBefore, addZeroBefore,
resizeArray, resizeArray,
isEmptyObject, isEmptyObject,
sleep, sleep,
convertUTCDateToLocalDate convertUTCDateToLocalDate
} }

View file

@ -3,9 +3,12 @@ exports.title = 'Info sender';
exports.version = '1.0.0'; exports.version = '1.0.0';
exports.group = 'Worksys'; exports.group = 'Worksys';
exports.color = '#2134B0'; exports.color = '#2134B0';
exports.input = 2; exports.input = 1;
exports.output = 1 exports.output = 1
exports.click = false;
exports.author = 'oms-is';
exports.icon = 'bolt'; exports.icon = 'bolt';
exports.options = { edge: "undefined" };
const { networkInterfaces } = require('os'); const { networkInterfaces } = require('os');
@ -19,12 +22,13 @@ exports.html = `<div class="padding">
exports.readme = `# send all data to projects.worksys.io, required to monitor status of controller(unipi)`; exports.readme = `# send all data to projects.worksys.io, required to monitor status of controller(unipi)`;
exports.install = function(instance) { const fs = require('fs');
var path = require('path');
exports.install = async function(instance) {
let id;
let allValues = {}; let allValues = {};
let sendAllValuesInterval; let sendAllValuesInterval;
let configured = false;
let now = new Date(); let now = new Date();
console.log(exports.title, "INSTALLED", now.toLocaleString("sk-SK")); console.log(exports.title, "INSTALLED", now.toLocaleString("sk-SK"));
@ -33,28 +37,43 @@ exports.install = function(instance) {
let ipAddresses = Object.create(null); // Or just '{}', an empty object let ipAddresses = Object.create(null); // Or just '{}', an empty object
for (const name of Object.keys(nets)) { for (const name of Object.keys(nets)) {
for (const net of nets[name]) { for (const net of nets[name]) {
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) { if (net.family === 'IPv4' && !net.internal) {
if (!ipAddresses[name]) { if (!ipAddresses[name]) {
ipAddresses[name] = []; ipAddresses[name] = [];
} }
ipAddresses[name].push(net.address); ipAddresses[name].push(net.address);
}
} }
}
} }
function sendValues() {
if (!configured) return;
if (Object.keys(allValues).length > 0) { function sendValues()
let dataToSend = { ...allValues }; {
dataToSend.id = id; const id = FLOW.OMS_projects_id;
dataToSend.ipAddresses = ipAddresses;
instance.send(0, dataToSend); 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");
}
allValues = {};
} }
} }
@ -62,20 +81,25 @@ exports.install = function(instance) {
clearInterval(sendAllValuesInterval); clearInterval(sendAllValuesInterval);
}) })
instance.on("0", _ => { instance.on("data", (flowdata) => {
id = FLOW.GLOBALS.settings.project_id;
if (id) configured = true; allValues = { ...allValues, ...flowdata.data};
else console.log(exports.title, "InfoSender: Unable to send data, no id");
})
instance.on("1", flowdata => {
allValues = { ...allValues, ...flowdata.data };
//console.log("DATA RECEIVED", flowdata.data); //console.log("DATA RECEIVED", flowdata.data);
//__force__
if(flowdata.data.hasOwnProperty("__force__"))
{
if(flowdata.data.__force__)
{
sendValues();
}
}
}) })
sendAllValuesInterval = setInterval(() => { sendAllValuesInterval = setInterval(() => {
sendValues(); sendValues();
}, 60000 * 3); }, 60000*3);
} }

View file

@ -3,7 +3,6 @@ exports.title = 'Modbus reader';
exports.version = '2.0.0'; exports.version = '2.0.0';
exports.group = 'Worksys'; exports.group = 'Worksys';
exports.color = '#2134B0'; exports.color = '#2134B0';
exports.input = 1;
exports.output = ["red", "white", "yellow"]; exports.output = ["red", "white", "yellow"];
exports.click = false; exports.click = false;
exports.author = 'Rastislav Kovac'; exports.author = 'Rastislav Kovac';
@ -16,7 +15,7 @@ exports.readme = `
`; `;
const modbus = require('jsmodbus'); const modbus = require('jsmodbus');
const {SerialPort} = require('serialport'); const SerialPort = require('serialport');
const { timeoutInterval, deviceConfig } = require("../databases/modbus_config"); const { timeoutInterval, deviceConfig } = require("../databases/modbus_config");
const { sendNotification } = require('./helper/notification_reporter'); const { sendNotification } = require('./helper/notification_reporter');
@ -32,16 +31,13 @@ const SEND_TO = {
const numberOfNotResponding = {}; const numberOfNotResponding = {};
let tbName = null; let tbName = null;
let mainSocket; let mainSocket;
//number of phases inRVO
let phases;
//phases where voltage is 0 (set)
let noVoltage;
exports.install = function(instance) { exports.install = function(instance) {
class SocketWithClients { class SocketWithClients {
constructor() { constructor () {
this.stream = null; this.stream = null;
this.socket = null; this.socket = null;
this.clients = {}; this.clients = {};
@ -59,35 +55,27 @@ exports.install = function(instance) {
// lampSwitchNotification helper variables // lampSwitchNotification helper variables
this.onNotificationSent = false; this.onNotificationSent = false;
this.offNotificationSent = false; this.offNotificationSent = false;
this.phases = this.buildPhases();
this.startSocket(); this.startSocket();
} }
buildPhases = () => {
let a = [];
for (let i = 1; i <= phases; i++) {
a.push(`Phase_${i}_voltage`)
}
return a;
}
startSocket = () => { startSocket = () => {
let obj = this; let obj = this;
this.socket = new SerialPort({path: "/dev/ttymxc0", this.socket = new SerialPort("/dev/ttymxc0", {
baudRate: 9600 baudRate: 9600,
}) })
// we create a client for every deviceAddress ( = address) in list and push them into dictionary // we create a client for every deviceAddress ( = address) in list and push them into dictionary
for (let i = 0; i < deviceConfig.length; i++) { for( let i = 0; i < deviceConfig.length; i++)
{
this.clients[deviceConfig[i].deviceAddress] = new modbus.client.RTU(this.socket, deviceConfig[i].deviceAddress, 2000); // 2000 is timeout in register request, default is 5000, which is too long this.clients[deviceConfig[i].deviceAddress] = new modbus.client.RTU(this.socket, deviceConfig[i].deviceAddress, 2000); // 2000 is timeout in register request, default is 5000, which is too long
} }
this.socket.on('error', function(e) { this.socket.on('error', function(e) {
console.log('socket connection error', e); console.log('socket connection error', e);
if (e.code == 'ECONNREFUSED' || e.code == 'ECONNRESET') { if(e.code == 'ECONNREFUSED' || e.code == 'ECONNRESET') {
console.log(exports.title + ' Waiting 10 seconds before trying to connect again'); console.log(exports.title + ' Waiting 10 seconds before trying to connect again');
setTimeout(obj.startSocket, 10000); setTimeout(obj.startSocket, 10000);
} }
@ -98,7 +86,7 @@ exports.install = function(instance) {
setTimeout(obj.startSocket, 10000); setTimeout(obj.startSocket, 10000);
}); });
this.socket.on('open', function() { this.socket.on('open', function () {
console.log("socket connected"); console.log("socket connected");
obj.getActualStreamAndDevice(); obj.getActualStreamAndDevice();
obj.timeoutInterval = timeoutInterval - DELAY_BETWEEN_DEVICES; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES obj.timeoutInterval = timeoutInterval - DELAY_BETWEEN_DEVICES; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES
@ -115,7 +103,7 @@ exports.install = function(instance) {
this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number
this.device = dev.device; //em340, twilight_sensor this.device = dev.device; //em340, twilight_sensor
if (this.indexInDeviceConfig == 0) setTimeout(this.readRegisters, this.timeoutInterval); if(this.indexInDeviceConfig == 0) setTimeout(this.readRegisters, this.timeoutInterval);
else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES); else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES);
} }
@ -129,18 +117,21 @@ exports.install = function(instance) {
let obj = this; let obj = this;
this.clients[this.deviceAddress].readHoldingRegisters(register, size) this.clients[this.deviceAddress].readHoldingRegisters(register, size)
.then(function(resp) { .then( function (resp) {
resp = resp.response._body.valuesAsArray; //resp is array of length 1 or 2, f.e. [2360,0] resp = resp.response._body.valuesAsArray; //resp is array of length 1 or 2, f.e. [2360,0]
// console.log(deviceAddress, register, tbAttribute, resp); // console.log(deviceAddress, register, tbAttribute, resp);
//device is responding again after NOK status //device is responding again after NOK status
if (numberOfNotResponding.hasOwnProperty(obj.device)) { if(numberOfNotResponding.hasOwnProperty(obj.device))
{
let message = ""; let message = "";
if (obj.device == "em340") { if(obj.device == "em340")
{
message = "electrometer_ok"; message = "electrometer_ok";
} }
else if (obj.device == "twilight_sensor") { else if(obj.device == "twilight_sensor")
{
message = "twilight_sensor_ok"; message = "twilight_sensor_ok";
} }
message && sendNotification("modbus_reader: readRegisters", tbName, message, {}, "", SEND_TO.tb, instance); message && sendNotification("modbus_reader: readRegisters", tbName, message, {}, "", SEND_TO.tb, instance);
@ -153,21 +144,25 @@ exports.install = function(instance) {
obj.index++; obj.index++;
obj.readAnotherRegister(); obj.readAnotherRegister();
}).catch(function() { }).catch (function () {
//console.log("errors pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute); console.log("errors pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute);
obj.errors++; obj.errors++;
if (obj.errors == obj.lengthOfActualDeviceStream) { if(obj.errors == obj.lengthOfActualDeviceStream)
instance.send(SEND_TO.dido_controller, { status: "NOK-" + obj.device }); // NOK-em340, NOK-em111, NOK-twilight_sensor, NOK-thermometer {
instance.send(SEND_TO.dido_controller, {status: "NOK-" + obj.device}); // NOK-em340, NOK-em111, NOK-twilight_sensor, NOK-thermometer
//todo - neposlalo notification, ked sme vypojili twilight a neposle to do tb, ale do dido ?? //todo - neposlalo notification, ked sme vypojili twilight a neposle to do tb, ale do dido ??
if (!numberOfNotResponding.hasOwnProperty(obj.device)) { if(!numberOfNotResponding.hasOwnProperty(obj.device))
{
let message = ""; let message = "";
if (obj.device == "twilight_sensor") { if(obj.device == "twilight_sensor")
{
message = "twilight_sensor_nok"; message = "twilight_sensor_nok";
} }
else if (obj.device == "em340") { else if(obj.device == "em340")
{
message = "electrometer_nok"; message = "electrometer_nok";
} }
message && sendNotification("modbus_reader: readingTimeouted", tbName, message, {}, "", SEND_TO.tb, instance); message && sendNotification("modbus_reader: readingTimeouted", tbName, message, {}, "", SEND_TO.tb, instance);
@ -178,13 +173,14 @@ exports.install = function(instance) {
numberOfNotResponding[obj.device] += 1; numberOfNotResponding[obj.device] += 1;
} }
// console.error(require('util').inspect(arguments, { console.error(require('util').inspect(arguments, {
// depth: null depth: null
// })) }))
// if reading out of device's last register returns error, we send accumulated allValues to dido_controller (if allValues are not an empty object) // if reading out of device's last register returns error, we send accumulated allValues to dido_controller (if allValues are not an empty object)
if (obj.index + 1 >= obj.lengthOfActualDeviceStream) { if(obj.index + 1 >= obj.lengthOfActualDeviceStream)
if (!isObjectEmpty(obj.allValues)) instance.send(SEND_TO.dido_controller, { values: obj.allValues }); {
if(!isObjectEmpty(obj.allValues)) instance.send(SEND_TO.dido_controller, {values: obj.allValues});
obj.allValues = {}; obj.allValues = {};
} }
obj.index++; obj.index++;
@ -194,7 +190,7 @@ exports.install = function(instance) {
}; };
readAnotherRegister = () => { readAnotherRegister = () => {
if (this.index < this.lengthOfActualDeviceStream) setTimeout(this.readRegisters, 0); if(this.index < this.lengthOfActualDeviceStream) setTimeout(this.readRegisters, 0);
else this.setNewStream(); else this.setNewStream();
} }
@ -203,7 +199,8 @@ exports.install = function(instance) {
for (let i = 0; i < this.lengthOfActualDeviceStream; i++) { for (let i = 0; i < this.lengthOfActualDeviceStream; i++) {
let a = this.stream[i]; let a = this.stream[i];
if (a.register === register) { if (a.register === register)
{
let tbAttribute = a.tbAttribute; let tbAttribute = a.tbAttribute;
let multiplier = a.multiplier; let multiplier = a.multiplier;
@ -212,7 +209,8 @@ exports.install = function(instance) {
// if(tbName == undefined) return; // if(tbName == undefined) return;
if (this.index + 1 < this.lengthOfActualDeviceStream) { if(this.index + 1 < this.lengthOfActualDeviceStream)
{
this.allValues[tbAttribute] = value; this.allValues[tbAttribute] = value;
return; return;
} }
@ -225,20 +223,26 @@ exports.install = function(instance) {
this.checkNullVoltage(values); this.checkNullVoltage(values);
this.lampSwitchNotification(values); this.lampSwitchNotification(values);
instance.send(SEND_TO.dido_controller, { values: values }); instance.send(SEND_TO.dido_controller, {values: values});
this.allValues = {}; this.allValues = {};
break; break;
} }
} }
} }
setNewStream = () => { setNewStream = () =>
if (this.lengthOfActualDeviceStream == this.index) { {
if (this.indexInDeviceConfig + 1 == deviceConfig.length) { if(this.lengthOfActualDeviceStream == this.index)
{
if(this.indexInDeviceConfig + 1 == deviceConfig.length)
{
this.indexInDeviceConfig = 0; this.indexInDeviceConfig = 0;
} }
else { else
{
this.indexInDeviceConfig += 1; this.indexInDeviceConfig += 1;
} }
@ -246,22 +250,25 @@ exports.install = function(instance) {
} }
} }
calculateValue = (response, multiplier) => { calculateValue = (response, multiplier) =>
{
let value = 0; let value = 0;
let l = response.length; let l = response.length;
if (l === 2) { if (l === 2)
value = (response[1] * (2 ** 16) + response[0]); {
value = (response[1]*(2**16) + response[0]);
if (value >= (2 ** 31)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x80000000), ak vieš robiť logický súčin if(value >= (2**31)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x80000000), ak vieš robiť logický súčin
{ {
value = value - "0xFFFFFFFF" + 1; value = value - "0xFFFFFFFF" + 1;
} }
} }
else if (l === 1) { else if (l === 1)
{
value = response[0]; value = response[0];
if (value >= (2 ** 15)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x8000), ak vieš robiť logický súčin if(value >= (2**15)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x8000), ak vieš robiť logický súčin
{ {
value = value - "0xFFFF" + 1; value = value - "0xFFFF" + 1;
} }
@ -272,47 +279,49 @@ exports.install = function(instance) {
checkNullVoltage = (values) => { checkNullVoltage = (values) => {
if (!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return; if(!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return;
Object.keys(values).map(singleValue => { Object.keys(values).map(singleValue => {
if (this.phases.includes(singleValue)) { if (["Phase_1_voltage", "Phase_2_voltage", "Phase_3_voltage"].includes(singleValue))
{
let l = singleValue.split("_"); let l = singleValue.split("_");
let phase = parseInt(l[1]); let phase = parseInt(l[1]);
if(FLOW.OMS_no_voltage == undefined) FLOW.OMS_no_voltage = new Set();
// console.log(values[singleValue], tbName); // console.log(values[singleValue], tbName);
if (values[singleValue] == 0) { if(values[singleValue] == 0)
noVoltage.add(phase); {
sendNotification("modbus_reader: checkNullVoltage", tbName, "no_voltage_on_phase", { phase: phase }, "", SEND_TO.tb, instance, "voltage" + phase); FLOW.OMS_no_voltage.add(phase);
sendNotification("modbus_reader: checkNullVoltage", tbName, "no_voltage_on_phase", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase );
// console.log('no voltage') // console.log('no voltage')
} }
else { else
noVoltage.delete(phase); {
FLOW.OMS_no_voltage.delete(phase);
// console.log('voltage detected') // console.log('voltage detected')
sendNotification("modbus_reader: checkNullVoltage", tbName, "voltage_on_phase_restored", { phase: phase }, "", SEND_TO.tb, instance, "voltage" + phase); sendNotification("modbus_reader: checkNullVoltage", tbName, "voltage_on_phase_restored", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase);
} }
} }
}) })
} }
/** /**
* function sends notification to slack and to tb, if EM total_power value changes more than numberOfNodes*15. This should show, that RVO lamps has been switched on or off * function sends notification to slack and to tb, if EM total_power value changes more than 500. This should show, that RVO lamps has been switched on or off
*/ */
lampSwitchNotification = (values) => { lampSwitchNotification = (values) => {
if (!values.hasOwnProperty("total_power")) return; if(!values.hasOwnProperty("total_power")) return;
const actualTotalPower = values.total_power; const actualTotalPower = values.total_power;
if(actualTotalPower > 600 && this.onNotificationSent == false)
const numberOfNodes = Object.keys(FLOW.GLOBALS.nodesData).length; {
if (numberOfNodes == 0) numberOfNodes = 20; // to make sure, we send notification if totalPower is more than 300
if (actualTotalPower > numberOfNodes * 15 && this.onNotificationSent == false) {
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_on", {}, "", SEND_TO.tb, instance); sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_on", {}, "", SEND_TO.tb, instance);
this.onNotificationSent = true; this.onNotificationSent = true;
this.offNotificationSent = false; this.offNotificationSent = false;
} }
else if (actualTotalPower <= numberOfNodes * 15 && this.offNotificationSent == false) { else if(actualTotalPower <= 600 && this.offNotificationSent == false)
{
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_off", {}, "", SEND_TO.tb, instance); sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_off", {}, "", SEND_TO.tb, instance);
this.onNotificationSent = false; this.onNotificationSent = false;
this.offNotificationSent = true; this.offNotificationSent = true;
@ -322,25 +331,18 @@ exports.install = function(instance) {
} }
const isObjectEmpty = (objectName) => { const isObjectEmpty = (objectName) => {
return Object.keys(objectName).length === 0 && objectName.constructor === Object; return Object.keys(objectName).length === 0 && objectName.constructor === Object;
} }
function main() { setTimeout(() => {
phases = FLOW.GLOBALS.settings.phases;
tbName = FLOW.GLOBALS.settings.rvoTbName;
noVoltage = FLOW.GLOBALS.settings.no_voltage;
mainSocket = new SocketWithClients(); mainSocket = new SocketWithClients();
tbName = FLOW.OMS_rvo_tbname;
console.log("novoltage: ", noVoltage, typeof noVoltage);
// this notification is to show, that flow (unipi) has been restarted // this notification is to show, that flow (unipi) has been restarted
sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance); sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance);
}
instance.on("0", function(_) { }, 25000);
main();
})
} }

View file

@ -1,70 +0,0 @@
exports.id = 'nodesdb_change_check';
exports.title = 'Nodes DB change check';
exports.group = 'Worksys';
exports.color = '#888600';
exports.version = '1.0.2';
exports.icon = 'sign-out';
exports.input = 1;
exports.output = 1;
exports.readme = `Check, if nodes.table db changed compared to original database`;
const fs = require('fs');
const path = require('path');
const { sendNotification } = require('./helper/notification_reporter');
const nodesOriginalFile = path.join(__dirname, '../databases/nodes_original/', 'nodes_original.table');
exports.install = function(instance) {
function compareArrays(array1, array2) {
let message = "";
let areEqual = true;
let zmenene = []
if (array1.length !== array2.length) {
message += "Nezhoda v pocte nodov. "
}
const set1 = new Set(array1.map(obj => JSON.stringify(obj)));
const set2 = new Set(array2.map(obj => JSON.stringify(obj)));
for (const objStr of set1) {
if (!set2.has(objStr)) {
zmenene.push(objStr)
areEqual = false;
} else {
set2.delete(objStr);
}
}
if(!areEqual) {
message += `Aktualne nody: ${zmenene.toString()}. Zmenene proti originalu: ${Array.from(set2).join(' ')}`;
sendNotification("Nodesdb_changecheck", FLOW.GLOBALS.settings.rvoTbName, "nodes_db_changed", "", message, 0, instance);
}
else console.log("Arrays are equal.");
console.log(message)
}
instance.on("data", _ => {
let nodesData = FLOW.GLOBALS.nodesData;
// we check if nodes.table has changed compared to nodes_original.table (we have array of nodes e.g. [{node:255, tbname: "agruhuwhgursuhgo34hgsdiguhrr"}]
const nodes_actual = Object.keys(nodesData).map(node => ({[node]: nodesData[node].tbname}))
let nodes_original = fs.readFileSync(nodesOriginalFile, { encoding: 'utf8', flag: 'r' });
try {
nodes_original = JSON.parse(nodes_original);
} catch(e) {
console.log(e)
}
setTimeout(() => compareArrays(nodes_actual, nodes_original),10000);
})
}

31
flow/notifikacie.csv Normal file
View file

@ -0,0 +1,31 @@
key;weight;en;sk
switching_profile_point_applied_to_line;INFO;Switching profile point applied to line no. ${line} : ${value};Aplikovaný bod spínacieho profilu na línií č. ${line} : ${value}
dusk_has_occured;INFO;Dusk has occured;Nastal súmrak
dawn_has_occured;INFO;Dawn has occured;Nastal úsvit
dimming_profile_was_successfully_received_by_node;NOTICE;Dimming profile was successfully received by node no. ${node};Stmievací profil bol úspešne prijatý nodom č. ${node}
master_node_is_responding_again;NOTICE;Master node is responding again;Master node začal znovu odpovedať
command_was_sent_from_terminal_interface;DEBUG;A command was sent from terminal interface;Z terminálu bol odoslaný príkaz
master_node_is_not_responding;ALERT;Master node is not responding;Master node neodpovedá
configuration_of_dimming_profile_to_node_failed;ALERT;Configuration of dimming profile to node no. ${node} has failed;Konfigurácia stmievacieho profilu pre node č. ${node} zlyhala
circuit_breaker_was_turned_on_line;NOTICE;Circuit breaker was turned on - line no. ${line};Zapnutie ističa na línii č. ${line}
circuit_breaker_was_turned_off_line;ERROR;Circuit breaker was turned off - line no. ${line};Vypnutie ističa na línií č. ${line}
dimming_profile_was_processed_for_node;INFO;Dimming profile was processed for node no. ${node};Stmievací profil bol spracovaný pre node č. ${node}
switching_profile_was_processed_for_line;INFO;Switching profile was processed for line no. ${line};Spínací profil bol spracovaný pre líniu č. ${line}
thermometer_is_not_responding;WARNING;Thermometer is not responding;Teplomer neodpovedá
thermometer_is_responding_again;NOTICE;Thermometer is responding again;Teplomer znovu odpovedá
thermometer_sends_invalid_data;WARNING;Thermometer sends invalid data;Teplomer posiela neplatné hodnoty
main_switch_has_been_turned_off;CRITICAL;Main switch has been turned off;Hlavný vypínač bol vypnutý
main_switch_has_been_turned_on;NOTICE;Main switch has been turned on;Hlavný vypínač bol zapnutý
power_supply_has_disconnected_input;ALERT;Power supply has disconnected input;Napájací zdroj nemá napätie na vstupe
power_supply_works_correctly;NOTICE;Power supply works correctly ;Napájací zdroj pracuje správne
battery_level_is_low;ERROR;Battery level is low;Batéria má nízku úroveň napätia
battery_level_is_ok;NOTICE;Battery level is OK;Batéria má správnu úroveň napätia
door_has_been_open;NOTICE;Door has been open;Dvere boli otvorené
door_has_been_closed;NOTICE;Door has been closed;Dvere boli zatvorené
door_has_been_open_without_permision_alarm_is_on;WARNING;Door has been open without permision - alarm is on;Dvere boli otvorené bez povolania - zapnutá siréna
state_of_contactor_for_line;INFO;State of contactor for line no. ${line} is ${value};Stav stýkača pre líniu č. ${line} je ${value}
local_database_is_corrupted;CRITICAL;;
electrometer_is_not_responding;ERROR;Electrometer is not responding;Elektromer neodpovedá
no_voltage_detected_on_phase;CRITICAL;No voltage detected on phase no. ${phase};Na fáze č. ${phase} nie je napätie
electrometer_is_responding_again;NOTICE;Electrometer is responding again;Elektromer znovu odpovedá
voltaga_on_phase_has_been_restored;NOTICE;Voltaga on phase no. ${phase} has been restored;Napätie na fáze č. ${phase} bolo obnovené
1 key weight en sk
2 switching_profile_point_applied_to_line INFO Switching profile point applied to line no. ${line} : ${value} Aplikovaný bod spínacieho profilu na línií č. ${line} : ${value}
3 dusk_has_occured INFO Dusk has occured Nastal súmrak
4 dawn_has_occured INFO Dawn has occured Nastal úsvit
5 dimming_profile_was_successfully_received_by_node NOTICE Dimming profile was successfully received by node no. ${node} Stmievací profil bol úspešne prijatý nodom č. ${node}
6 master_node_is_responding_again NOTICE Master node is responding again Master node začal znovu odpovedať
7 command_was_sent_from_terminal_interface DEBUG A command was sent from terminal interface Z terminálu bol odoslaný príkaz
8 master_node_is_not_responding ALERT Master node is not responding Master node neodpovedá
9 configuration_of_dimming_profile_to_node_failed ALERT Configuration of dimming profile to node no. ${node} has failed Konfigurácia stmievacieho profilu pre node č. ${node} zlyhala
10 circuit_breaker_was_turned_on_line NOTICE Circuit breaker was turned on - line no. ${line} Zapnutie ističa na línii č. ${line}
11 circuit_breaker_was_turned_off_line ERROR Circuit breaker was turned off - line no. ${line} Vypnutie ističa na línií č. ${line}
12 dimming_profile_was_processed_for_node INFO Dimming profile was processed for node no. ${node} Stmievací profil bol spracovaný pre node č. ${node}
13 switching_profile_was_processed_for_line INFO Switching profile was processed for line no. ${line} Spínací profil bol spracovaný pre líniu č. ${line}
14 thermometer_is_not_responding WARNING Thermometer is not responding Teplomer neodpovedá
15 thermometer_is_responding_again NOTICE Thermometer is responding again Teplomer znovu odpovedá
16 thermometer_sends_invalid_data WARNING Thermometer sends invalid data Teplomer posiela neplatné hodnoty
17 main_switch_has_been_turned_off CRITICAL Main switch has been turned off Hlavný vypínač bol vypnutý
18 main_switch_has_been_turned_on NOTICE Main switch has been turned on Hlavný vypínač bol zapnutý
19 power_supply_has_disconnected_input ALERT Power supply has disconnected input Napájací zdroj nemá napätie na vstupe
20 power_supply_works_correctly NOTICE Power supply works correctly Napájací zdroj pracuje správne
21 battery_level_is_low ERROR Battery level is low Batéria má nízku úroveň napätia
22 battery_level_is_ok NOTICE Battery level is OK Batéria má správnu úroveň napätia
23 door_has_been_open NOTICE Door has been open Dvere boli otvorené
24 door_has_been_closed NOTICE Door has been closed Dvere boli zatvorené
25 door_has_been_open_without_permision_alarm_is_on WARNING Door has been open without permision - alarm is on Dvere boli otvorené bez povolania - zapnutá siréna
26 state_of_contactor_for_line INFO State of contactor for line no. ${line} is ${value} Stav stýkača pre líniu č. ${line} je ${value}
27 local_database_is_corrupted CRITICAL
28 electrometer_is_not_responding ERROR Electrometer is not responding Elektromer neodpovedá
29 no_voltage_detected_on_phase CRITICAL No voltage detected on phase no. ${phase} Na fáze č. ${phase} nie je napätie
30 electrometer_is_responding_again NOTICE Electrometer is responding again Elektromer znovu odpovedá
31 voltaga_on_phase_has_been_restored NOTICE Voltaga on phase no. ${phase} has been restored Napätie na fáze č. ${phase} bolo obnovené

View file

@ -1,243 +0,0 @@
exports.id = 'showdb';
exports.title = 'Show db data';
exports.group = 'Worksys';
exports.color = '#888600';
exports.version = '1.0.2';
exports.icon = 'sign-out';
exports.input = 8;
exports.output = 1;
const { exec } = require('child_process');
exports.install = function(instance) {
instance.on("0", _ => {
instance.send(0, FLOW.GLOBALS.settings);
})
instance.on("1", _ => {
instance.send(0, FLOW.GLOBALS.relaysData);
})
instance.on("2", _ => {
instance.send(0, FLOW.GLOBALS.nodesData);
})
instance.on("3", _ => {
instance.send(0, FLOW.GLOBALS.pinsData);
})
instance.on("4", _ => {
instance.send(0, { rpcSwitchOffLine, rpcSetNodeDimming, rpcLineProfile, rpcNodeProfile, sunCalcExample, dataFromTerminalBroadcast })
})
instance.on("5", _ => {
exec("sudo tail -n 25 monitor.txt", (err, stdout, stderr) => {
if (err || stderr) instance.send(0, { err, stderr });
else instance.send(0, stdout);
})
})
instance.on("6", _ => {
exec("sudo tail -n 25 err.txt", (err, stdout, stderr) => {
if (err || stderr) instance.send(0, { err, stderr });
else instance.send(0, stdout);
})
})
instance.on("7", _ => {
instance.send(0, FLOW.deviceStatus);
})
};
const rpcSwitchOffLine =
{
"topic": "v1/gateway/rpc",
"content": {
"device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV",
"data": {
"id": 8,
"method": "set_command",
"params": {
"entities": [
{
"entity_type": "edb_line",
"tb_name": "MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O"
}
],
"command": "switch",
"payload": {
"value": false
}
}
}
}
}
const rpcSetNodeDimming =
{
"topic": "v1/gateway/rpc",
"content": {
"device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV",
"data": {
"id": 10,
"method": "set_command",
"params": {
"entities": [
{
"entity_type": "street_luminaire",
"tb_name": "jbN4q7JPZmexgdnz2yKbWdDYAWwO0Q3BMX6ERLoV"
}
],
"command": "dimming",
"payload": {
"value": 5
}
}
}
}
}
const rpcLineProfile =
{
"topic": "v1/gateway/rpc",
"content": {
"device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV",
"data": {
"id": 9,
"method": "set_profile",
"params": {
"entities": [
{
"entity_type": "edb_line",
"tb_name": "MgnK93rkoAazbqdQ4yB2Q0yZ1YXGx6pmwBeVEP2O"
}
],
"payload": {
"intervals": [
{
"value": 0,
"end_time": "20:00",
"start_time": "13:00"
},
{
"value": 1,
"end_time": "05:30",
"start_time": "20:00"
},
{
"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
}
}
}
}
}
const rpcNodeProfile =
{
"topic": "v1/gateway/rpc",
"content": {
"device": "jbN4q7JPZmexgdnz2yKbWGDYAWwO0Q3BMX6ERLoV",
"data": {
"id": 11,
"method": "set_profile",
"params": {
"entities": [
{
"entity_type": "street_luminaire",
"tb_name": "jbN4q7JPZmexgdnz2yKbWdDYAWwO0Q3BMX6ERLoV"
}
],
"payload": {
"intervals": [
{
"cct": 3000,
"value": 0,
"end_time": "17:50",
"start_time": "13:00"
},
{
"cct": 3000,
"value": 100,
"end_time": "21:30",
"start_time": "17:50"
},
{
"cct": 3000,
"value": 0,
"end_time": "13:00",
"start_time": "07:10"
},
{
"cct": 3000,
"value": 50,
"end_time": "00:00",
"start_time": "21:30"
},
{
"cct": 3000,
"value": 10,
"end_time": "04:30",
"start_time": "00:00"
},
{
"cct": 3000,
"value": 100,
"end_time": "07:10",
"start_time": "04: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": 30,
"dusk_astro_clock_offset": 20,
"dawn_lux_sensor_time_window": 30,
"dusk_lux_sensor_time_window": 30,
"dawn_astro_clock_time_window": 60,
"dusk_astro_clock_time_window": 60
}
}
}
}
}
const sunCalcExample = {
dusk_no_offset: '20:18',
dawn_no_offset: '05:19',
dusk: '20:18',
dusk_hours: 20,
dusk_minutes: 18,
dawn: '05:19',
dawn_hours: 5,
dawn_minutes: 19,
dusk_time: 1715278688962,
dawn_time: 1715224744357,
dusk_astro_clock_offset: 0,
dawn_astro_clock_offset: 0
}
const dataFromTerminalBroadcast = {
address: 4294967295,
byte1: 0,
byte2: 0,
byte3: 0,
byte4: 96,
name: "Time Schedule settings",
recipient: 2,
register: 8,
rw: 1
}

124
flow/slack_connector.js Normal file
View file

@ -0,0 +1,124 @@
exports.id = 'slack_connector';
exports.title = 'Slack_Connector';
exports.version = '1.0.0';
exports.group = 'Worksys';
exports.color = '#888600';
exports.input = 1;
exports.output = 1;
exports.click = false;
exports.author = 'Jakub Klena';
exports.icon = 'sign-out';
exports.options = { slack_channel: "C071KN2Q8SK", api_key: "", bot_name: "Flow DEMO", bot_icon: ":totaljs:" };
// Slack channel - where to post the messages, can be name like "backend-alerts"
// Bot Name - Name of the "user" that will post these messages, it should be based on which server it is running on.
// Bot Icon - We can use any slack icon (even custom ones uploaded by us) as the "user" profile picture
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="slack_channel" data-jc-config="placeholder:name or id;required:true" class="m">Slack Channel</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="api_key" data-jc-config="placeholder:api key;required:true" class="m">API Key:</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="bot_name" data-jc-config="placeholder:Flow DEMO;required:false" class="m">Bot Name</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="bot_icon" data-jc-config="placeholder:\:totaljs\:;required:true" class="m">Bot Icon</div>
</div>
</div>
</div>`;
exports.readme = `Sends any string received on input to Slack Channel.`;
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");
exports.install = function(instance) {
var can = false;
process.on('uncaughtException', function (err) {
errLogger.error('uncaughtException:', err.message);
errLogger.error(err.stack);
instance.error(err);
});
instance.on('data', function(data) {
if (!can) return;
let str = String(data.data); // Ensuring data get converted to string
let message = {
'channel': instance.options.slack_channel,
'username': instance.options.bot_name,
'icon_emoji': instance.options.bot_icon,
'text': str
};
let headers = {
'Content-type': `application/json`,
'Authorization': `Bearer ${instance.options.api_key}`
};
if (F.is4) {
let opt = {
'method': 'post',
'url': 'https://slack.com/api/chat.postMessage',
'headers': headers,
'body': JSON.stringify(message),
'type': 'json',
'callback': function(err, response) {
if (response && !err) {
var msg = { data: response.body, status: response.status, headers: response.headers, host: response.host, cookies: response.cookies };
instance.send2(msg);
} else if (err) {
errLogger.error('Slack post failed - err:', err, '\n - response was:', response);
instance.error(err, response);
}
}
};
REQUEST(opt);
} else {
U.request('https://slack.com/api/chat.postMessage', ['json', 'post'], JSON.stringify(message), 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) {
errLogger.error('Slack post failed - err:', err, '\n - response was:', response);
instance.error(err, response);
}
}, null, headers);
}
});
instance.reconfigure = function() {
var options = instance.options;
can = options.slack_channel && options.bot_name && options.bot_icon && options.api_key ? true : false;
instance.status(can ? '' : 'Not configured', can ? undefined : 'red');
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
}

View file

@ -7,10 +7,13 @@ exports.output = 1;
exports.author = 'Jakub Klena'; exports.author = 'Jakub Klena';
exports.icon = 'plug'; exports.icon = 'plug';
exports.version = '1.0.8'; exports.version = '1.0.8';
exports.options = { 'name': '', 'types': '["emergency", "critical", "error", "alert"]', 'message_includes': '["is responding again"]', 'tag_on_include': '[{"user_id":"U072JE5JUQG", "includes":["Electrometer", "Twilight sensor"]}]', 'slack_channel': '' }; exports.options = { 'name':'', 'types': '["emergency", "critical", "error", "alert"]', 'message_includes':'["is responding again"]', 'tag_on_include':'[{"user_id":"U072JE5JUQG", "includes":["Electrometer", "Twilight sensor"]}]', 'slack_channel':'' };
exports.html = `<div class="padding"> exports.html = `<div class="padding">
<div class="row"> <div class="row">
<div class="col-md-12">
<div data-jc="textbox" data-jc-path="name" data-jc-config="required:true">@(Name of this server)</div>
</div>
<div class="col-md-12"> <div class="col-md-12">
<div data-jc="textbox" data-jc-path="slack_channel" data-jc-config="required:false">@(Slack channel to receive the alerts)</div> <div data-jc="textbox" data-jc-path="slack_channel" data-jc-config="required:false">@(Slack channel to receive the alerts)</div>
</div> </div>
@ -57,11 +60,11 @@ exports.install = function(instance) {
let icon = ':totaljs:'; let icon = ':totaljs:';
let type = value[k[0]][0]['values']['_event']['type']; let type = value[k[0]][0]['values']['_event']['type'];
let source = value[k[0]][0]['values']['_event']['source']['func']; let source = value[k[0]][0]['values']['_event']['source']['func'];
let message = value[k[0]][0]['values']['_event']['message']['en']; let message = value[k[0]][0]['values']['_event']['message'];
let message_data = value[k[0]][0]['values']['_event']['message_data']; let message_data = value[k[0]][0]['values']['_event']['message_data'];
let tag = ''; let tag = '';
switch (type) { switch(type){
case 'debug': case 'debug':
icon = ':beetle:'; icon = ':beetle:';
break; break;
@ -89,15 +92,15 @@ exports.install = function(instance) {
} }
// Check if this message includes one of the strings we are watching for // Check if this message includes one of the strings we are watching for
for (const msg of msg_incl) { for (const msg of msg_incl){
if (message.includes(msg)) { if (message.includes(msg)){
if (msg == 'is responding again') icon = ':large_green_circle:'; if (msg == 'is responding again') icon = ':large_green_circle:';
can = true; can = true;
break; break;
} }
} }
// Check if message is one of the types we are watching for // Check if message is one of the types we are watching for
if (interested.includes(type)) { if (interested.includes(type)){
can = true; can = true;
} }
@ -105,10 +108,10 @@ exports.install = function(instance) {
// Check for each person tags based on what the message includes // Check for each person tags based on what the message includes
for (const person of tags) { for (const person of tags){
for (const msg of person.includes) { for (const msg of person.includes){
if (message.includes(msg)) { if (message.includes(msg)){
tag += '<@' + person.user_id + '> '; tag += '<@'+person.user_id+'> ';
break; // Break out from this person checks as they are already tagged now break; // Break out from this person checks as they are already tagged now
} }
} }
@ -116,46 +119,46 @@ exports.install = function(instance) {
// Now that all people are tagged add new line symbol // Now that all people are tagged add new line symbol
if (tag != '') tag += '\n'; if (tag != '') tag += '\n';
let send_data = tag + instance.options.name + ' ' + type.toUpperCase() + '\n*Source*: ' + source + '\n*Message*: ' + message; let send_data = tag+instance.options.name+' '+type.toUpperCase()+'\n*Source*: '+source+'\n*Message*: '+message;
if (message_data) { if (message_data) {
send_data += '\nData: ' + message_data; send_data += '\nData: '+message_data;
} }
let ignore_msg = false let ignore_msg = false
if (message.includes('Configuration of dimming profile to node no')) { if (message.includes('Configuration of dimming profile to node no')){
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++) { for (let i = 0; i < FLOW["savedSlackMessages"].length; i++){
if (FLOW["savedSlackMessages"][i].message == message) { if (FLOW["savedSlackMessages"][i].message == message){
ignore_msg = true; ignore_msg = true;
break; break;
} }
} }
if (!ignore_msg) { if (!ignore_msg){
FLOW["savedSlackMessages"].push({ message, 'dateandtime': Date.now() }); FLOW["savedSlackMessages"].push({message, 'dateandtime': Date.now()});
if (timer === null) { if (timer === null){
timer = setTimeout(checkSavedMessages, 60 * 60000); timer = setTimeout(checkSavedMessages, 60*60000);
} }
} }
} }
if (!ignore_msg) { if (!ignore_msg){
instance.send2({ 'msg': send_data, 'bot_name': instance.options.name + ' ' + type.toUpperCase(), 'bot_icon': icon, 'channel': instance.options.slack_channel }); instance.send2({'msg':send_data,'bot_name':instance.options.name+' '+type.toUpperCase(),'bot_icon':icon,'channel':instance.options.slack_channel});
} }
}); });
function checkSavedMessages() { function checkSavedMessages(){
var d = Date.now(); var d = Date.now();
d = d - 86400000; // older then 24hr d = d - 86400000; // older then 24hr
var a = []; var a = [];
//Remove msgs older then 24hr //Remove msgs older then 24hr
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++) { for (let i = 0; i < FLOW["savedSlackMessages"].length; i++){
if (FLOW["savedSlackMessages"][i].dateandtime > d) { if (FLOW["savedSlackMessages"][i].dateandtime > d){
a.push(FLOW["savedSlackMessages"][i]); a.push(FLOW["savedSlackMessages"][i]);
} }
} }
FLOW["savedSlackMessages"] = a; FLOW["savedSlackMessages"] = a;
if (FLOW["savedSlackMessages"].length > 0) { if (FLOW["savedSlackMessages"].length > 0) {
timer = setTimeout(checkSavedMessages, 60 * 60000); timer = setTimeout(checkSavedMessages, 60*60000);
} else { } else {
timer = null; timer = null;
} }
@ -163,16 +166,15 @@ exports.install = function(instance) {
instance.reconfigure = function() { instance.reconfigure = function() {
try { try {
if (!FLOW["savedSlackMessages"]) { if (!FLOW["savedSlackMessages"]){
FLOW["savedSlackMessages"] = []; FLOW["savedSlackMessages"] = [];
} }
instance.options.name = FLOW.GLOBALS.settings.rvo_name;
if (instance.options.name) { if (instance.options.name) {
instance.status('Running'); instance.status('Running');
running = true; running = true;
} else { } else {
instance.status('Please run options again', 'red'); instance.status('Please enter name', 'red');
running = false; running = false;
} }
} catch (e) { } catch (e) {
@ -181,6 +183,5 @@ exports.install = function(instance) {
}; };
instance.on('options', instance.reconfigure); instance.on('options', instance.reconfigure);
setTimeout(instance.reconfigure, 10000); instance.reconfigure();
}; };

View file

@ -2,16 +2,14 @@ exports.id = 'thermometer';
exports.title = 'Thermometer'; exports.title = 'Thermometer';
exports.group = 'Worksys'; exports.group = 'Worksys';
exports.color = '#5CB36D'; exports.color = '#5CB36D';
exports.input = 1;
exports.version = '1.0.3'; exports.version = '1.0.3';
exports.output = ["red", "white", "blue"]; exports.output = ["red", "white", "blue"];
exports.author = 'Rastislav Kovac';
exports.icon = 'thermometer-three-quarters'; exports.icon = 'thermometer-three-quarters';
exports.readme = `# Getting temperature values for RVO. In case of LM, you need device address. In case of unipi, evok sends values, in case thermometer is installed`; exports.readme = `# Getting temperature values for RVO. In case of LM, you need device address. In case of unipi, evok sends values, in case thermometer is installed`;
const { errLogger, logger, monitor } = require('./helper/logger'); const instanceSendTo = {
const SEND_TO = {
debug: 0, debug: 0,
tb: 1, tb: 1,
dido_controller: 2 dido_controller: 2
@ -19,81 +17,204 @@ const SEND_TO = {
//read temperature - frequency //read temperature - frequency
let timeoutMin = 5;//minutes let timeoutMin = 5;//minutes
let NUMBER_OF_FAILURES_TO_SEND_ERROR = 13;
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');
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) { exports.install = function(instance) {
const { exec } = require('child_process'); const { exec } = require('child_process');
const { sendNotification } = require('./helper/notification_reporter'); const { sendNotification, ERRWEIGHT } = require('./helper/notification_reporter');
let startRead; let startRead;
let dataToTb;
let counter = 0; let counter = 0;
let rvoTbName = "";
let temperatureAddress = ""; let edgeName = "";
logger.debug(exports.title, "installed"); logger.debug(exports.title, "installed");
instance.on("close", function() { instance.on("close", function(){
clearInterval(startRead); clearInterval(startRead);
}) })
const main = function() { const start = function() {
try { try {
if (temperatureAddress === "") throw "Thermometer: temperatureAddress is not defined"; 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) => { exec(`owread -C ${temperatureAddress}/temperature`, (error, stdout, stderr) => {
if (!error) { edgeName = FLOW.OMS_edgeName;
parseData(stdout)
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); // poslat stav nok do tb, ak to handluje dido_controller ??
instance.send(instanceSendTo.dido_controller, {status: "NOK-thermometer"});
}
else parseData(stdout);
}
else
{
monitor.info("gettemperature: edgeName is not defined", FLOW.OMS_edgeName);
setTimeout(function(){
start();
}, 3000);
return; return;
} }
counter++;
if (counter == NUMBER_OF_FAILURES_TO_SEND_ERROR) { //instance.send({"Temp":stdout,"stderr":stderr,"err":error});
sendNotification("Thermometer_main", rvoTbName, "thermometer_is_not_responding", {}, { "Error": error }, SEND_TO.tb, instance, "thermometer");
monitor.info("Thermometer is not responding", error);
}
instance.send(SEND_TO.dido_controller, { status: "NOK-thermometer" });
}); });
} }
catch (err) { catch(err) {
errLogger.error(exports.title, err); errLogger.error(exports.title, err);
clearInterval(startRead);
} }
} }
const parseData = function(data) { const parseData = function(data) {
data = parseFloat(data); data = parseFloat(data);
//logger.debug("Thermometer", data);
if (isNaN(data)) { logger.debug("gettemperature", data);
errLogger.error("Thermometer sends invalid data");
return; 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);
const values = {
"temperature": Number(data.toFixed(2)),
"status": "OK"
}
dataToTb = {
[edgeName]: [
{
"ts": Date.now(),
"values":values
}
]
}
instance.send(instanceSendTo.tb, dataToTb);
instance.send(instanceSendTo.dido_controller, values);
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.dido_controller, {status: "NOK-thermometer"});
}
} }
if (counter > NUMBER_OF_FAILURES_TO_SEND_ERROR) //1 hour
{
instance.send(SEND_TO.debug, "Thermometer - temperature data are comming again");
sendNotification("Thermometer_parseData", rvoTbName, "thermometer_is_responding_again", {}, "", SEND_TO.tb, instance, "thermometer");
}
const values = {
"temperature": Number(data.toFixed(2)),
}
instance.send(SEND_TO.dido_controller, { values: values });
counter = 0;
} }
instance.on("data", _ => { setTimeout(function(){
temperatureAddress = FLOW.GLOBALS.settings.temperature_address; start();
rvoTbName = FLOW.GLOBALS.settings.rvoTbName; }, 15000);
startRead = setInterval(main, timeoutMin * 1000 * 60);
setTimeout(main, 20000); startRead = setInterval(start, timeoutMin * 1000 * 60);
})
}; };

View file

@ -4,27 +4,30 @@ exports.group = 'MQTT';
exports.color = '#888600'; exports.color = '#888600';
exports.version = '1.0.2'; exports.version = '1.0.2';
exports.icon = 'sign-out'; exports.icon = 'sign-out';
exports.input = 2; exports.input = 1;
exports.output = 3; exports.output = ["red", "white", "blue"];
exports.author = 'Daniel Segeš';
exports.options = { host: 'tb-stage.worksys.io', port: 1883, clientid: "", username: "" }; exports.options = { host: 'tb-stage.worksys.io', port: 1883, clientid: "", username: "" };
exports.npm = ['mqtt'];
exports.html = `<div class="padding"> exports.html = `<div class="padding">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div data-jc="textbox" data-jc-path="host" data-jc-config="placeholder:test.mosquitto.org;required:false" class="m">Hostname or IP address (if not empty - setting will override db setting)</div> <div data-jc="textbox" data-jc-path="host" data-jc-config="placeholder:test.mosquitto.org;required:false" class="m">Hostname or IP address (if not empty - setting will override db setting)</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:1883;required:true" class="m">Port</div> <div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:1883;required:true" class="m">Port</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div data-jc="textbox" data-jc-path="clientid">@(Client id)</div> <div data-jc="textbox" data-jc-path="clientid">@(Client id)</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div data-jc="textbox" data-jc-path="username" class="m">@(Username)</div> <div data-jc="textbox" data-jc-path="username" class="m">@(Username)</div>
</div> </div>
</div> </div>
</div>`; </div>`;
@ -38,47 +41,79 @@ Added:
- rpc response - rpc response
`; `;
const { promisifyBuilder } = require('./helper/db_helper'); const instanceSendTo = {
const { errLogger, monitor } = require('./helper/logger'); debug: 0,
const fs = require('fs'); rpcCall: 1,
const mqtt = require('mqtt'); services: 2
const SEND_TO = {
debug: 0,
rpcCall: 1,
services: 2
} }
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
//CONFIG //CONFIG
let useLog4js = true;
let createTelemetryBackup = true; let createTelemetryBackup = true;
let saveTelemetryOnError = true;//backup_on_failure overrides this value let saveTelemetryOnError = true;//backup_on_failure overrides this value
//------------------------ //------------------------
var fs = require('fs');
let rollers; let rollers;
if (createTelemetryBackup) rollers = require('streamroller'); if(createTelemetryBackup) rollers = require('streamroller');
const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304 const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304
let insertNoSqlCounter = 0; let insertNoSqlCounter = 0;
let insertBackupNoSqlCounter = 0; let insertBackupNoSqlCounter = 0;
let processingData = false; let processingData = false;
let backup_on_failure = false;//== saveTelemetryOnError - create backup client send failure let backup_on_failure = false;//== saveTelemetryOnError - create backup broker send failure
let restore_from_backup = 0; //how many rows process at once? let restore_from_backup = 0; //how many rows process at once?
let restore_backup_wait = 0;//wait seconds let restore_backup_wait = 0;//wait seconds
let lastRestoreTime = 0; let lastRestoreTime = 0;
// if there is an error in client connection, flow logs to monitor.txt. Not to log messages every second, we use sendClientError variable let errLogger;
let sendClientError = true; let logger;
let monitor;
process.on('uncaughtException', function(err) { if(useLog4js)
{
var path = require('path');
var log4js = require("log4js");
errLogger.error('uncaughtException:', err.message) log4js.configure({
errLogger.error(err.stack); 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' }
}
});
//TODO errLogger = log4js.getLogger("errLogs");
//send to service logger = log4js.getLogger();
monitor = log4js.getLogger("monitorLogs");
//process.exit(1); //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 nosql = NOSQL('tbdata');
@ -87,364 +122,410 @@ const nosqlBackup = NOSQL('/backup/tbdata');
exports.install = function(instance) { exports.install = function(instance) {
var client; var broker;
var opts; var opts;
var clientReady = false; var brokerready = false;
// wsmqtt status for notification purposes on projects.worksys.io database instance.on('options', loadSettings);
let wsmqttName = null;
let sendWsStatusVar = null;
let wsmqtt_status = 'disconnected';
function getWsmqttName(host) { mqtt = require('mqtt');
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() { // wsmqtt status for notification purposes on projects.worksys.io database
instance.send(SEND_TO.services, { [wsmqttName]: wsmqtt_status }); 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);
function main() { //set opts according to db settings
if (!FLOW.dbLoaded) return; async function loadSettings()
{
loadSettings(); if(instance.options.host !== "")
clearInterval(sendWsStatus); {
sendWsStatusVar = setInterval(sendWsStatus, 180000); //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
};
//set opts according to db settings wsmqttName = getWsmqttName(o.host);
function loadSettings() {
if (instance.options.host !== "") { console.log("wsmqttpublich -> loadSettings from instance.options", instance.options);
//override settings from database }
var o = instance.options; else
opts = { {
host: o.host,
port: o.port,
clientId: o.clientid,
username: o.username,
rejectUnauthorized: false,
resubscribe: false
};
wsmqttName = getWsmqttName(o.host); const dbSettings = TABLE("settings");
let responseSettings = await promisifyBuilder(dbSettings.find());
console.log("wsmqttpublich -> loadSettings from instance.options", instance.options); backup_on_failure = responseSettings[0]["backup_on_failure"];
} saveTelemetryOnError = backup_on_failure;
else {
const SETTINGS = FLOW.GLOBALS.settings; restore_from_backup = responseSettings[0]["restore_from_backup"];
backup_on_failure = SETTINGS.backup_on_failure; restore_backup_wait = responseSettings[0]["restore_backup_wait"];
saveTelemetryOnError = backup_on_failure;
restore_from_backup = SETTINGS.restore_from_backup; let mqtt_host = responseSettings[0]["mqtt_host"];
restore_backup_wait = SETTINGS.restore_backup_wait; let mqtt_clientid = responseSettings[0]["mqtt_clientid"];
let mqtt_username = responseSettings[0]["mqtt_username"];
let mqtt_port = responseSettings[0]["mqtt_port"];
let mqtt_host = SETTINGS.mqtt_host; console.log("wsmqttpublich -> loadSettings from db", responseSettings[0]);
let mqtt_clientid = SETTINGS.mqtt_clientid;
let mqtt_username = SETTINGS.mqtt_username;
let mqtt_port = SETTINGS.mqtt_port;
opts = { opts = {
host: mqtt_host, host: mqtt_host,
port: mqtt_port, port: mqtt_port,
keepalive: 10, keepalive: 10,
clientId: mqtt_clientid, clientId: mqtt_clientid,
username: mqtt_username, username: mqtt_username,
rejectUnauthorized: false, rejectUnauthorized: false,
resubscribe: false, resubscribe: false
}; };
wsmqttName = getWsmqttName(mqtt_host); wsmqttName = getWsmqttName(mqtt_host);
} }
connectToTbServer(); connectToTbServer();
} }
function connectToTbServer() { function connectToTbServer()
var url = "mqtt://" + opts.host + ":" + opts.port; {
console.log("MQTT URL: ", url); var url = "mqtt://" + opts.host + ":" + opts.port;
console.log("MQTT URL: ", url);
client = mqtt.connect(url, opts); broker = mqtt.connect(url, opts);
client.on('connect', function() { broker.on('connect', function() {
instance.status("Connected", "green"); instance.status("Connected", "green");
//monitor.info("MQTT client connected"); monitor.info("MQTT broker connected");
sendClientError = true; brokerready = true;
clientReady = true; FLOW.OMS_brokerready = brokerready;
wsmqtt_status = 'connected'; wsmqtt_status = 'connected';
}); });
client.on('reconnect', function() { broker.on('reconnect', function() {
instance.status("Reconnecting", "yellow"); instance.status("Reconnecting", "yellow");
clientReady = false; brokerready = false;
});
client.on('message', function(topic, message) { FLOW.OMS_brokerready = brokerready;
// message is type of buffer });
message = message.toString();
if (message[0] === '{') {
try { broker.on('message', function(topic, message) {
message = JSON.parse(message); // message is type of buffer
if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) { message = message.toString();
client.publish(topic, `{"device": ${message.device}, "id": ${message.data.id}, "data": {"success": true}}`, { qos: 1 }); if (message[0] === '{') {
instance.send(SEND_TO.rpcCall, { "device": message.device, "id": message.data.id, "RPC response": { "success": true } }); TRY(function() {
}
} catch (e) { message = JSON.parse(message);
console.log('MQTT: Error parsing data', e); 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.send(SEND_TO.rpcCall, { "topic": topic, "content": message }); }, () => instance.debug('MQTT: Error parsing data', message));
}); }
client.on('close', function() { instance.send(instanceSendTo.rpcCall, {"topic":topic, "content":message });
clientReady = false; });
wsmqtt_status = 'disconnected';
instance.status("Disconnected", "red"); broker.on('close', function(err) {
instance.send(SEND_TO.debug, { "message": "Client CLOSE signal received !" }); brokerready = false;
}); FLOW.OMS_brokerready = brokerready;
wsmqtt_status = 'disconnected';
client.on('error', function(err) { if (err && err.toString().indexOf('Error')) {
instance.status("Err: " + err.code, "red"); instance.status("Err: "+err.code, "red");
instance.send(SEND_TO.debug, { "message": "Client ERROR signal received !", "error": err, "opt": opts }); instance.send(instanceSendTo.debug, {"message":"Broker CLOSE signal received !", "error":err, "opt":opts });
if (sendClientError) { } else {
monitor.info('MQTT client error', err); instance.status("Disconnected", "red");
sendClientError = false; instance.send(instanceSendTo.debug, {"message":"Broker CLOSE signal received !", "error":err, "opt":opts });
} }
clientReady = false;
wsmqtt_status = 'disconnected';
});
} broker.reconnect();
});
broker.on('error', function(err) {
instance.status("Err: "+ err.code, "red");
instance.send(instanceSendTo.debug, {"message":"Broker ERROR signal received !", "error":err, "opt":opts });
monitor.info('MQTT broker error', err);
instance.on("0", _ => { brokerready = false;
main(); FLOW.OMS_brokerready = brokerready;
}) wsmqtt_status = 'disconnected';
});
instance.on('1', function(data) { }
if (clientReady) { instance.on('data', function(data) {
//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();
}
let stringifiedJson = JSON.stringify(data.data); if (brokerready)
client.publish("v1/gateway/telemetry", stringifiedJson, { qos: 1 }); {
//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();
}
//backup telemetry }
if (createTelemetryBackup) {
data.data.id = UID();
nosqlBackup.insert(data.data);
insertBackupNoSqlCounter++; if (brokerready)
if (insertBackupNoSqlCounter > 150) { {
let options = { compress: true }; let stringifiedJson = JSON.stringify(data.data);
let path = __dirname + "/../databases/backup/tbdata.nosql"; broker.publish("v1/gateway/telemetry", stringifiedJson, {qos: 1});
var stream = new rollers.RollingFileStream(path, noSqlFileSizeLimit, 150, options);
stream.write("");
stream.end();
insertBackupNoSqlCounter = 0; //backup telemetry
} if(createTelemetryBackup)
} {
data.data.id = UID();
nosqlBackup.insert(data.data);
} insertBackupNoSqlCounter++;
else { if(insertBackupNoSqlCounter > 150)
//logger.debug("Client unavailable. Data not sent !", JSON.stringify(data.data)); {
instance.send(SEND_TO.debug, { "message": "Client unavailable. Data not sent !", "data": data.data }); let options = {compress: true};
let path = __dirname + "/../databases/backup/tbdata.nosql";
var stream = new rollers.RollingFileStream(path, noSqlFileSizeLimit, 150, options);
stream.write("");
stream.end();
if (saveTelemetryOnError) { insertBackupNoSqlCounter = 0;
//create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql }
makeBackupFromDbFile(); }
//write to tb }
data.data.id = UID(); else
nosql.insert(data.data); {
}
} 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) { instance.close = function(done) {
if (clientReady) { if (brokerready){
client.end(); broker.end();
clearInterval(sendWsStatusVar); clearInterval(sendWsStatusVar);
} }
}; };
function getDbBackupFileCounter(type) { function getDbBackupFileCounter(type)
var files = fs.readdirSync(__dirname + "/../databases"); {
var files = fs.readdirSync(__dirname + "/../databases");
let counter = 0; let counter = 0;
for (var i = 0; i < files.length; i++) { for(var i = 0; i < files.length; i++)
{
if (files[i] == "tbdata.nosql") continue; if(files[i] == "tbdata.nosql") continue;
if (files[i].endsWith(".nosql")) { if(files[i].endsWith(".nosql"))
{
let pos = files[i].indexOf("."); let pos = files[i].indexOf(".");
if (pos > -1) { if(pos > -1)
{
let fileCounter = counter; let fileCounter = counter;
let firstDigit = files[i].slice(0, pos); let firstDigit = files[i].slice(0, pos);
fileCounter = parseInt(firstDigit); fileCounter = parseInt(firstDigit);
if (isNaN(fileCounter)) fileCounter = 0; if (isNaN(fileCounter)) fileCounter = 0;
//console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type); //console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type);
if (type == "max") { if(type == "max")
if (fileCounter > counter) { {
counter = fileCounter; if(fileCounter > counter)
} {
} counter = fileCounter;
else if (type == "min") { }
if (counter == 0) counter = fileCounter; }
else if(type == "min")
{
if(counter == 0) counter = fileCounter;
if (fileCounter < counter) { if(fileCounter < counter)
counter = fileCounter; {
} counter = fileCounter;
} }
} }
} }
}
} }
if (type == "max") counter++; if(type == "max") counter++;
return counter; return counter;
} }
const makeBackupFromDbFile = async () => { const makeBackupFromDbFile = async () => {
if (!saveTelemetryOnError) return; if(!saveTelemetryOnError) return;
//to avoid large file: tbdata.nosql //to avoid large file: tbdata.nosql
//init value is 0! //init value is 0!
if (insertNoSqlCounter > 0) { if(insertNoSqlCounter > 0)
--insertNoSqlCounter; {
return; --insertNoSqlCounter;
} return;
}
insertNoSqlCounter = 100; insertNoSqlCounter = 100;
let source = __dirname + "/../databases/tbdata.nosql"; let source = __dirname + "/../databases/tbdata.nosql";
var stats = fs.statSync(source); var stats = fs.statSync(source);
var fileSizeInBytes = stats.size; var fileSizeInBytes = stats.size;
if (fileSizeInBytes > noSqlFileSizeLimit) { if(fileSizeInBytes > noSqlFileSizeLimit)
{
let counter = 1; let counter = 1;
counter = getDbBackupFileCounter("max"); counter = getDbBackupFileCounter("max");
let destination = __dirname + "/../databases/" + counter + "." + "tbdata.nosql"; let destination = __dirname + "/../databases/" + counter + "." + "tbdata.nosql";
//make backup file //make backup file
fs.copyFileSync(source, destination); fs.copyFileSync(source, destination);
//fs.renameSync(p, p + "." + counter); //fs.renameSync(p, p + "." + counter);
//clear tbdata.nosql //clear tbdata.nosql
fs.writeFileSync(source, ""); fs.writeFileSync(source, "");
fs.truncateSync(source, 0); fs.truncateSync(source, 0);
} }
} }
const processDataFromDatabase = async () => { const processDataFromDatabase = async () => {
if (restore_from_backup <= 0) return; if(restore_from_backup <= 0)
{
return;
}
//calculate diff //calculate diff
const now = new Date(); const now = new Date();
let currentTime = now.getTime(); let currentTime = now.getTime();
let diff = currentTime - lastRestoreTime; let diff = currentTime - lastRestoreTime;
if ((diff / 1000) < restore_backup_wait) { if( (diff / 1000) < restore_backup_wait)
//console.log("*********restore_backup_wait", diff, restore_backup_wait); {
return; //console.log("*********restore_backup_wait", diff, restore_backup_wait);
} return;
}
processingData = true; processingData = true;
//get filename to process //get filename to process
let counter = getDbBackupFileCounter("min"); let counter = getDbBackupFileCounter("min");
//we have some backup files //we have some backup files
let dataBase = 'tbdata'; let dataBase = 'tbdata';
var nosql; var nosql;
if (counter == 0) dataBase = 'tbdata'; if(counter == 0) dataBase = 'tbdata';
else dataBase = counter + "." + 'tbdata'; else dataBase = counter + "." + 'tbdata';
nosql = NOSQL(dataBase); nosql = NOSQL(dataBase);
//select all data - use limit restore_from_backup //select all data - use limit restore_from_backup
let records = await promisifyBuilder(nosql.find().take(restore_from_backup)); let records = await promisifyBuilder(nosql.find().take(restore_from_backup));
for (let i = 0; i < records.length; i++) { for(let i = 0; i < records.length; i++)
if (clientReady) { {
if (brokerready) {
let item = records[i]; let item = records[i];
let id = item.id; let id = item.id;
if (id !== undefined) { if(id !== undefined)
//console.log("------------processDataFromDatabase - remove", id, dataBase, i); {
//console.log("------------processDataFromDatabase - remove", id, dataBase, i);
try { try{
let message = JSON.parse(JSON.stringify(item)); let o = JSON.parse(JSON.stringify(item));
delete message.id; delete o.id;
let message = JSON.stringify(o);
client.publish("v1/gateway/telemetry", JSON.stringify(message), { qos: 1 }); broker.publish("v1/gateway/telemetry", message, {qos:1});
//remove from database //remove from database
await promisifyBuilder(nosql.remove().where("id", id)); await promisifyBuilder(nosql.remove().where("id", id));
} catch (error) { } catch (error) {
//process error //process error
console.log("processDataFromDatabase", error); console.log("processDataFromDatabase", error);
} }
}
} }
else {
processingData = false;
return;
}
}
if (records.length > 0) { }
//clean backup file else
if (counter > 0) nosql.clean(); {
} processingData = false;
return;
}
}
//no data in db, remove if(records.length > 0)
if (records.length == 0) { {
if (counter > 0) nosql.drop(); //clean backup file
} if(counter > 0) nosql.clean();
}
const d = new Date(); //no data in db, remove
lastRestoreTime = d.getTime(); if(records.length == 0)
{
if(counter > 0) nosql.drop();
}
processingData = false; const d = new Date();
lastRestoreTime = d.getTime();
} processingData = false;
instance.on('options', main); }
loadSettings();
//instance.on('options', instance.reconfigure);
//instance.reconfigure(); //instance.reconfigure();
}; };

View file

@ -8,7 +8,6 @@
"easy-crc": "0.0.2", "easy-crc": "0.0.2",
"jsmodbus": "^4.0.6", "jsmodbus": "^4.0.6",
"log4js": "^6.3.0", "log4js": "^6.3.0",
"moment": "^2.30.1",
"mqtt": "^4.2.6", "mqtt": "^4.2.6",
"nodemailer": "^6.9.7", "nodemailer": "^6.9.7",
"serialport": "^9.2.8", "serialport": "^9.2.8",