Backup senica-RVO35 on 16.10.2025
This commit is contained in:
parent
1467900c80
commit
15c2f1eb9c
100 changed files with 32138 additions and 0 deletions
36
RVO35/addSwitch.py
Executable file
36
RVO35/addSwitch.py
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
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') + "|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()
|
||||
76
RVO35/cloud_topic.py
Executable file
76
RVO35/cloud_topic.py
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
# 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)
|
||||
12
RVO35/config
Executable file
12
RVO35/config
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
name : Total.js Flow
|
||||
default_timezone : Europe/Bratislava
|
||||
|
||||
// Packages settings
|
||||
package#flow (Object) : { url: '/' }
|
||||
|
||||
|
||||
table.relays : line:number|tbname:string|contactor:number|profile:string
|
||||
table.nodes : node:number|pole_number:string|node_type:string|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number
|
||||
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|daily_report:boolean|send_changed_node_numbers:boolean
|
||||
table.pins : pin:string|type:string|line:number
|
||||
table.notifications : key:string|weight:string|sk:string|en:string
|
||||
43
RVO35/createNode.py
Executable file
43
RVO35/createNode.py
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
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()
|
||||
#
|
||||
3055
RVO35/databases/accelerometer_db.js
Executable file
3055
RVO35/databases/accelerometer_db.js
Executable file
File diff suppressed because it is too large
Load diff
114
RVO35/databases/modbus_config.js
Executable file
114
RVO35/databases/modbus_config.js
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
const timeoutInterval = 150000;
|
||||
|
||||
const deviceConfig = [
|
||||
{
|
||||
device: "em340",
|
||||
deviceAddress: 1,
|
||||
stream: [
|
||||
{
|
||||
"tbAttribute": "Phase_1_voltage",
|
||||
"register": 0,
|
||||
"size": 2,
|
||||
"multiplier": 0.1
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_2_voltage",
|
||||
"register": 2,
|
||||
"size": 2,
|
||||
"multiplier": 0.1
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_3_voltage",
|
||||
"register": 4,
|
||||
"size": 2,
|
||||
"multiplier": 0.1
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_1_current",
|
||||
"register": 12,
|
||||
"size": 2,
|
||||
"multiplier": 0.001
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_2_current",
|
||||
"register": 14,
|
||||
"size": 2,
|
||||
"multiplier": 0.001
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_3_current",
|
||||
"register": 16,
|
||||
"size": 2,
|
||||
"multiplier": 0.001
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_1_power",
|
||||
"register": 18,
|
||||
"size": 2,
|
||||
"multiplier": 0.1
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_2_power",
|
||||
"register": 20,
|
||||
"size": 2,
|
||||
"multiplier": 0.1
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_3_power",
|
||||
"register": 22,
|
||||
"size": 2,
|
||||
"multiplier": 0.1
|
||||
},
|
||||
{
|
||||
"tbAttribute": "total_power",
|
||||
"register": 40,
|
||||
"size": 2,
|
||||
"multiplier": 0.1
|
||||
},
|
||||
{
|
||||
"tbAttribute": "total_energy",
|
||||
"register": 52,
|
||||
"size": 2,
|
||||
"multiplier": 0.1
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_1_pow_factor",
|
||||
"register": 46,
|
||||
"size": 1,
|
||||
"multiplier": 0.001
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_2_pow_factor",
|
||||
"register": 47,
|
||||
"size": 1,
|
||||
"multiplier": 0.001
|
||||
},
|
||||
{
|
||||
"tbAttribute": "Phase_3_pow_factor",
|
||||
"register": 48,
|
||||
"size": 1,
|
||||
"multiplier": 0.001
|
||||
},
|
||||
{
|
||||
"tbAttribute": "power_factor",
|
||||
"register": 49,
|
||||
"size": 1,
|
||||
"multiplier": 0.001
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
device: "twilight_sensor",
|
||||
deviceAddress: 2,
|
||||
stream: [
|
||||
{
|
||||
"tbAttribute": "twilight_sensor",
|
||||
"register": 60,
|
||||
"size": 2,
|
||||
"multiplier": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = { timeoutInterval, deviceConfig };
|
||||
80
RVO35/databases/nodes.table
Executable file
80
RVO35/databases/nodes.table
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
node:number|pole_number:string|node_type:string|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number
|
||||
+|3646|32|NENA|zrR51V2ajQ9ZLygPKkEMRakYDq38xOJolENBXGnv|1||1|0|1760287302207|.............
|
||||
+|3658|22|NEMA|PLBJzmK1r3Gynd6OW0gKBx0e5wV4vx9bDEqNgYR8|1||1|0|1760287333876|.............
|
||||
+|3671|63|NEMA|EjgWGnXaLy9opPOz20n4Pa786BlYM3w1deVQvbKr|1||1|0|1760287346569|.............
|
||||
+|3672|65|NEMA|Nzp2OoJlqn6r1ZgvdA3BbL7abBwP5G4eE3RQmyxD|1||1|0|1760287347785|.............
|
||||
+|3673|67|NEMA|52dD6ZlV1QaOpRBmbAqGpqkKnGzWMLj4eJq38Pgo|1||1|0|1760287300640|.............
|
||||
+|3674|68|NEMA|rDbQ84xzwgdqEoPm3kbE2y09anOZY1RXyBv2LVM6|1||1|0|1760287302447|.............
|
||||
+|3677|72|NEMA|ZmRwd93QL4gaezxEbAx2P9k1prn2XjlPvGyqJ6BO|1||1|0|1760287309289|.............
|
||||
+|3679|69|NEMA|E6Kg9oDnLWyzPRMva7v5NbkJxp4VG58qO2w1lZYe|1||1|0|1760287374706|.............
|
||||
+|3682|64|NEMA|wvKJdZML6mXP4DzWBAXxnPAjxNloa5g23Ve9Y1ry|1||1|0|1760287318114|.............
|
||||
+|3683|66|NEMA|PLBJzmK1r3Gynd6OW0gKmx0e5wV4vx9bDEqNgYR8|1||1|0|1760287385641|.............
|
||||
+|3687|71|NEMA|nJL5lPMwBx23YpqRe0rpz27damXvWVbOrD4gNzy8|1||1|0|1760287322398|.............
|
||||
+|3980|24|NEMA|rDbQ84xzwgdqEoPm3kbEzy09anOZY1RXyBv2LVM6|1||1|0|1760287435359|.............
|
||||
+|4012|25|NEMA|E6Kg9oDnLWyzPRMva7v5bbkJxp4VG58qO2w1lZYe|1||1|0|1760287435742|.............
|
||||
+|3429|30|NEMA|zdQO8GwxDqjRgP4137YVX5ANyKlpem2nL65rvVJY|1||1|0|1760287264511|.............
|
||||
+|3569|29|NEMA|gYbDLqlyZVoRerQpB72Mq8kWJnwM5z24POKa8Exj|1||1|0|1760287269131|.............
|
||||
+|3962|27|NEMA|nJL5lPMwBx23YpqRe0rpX27damXvWVbOrD4gNzy8|1||1|0|1760287408757|.............
|
||||
+|3804|50|NEMA|Z5KyJe9nEg1QNbWlX0w4wzkoDjBLdqzR83VGv624|1||1|1|1760574057919|.............
|
||||
+|3810|51|NEMA|1JMYvnx2RzKEo4aWQ7DGPvkL8yZV3m9NBePXbrdj|1||1|1|1760574058130|.............
|
||||
+|3844|49|NEMA|3JjOWdylwgNLzxVab7NaD8kZ2vG64rq8PEB5QmDo|1||1|1|1760574058367|.............
|
||||
+|3878|37|NEMA|o9vbeQlLMVg8j5dq4keL940NxZpEmnXzwYKO1ar2|1||1|1|1760574058558|.............
|
||||
+|3944|10|NEMA|B5EoxeMVp4zwr8nqW0GB8YARjvD1PNamOGbLg63Z|1||1|1|1760574061785|.............
|
||||
+|3945|28|NEMA|XMBbew5z4ELrZa2mRAd3Mw78vPN6gy3DdVYlpKjq|1||1|0|1760287236503|.............
|
||||
+|3947|3|NEMA|Z5KyJe9nEg1QNbWlX0w4vzkoDjBLdqzR83VGv624|1||1|1|1760574074726|.............
|
||||
+|3948|6|kamera|dz4ojlpP85JMgDLZWkQJdn7aKYqQexEr62GXRV1y|1||1|1|1760574081004|.............
|
||||
+|3955|35|NEMA|apKVJBwOyrP35m2lv7KYo10YXbeWNd64En9GxRqg|1||1|1|1760574081164|.............
|
||||
+|3956|8|NEMA|K94XLav1glVRnyQ6r01PewAme3YJwBxM5oOzdP2j|1||1|1|1760574084407|.............
|
||||
+|3958|12|NEMA|ZmRwd93QL4gaezxEbAx2jVk1prn2XjlPvGyqJ6BO|1||1|1|1760574090670|.............
|
||||
+|2617|11|NEMA|aw4eELG2DlPMdn1JW0BMyaAqQXOZRN3xB5yp8VKr|1||1|1|1760574287382|.............
|
||||
+|3961|7|NEMA|gRoJEyXVx4qD9er287L4qQ7wBzGldaPjLWQKm3Mv|1||1|1|1760574091006|.............
|
||||
+|3965|38|NEMA|gP1eOZVj3Q9lv5aDEk45vV7rdpqW8yLm2BbKzJxM|1||1|1|1760574125274|.............
|
||||
+|3966|53|NEMA|dz4ojlpP85JMgDLZWkQJyn7aKYqQexEr62GXRV1y|1||1|1|1760574125450|.............
|
||||
+|3968|1|NEMA|RO8rjaBDy21qPQJzW7omVL0pK3xmNleVZg9Ed4Gw|1||1|1|1760574125625|.............
|
||||
+|3969|2|NEMA|3JjOWdylwgNLzxVab7Nav8kZ2vG64rq8PEB5QmDo|1||1|1|1760574128852|.............
|
||||
+|3970|39|NEMA|2O14VBzl8aDmWdNw3A59jgAGyZ5qLJoEMpj6R9ng|1||1|1|1760574130115|.............
|
||||
+|3973|46|NEMA|JX1ObgmqGZ54DMyYL7aDdGkEVdve38WKRzwjNrQ9|1||1|1|1760574152784|.............
|
||||
+|3974|47|NEMA|RvmwNz8QPblKp41GD7lK5QkJrLVYoBO92dMegn6W|1||1|1|1760574155980|.............
|
||||
+|3076|23|NEMA|52dD6ZlV1QaOpRBmbAqGnqkKnGzWMLj4eJq38Pgo|1||1|0|1760287206416|.............
|
||||
+|3978|42|NEMA|m6EYyZoJ4gWexdjVPAR5Lv7RDOq9wv2N5XzKGplr|1||1|1|1760574156139|.............
|
||||
+|3979|4|NEMA|1JMYvnx2RzKEo4aWQ7DG1vkL8yZV3m9NBePXbrdj|1||1|1|1760574159335|.............
|
||||
+|3984|44|NEMA|g9OxBZ5KRwNznlY6pAppW1AWXvjdEL4eGQobMDy2|1||1|1|1760574331748|.............
|
||||
+|4213|43|NEMA|JzwxZXOvDj1bVrN4nkW5BbA8qdyBl3MRKLpGPgaQ|1||1|1|1760574277253|.............
|
||||
+|4218|45|NEMA|OzNMgZ9n43qPbjXmy7zwQbA2DKdYvW5e6pxGRrVa|1||1|1|1760574287222|.............
|
||||
+|3611|40|NEMA|pE5X8NQPaow6vlOZxk6arX0q42ezGBMyWgDVjR3L|1||1|1|1760573606536|.............
|
||||
+|3612|34|NEMA|DbQY6zyveZRwK5drV0Z83J7joE4XJM83N9xl2nWq|1||1|1|1760573697007|.............
|
||||
+|3622|33|NEMA|BaY3Xpy1EbKGjLq2O7ma6w7rx8owgQz9P4dDJRmN|1||1|1|1760573709388|.............
|
||||
+|3643|52|NEMA|PjLblDgRBO6WQqnxmkJ1lg0Jv3ewZN4p5a89yKdY|1||1|1|1760573709692|.............
|
||||
+|3644|62|NEMA|3a5oqJN1bgnx4Ol9dk892Q7ByE6jQ8mKDWMpGrLV|1||1|1|1760573621952|.............
|
||||
+|2650|59|NEMA|aw4eELG2DlPMdn1JW0BM5aAqQXOZRN3xB5yp8VKr|0||1|1|1759206522859|.............
|
||||
+|3647|61|NEMA|eod9aRWLVl34Gx1Dn7VNVr72rz6qjgmpEXwQJN5Z|1||1|1|1760573640420|.............
|
||||
+|3648|58|NEMA|B5EoxeMVp4zwr8nqW0GBpYARjvD1PNamOGbLg63Z|1||1|1|1760573643647|.............
|
||||
+|3649|70|NEMA|roKgWqY95V3mXMRzyAjKee0bLjexpJPvaGDBw826|1||1|0|1760287319440|.............
|
||||
+|3650|55|NEMA|gRoJEyXVx4qD9er287L4NQ7wBzGldaPjLWQKm3Mv|1||1|1|1760573674705|.............
|
||||
+|3651|56|NEMA|K94XLav1glVRnyQ6r01PgwAme3YJwBxM5oOzdP2j|1||1|1|1760573674898|.............
|
||||
+|3654|83|NEMA|roKgWqY95V3mXMRzyAjK1D0bLjexpJPvaGDBw826|1||1|1|1760573678171|.............
|
||||
+|3655|26|NEMA|roKgWqY95V3mXMRzyAjKQe0bLjexpJPvaGDBw826|1||1|0|1760287333253|.............
|
||||
+|3657|31|NEMA|5dBNwRp9graYJxZn409NYwklVov1b2QLPDqGm6XK|1||1|0|1760287333477|.............
|
||||
+|3659|60|NEMA|ZmRwd93QL4gaezxEbAx2DVk1prn2XjlPvGyqJ6BO|1||1|0|1757706655051|.............
|
||||
+|3659|78|NEMA|ZmRwd93QL4gaezxEbAx2DVk1prn2XjlPvGyqJ6BO|1||1|0|1757706655051|.............
|
||||
+|3668|77|NEMA|Nzp2OoJlqn6r1ZgvdA3B9d7abBwP5G4eE3RQmyxD|1||1|1|1760573777509|.............
|
||||
+|3669|57|NEMA|d9x2V5LGYBzXp4mMRAOmJvkPloaqJwnQj6DgrNe3|1||1|1|1760573777701|.............
|
||||
+|3670|79|NEMA|52dD6ZlV1QaOpRBmbAqGrgkKnGzWMLj4eJq38Pgo|1||1|1|1760573780928|.............
|
||||
+|3676|75|10-Repeater|EjgWGnXaLy9opPOz20n434786BlYM3w1deVQvbKr|1||1|1|1760573928051|.............
|
||||
+|3678|81|NEMA|E6Kg9oDnLWyzPRMva7v5eakJxp4VG58qO2w1lZYe|1||1|1|1760573963310|.............
|
||||
+|3680|76|NEMA|wvKJdZML6mXP4DzWBAXxXWAjxNloa5g23Ve9Y1ry|1||1|1|1760573990980|.............
|
||||
+|3681|78|NEMA|PLBJzmK1r3Gynd6OW0gKwM0e5wV4vx9bDEqNgYR8|1||1|1|1760573991220|.............
|
||||
+|3689|80|NEMA|rDbQ84xzwgdqEoPm3kbE6309anOZY1RXyBv2LVM6|1||1|1|1760574047934|.............
|
||||
+|3690|73|NEMA|eod9aRWLVl34Gx1Dn7VNxa72rz6qjgmpEXwQJN5Z|1||1|1|1760574054165|.............
|
||||
+|3691|74|NEMA|3a5oqJN1bgnx4Ol9dk89RB7ByE6jQ8mKDWMpGrLV|1||1|1|1760574054308|.............
|
||||
+|3693|82|NEMA|nJL5lPMwBx23YpqRe0rp2V7damXvWVbOrD4gNzy8|1||1|1|1760574054500|.............
|
||||
+|4115|21|NEMA|Nzp2OoJlqn6r1ZgvdA3B5L7abBwP5G4eE3RQmyxD|1||1|1|1760574129923|.............
|
||||
+|3759|48|NEMA|RO8rjaBDy21qPQJzW7omRL0pK3xmNleVZg9Ed4Gw|1||1|1|1760574057712|.............
|
||||
+|4017|20|NEMA|wvKJdZML6mXP4DzWBAXxZPAjxNloa5g23Ve9Y1ry|1||1|1|1760574236786|.............
|
||||
+|3959|5|NEMA|PjLblDgRBO6WQqnxmkJ13g0Jv3ewZN4p5a89yKdY|1||1|1|1760574090846|.............
|
||||
+|3214|18|NEMA|3a5oqJN1bgnx4Ol9dk89pQ7ByE6jQ8mKDWMpGrLV|1||1|1|1760574325453|.............
|
||||
+|4108|19|NEMA|EjgWGnXaLy9opPOz20n4xa786BlYM3w1deVQvbKr|1||1|1|1760574249807|.............
|
||||
+|3604|9|NEMA|d9x2V5LGYBzXp4mMRAOmjvkPloaqJwnQj6DgrNe3|1||1|1|1760573597030|.............
|
||||
+|4000|13|NEMA|eod9aRWLVl34Gx1Dn7VNEr72rz6qjgmpEXwQJN5Z|1||1|1|1760574196543|.............
|
||||
+|2635|41|NEMA|6lQGaY9RDywdVzObj0PaQWkPg4NBn3exEK51LWZq|1||1|1|1760574293677|.............
|
||||
+|3971|54|NEMA|d5xjWYMwEJon6rLlK7yEJN7qgV4DaOeNB9ZX3Gzb|1||1|1|1760574134332|.............
|
||||
1
RVO35/databases/nodes_original/nodes_original.table
Executable file
1
RVO35/databases/nodes_original/nodes_original.table
Executable file
|
|
@ -0,0 +1 @@
|
|||
[{"3646": "zrR51V2ajQ9ZLygPKkEMRakYDq38xOJolENBXGnv"}, {"3658": "PLBJzmK1r3Gynd6OW0gKBx0e5wV4vx9bDEqNgYR8"}, {"3671": "EjgWGnXaLy9opPOz20n4Pa786BlYM3w1deVQvbKr"}, {"3672": "Nzp2OoJlqn6r1ZgvdA3BbL7abBwP5G4eE3RQmyxD"}, {"3673": "52dD6ZlV1QaOpRBmbAqGpqkKnGzWMLj4eJq38Pgo"}, {"3674": "rDbQ84xzwgdqEoPm3kbE2y09anOZY1RXyBv2LVM6"}, {"3677": "ZmRwd93QL4gaezxEbAx2P9k1prn2XjlPvGyqJ6BO"}, {"3679": "E6Kg9oDnLWyzPRMva7v5NbkJxp4VG58qO2w1lZYe"}, {"3682": "wvKJdZML6mXP4DzWBAXxnPAjxNloa5g23Ve9Y1ry"}, {"3683": "PLBJzmK1r3Gynd6OW0gKmx0e5wV4vx9bDEqNgYR8"}, {"3687": "nJL5lPMwBx23YpqRe0rpz27damXvWVbOrD4gNzy8"}, {"3980": "rDbQ84xzwgdqEoPm3kbEzy09anOZY1RXyBv2LVM6"}, {"4012": "E6Kg9oDnLWyzPRMva7v5bbkJxp4VG58qO2w1lZYe"}, {"3429": "zdQO8GwxDqjRgP4137YVX5ANyKlpem2nL65rvVJY"}, {"3569": "gYbDLqlyZVoRerQpB72Mq8kWJnwM5z24POKa8Exj"}, {"3962": "nJL5lPMwBx23YpqRe0rpX27damXvWVbOrD4gNzy8"}, {"3804": "Z5KyJe9nEg1QNbWlX0w4wzkoDjBLdqzR83VGv624"}, {"3810": "1JMYvnx2RzKEo4aWQ7DGPvkL8yZV3m9NBePXbrdj"}, {"3844": "3JjOWdylwgNLzxVab7NaD8kZ2vG64rq8PEB5QmDo"}, {"3878": "o9vbeQlLMVg8j5dq4keL940NxZpEmnXzwYKO1ar2"}, {"3944": "B5EoxeMVp4zwr8nqW0GB8YARjvD1PNamOGbLg63Z"}, {"3945": "XMBbew5z4ELrZa2mRAd3Mw78vPN6gy3DdVYlpKjq"}, {"3947": "Z5KyJe9nEg1QNbWlX0w4vzkoDjBLdqzR83VGv624"}, {"3948": "dz4ojlpP85JMgDLZWkQJdn7aKYqQexEr62GXRV1y"}, {"3955": "apKVJBwOyrP35m2lv7KYo10YXbeWNd64En9GxRqg"}, {"3956": "K94XLav1glVRnyQ6r01PewAme3YJwBxM5oOzdP2j"}, {"3958": "ZmRwd93QL4gaezxEbAx2jVk1prn2XjlPvGyqJ6BO"}, {"2617": "aw4eELG2DlPMdn1JW0BMyaAqQXOZRN3xB5yp8VKr"}, {"3961": "gRoJEyXVx4qD9er287L4qQ7wBzGldaPjLWQKm3Mv"}, {"3965": "gP1eOZVj3Q9lv5aDEk45vV7rdpqW8yLm2BbKzJxM"}, {"3966": "dz4ojlpP85JMgDLZWkQJyn7aKYqQexEr62GXRV1y"}, {"3968": "RO8rjaBDy21qPQJzW7omVL0pK3xmNleVZg9Ed4Gw"}, {"3969": "3JjOWdylwgNLzxVab7Nav8kZ2vG64rq8PEB5QmDo"}, {"3970": "2O14VBzl8aDmWdNw3A59jgAGyZ5qLJoEMpj6R9ng"}, {"3973": "JX1ObgmqGZ54DMyYL7aDdGkEVdve38WKRzwjNrQ9"}, {"3974": "RvmwNz8QPblKp41GD7lK5QkJrLVYoBO92dMegn6W"}, {"3076": "52dD6ZlV1QaOpRBmbAqGnqkKnGzWMLj4eJq38Pgo"}, {"3978": "m6EYyZoJ4gWexdjVPAR5Lv7RDOq9wv2N5XzKGplr"}, {"3979": "1JMYvnx2RzKEo4aWQ7DG1vkL8yZV3m9NBePXbrdj"}, {"3984": "g9OxBZ5KRwNznlY6pAppW1AWXvjdEL4eGQobMDy2"}, {"4213": "JzwxZXOvDj1bVrN4nkW5BbA8qdyBl3MRKLpGPgaQ"}, {"4218": "OzNMgZ9n43qPbjXmy7zwQbA2DKdYvW5e6pxGRrVa"}, {"3611": "pE5X8NQPaow6vlOZxk6arX0q42ezGBMyWgDVjR3L"}, {"3612": "DbQY6zyveZRwK5drV0Z83J7joE4XJM83N9xl2nWq"}, {"3622": "BaY3Xpy1EbKGjLq2O7ma6w7rx8owgQz9P4dDJRmN"}, {"3643": "PjLblDgRBO6WQqnxmkJ1lg0Jv3ewZN4p5a89yKdY"}, {"3644": "3a5oqJN1bgnx4Ol9dk892Q7ByE6jQ8mKDWMpGrLV"}, {"3645": "aw4eELG2DlPMdn1JW0BM5aAqQXOZRN3xB5yp8VKr"}, {"3647": "eod9aRWLVl34Gx1Dn7VNVr72rz6qjgmpEXwQJN5Z"}, {"3648": "B5EoxeMVp4zwr8nqW0GBpYARjvD1PNamOGbLg63Z"}, {"3649": "roKgWqY95V3mXMRzyAjKee0bLjexpJPvaGDBw826"}, {"3650": "gRoJEyXVx4qD9er287L4NQ7wBzGldaPjLWQKm3Mv"}, {"3651": "K94XLav1glVRnyQ6r01PgwAme3YJwBxM5oOzdP2j"}, {"3654": "roKgWqY95V3mXMRzyAjK1D0bLjexpJPvaGDBw826"}, {"3655": "roKgWqY95V3mXMRzyAjKQe0bLjexpJPvaGDBw826"}, {"3657": "5dBNwRp9graYJxZn409NYwklVov1b2QLPDqGm6XK"}, {"3659": "ZmRwd93QL4gaezxEbAx2DVk1prn2XjlPvGyqJ6BO"}, {"3668": "Nzp2OoJlqn6r1ZgvdA3B9d7abBwP5G4eE3RQmyxD"}, {"3669": "d9x2V5LGYBzXp4mMRAOmJvkPloaqJwnQj6DgrNe3"}, {"3670": "52dD6ZlV1QaOpRBmbAqGrgkKnGzWMLj4eJq38Pgo"}, {"3676": "EjgWGnXaLy9opPOz20n434786BlYM3w1deVQvbKr"}, {"3678": "E6Kg9oDnLWyzPRMva7v5eakJxp4VG58qO2w1lZYe"}, {"3680": "wvKJdZML6mXP4DzWBAXxXWAjxNloa5g23Ve9Y1ry"}, {"3681": "PLBJzmK1r3Gynd6OW0gKwM0e5wV4vx9bDEqNgYR8"}, {"3689": "rDbQ84xzwgdqEoPm3kbE6309anOZY1RXyBv2LVM6"}, {"3690": "eod9aRWLVl34Gx1Dn7VNxa72rz6qjgmpEXwQJN5Z"}, {"3691": "3a5oqJN1bgnx4Ol9dk89RB7ByE6jQ8mKDWMpGrLV"}, {"3693": "nJL5lPMwBx23YpqRe0rp2V7damXvWVbOrD4gNzy8"}, {"4115": "Nzp2OoJlqn6r1ZgvdA3B5L7abBwP5G4eE3RQmyxD"}, {"3759": "RO8rjaBDy21qPQJzW7omRL0pK3xmNleVZg9Ed4Gw"}, {"4017": "wvKJdZML6mXP4DzWBAXxZPAjxNloa5g23Ve9Y1ry"}, {"3959": "PjLblDgRBO6WQqnxmkJ13g0Jv3ewZN4p5a89yKdY"}, {"3214": "3a5oqJN1bgnx4Ol9dk89pQ7ByE6jQ8mKDWMpGrLV"}, {"4108": "EjgWGnXaLy9opPOz20n4xa786BlYM3w1deVQvbKr"}, {"3604": "d9x2V5LGYBzXp4mMRAOmjvkPloaqJwnQj6DgrNe3"}, {"4000": "eod9aRWLVl34Gx1Dn7VNEr72rz6qjgmpEXwQJN5Z"}, {"2635": "6lQGaY9RDywdVzObj0PaQWkPg4NBn3exEK51LWZq"}, {"3971": "d5xjWYMwEJon6rLlK7yEJN7qgV4DaOeNB9ZX3Gzb"}]
|
||||
41
RVO35/databases/notifications.table
Executable file
41
RVO35/databases/notifications.table
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
key:string|weight:string|sk:string|en:string
|
||||
+|switching_profile_point_applied_to_line|INFORMATIONAL|Aplikovaný bod spínacieho profilu na línií č. ${line} : ${value}|Switching profile point applied to line no. ${line} : ${value}|...............
|
||||
+|dusk_has_occured|INFORMATIONAL|Nastal súmrak: ${value}|Dusk has occured: ${value}|...............
|
||||
+|dawn_has_occured|INFORMATIONAL|Nastal úsvit: ${value}|Dawn has occured: ${value}|...............
|
||||
+|dimming_profile_was_successfully_received_by_node|NOTICE|Stmievací profil bol úspešne prijatý nodom č. ${node}|Dimming profile was successfully received by node no. ${node}|...............
|
||||
+|master_node_is_responding_again|NOTICE|Master node začal znovu odpovedať|Master node is responding again|...............
|
||||
+|command_was_sent_from_terminal_interface|DEBUG|Z terminálu bol odoslaný príkaz|A command was sent from terminal interface|...............
|
||||
+|master_node_is_not_responding|ALERT|Master node neodpovedá|Master node is not responding|...............
|
||||
+|configuration_of_dimming_profile_to_node_failed|ALERT|Konfigurácia stmievacieho profilu pre node č. ${node} zlyhala|Configuration of dimming profile to node no. ${node} has failed|...............
|
||||
+|circuit_breaker_was_turned_on_line|NOTICE|Zapnutie ističa na línii č. ${line}|Circuit breaker was turned on - line no. ${line}|...............
|
||||
+|circuit_breaker_was_turned_off_line|ERROR|Vypnutie ističa na línií č. ${line}|Circuit breaker was turned off - line no. ${line}|...............
|
||||
+|dimming_profile_was_processed_for_node|INFORMATIONAL|Stmievací profil bol spracovaný pre node č. ${node}|Dimming profile was processed for node no. ${node}|...............
|
||||
+|switching_profile_was_processed_for_line|INFORMATIONAL|Spínací profil bol spracovaný pre líniu č. ${line}|Switching profile was processed for line no. ${line}|...............
|
||||
+|thermometer_is_not_responding|WARNING|Teplomer neodpovedá|Thermometer is not responding|...............
|
||||
+|thermometer_is_responding_again|NOTICE|Teplomer znovu odpovedá|Thermometer is responding again|...............
|
||||
+|thermometer_sends_invalid_data|WARNING|Teplomer posiela neplatné hodnoty|Thermometer sends invalid data|...............
|
||||
+|main_switch_has_been_turned_off|CRITICAL|Hlavný vypínač bol vypnutý|Main switch has been turned off|...............
|
||||
+|main_switch_has_been_turned_on|NOTICE|Hlavný vypínač bol zapnutý|Main switch has been turned on|...............
|
||||
+|power_supply_has_disconnected_input|ALERT|Napájací zdroj nemá napätie na vstupe|Power supply has disconnected input|...............
|
||||
+|power_supply_works_correctly|NOTICE|Napájací zdroj pracuje správne|Power supply works correctly|...............
|
||||
+|battery_level_is_low|ERROR|Batéria má nízku úroveň napätia|Battery level is low|...............
|
||||
+|battery_level_is_ok|NOTICE|Batéria má správnu úroveň napätia|Battery level is OK|...............
|
||||
+|door_main_open|NOTICE|Hlavné dvere boli otvorené|Main door has been opened|...............
|
||||
+|door_em_open|NOTICE|Dvere silovej časti boli otvorené|Power door has been opened|...............
|
||||
+|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_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}|...............
|
||||
+|local_database_is_corrupted|CRITICAL|||...............
|
||||
+|electrometer_nok|ERROR|Elektromer neodpovedá|Electrometer is not responding|...............
|
||||
+|electrometer_ok|NOTICE|Elektromer znovu odpovedá|Electrometer is responding again|...............
|
||||
+|no_voltage_on_phase|CRITICAL|Na fáze č. ${phase} nie je napätie|No voltage detected on phase no. ${phase}|...............
|
||||
+|voltage_on_phase_restored|NOTICE|Napätie na fáze č. ${phase} bolo obnovené|Voltage on phase no. ${phase} has been restored|...............
|
||||
+|flow_start|NOTICE|FLOW bol spustený|FLOW has been started |...............
|
||||
+|twilight_sensor_nok|ERROR|Sensor súmraku neodpovedá|Twilight sensor is not responding|...............
|
||||
+|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_off|NOTICE|Lampy sa vypli|Lamps have turned off|...............
|
||||
+|flow_restart|NOTICE|FLOW bol reštartovaný|FLOW has been restarted|...............
|
||||
+|nodes_db_changed|NOTICE|Zmena v node databáze|Node db has changed|...............
|
||||
17
RVO35/databases/pins.table
Executable file
17
RVO35/databases/pins.table
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
pin:string|type:string|line:number
|
||||
*|input1_01|door_condition|0|...........
|
||||
*|input1_02|rotary_switch_state|0|...........
|
||||
*|input1_03|rotary_switch_state|0|...........
|
||||
*|input1_04|power_supply|0|...........
|
||||
*|input1_05|state_of_main_switch|0|...........
|
||||
*|input1_06|state_of_breaker|1|...........
|
||||
*|input1_07|state_of_breaker|2|...........
|
||||
*|input1_08|state_of_breaker|3|...........
|
||||
*|relay1_02|state_of_contactor|1|...........
|
||||
*|relay1_03|state_of_contactor|2|...........
|
||||
*|relay1_04|state_of_contactor|3|...........
|
||||
*|relay1_05|state_of_contactor|4|...........
|
||||
*|relay1_06|state_of_contactor|5|...........
|
||||
*|relay1_07|state_of_contactor|6|...........
|
||||
*|28F46E9D0E00008B|temperature|0|...........
|
||||
*|twilight_sensor|twilight_sensor|0|...........
|
||||
8
RVO35/databases/relays.table
Executable file
8
RVO35/databases/relays.table
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
line:number|tbname:string|contactor:number|profile:string
|
||||
+|0|gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM|1||...........
|
||||
+|1|pE5X8NQPaow6vlOZxk6aOE0q42ezGBMyWgDVjR3L|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|6lQGaY9RDywdVzObj0Pa2okPg4NBn3exEK51LWZq|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|m6EYyZoJ4gWexdjVPAR5Ma7RDOq9wv2N5XzKGplr|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}|...........
|
||||
+|4|apKVJBwOyrP35m2lv7KYev0YXbeWNd64En9GxRqg|9||...........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
|
||||
+|5|o9vbeQlLMVg8j5dq4keLaX0NxZpEmnXzwYKO1ar2|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}|...........
|
||||
+|6|gP1eOZVj3Q9lv5aDEk45mZ7rdpqW8yLm2BbKzJxM|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
RVO35/databases/settings.table
Executable file
2
RVO35/databases/settings.table
Executable file
|
|
@ -0,0 +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|daily_report:boolean|send_changed_node_numbers:boolean
|
||||
+|rvo_senica_35_10.0.0.129|en|28.64339D0E0000|48.70826502|17.28455203|192.168.252.1|rvo_senica_35_10.0.0.129|XzLmhOhYVANSqM65h14r|1883|0|65|unipi|ttyUSB0|1|20|5|6|3|u129|0|1|1|...................................................
|
||||
4
RVO35/databases/tbdata.nosql
Executable file
4
RVO35/databases/tbdata.nosql
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
-"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM":[{"ts":1760519892440,"values":{"_event":{"type":"notice","status":"new","source":{"func":"modbus_reader","component":"1699965957410","component_name":"Modbus reader","edge":"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM"},"message":{"sk":"rvo_senica_35_10.0.0.129: FLOW bol reštartovaný","en":"rvo_senica_35_10.0.0.129: FLOW has been restarted"},"message_data":""}}}],"id":"3000138001gn71b"}
|
||||
-"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM":[{"ts":1760519892562,"values":{"edge_fw_version":"2025-10-08","maintenance_mode":false}}],"id":"3000138002gn70b"}
|
||||
-"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM":[{"ts":1760519892575,"values":{"statecode":2,"power_mode":"Automatic"}}],"id":"3000138004gn70b"}
|
||||
-"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM":[{"ts":1760519892627,"values":{"_event":{"type":"notice","status":"new","source":{"func":"rsPort.open()","component":"1699963668903","component_name":"DIDO_Controller","edge":"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM"},"message":{"sk":"rvo_senica_35_10.0.0.129: FLOW bol spustený","en":"rvo_senica_35_10.0.0.129: FLOW has been started "},"message_data":""}}}],"id":"3000138006gn70b"}
|
||||
3
RVO35/databases/tbdatacloud.nosql
Executable file
3
RVO35/databases/tbdatacloud.nosql
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
-"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM":[{"ts":1760519892562,"values":{"edge_fw_version":"2025-10-08","maintenance_mode":false}}],"id":"3000138003gn71b"}
|
||||
-"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM":[{"ts":1760519892575,"values":{"statecode":2,"power_mode":"Automatic"}}],"id":"3000138005gn71b"}
|
||||
-"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM":[{"ts":1760519892627,"values":{"_event":{"type":"notice","status":"new","source":{"func":"rsPort.open()","component":"1699963668903","component_name":"DIDO_Controller","edge":"gP1eOZVj3Q9lv5aDEk45bM7rdpqW8yLm2BbKzJxM"},"message":{"sk":"rvo_senica_35_10.0.0.129: FLOW bol spustený","en":"rvo_senica_35_10.0.0.129: FLOW has been started "},"message_data":""}}}],"id":"3000138007gn71b"}
|
||||
38
RVO35/databases/total_energy.js
Executable file
38
RVO35/databases/total_energy.js
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
//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,
|
||||
53: 1250,
|
||||
55: 1000,
|
||||
56: 5500
|
||||
}
|
||||
|
||||
module.exports = total_energy;
|
||||
16
RVO35/debug.js
Executable file
16
RVO35/debug.js
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
// ===================================================
|
||||
// FOR DEVELOPMENT
|
||||
// Total.js - framework for Node.js platform
|
||||
// https://www.totaljs.com
|
||||
// ===================================================
|
||||
|
||||
const options = {};
|
||||
|
||||
// options.ip = '127.0.0.1';
|
||||
// options.port = parseInt(process.argv[2]);
|
||||
// options.config = { name: 'Total.js' };
|
||||
// options.sleep = 3000;
|
||||
// options.inspector = 9229;
|
||||
// options.watch = ['private'];
|
||||
|
||||
require('total.js/debug')(options);
|
||||
107
RVO35/err.txt
Executable file
107
RVO35/err.txt
Executable file
|
|
@ -0,0 +1,107 @@
|
|||
[2024-10-25T07:54:54.308] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T08:55:23.828] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T09:55:51.343] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T10:56:18.900] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T11:56:56.265] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T12:57:06.410] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T13:57:37.860] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T14:58:15.108] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T15:58:42.765] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T16:58:58.706] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T17:59:18.545] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T18:59:53.906] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T20:00:19.540] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T21:01:00.702] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T22:01:34.108] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-25T23:02:15.295] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T00:02:44.853] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T01:03:27.942] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T02:04:05.285] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T03:04:36.753] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T04:05:09.220] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T05:05:36.806] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T06:06:25.746] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T07:06:41.733] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T08:07:42.348] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T09:08:06.029] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T10:08:43.400] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T11:08:53.494] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T12:09:34.586] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T13:10:11.796] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T14:10:54.864] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T15:11:20.439] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T16:11:55.749] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T17:12:25.250] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T18:12:52.879] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T19:13:16.564] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T20:13:46.115] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-10-26T21:14:23.399] [ERROR] errLogs - checkFinalRVOStatus: temperature status is NOK
|
||||
[2024-12-21T09:26:51.132] [ERROR] errLogs - uncaughtException: message is not defined
|
||||
[2024-12-21T09:26:51.132] [ERROR] errLogs - ReferenceError: message is not defined
|
||||
at turnLine (/home/unipi/flowserver/flow/dido_controller.js:571:150)
|
||||
at initialSetting (/home/unipi/flowserver/flow/dido_controller.js:215:9)
|
||||
at WebSocket.open (/home/unipi/flowserver/flow/dido_controller.js:333:7)
|
||||
at WebSocket.onOpen (/home/unipi/flowserver/node_modules/ws/lib/event-target.js:144:16)
|
||||
at WebSocket.emit (events.js:400:28)
|
||||
at WebSocket.setSocket (/home/unipi/flowserver/node_modules/ws/lib/websocket.js:225:10)
|
||||
at ClientRequest.<anonymous> (/home/unipi/flowserver/node_modules/ws/lib/websocket.js:882:15)
|
||||
at ClientRequest.emit (events.js:400:28)
|
||||
at Socket.socketOnData (_http_client.js:553:11)
|
||||
at Socket.emit (events.js:400:28)
|
||||
[2024-12-21T09:26:51.133] [ERROR] errLogs - uncaughtException: message is not defined
|
||||
[2024-12-21T09:26:51.134] [ERROR] errLogs - ReferenceError: message is not defined
|
||||
at turnLine (/home/unipi/flowserver/flow/dido_controller.js:571:150)
|
||||
at initialSetting (/home/unipi/flowserver/flow/dido_controller.js:215:9)
|
||||
at WebSocket.open (/home/unipi/flowserver/flow/dido_controller.js:333:7)
|
||||
at WebSocket.onOpen (/home/unipi/flowserver/node_modules/ws/lib/event-target.js:144:16)
|
||||
at WebSocket.emit (events.js:400:28)
|
||||
at WebSocket.setSocket (/home/unipi/flowserver/node_modules/ws/lib/websocket.js:225:10)
|
||||
at ClientRequest.<anonymous> (/home/unipi/flowserver/node_modules/ws/lib/websocket.js:882:15)
|
||||
at ClientRequest.emit (events.js:400:28)
|
||||
at Socket.socketOnData (_http_client.js:553:11)
|
||||
at Socket.emit (events.js:400:28)
|
||||
[2024-12-21T09:26:51.145] [ERROR] errLogs - uncaughtException: message is not defined
|
||||
[2024-12-21T09:26:51.146] [ERROR] errLogs - ReferenceError: message is not defined
|
||||
at turnLine (/home/unipi/flowserver/flow/dido_controller.js:571:150)
|
||||
at initialSetting (/home/unipi/flowserver/flow/dido_controller.js:215:9)
|
||||
at WebSocket.open (/home/unipi/flowserver/flow/dido_controller.js:333:7)
|
||||
at WebSocket.onOpen (/home/unipi/flowserver/node_modules/ws/lib/event-target.js:144:16)
|
||||
at WebSocket.emit (events.js:400:28)
|
||||
at WebSocket.setSocket (/home/unipi/flowserver/node_modules/ws/lib/websocket.js:225:10)
|
||||
at ClientRequest.<anonymous> (/home/unipi/flowserver/node_modules/ws/lib/websocket.js:882:15)
|
||||
at ClientRequest.emit (events.js:400:28)
|
||||
at Socket.socketOnData (_http_client.js:553:11)
|
||||
at Socket.emit (events.js:400:28)
|
||||
[2025-09-23T14:00:16.696] [ERROR] errLogs - uncaughtException: Cannot read property 'getHours' of undefined
|
||||
[2025-09-23T14:00:16.697] [ERROR] errLogs - TypeError: Cannot read property 'getHours' of undefined
|
||||
at Timeout.emptyReportToSend [as _onTimeout] (/home/unipi/flowserver/flow/cmd_manager.js:1419:14)
|
||||
at listOnTimeout (internal/timers.js:557:17)
|
||||
at processTimers (internal/timers.js:500:7)
|
||||
[2025-09-23T14:00:16.698] [ERROR] errLogs - uncaughtException: Cannot read property 'getHours' of undefined
|
||||
[2025-09-23T14:00:16.699] [ERROR] errLogs - TypeError: Cannot read property 'getHours' of undefined
|
||||
at Timeout.emptyReportToSend [as _onTimeout] (/home/unipi/flowserver/flow/cmd_manager.js:1419:14)
|
||||
at listOnTimeout (internal/timers.js:557:17)
|
||||
at processTimers (internal/timers.js:500:7)
|
||||
[2025-09-23T14:00:16.707] [ERROR] errLogs - uncaughtException: Cannot read property 'getHours' of undefined
|
||||
[2025-09-23T14:00:16.708] [ERROR] errLogs - TypeError: Cannot read property 'getHours' of undefined
|
||||
at Timeout.emptyReportToSend [as _onTimeout] (/home/unipi/flowserver/flow/cmd_manager.js:1419:14)
|
||||
at listOnTimeout (internal/timers.js:557:17)
|
||||
at processTimers (internal/timers.js:500:7)
|
||||
[2025-09-23T14:00:23.097] [ERROR] errLogs - uncaughtException: date.getHours is not a function
|
||||
[2025-09-23T14:00:23.098] [ERROR] errLogs - TypeError: date.getHours is not a function
|
||||
at emptyReportToSend (/home/unipi/flowserver/flow/cmd_manager.js:1419:14)
|
||||
at Timeout.dailyReportCheck [as _onTimeout] (/home/unipi/flowserver/flow/cmd_manager.js:1351:5)
|
||||
at listOnTimeout (internal/timers.js:557:17)
|
||||
at processTimers (internal/timers.js:500:7)
|
||||
[2025-09-23T14:00:23.098] [ERROR] errLogs - uncaughtException: date.getHours is not a function
|
||||
[2025-09-23T14:00:23.099] [ERROR] errLogs - TypeError: date.getHours is not a function
|
||||
at emptyReportToSend (/home/unipi/flowserver/flow/cmd_manager.js:1419:14)
|
||||
at Timeout.dailyReportCheck [as _onTimeout] (/home/unipi/flowserver/flow/cmd_manager.js:1351:5)
|
||||
at listOnTimeout (internal/timers.js:557:17)
|
||||
at processTimers (internal/timers.js:500:7)
|
||||
[2025-09-23T14:00:23.104] [ERROR] errLogs - uncaughtException: date.getHours is not a function
|
||||
[2025-09-23T14:00:23.104] [ERROR] errLogs - TypeError: date.getHours is not a function
|
||||
at emptyReportToSend (/home/unipi/flowserver/flow/cmd_manager.js:1419:14)
|
||||
at Timeout.dailyReportCheck [as _onTimeout] (/home/unipi/flowserver/flow/cmd_manager.js:1351:5)
|
||||
at listOnTimeout (internal/timers.js:557:17)
|
||||
at processTimers (internal/timers.js:500:7)
|
||||
357
RVO35/flow/cloudmqttconnect.js
Executable file
357
RVO35/flow/cloudmqttconnect.js
Executable file
|
|
@ -0,0 +1,357 @@
|
|||
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 = {}; //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);
|
||||
|
||||
if (!o.topic) {
|
||||
instance.status("Not configured", "white");
|
||||
console.log("Cloud mqtt connect: no topic selected");
|
||||
return;
|
||||
}
|
||||
|
||||
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(function() {
|
||||
|
||||
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 } });
|
||||
}
|
||||
|
||||
}, () => 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 && o.topic) {
|
||||
//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);
|
||||
|
||||
};
|
||||
3071
RVO35/flow/cmd_manager.js
Executable file
3071
RVO35/flow/cmd_manager.js
Executable file
File diff suppressed because it is too large
Load diff
90
RVO35/flow/code.js
Executable file
90
RVO35/flow/code.js
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
exports.id = 'code';
|
||||
exports.title = 'Code';
|
||||
exports.group = 'Common';
|
||||
exports.color = '#656D78';
|
||||
exports.input = true;
|
||||
exports.output = 1;
|
||||
exports.author = 'Peter Širka';
|
||||
exports.icon = 'code';
|
||||
exports.version = '1.2.0';
|
||||
exports.options = { outputs: 1, code: 'send(0, value);', keepmessage: true };
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div data-jc="textbox" data-jc-path="outputs" data-jc-config="type:number;validation:value > 0;increment:true;maxlength:3">@(Number of outputs)</div>
|
||||
<div class="help m">@(Minimum is 1)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-jc="codemirror" data-jc-path="code" data-jc-config="type:javascript;required:true;height:500;tabs:true;trim:true" class="m">@(Code)</div>
|
||||
<div data-jc="checkbox" data-jc-path="keepmessage">@(Keep message instance)</div>
|
||||
</div>
|
||||
<script>
|
||||
var code_outputs_count;
|
||||
|
||||
ON('open.code', function(component, options) {
|
||||
code_outputs_count = options.outputs = options.outputs || 1;
|
||||
});
|
||||
|
||||
ON('save.code', function(component, options) {
|
||||
if (code_outputs_count !== options.outputs) {
|
||||
if (flow.version < 511) {
|
||||
component.connections = {};
|
||||
setState(MESSAGES.apply);
|
||||
}
|
||||
component.output = options.outputs || 1;
|
||||
}
|
||||
});
|
||||
</script>`;
|
||||
|
||||
exports.readme = `# Code
|
||||
|
||||
This component executes custom JavaScript code as it is and it doesn't contain any secure scope.
|
||||
|
||||
\`\`\`javascript
|
||||
// value {Object} contains received data
|
||||
// send(outputIndex, newValue) sends a new value
|
||||
// error(value) sends an error
|
||||
// instance {Object} a current component instance
|
||||
// flowdata {Object} a current flowdata
|
||||
// repository {Object} a current repository of flowdata
|
||||
// Example:
|
||||
|
||||
// send() can be execute multiple times
|
||||
send(0, value);
|
||||
\`\`\``;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var fn;
|
||||
|
||||
instance.on('data', function(response) {
|
||||
if (fn) {
|
||||
try {
|
||||
fn(response.data, instance, response, instance.options, response.repository, require);
|
||||
} catch (e) {
|
||||
response.data = e;
|
||||
instance.throw(response);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
instance.reconfigure = function() {
|
||||
try {
|
||||
if (instance.options.code) {
|
||||
instance.status('');
|
||||
var code = 'var send = function(index, value) { if (options.keepmessage) { flowdata.data = value; instance.send2(index, flowdata); } else instance.send2(index, value);}; var error = function(err) { instance.throw(err); }; ' + instance.options.code;
|
||||
fn = new Function('value', 'instance', 'flowdata', 'options', 'repository', 'require', code);
|
||||
} else {
|
||||
instance.status('Not configured', 'red');
|
||||
fn = null;
|
||||
}
|
||||
} catch (e) {
|
||||
fn = null;
|
||||
instance.error('Code: ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
instance.on('options', instance.reconfigure);
|
||||
instance.reconfigure();
|
||||
};
|
||||
11
RVO35/flow/comment.js
Executable file
11
RVO35/flow/comment.js
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
exports.id = 'comment';
|
||||
exports.title = 'Comment';
|
||||
exports.group = 'Common';
|
||||
exports.color = '#704cff';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.icon = 'comment';
|
||||
exports.traffic = false;
|
||||
exports.version = '1.0.0';
|
||||
exports.readme = '# Comment';
|
||||
|
||||
exports.install = function() {};
|
||||
60
RVO35/flow/count.js
Executable file
60
RVO35/flow/count.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
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;
|
||||
});
|
||||
|
||||
};
|
||||
286
RVO35/flow/db_connector.js
Executable file
286
RVO35/flow/db_connector.js
Executable file
|
|
@ -0,0 +1,286 @@
|
|||
exports.id = 'db_connector';
|
||||
exports.title = 'DbConnector';
|
||||
exports.version = '1.0.0';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#2134B0';
|
||||
exports.input = 1;
|
||||
exports.output = ["red", "white"];
|
||||
exports.click = false;
|
||||
exports.author = 'Daniel Segeš';
|
||||
exports.icon = 'bolt';
|
||||
exports.options = { edge: "undefined" };
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">DbConnector</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# read/write data to tables`;
|
||||
|
||||
const instanceSendTo = {
|
||||
debug: 0,
|
||||
http_response: 1,
|
||||
}
|
||||
|
||||
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
|
||||
|
||||
function extractWhereParams(params)
|
||||
{
|
||||
let name = params[0];
|
||||
let operator = '=';
|
||||
let value = params[1];
|
||||
|
||||
if(params.length == 3)
|
||||
{
|
||||
operator = params[1];
|
||||
value = params[2];
|
||||
}
|
||||
|
||||
return {name: name, operator: operator, value: value};
|
||||
}
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
let refFlowdata = null;//holds reference to httprequest flowdata
|
||||
|
||||
instance.on("close", () => {
|
||||
|
||||
})
|
||||
|
||||
|
||||
instance.on("data", async function(flowdata) {
|
||||
|
||||
let params = flowdata.data.body;
|
||||
console.log("DbConnector", params);
|
||||
|
||||
refFlowdata = flowdata;
|
||||
|
||||
if(refFlowdata != undefined)
|
||||
{
|
||||
//make http response
|
||||
let responseObj = {};
|
||||
responseObj["type"] = "SUCESS";
|
||||
|
||||
try{
|
||||
|
||||
let table = params.table;
|
||||
let action = params.action;
|
||||
|
||||
|
||||
if(params.data != undefined)
|
||||
{
|
||||
let className = params.data.className;
|
||||
|
||||
if(className == "SqlQueryBuilder")
|
||||
{
|
||||
let type = params.data.type;
|
||||
|
||||
console.log("SqlQueryBuilder---->", params.data);
|
||||
|
||||
if(type == "SELECT")
|
||||
{
|
||||
let table = params.data.queryData.tables[0].table;
|
||||
|
||||
const db = TABLE(table);
|
||||
var builder = db.find();
|
||||
|
||||
let result = await promisifyBuilder(builder);
|
||||
|
||||
let response = {};
|
||||
response["result"] = result;
|
||||
response["success"] = true;
|
||||
|
||||
responseObj["data"] = response;
|
||||
|
||||
//console.log(responseObj);
|
||||
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log("db_connector---->", table, action);
|
||||
|
||||
//actions: read, replace, insert, delete, update...
|
||||
|
||||
if(action == "read")
|
||||
{
|
||||
const db = TABLE(params.table);
|
||||
|
||||
//where builder.where('age', '<', 15);
|
||||
//builder.where('id', 3403);
|
||||
|
||||
var builder = db.find();
|
||||
|
||||
//https://docs.totaljs.com/latest/en.html#api~DatabaseBuilder~builder.where
|
||||
if(params.hasOwnProperty("where"))
|
||||
{
|
||||
//optionalCan contain "=", "<=", "<", ">=", ">".
|
||||
//Default value: '='
|
||||
|
||||
//1.["production_line", 1]
|
||||
//2. ["or", ["production_line", 1], ["production_line", 2], "end"]
|
||||
|
||||
if (Array.isArray(params.where)) {
|
||||
|
||||
let multipleConditions = false;
|
||||
|
||||
if(params.where[0] == "or") multipleConditions = true;
|
||||
if (Array.isArray(params.where[0])) multipleConditions = true;
|
||||
|
||||
if(multipleConditions)
|
||||
{
|
||||
|
||||
for(var i = 0; i < params.where.length; i++)
|
||||
{
|
||||
const item = params.where[i];
|
||||
|
||||
if(item === "or") builder.or();
|
||||
|
||||
if (Array.isArray(item))
|
||||
{
|
||||
const { name, operator, value } = extractWhereParams(item);
|
||||
builder.where(name, operator, value);
|
||||
}
|
||||
|
||||
if(item === "end") builder.end();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
const { name, operator, value } = extractWhereParams(params.where);
|
||||
builder.where(name, operator, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
if(params.where.length >=2 )
|
||||
{
|
||||
let name = params.where[0];
|
||||
let operator = '=';
|
||||
let value = params.where[1];
|
||||
|
||||
if(params.where.length == 3)
|
||||
{
|
||||
operator = params.where[1];
|
||||
value = params.where[2];
|
||||
}
|
||||
|
||||
builder.where(name, operator, value);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if(params.hasOwnProperty("between"))
|
||||
{
|
||||
builder.between(params.between[0], params.between[1], params.between[2]);
|
||||
}
|
||||
|
||||
let response = await promisifyBuilder(builder);
|
||||
responseObj["data"] = response;
|
||||
|
||||
//console.log(responseObj);
|
||||
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(action == "delete")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
if(action == "update")
|
||||
{
|
||||
//! data receiving from terminal (params)
|
||||
// {
|
||||
// hostname: 'localhost',
|
||||
// table: 'settings',
|
||||
// action: 'update',
|
||||
// body: {
|
||||
// rvo_name: 'terrrr',
|
||||
// lang: 'en',
|
||||
// temperature_adress: '28.427B45920702',
|
||||
// latitude: 48.70826502,
|
||||
// longitude: 17.28455203,
|
||||
// mqtt_host: '192.168.252.4',
|
||||
// mqtt_clientid: 'showroom_test_panel_led',
|
||||
// mqtt_username: 'xmRd6RJxW53WZe4vMFLU',
|
||||
// mqtt_port: 1883,
|
||||
// maintanace_mode: false
|
||||
// }
|
||||
// }
|
||||
const tableToModify = TABLE(params.table);
|
||||
const newValues = params.body;
|
||||
|
||||
tableToModify.modify(newValues).make(function(builder) {
|
||||
|
||||
builder.callback(function(err, response) {
|
||||
|
||||
if(!err)
|
||||
{
|
||||
responseObj["data"] = response;
|
||||
responseObj["tableUpdated"] = true;
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if(action == "replace")
|
||||
{
|
||||
//truncate table
|
||||
const db = TABLE(params.table);
|
||||
var builder = db.remove();
|
||||
db.clean();
|
||||
|
||||
//insert data
|
||||
let data = params.data;
|
||||
|
||||
for(let i = 0; i < data.length; i++)
|
||||
{
|
||||
//console.log(data[i]);
|
||||
db.insert(data[i]);
|
||||
}
|
||||
|
||||
console.log("insert done");
|
||||
|
||||
let responseObj = {};
|
||||
responseObj["type"] = "SUCESS";
|
||||
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
} catch (error) {
|
||||
//console.log(error);
|
||||
responseObj["type"] = "ERROR";
|
||||
responseObj["message"] = error;
|
||||
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
113
RVO35/flow/db_init.js
Executable file
113
RVO35/flow/db_init.js
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
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.readme = `
|
||||
# DB initialization
|
||||
`;
|
||||
|
||||
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
|
||||
const { initNotification } = require('./helper/notification_reporter');
|
||||
const errorHandler = require('./helper/ErrorToServiceHandler');
|
||||
const total_energy = require('../databases/total_energy');
|
||||
|
||||
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 = {})
|
||||
|
||||
let rvo_number = responseSettings[0]["rvo_name"].match(/\D+(\d{1,2})_/)[1];
|
||||
|
||||
dbs.settings = {
|
||||
edge_fw_version: "2025-10-08", //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"],
|
||||
daily_report: responseSettings[0]["daily_report"],
|
||||
send_changed_node_numbers: responseSettings[0]["send_changed_node_numbers"],
|
||||
rvo_number: rvo_number,
|
||||
|
||||
//dynamic values
|
||||
masterNodeIsResponding: true, //cmd_manager
|
||||
maintenance_mode: false,
|
||||
}
|
||||
|
||||
|
||||
dbs.settings.energy_to_switch_lamps = total_energy[rvo_number];
|
||||
if (dbs.settings.energy_to_switch_lamps === undefined) console.log('=============== db_init.js: energy_to_switch_lamps is undefined');
|
||||
|
||||
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)
|
||||
|
||||
};
|
||||
|
||||
100
RVO35/flow/debug.js
Executable file
100
RVO35/flow/debug.js
Executable file
|
|
@ -0,0 +1,100 @@
|
|||
exports.id = 'debug';
|
||||
exports.title = 'Debug';
|
||||
exports.author = 'Peter Širka';
|
||||
exports.color = '#967ADC';
|
||||
exports.click = true;
|
||||
exports.input = true;
|
||||
exports.icon = 'bug';
|
||||
exports.version = '2.0.4';
|
||||
exports.options = { enabled: true, repository: false, type: 'data' };
|
||||
exports.readme = `# Debug
|
||||
|
||||
Prints data to the debug tab.`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div data-jc="dropdown" data-jc-path="type" data-jc-config="items:Message data|data,Message repository|repository,Message data + Message repository|both;required:true" class="m">@(Output type)</div>
|
||||
<div data-jc="textbox" data-jc-path="property" data-jc-config="placeholder: @(e.g. address.street)" class="m">@(Path to the property (leave empty to show the whole data object))</div>
|
||||
<div data-jc="textbox" data-jc-path="group" data-jc-config="placeholder: @(e.g. Temperature)" class="m">@(A group name)</div>
|
||||
<div data-jc="checkbox" data-jc-path="enabled">@(Enabled)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
instance.on('data', function(response) {
|
||||
if (instance.options.enabled) {
|
||||
|
||||
var opt = instance.options;
|
||||
var rep = response.repository;
|
||||
var val = response.data;
|
||||
var id = response.id;
|
||||
|
||||
switch (instance.options.type){
|
||||
case 'both':
|
||||
var data = {};
|
||||
data.repository = rep;
|
||||
data.data = val instanceof Error ? { error: val.message, stack: val.stack } : val;
|
||||
instance.debug(safeparse(opt.property ? U.get(data, opt.property) : data), undefined, opt.group, id);
|
||||
break;
|
||||
case 'repository':
|
||||
instance.debug(safeparse(opt.property ? U.get(rep, opt.property) : rep), undefined, opt.group, id);
|
||||
break;
|
||||
case 'data':
|
||||
default:
|
||||
if (val instanceof Error)
|
||||
instance.debug({ error: val.message, stack: val.stack }, undefined, opt.group, id);
|
||||
else
|
||||
instance.debug(safeparse(opt.property ? U.get(val, opt.property) : val), undefined, opt.group, id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
instance.on('click', function() {
|
||||
instance.options.enabled = !instance.options.enabled;
|
||||
instance.custom.status();
|
||||
instance.save();
|
||||
});
|
||||
|
||||
instance.on('options', function() {
|
||||
instance.custom.status();
|
||||
});
|
||||
|
||||
instance.custom.status = function() {
|
||||
instance.status(instance.options.enabled ? 'Enabled' : 'Disabled');
|
||||
};
|
||||
|
||||
instance.custom.status();
|
||||
|
||||
function safeparse(o) {
|
||||
|
||||
if (o instanceof Buffer)
|
||||
return o;
|
||||
|
||||
if (o === undefined)
|
||||
return 'undefined';
|
||||
|
||||
if (o === null)
|
||||
return 'null';
|
||||
|
||||
var cache = [];
|
||||
var str = JSON.stringify(o, function(key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.indexOf(value) !== -1) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
cache.push(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
cache = null;
|
||||
return JSON.parse(str);
|
||||
}
|
||||
};
|
||||
3102
RVO35/flow/designer.json
Executable file
3102
RVO35/flow/designer.json
Executable file
File diff suppressed because it is too large
Load diff
1486
RVO35/flow/dido_controller.js
Executable file
1486
RVO35/flow/dido_controller.js
Executable file
File diff suppressed because it is too large
Load diff
187
RVO35/flow/helper/DataToTbHandler.js
Executable file
187
RVO35/flow/helper/DataToTbHandler.js
Executable file
|
|
@ -0,0 +1,187 @@
|
|||
class DataToTbHandler {
|
||||
|
||||
constructor(index) {
|
||||
this.index = index;
|
||||
|
||||
// time, after new value for the given key will be resend to tb (e.g. {status: "OK"})
|
||||
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.
|
||||
this.attributeChangeLimit = {
|
||||
temperature: 0.5,
|
||||
Phase_1_voltage: 2,
|
||||
Phase_2_voltage: 2,
|
||||
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
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
dump() {
|
||||
console.log("----------------------------");
|
||||
console.log("previousValues", this.previousValues);
|
||||
console.log("----------------------------");
|
||||
}
|
||||
|
||||
setSender(sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
isEmptyObject(obj) {
|
||||
for (var _ in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
sendToTb(data, instance) {
|
||||
|
||||
//not to modify data object, we do deep copy:
|
||||
let dataCopy = JSON.parse(JSON.stringify(data));
|
||||
|
||||
let keys = Object.keys(dataCopy);
|
||||
|
||||
if (keys.length == 0) {
|
||||
if (this.debug) console.log("sendToTb received empty object", dataCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
let tbname = keys[0];
|
||||
let ts;
|
||||
|
||||
let arrayOfValues = dataCopy[tbname];
|
||||
let arrayOfValuesToSend = [];
|
||||
|
||||
for (let i = 0; i < arrayOfValues.length; i++) {
|
||||
|
||||
ts = arrayOfValues[i].ts;
|
||||
let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values);
|
||||
|
||||
if (!this.isEmptyObject(values)) {
|
||||
arrayOfValuesToSend.push({ ts: ts, values: values });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (arrayOfValuesToSend.length == 0) {
|
||||
//if(this.debug) console.log("data not sent - empty array");
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageCounter++;
|
||||
|
||||
let dataToTbModified = {
|
||||
[tbname]: arrayOfValuesToSend
|
||||
}
|
||||
|
||||
//console.log(this.sender + " DATA SEND TO TB ", tbname, this.messageCounter, new Date(ts), dataToTbModified[tbname][0].values, this.instance);
|
||||
//if(this.debug) console.log(this.sender + " DATA SEND TO TB ", this.index, tbname, arrayOfValuesToSend);
|
||||
instance.send(this.index, dataToTbModified);
|
||||
}
|
||||
|
||||
|
||||
getDiffTimestamp(key) {
|
||||
//TODO set different value for given key!!!
|
||||
//if(key == "status") this.timeToHoldTbValue = 2*60*60;//2h
|
||||
return this.timeToHoldTbValue * 1000;
|
||||
}
|
||||
|
||||
|
||||
prepareValuesForTb(tbname, timestamp, values) {
|
||||
|
||||
let keys = Object.keys(values);
|
||||
|
||||
if (keys.includes("lifetime")) this.itIsNodeReadout = true;
|
||||
|
||||
if (!this.previousValues.hasOwnProperty(tbname)) {
|
||||
this.previousValues[tbname] = {};
|
||||
}
|
||||
|
||||
//if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
|
||||
let key = keys[i];
|
||||
let value = values[key];
|
||||
|
||||
if (!this.previousValues[tbname].hasOwnProperty(key)) {
|
||||
this.previousValues[tbname][key] = { ts: timestamp, value: value };
|
||||
continue;
|
||||
}
|
||||
|
||||
// attributeData ==> {voltage: {ts:333333, value:5}}
|
||||
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;
|
||||
|
||||
91
RVO35/flow/helper/ErrorToServiceHandler.js
Executable file
91
RVO35/flow/helper/ErrorToServiceHandler.js
Executable file
|
|
@ -0,0 +1,91 @@
|
|||
const { MD5 } = require('./md5.js');
|
||||
const { networkInterfaces } = require('os');
|
||||
|
||||
class ErrorToServiceHandler {
|
||||
constructor() {
|
||||
this.previousValues = {};
|
||||
|
||||
this.project_id = undefined;
|
||||
|
||||
const nets = networkInterfaces();
|
||||
this.ipAddresses = {};
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
if (!this.ipAddresses[name]) {
|
||||
this.ipAddresses[name] = [];
|
||||
}
|
||||
this.ipAddresses[name].push(net.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setProjectId(project_id) {
|
||||
this.project_id = project_id;
|
||||
}
|
||||
|
||||
processMessage(message, seconds) {
|
||||
if (Array.isArray(message)) message = message.join(', ');
|
||||
|
||||
let key = MD5(message);
|
||||
let ts = Date.now();
|
||||
|
||||
//keep in memory - default value is 1h
|
||||
if (seconds === undefined) seconds = 60 * 60;
|
||||
|
||||
if (!this.previousValues.hasOwnProperty(key)) {
|
||||
this.previousValues[key] = { ts: ts, duration: seconds };
|
||||
}
|
||||
|
||||
let diff = (ts - this.previousValues[key].ts);
|
||||
if (diff < this.previousValues[key].duration * 1000) return false;
|
||||
|
||||
this.previousValues[key].ts = ts;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
sendMessageToService(message, seconds, message_type) {
|
||||
|
||||
// if error occures too early FLOW.GLOBALS.settings.project_id is still undefined
|
||||
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
|
||||
toService[message_type] = message;
|
||||
|
||||
console.log("ErrorToServiceHandler------------------------>send to service", toService);
|
||||
|
||||
RESTBuilder.make(function(builder) {
|
||||
builder.method('POST');
|
||||
builder.post(toService);
|
||||
builder.url('http://192.168.252.2:8004/sentmessage');
|
||||
|
||||
builder.callback(function(err, response, output) {
|
||||
console.log("process.on error send", err, response, output, toService);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const errorHandler = new ErrorToServiceHandler();
|
||||
|
||||
|
||||
module.exports = errorHandler;
|
||||
//module.exports = ErrorToServiceHandler;
|
||||
44
RVO35/flow/helper/db_helper.js
Executable file
44
RVO35/flow/helper/db_helper.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
function promisifyBuilder(builder)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
try{
|
||||
|
||||
builder.callback(function(err, response) {
|
||||
|
||||
if(err != null) reject(err);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function makeMapFromDbResult(response, ...keys)
|
||||
{
|
||||
let s = "-";
|
||||
let data = {};
|
||||
|
||||
for(let i = 0; i < response.length; i++)
|
||||
{
|
||||
let record = response[i];
|
||||
|
||||
let k = [];
|
||||
for(let j = 0; j < keys.length; j++)
|
||||
{
|
||||
k.push( record[keys[j]] );
|
||||
}
|
||||
|
||||
let key = k.join(s);
|
||||
data[ key ] = record;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
promisifyBuilder,
|
||||
makeMapFromDbResult
|
||||
}
|
||||
30
RVO35/flow/helper/logger.js
Executable file
30
RVO35/flow/helper/logger.js
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
//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 };
|
||||
5
RVO35/flow/helper/md5.js
Executable file
5
RVO35/flow/helper/md5.js
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
function MD5(d){var r = M(V(Y(X(d),8*d.length)));return r.toLowerCase()};function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
|
||||
|
||||
module.exports = {
|
||||
MD5
|
||||
}
|
||||
121
RVO35/flow/helper/notification_reporter.js
Executable file
121
RVO35/flow/helper/notification_reporter.js
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
//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);
|
||||
|
||||
let ERRWEIGHT = {
|
||||
EMERGENCY: "emergency", // System unusable
|
||||
ALERT: "alert", // Action must be taken immidiately
|
||||
CRITICAL: "critical", // Component unable to function
|
||||
ERROR: "error", // Error, but component able to recover from it
|
||||
WARNING: "warning", // Possibility of error, system running futher
|
||||
NOTICE: "notice", // Significant message but not an error, things user might want to know about
|
||||
INFO: "informational", // Info
|
||||
DEBUG: "debug" // Debug - only if CONFIG.debug is enabled
|
||||
};
|
||||
|
||||
function getKey(map, val) {
|
||||
return Object.keys(map).findItem(key => map[key] === val);
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/41117799/string-interpolation-on-variable
|
||||
var template = (tpl, args) => tpl.replace(/\${(\w+)}/g, (_, v) => args[v]);
|
||||
|
||||
|
||||
function initNotification() {
|
||||
notificationsData = FLOW.GLOBALS.notificationsData;
|
||||
rvoName = FLOW.GLOBALS.settings.rvo_name;
|
||||
}
|
||||
|
||||
|
||||
function sendNotification(func, device, key, params, extra, tb_output, instance, saveKey) {
|
||||
|
||||
let storeToSendValues = true;
|
||||
if (saveKey == undefined) storeToSendValues = false;
|
||||
|
||||
let weight = "";
|
||||
let message = {};
|
||||
|
||||
let notification = notificationsData[key];
|
||||
|
||||
if (notification) {
|
||||
weight = notification.weight.toLowerCase();
|
||||
|
||||
Object.keys(notification).forEach(item => {
|
||||
if (["en", "sk", "de", "cz", "it", "es"].includes(item)) {
|
||||
message[item] = rvoName + ": " + template(notification[item], params);
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
//console.error("sendNotification: Notifications: undefined key", key, func, notificationsData);
|
||||
console.error("sendNotification: Notifications: undefined key", key, func);
|
||||
return false;
|
||||
}
|
||||
|
||||
//detect invalid err weight
|
||||
if (getKey(ERRWEIGHT, weight) == undefined) {
|
||||
console.error("sendNotification: Notifications: undefined weight", weight, key, func);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sentValues.hasOwnProperty(saveKey)) {
|
||||
if (JSON.stringify(sentValues[saveKey]) == JSON.stringify(message)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sentValues[saveKey] == undefined) {
|
||||
if (storeToSendValues) {
|
||||
//do not send - flow is was started
|
||||
sentValues[saveKey] = message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (storeToSendValues) sentValues[saveKey] = message;
|
||||
|
||||
let content = {
|
||||
"type": weight,
|
||||
"status": "new",
|
||||
"source": {
|
||||
"func": func,
|
||||
"component": instance.id,
|
||||
"component_name": instance.name,
|
||||
"edge": device
|
||||
},
|
||||
"message": message,
|
||||
"message_data": extra
|
||||
};
|
||||
|
||||
let msg = {};
|
||||
msg[device] = [
|
||||
{
|
||||
"ts": Date.now(),
|
||||
"values": {
|
||||
"_event": content
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Msg can be outputted from components only after configuration
|
||||
/*if (canSendErrData()){
|
||||
sendBufferedErrors();
|
||||
} else {
|
||||
bufferError(msg);
|
||||
}*/
|
||||
|
||||
instance.send(tb_output, msg); // Even if error server is unavailable, send this message to output, for other possible component connections
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendNotification,
|
||||
ERRWEIGHT,
|
||||
initNotification
|
||||
}
|
||||
|
||||
144
RVO35/flow/helper/register.js
Executable file
144
RVO35/flow/helper/register.js
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
0 - cislo registra / prikaz
|
||||
1 - recepient - 0 = master, 1 = slave (slave je automaticky group a broadcast)
|
||||
2 - r/rw - read/write
|
||||
3- register name - nazov registra / prikazu (len pre info - zobrazenie v aplikacii)
|
||||
|
||||
4,5,6,7, - jednotlive byte-y - nazov byte-u
|
||||
4-7 - RES. a prazdny string "" sa nezobrazia!!!
|
||||
*/
|
||||
|
||||
//124 zaznamov
|
||||
const registers = [
|
||||
["0","1","R","Status","","",""],
|
||||
["1","1","RW","Dimming","R-Channel ","G-Channel","B-Channel ","W - Channel"],
|
||||
["2","1","R","Device types","","",".",""],
|
||||
["3","1","RW","Group addresses 1-4","Groups Add. 4","Groups Add. 3","Groups Add. 2","Groups Add. 1"],
|
||||
["4","1","RW","Group addresses 5-8","Groups Add. 8","Groups Add. 7","Groups Add. 6","Groups Add. 5"],
|
||||
["5","1","RW","Serial number (MAC)","","","",""],
|
||||
["6","1","RW","Time of dusk","HH","MM","SS","EXTRA"],
|
||||
["7","1","RW","Time of dawn","HH","MM","SS","EXTRA"],
|
||||
["8","1","RW","Time Schedule settings","TBD","TBD","Movement sensor","Time Schedule"],
|
||||
["9","1","RW","TS1 Time point 1","HH","MM","SS","Ext. Device"],
|
||||
["10","1","RW","TS1 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["11","1","RW","TS1 Time point 2","HH","MM","SS","Ext. Device"],
|
||||
["12","1","RW","TS1 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["13","1","RW","TS1 Time point 3","HH","MM","SS","Ext. Device"],
|
||||
["14","1","RW","TS1 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["15","1","RW","TS1 Time point 4","HH","MM","SS","Ext. Device"],
|
||||
["16","1","RW","TS1 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["17","1","RW","TS1 Time point 5","HH","MM","SS","Ext. Device"],
|
||||
["18","1","RW","TS1 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["19","1","RW","TS1 Time point 6","HH","MM","SS","Ext. Device"],
|
||||
["20","1","RW","TS1 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["21","1","RW","TS1 Time point 7","HH","MM","SS","Ext. Device"],
|
||||
["22","1","RW","TS1 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["23","1","RW","TS1 Time point 8","HH","MM","SS","Ext. Device"],
|
||||
["24","1","RW","TS1 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["25","1","RW","TS1 Time point 9","HH","MM","SS","Ext. Device"],
|
||||
["26","1","RW","TS1 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["27","1","RW","TS1 Time point 10","HH","MM","SS","Ext. Device"],
|
||||
["28","1","RW","TS1 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["29","1","RW","TS1 Time point 11","HH","MM","SS","Ext. Device"],
|
||||
["30","1","RW","TS1 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["31","1","RW","TS1 Time point 12","HH","MM","SS","Ext. Device"],
|
||||
["32","1","RW","TS1 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["33","1","RW","TS1 Time point 13","HH","MM","SS","Ext. Device"],
|
||||
["34","1","RW","TS1 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["35","1","RW","TS1 Time point 14","HH","MM","SS","Ext. Device"],
|
||||
["36","1","RW","TS1 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["37","1","RW","TS1 Time point 15","HH","MM","SS","Ext. Device"],
|
||||
["38","1","RW","TS1 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["39","1","RW","TS1 Time point 16","HH","MM","SS","Ext. Device"],
|
||||
["40","1","RW","TS1 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["41","1","RW","TS2 Time point 1","HH","MM","SS","Ext. Device"],
|
||||
["42","1","RW","TS2 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["43","1","RW","TS2 Time point 2","HH","MM","SS","Ext. Device"],
|
||||
["44","1","RW","TS2 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["45","1","RW","TS2 Time point 3","HH","MM","SS","Ext. Device"],
|
||||
["46","1","RW","TS2 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["47","1","RW","TS2 Time point 4","HH","MM","SS","Ext. Device"],
|
||||
["48","1","RW","TS2 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["49","1","RW","TS2 Time point 5","HH","MM","SS","Ext. Device"],
|
||||
["50","1","RW","TS2 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["51","1","RW","TS2 Time point 6","HH","MM","SS","Ext. Device"],
|
||||
["52","1","RW","TS2 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["53","1","RW","TS2 Time point 7","HH","MM","SS","Ext. Device"],
|
||||
["54","1","RW","TS2 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["55","1","RW","TS2 Time point 8","HH","MM","SS","Ext. Device"],
|
||||
["56","1","RW","TS2 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["57","1","RW","TS2 Time point 9","HH","MM","SS","Ext. Device"],
|
||||
["58","1","RW","TS2 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["59","1","RW","TS2 Time point 10","HH","MM","SS","Ext. Device"],
|
||||
["60","1","RW","TS2 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["61","1","RW","TS2 Time point 11","HH","MM","SS","Ext. Device"],
|
||||
["62","1","RW","TS2 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["63","1","RW","TS2 Time point 12","HH","MM","SS","Ext. Device"],
|
||||
["64","1","RW","TS2 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["65","1","RW","TS2 Time point 13","HH","MM","SS","Ext. Device"],
|
||||
["66","1","RW","TS2 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["67","1","RW","TS2 Time point 14","HH","MM","SS","Ext. Device"],
|
||||
["68","1","RW","TS2 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["69","1","RW","TS2 Time point 15","HH","MM","SS","Ext. Device"],
|
||||
["70","1","RW","TS2 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["71","1","RW","TS2 Time point 16","HH","MM","SS","Ext. Device"],
|
||||
["72","1","RW","TS2 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["73","1","RW","Power meter status","TBD","","",""],
|
||||
["74","1","R","Input Voltage","","","",""],
|
||||
["75","1","R","Input Current","","","",""],
|
||||
["76","1","R","Input Power","","","",""],
|
||||
["77","1","R","Cos phi","","","",""],
|
||||
["78","1","R","Frequency","","","",""],
|
||||
["79","1","RW","Energy","","","",""],
|
||||
["80","1","RW","Lifetime","","","",""],
|
||||
["81","1","RW","Power on cycles (input)","","","",""],
|
||||
["82","1","RW","Power on cycles (relay)","","","",""],
|
||||
["83","1","R","Time since last power on","","","",""],
|
||||
["84","1","R","Accelerometer data","","","",""],
|
||||
["85","1","RW","GPS latitude","pos/neg","deg","min ","sec"],
|
||||
["86","1","RW","GPS longitude","pos/neg","deg","min ","sec"],
|
||||
["87","1","RW","Actual time","HH","MM","SS","RES."],
|
||||
["88","1","RW","Actual date","Day of week","Day","Month","Year"],
|
||||
["89","1","R","Production data 1","","","",""],
|
||||
["90","1","R","Production data 2","","","",""],
|
||||
["91","1","RW","Network ID","NID3","NID2","NID1","NID0"],
|
||||
["95","1","RW","Actual Lux level from cabinet","RES.","RES.","HB","LB"],
|
||||
["96","1","RW","Threshold lux level","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
|
||||
["97","1","RW","Adjust period","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
|
||||
["98","1","RW","Offset","RES.","RES.","Dusk","Dawn"],
|
||||
["99","1","RW","CCT min/max range","max-H","max-L","min-H","min-L"],
|
||||
["100","1","RW","DALI interface","Cmd ID","Add","Cmd","Resp"],
|
||||
["101","1","RW","Module FW ver","v1","v2","v3","v4"],
|
||||
["102","1","RW","Module MAC-H","unused","unused","M1","M2"],
|
||||
["103","1","RW","Module MAC-L","M3","M4","M5","M6"],
|
||||
["122","1","R","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["123","1","R","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
|
||||
["124","1","R","FW update - data","Byte3","Byte2","Byte1","Byte0"],
|
||||
["0","0","R","Status","","","",""],
|
||||
["1","0","RW","Control register","RES.","RES.","RES.","init mode enable"],
|
||||
["2","0","R","Device types","","","",""],
|
||||
["3","0","R","Serial number (MAC)","","","",""],
|
||||
["4","0","R","Production data 1","","","",""],
|
||||
["5","0","R","Production data 2","","","",""],
|
||||
["6","0","RW","Network ID","NID3","NID2","NID1","NID0"],
|
||||
["7","0","RW","RS232 settings","param.","param.","Baudrate H","Baudrate L"],
|
||||
["8","0","R","Accelerometer data","","","",""],
|
||||
["9","0","RW","Module FW ver","v1","v2","v3","v4"],
|
||||
["10","0","RW","Module MAC-H","unused","unused","M1","M2"],
|
||||
["11","0","RW","Module MAC-L","M3","M4","M5","M6"],
|
||||
["32","0","RW","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["33","0","RW","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
|
||||
["34","0","RW","FW update - data","Byte3","Byte2","Byte1","Byte0"],
|
||||
["125","0","RW","Debug Register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["126","0","RW","Network Control Register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["127","0","R","Network Status Register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["128","0","RW","Node XX Serial Number Register","SER3","SER2","SER1","SER0"],
|
||||
["256","0","R","Node XX Network Status Register","","","",""]
|
||||
];
|
||||
|
||||
let register = {};
|
||||
|
||||
module.exports = {
|
||||
registers,
|
||||
register
|
||||
}
|
||||
99
RVO35/flow/helper/serialport_helper.js
Executable file
99
RVO35/flow/helper/serialport_helper.js
Executable file
|
|
@ -0,0 +1,99 @@
|
|||
const { exec } = require('child_process');
|
||||
|
||||
function openPort(port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
var callbackError = function(err) {
|
||||
port.removeListener('error', callbackError);
|
||||
port.removeListener('open', callbackError);
|
||||
|
||||
reject(err.message);
|
||||
};
|
||||
|
||||
var callbackOpen = function(data) {
|
||||
port.removeListener('error', callbackError);
|
||||
port.removeListener('open', callbackOpen);
|
||||
|
||||
resolve("port open: ok");
|
||||
};
|
||||
|
||||
port.on('error', callbackError);
|
||||
port.on('open', callbackOpen);
|
||||
|
||||
port.open();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function runSyncExec(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
if (error == null) resolve(stdout);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async function writeData(port, data, readbytes, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// If first item in data array is 255, we just write broadcast command to rsPort
|
||||
// We wait 3 seconds and resolve(["broadcast"])
|
||||
// It is important to resolve with array
|
||||
if (data[0] == 255) {
|
||||
|
||||
port.write(Buffer.from(data), function(err) {
|
||||
if (err) {
|
||||
reject(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
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 = {
|
||||
openPort,
|
||||
runSyncExec,
|
||||
writeData
|
||||
}
|
||||
317
RVO35/flow/helper/suncalc.js
Executable file
317
RVO35/flow/helper/suncalc.js
Executable file
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
(c) 2011-2015, Vladimir Agafonkin
|
||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
||||
https://github.com/mourner/suncalc
|
||||
*/
|
||||
|
||||
(function () { 'use strict';
|
||||
|
||||
// shortcuts for easier to read formulas
|
||||
|
||||
var PI = Math.PI,
|
||||
sin = Math.sin,
|
||||
cos = Math.cos,
|
||||
tan = Math.tan,
|
||||
asin = Math.asin,
|
||||
atan = Math.atan2,
|
||||
acos = Math.acos,
|
||||
rad = PI / 180;
|
||||
|
||||
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
|
||||
|
||||
|
||||
// date/time constants and conversions
|
||||
|
||||
var dayMs = 1000 * 60 * 60 * 24,
|
||||
J1970 = 2440588,
|
||||
J2000 = 2451545;
|
||||
|
||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
||||
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
|
||||
function toDays(date) { return toJulian(date) - J2000; }
|
||||
|
||||
|
||||
// general calculations for position
|
||||
|
||||
var e = rad * 23.4397; // obliquity of the Earth
|
||||
|
||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
||||
|
||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
||||
|
||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
||||
|
||||
function astroRefraction(h) {
|
||||
if (h < 0) // the following formula works for positive altitudes only.
|
||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
||||
|
||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
||||
}
|
||||
|
||||
// general sun calculations
|
||||
|
||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
||||
|
||||
function eclipticLongitude(M) {
|
||||
|
||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
||||
P = rad * 102.9372; // perihelion of the Earth
|
||||
|
||||
return M + C + P + PI;
|
||||
}
|
||||
|
||||
function sunCoords(d) {
|
||||
|
||||
var M = solarMeanAnomaly(d),
|
||||
L = eclipticLongitude(M);
|
||||
|
||||
return {
|
||||
dec: declination(L, 0),
|
||||
ra: rightAscension(L, 0)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var SunCalc = {};
|
||||
|
||||
|
||||
// calculates sun position for a given date and latitude/longitude
|
||||
|
||||
SunCalc.getPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = sunCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra;
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: altitude(H, phi, c.dec)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// sun times configuration (angle, morning name, evening name)
|
||||
|
||||
var times = SunCalc.times = [
|
||||
[-0.833, 'sunrise', 'sunset' ],
|
||||
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
|
||||
[ -6, 'dawn', 'dusk' ],
|
||||
[ -12, 'nauticalDawn', 'nauticalDusk'],
|
||||
[ -18, 'nightEnd', 'night' ],
|
||||
[ 6, 'goldenHourEnd', 'goldenHour' ]
|
||||
];
|
||||
|
||||
// adds a custom time to the times config
|
||||
|
||||
SunCalc.addTime = function (angle, riseName, setName) {
|
||||
times.push([angle, riseName, setName]);
|
||||
};
|
||||
|
||||
|
||||
// calculations for sun times
|
||||
|
||||
var J0 = 0.0009;
|
||||
|
||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
||||
|
||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
||||
|
||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
||||
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
|
||||
|
||||
// returns set time for the given sun altitude
|
||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
||||
|
||||
var w = hourAngle(h, phi, dec),
|
||||
a = approxTransit(w, lw, n);
|
||||
return solarTransitJ(a, M, L);
|
||||
}
|
||||
|
||||
|
||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
||||
// the observer height (in meters) relative to the horizon
|
||||
|
||||
SunCalc.getTimes = function (date, lat, lng, height) {
|
||||
|
||||
height = height || 0;
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
|
||||
dh = observerAngle(height),
|
||||
|
||||
d = toDays(date),
|
||||
n = julianCycle(d, lw),
|
||||
ds = approxTransit(0, lw, n),
|
||||
|
||||
M = solarMeanAnomaly(ds),
|
||||
L = eclipticLongitude(M),
|
||||
dec = declination(L, 0),
|
||||
|
||||
Jnoon = solarTransitJ(ds, M, L),
|
||||
|
||||
i, len, time, h0, Jset, Jrise;
|
||||
|
||||
|
||||
var result = {
|
||||
solarNoon: fromJulian(Jnoon),
|
||||
nadir: fromJulian(Jnoon - 0.5)
|
||||
};
|
||||
|
||||
for (i = 0, len = times.length; i < len; i += 1) {
|
||||
time = times[i];
|
||||
h0 = (time[0] + dh) * rad;
|
||||
|
||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
||||
Jrise = Jnoon - (Jset - Jnoon);
|
||||
|
||||
result[time[1]] = fromJulian(Jrise);
|
||||
result[time[2]] = fromJulian(Jset);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
||||
|
||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
||||
|
||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
||||
|
||||
l = L + rad * 6.289 * sin(M), // longitude
|
||||
b = rad * 5.128 * sin(F), // latitude
|
||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
||||
|
||||
return {
|
||||
ra: rightAscension(l, b),
|
||||
dec: declination(l, b),
|
||||
dist: dt
|
||||
};
|
||||
}
|
||||
|
||||
SunCalc.getMoonPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = moonCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra,
|
||||
h = altitude(H, phi, c.dec),
|
||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
||||
|
||||
h = h + astroRefraction(h); // altitude correction for refraction
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: h,
|
||||
distance: c.dist,
|
||||
parallacticAngle: pa
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// calculations for illumination parameters of the moon,
|
||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
|
||||
SunCalc.getMoonIllumination = function (date) {
|
||||
|
||||
var d = toDays(date || new Date()),
|
||||
s = sunCoords(d),
|
||||
m = moonCoords(d),
|
||||
|
||||
sdist = 149598000, // distance from Earth to Sun in km
|
||||
|
||||
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
|
||||
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
|
||||
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
|
||||
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
|
||||
|
||||
return {
|
||||
fraction: (1 + cos(inc)) / 2,
|
||||
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
|
||||
angle: angle
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function hoursLater(date, h) {
|
||||
return new Date(date.valueOf() + h * dayMs / 24);
|
||||
}
|
||||
|
||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
||||
|
||||
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
|
||||
var t = new Date(date);
|
||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
||||
else t.setHours(0, 0, 0, 0);
|
||||
|
||||
var hc = 0.133 * rad,
|
||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
||||
|
||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
||||
for (var i = 1; i <= 24; i += 2) {
|
||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
||||
|
||||
a = (h0 + h2) / 2 - h1;
|
||||
b = (h2 - h0) / 2;
|
||||
xe = -b / (2 * a);
|
||||
ye = (a * xe + b) * xe + h1;
|
||||
d = b * b - 4 * a * h1;
|
||||
roots = 0;
|
||||
|
||||
if (d >= 0) {
|
||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
||||
x1 = xe - dx;
|
||||
x2 = xe + dx;
|
||||
if (Math.abs(x1) <= 1) roots++;
|
||||
if (Math.abs(x2) <= 1) roots++;
|
||||
if (x1 < -1) x1 = x2;
|
||||
}
|
||||
|
||||
if (roots === 1) {
|
||||
if (h0 < 0) rise = i + x1;
|
||||
else set = i + x1;
|
||||
|
||||
} else if (roots === 2) {
|
||||
rise = i + (ye < 0 ? x2 : x1);
|
||||
set = i + (ye < 0 ? x1 : x2);
|
||||
}
|
||||
|
||||
if (rise && set) break;
|
||||
|
||||
h0 = h2;
|
||||
}
|
||||
|
||||
var result = {};
|
||||
|
||||
if (rise) result.rise = hoursLater(t, rise);
|
||||
if (set) result.set = hoursLater(t, set);
|
||||
|
||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// export as Node module / AMD module / browser variable
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
|
||||
else if (typeof define === 'function' && define.amd) define(SunCalc);
|
||||
else window.SunCalc = SunCalc;
|
||||
|
||||
}());
|
||||
161
RVO35/flow/helper/utils.js
Executable file
161
RVO35/flow/helper/utils.js
Executable file
|
|
@ -0,0 +1,161 @@
|
|||
const fs = require('fs').promises;
|
||||
|
||||
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 result = 0;
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
result = (result << 8) | buffer[i];
|
||||
}
|
||||
|
||||
return result >>> 0; //ensure it's an unsigned 32-bit number
|
||||
}
|
||||
|
||||
function resizeArray(arr, newSize, defaultValue) {
|
||||
while (newSize > arr.length)
|
||||
arr.push(defaultValue);
|
||||
arr.length = newSize;
|
||||
}
|
||||
|
||||
longToByteArray = function(/*long*/long) {
|
||||
// we want to represent the input as a 8-bytes array
|
||||
var byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
for (var index = 0; index < byteArray.length; index++) {
|
||||
var byte = long & 0xff;
|
||||
byteArray[index] = byte;
|
||||
long = (long - byte) / 256;
|
||||
}
|
||||
|
||||
return byteArray;
|
||||
};
|
||||
|
||||
function addDays(date, days) {
|
||||
var result = new Date(date);
|
||||
result.setDate(result.getDate() + days);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
sleep(2000).then(() => {
|
||||
// Do something after the sleep!
|
||||
|
||||
|
||||
});
|
||||
*/
|
||||
|
||||
function sleep(time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
function isEmptyObject(obj) {
|
||||
for (var name in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function emptyJsObject(jsObject) {
|
||||
Object.keys(jsObject).forEach(key => delete jsObject[key]);
|
||||
}
|
||||
|
||||
function convertUTCDateToLocalDate(date) {
|
||||
var newDate = new Date(date);
|
||||
newDate.setMinutes(date.getMinutes() + date.getTimezoneOffset());
|
||||
return newDate;
|
||||
}
|
||||
|
||||
function addZeroBefore(n) {
|
||||
return (n < 10 ? '0' : '') + n;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronously writes data to a file.
|
||||
*
|
||||
* @param {string} filePath The path to the file.
|
||||
* @param {string} data The data to write to the file.
|
||||
* @param {boolean} [append=false] If true, appends the data to the file. If false, it overwrites the file.
|
||||
*/
|
||||
async function writeToFile(filePath, data, append = false) {
|
||||
if (typeof data !== 'string') data = JSON.stringify(data, null, 2);
|
||||
try {
|
||||
if (append) {
|
||||
// Append the data to the end of the file. This is the simplest way to append.
|
||||
await fs.appendFile(filePath, data, 'utf8');
|
||||
console.log(`Successfully appended data to ${filePath} using fs.appendFile.`);
|
||||
} else {
|
||||
// Overwrite the file with the new data.
|
||||
await fs.writeFile(filePath, data, 'utf8');
|
||||
console.log(`Successfully wrote (overwrote) data to ${filePath} using fs.writeFile.`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error writing to file ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if an item is present in an array and adds it if it's not.
|
||||
* * @param {Array} arr The array to check.
|
||||
* @param {*} item The item to add.
|
||||
* @returns {Array} The modified array.
|
||||
*/
|
||||
const addToArrayIfUnique = (arr, item) => {
|
||||
if (!arr.includes(item)) {
|
||||
arr.push(item);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
|
||||
var convertBase = function() {
|
||||
|
||||
function convertBase(baseFrom, baseTo) {
|
||||
return function(num) {
|
||||
return parseInt(num, baseFrom).toString(baseTo);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// binary to decimal
|
||||
convertBase.bin2dec = convertBase(2, 10);
|
||||
|
||||
// binary to hexadecimal
|
||||
convertBase.bin2hex = convertBase(2, 16);
|
||||
|
||||
// decimal to binary
|
||||
convertBase.dec2bin = convertBase(10, 2);
|
||||
|
||||
// decimal to hexadecimal
|
||||
convertBase.dec2hex = convertBase(10, 16);
|
||||
|
||||
// hexadecimal to binary
|
||||
convertBase.hex2bin = convertBase(16, 2);
|
||||
|
||||
// hexadecimal to decimal
|
||||
convertBase.hex2dec = convertBase(16, 10);
|
||||
|
||||
return convertBase;
|
||||
}();
|
||||
|
||||
module.exports = {
|
||||
bytesToInt,
|
||||
longToByteArray,
|
||||
addDays,
|
||||
addZeroBefore,
|
||||
resizeArray,
|
||||
isEmptyObject,
|
||||
emptyJsObject,
|
||||
sleep,
|
||||
convertUTCDateToLocalDate,
|
||||
writeToFile,
|
||||
addToArrayIfUnique
|
||||
}
|
||||
137
RVO35/flow/httprequest.js
Executable file
137
RVO35/flow/httprequest.js
Executable file
|
|
@ -0,0 +1,137 @@
|
|||
exports.id = 'httprequest';
|
||||
exports.title = 'HTTP Request';
|
||||
exports.group = 'HTTP';
|
||||
exports.color = '#5D9CEC';
|
||||
exports.input = true;
|
||||
exports.version = '2.0.1';
|
||||
exports.output = 1;
|
||||
exports.author = 'Peter Širka';
|
||||
exports.icon = 'cloud-upload';
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="textbox" data-jc-path="url" class="m" data-jc-config="required:true;maxlength:500;placeholder:@(E.g. https\\://www.totaljs.com)">@(URL address)</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE">@(HTTP method)</div>
|
||||
</div>
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="dropdown" data-jc-path="stringify" data-jc-config="required:true;items:,URL encoded|encoded,JSON|json,RAW|raw,None|none">@(Serialization)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-jc="checkbox" data-jc-path="chunks">@(Download the content <b>in chunks</b>)</div>
|
||||
<div data-jc="checkbox" data-jc-path="persistentcookies">@(Keep persistent cookies)</div>
|
||||
<div data-jc="checkbox" data-jc-path="nodns">@(Disable DNS cache)</div>
|
||||
<div data-jc="checkbox" data-jc-path="keepalive">@(Keep alive connection)</div>
|
||||
</div>
|
||||
<hr class="nmt nmb" />
|
||||
<div class="padding">
|
||||
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
|
||||
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
|
||||
</div>
|
||||
<div class="padding bg-smoke">
|
||||
<section>
|
||||
<label><i class="fa fa-lock"></i>@(HTTP basic access authentication)</label>
|
||||
<div class="padding npb">
|
||||
<div class="row">
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="textbox" data-jc-path="username">@(User)</div>
|
||||
</div>
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="textbox" data-jc-path="userpassword">@(Password)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# Request
|
||||
|
||||
This component creates a request with received data.
|
||||
|
||||
__Response:__
|
||||
\`\`\`javascript
|
||||
{
|
||||
data: String,
|
||||
headers: Object,
|
||||
status: Number,
|
||||
host: String
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
__Dynamic arguments__:
|
||||
Are performed via FlowData repository and can be used for URL address or for custom headers/cookies/auth. Use \`repository\` component for creating of dynamic arguments. Dynamic values are replaced in the form \`{key}\`:
|
||||
|
||||
- url address e.g. \`https://.../{key}/\`
|
||||
- headers values e.g. \`{token}\`
|
||||
- cookies values e.g. \`{token}\``;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var can = false;
|
||||
var flags = null;
|
||||
var cookies2 = null;
|
||||
|
||||
instance.on('data', function(response) {
|
||||
can && instance.custom.send(response);
|
||||
});
|
||||
|
||||
instance.custom.send = function(response) {
|
||||
var options = instance.options;
|
||||
|
||||
var headers = null;
|
||||
var cookies = null;
|
||||
|
||||
options.headers && Object.keys(options.headers).forEach(function(key) {
|
||||
!headers && (headers = {});
|
||||
headers[key] = response.arg(options.headers[key]);
|
||||
});
|
||||
|
||||
if (options.username && options.userpassword) {
|
||||
!headers && (headers = {});
|
||||
headers['Authorization'] = 'Basic ' + U.createBuffer(response.arg(options.username + ':' + options.userpassword)).toString('base64');
|
||||
}
|
||||
|
||||
options.cookies && Object.keys(options.cookies).forEach(function(key) {
|
||||
!cookies && (cookies = {});
|
||||
cookies[key] = response.arg(options.cookies[key]);
|
||||
});
|
||||
|
||||
if (options.chunks) {
|
||||
U.download(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, response) {
|
||||
response.on('data', (chunks) => instance.send2(chunks));
|
||||
}, cookies || cookies2, headers);
|
||||
} else {
|
||||
U.request(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, data, status, headers, host) {
|
||||
if (response && !err) {
|
||||
response.data = { data: data, status: status, headers: headers, host: host };
|
||||
instance.send2(response);
|
||||
} else if (err)
|
||||
instance.error(err, response);
|
||||
}, cookies || cookies2, headers);
|
||||
}
|
||||
};
|
||||
|
||||
instance.reconfigure = function() {
|
||||
var options = instance.options;
|
||||
can = options.url && options.url && options.method && options.stringify ? true : false;
|
||||
instance.status(can ? '' : 'Not configured', can ? undefined : 'red');
|
||||
|
||||
if (!can)
|
||||
return;
|
||||
|
||||
flags = [];
|
||||
flags.push(options.method.toLowerCase());
|
||||
options.stringify === 'json' && flags.push('json');
|
||||
options.stringify === 'raw' && flags.push('raw');
|
||||
options.keepalive && flags.push('keepalive');
|
||||
!options.nodns && flags.push('dnscache');
|
||||
if (options.persistentcookies) {
|
||||
flags.push('cookies');
|
||||
cookies2 = {};
|
||||
} else
|
||||
cookies2 = null;
|
||||
};
|
||||
|
||||
instance.on('options', instance.reconfigure);
|
||||
instance.reconfigure();
|
||||
};
|
||||
76
RVO35/flow/httpresponse.js
Executable file
76
RVO35/flow/httpresponse.js
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
exports.id = 'httpresponse';
|
||||
exports.title = 'HTTP Response';
|
||||
exports.group = 'HTTP';
|
||||
exports.color = '#5D9CEC';
|
||||
exports.icon = 'arrow-right';
|
||||
exports.input = true;
|
||||
exports.output = ['#666D76'];
|
||||
exports.version = '2.0.0';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.readme = `# HTTP response
|
||||
|
||||
HTTP response will respond with data recieved using data-type set in Settings form or plain text if not set. Output is the message duration \`Number\` in seconds.`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="dropdown" data-jc-path="datatype" data-jc-config="required:true;items:,Empty response|emptyresponse,JSON|json,HTML|html,Plain text|plain,XML|xml">@(Response data-type)</div>
|
||||
<div class="help"><code>JSON</code> is by default.</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var dursum = 0;
|
||||
var durcount = 0;
|
||||
|
||||
instance.on('data', function(flowdata) {
|
||||
|
||||
var ctrl = flowdata.repository.controller;
|
||||
var data = flowdata.data;
|
||||
|
||||
if (!ctrl) {
|
||||
instance.throw('No controller to use for response!');
|
||||
return;
|
||||
}
|
||||
|
||||
durcount++;
|
||||
dursum += ((new Date() - flowdata.begin) / 1000).floor(2);
|
||||
setTimeout2(instance.id, instance.custom.duration, 500, 10);
|
||||
|
||||
ctrl.$flowdata = flowdata;
|
||||
|
||||
var datatype = instance.options.datatype || 'json';
|
||||
if (datatype === 'emptyresponse')
|
||||
return ctrl.plain('');
|
||||
|
||||
if (datatype !== 'json' && typeof(data) !== 'string') {
|
||||
instance.throw('Incorect type of data, expected string, got ' + typeof(data));
|
||||
ctrl.plain(data == null ? '' : data.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
switch(datatype) {
|
||||
case 'html':
|
||||
ctrl.content(data, 'text/html');
|
||||
break;
|
||||
case 'plain':
|
||||
ctrl.plain(data);
|
||||
break;
|
||||
case 'xml':
|
||||
ctrl.content(data, 'text/xml');
|
||||
break;
|
||||
default:
|
||||
ctrl.json(data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
instance.on('service', function() {
|
||||
dursum = 0;
|
||||
durcount = 0;
|
||||
});
|
||||
|
||||
instance.custom.duration = function() {
|
||||
var avg = (dursum / durcount).floor(2);
|
||||
instance.status(avg + ' sec.');
|
||||
instance.send2(0, avg);
|
||||
};
|
||||
};
|
||||
326
RVO35/flow/httproute.js
Executable file
326
RVO35/flow/httproute.js
Executable file
|
|
@ -0,0 +1,326 @@
|
|||
exports.id = 'httproute';
|
||||
exports.title = 'HTTP Route';
|
||||
exports.group = 'HTTP';
|
||||
exports.color = '#5D9CEC';
|
||||
exports.icon = 'globe';
|
||||
exports.input = false;
|
||||
exports.output = ['#6CAC5A', '#37BC9B'];
|
||||
exports.version = '1.2.3';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.cloning = false;
|
||||
exports.options = { method: 'GET', url: '', size: 5, cacheexpire: '5 minutes', cachepolicy: 0, timeout: 5 };
|
||||
exports.dateupdated = '2021-01-21 18:30d';
|
||||
exports.readme = `# HTTP route
|
||||
|
||||
__Outputs__:
|
||||
- first output: raw data (cache is empty or is disabled)
|
||||
- second output: cached data
|
||||
|
||||
If one of the outputs is disabled then automatic responce with code "503 service unavailable" is sent.
|
||||
|
||||
When a request comes in bellow object is available at \`flowdata.data\`:
|
||||
|
||||
\`\`\`javascript
|
||||
{
|
||||
params: { id: '1' }, // params for dynamic routes, e.g. /test/{id}
|
||||
query: { msg: 'Hello' }, // parsed query string, e.g. /test/1?msg=Hello
|
||||
body: { test: 'OK' }, // object if json requests otherwise string
|
||||
headers: {}, // headers data
|
||||
session: {}, // session data
|
||||
user: {}, // user data
|
||||
files: [], // uploaded files
|
||||
url: '/users/', // a relative URL address
|
||||
referrer: '/', // referrer
|
||||
mobile: false, // determines mobile device
|
||||
robot: false, // determines search robots/crawlsers
|
||||
language: 'en' // determines language
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
See [documentation for flags](https://docs.totaljs.com/latest/en.html#api~HttpRouteOptionsFlags~unauthorize). These method flags are set automatically e.g. \`get, post, put, patch or delete\`
|
||||
|
||||
---
|
||||
|
||||
\`id:ROUTE_ID\` flag cannot be used since it's already used by this component internally`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<section>
|
||||
<label>@(Main settings)</label>
|
||||
<div class="padding npb">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE,PATCH,OPTIONS">@(HTTP method)</div>
|
||||
</div>
|
||||
<div class="col-md-9 m">
|
||||
<div data-jc="textbox" data-jc-path="url" data-jc-config="required:true;maxlength:500;placeholder:/api/test;error:URL already in use">@(URL address)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="textbox" data-jc-path="flags" data-jc-config="placeholder:json">@(Additional flags)</div>
|
||||
<div class="help m">@(Separate flags by comma e.g. <code>json, authorize</code>)</div>
|
||||
</div>
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="size" data-jc-config="placeholder:@(in kB);increment:true;type:number;maxlength:10;align:center">@(Max. request size)</div>
|
||||
<div class="help m">@(In <code>kB</code> kilobytes)</div>
|
||||
</div>
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="timeout" data-jc-config="placeholder:@(in seconds);increment:true;type:number;maxlength:5;align:center">@(Timeout)</div>
|
||||
<div class="help m">@(In seconds.)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<br />
|
||||
<div data-jc="checkbox" data-jc-path="emptyresponse" class="b black">@(Automatically respond with 200 OK?)</div>
|
||||
<div class="help m">@(If not checked you need to use HTTP response component to respond to the request.)</div>
|
||||
<hr />
|
||||
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
|
||||
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
|
||||
</div>
|
||||
<hr class="nmt" />
|
||||
<div class="padding npt">
|
||||
<div class="row">
|
||||
<div class="col-md-9 m">
|
||||
<div data-jc="dropdown" data-jc-path="cachepolicy" data-jc-config="type:number;items:@(no cache)|0,@(URL)|1,@(URL + query string)|2,@(URL + query string + user instance)|3">@(Cache policy)</div>
|
||||
<div class="help">@(User instance must contain <code>id</code> property.)</div>
|
||||
</div>
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="cacheexpire" data-jc-config="maxlength:20;align:center">@(Expiration)</div>
|
||||
<div class="help">@(E.g. <code>5 minutes</code>)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
var httproute_currenturl = '';
|
||||
var httproute_currentmethod = 'GET';
|
||||
|
||||
ON('open.httproute', function(component, options) {
|
||||
if (options.flags instanceof Array) {
|
||||
var method = options.method.toLowerCase();
|
||||
options.flags = options.flags.remove(function(item) {
|
||||
switch (typeof(item)) {
|
||||
case 'string':
|
||||
return item.substring(0, 3) === 'id:' || item === method;
|
||||
case 'number': // timeout
|
||||
return true;
|
||||
}
|
||||
}).join(', ');
|
||||
}
|
||||
if (component.isnew) {
|
||||
options.url = '';
|
||||
options.name = '';
|
||||
} else {
|
||||
httproute_currenturl = options.url;
|
||||
httproute_currentmethod = options.method;
|
||||
}
|
||||
});
|
||||
|
||||
WATCH('settings.httproute.url', httproutecheckurl);
|
||||
WATCH('settings.httproute.method', httproutecheckurl);
|
||||
|
||||
function httproutecheckurl() {
|
||||
if (httproute_currenturl !== settings.httproute.url || httproute_currentmethod !== settings.httproute.method) {
|
||||
TRIGGER('httproutecheckurl', { url: settings.httproute.url, method: settings.httproute.method }, function(e) {
|
||||
var p = 'settings.httproute.url';
|
||||
if (e) {
|
||||
// invalid
|
||||
INVALID(p);
|
||||
} else {
|
||||
if (!CAN(p))
|
||||
RESET(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ON('save.httproute', function(component, options) {
|
||||
!component.name && (component.name = options.method + ' ' + options.url);
|
||||
|
||||
var builder = [];
|
||||
builder.push('### @(Configuration)');
|
||||
builder.push('');
|
||||
builder.push('- __' + options.method + ' ' + options.url + '__');
|
||||
builder.push('- @(flags): ' + options.flags);
|
||||
builder.push('- @(maximum request data length): __' + options.size + ' kB__');
|
||||
builder.push('- @(empty response): __' + options.emptyresponse + '__');
|
||||
|
||||
if (options.headers) {
|
||||
var headers = [];
|
||||
Object.keys(options.headers).forEach(function(key){
|
||||
headers.push(key + ': ' + options.headers[key]);
|
||||
});
|
||||
headers.length && builder.push('- @(headers):\\n\`\`\`' + headers.join('\\n') + '\`\`\`');
|
||||
};
|
||||
|
||||
var cp = '@(no cache)';
|
||||
if (options.cachepolicy === 1)
|
||||
cp = '@(URL)';
|
||||
if (options.cachepolicy === 2)
|
||||
cp = '@(URL + query string)';
|
||||
|
||||
if (options.cachepolicy === 3)
|
||||
cp = '@(URL + query string + user instance)';
|
||||
|
||||
builder.push('- @(cache policy): __' + cp + '__');
|
||||
|
||||
options.cacheexpire && builder.push('- @(cache expire): __' + options.cacheexpire + '__');
|
||||
|
||||
component.notes = builder.join('\\n');
|
||||
});
|
||||
</script>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var route;
|
||||
|
||||
var uninstall = function(id) {
|
||||
if (F.is4) {
|
||||
route && route.remove();
|
||||
route = null;
|
||||
} else
|
||||
UNINSTALL('route', id);
|
||||
};
|
||||
|
||||
instance.custom.emptyresponse = function(self) {
|
||||
self.plain();
|
||||
};
|
||||
|
||||
instance.reconfigure = function() {
|
||||
|
||||
var options = instance.options;
|
||||
|
||||
if (!options.url) {
|
||||
instance.status('Not configured', 'red');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(options.flags) === 'string')
|
||||
options.flags = options.flags.split(',').trim();
|
||||
|
||||
uninstall('id:' + instance.id);
|
||||
|
||||
var flags = options.flags || [];
|
||||
|
||||
flags.push('id:' + instance.id);
|
||||
|
||||
if (!F.is4)
|
||||
flags.push(options.method.toLowerCase());
|
||||
|
||||
options.timeout && flags.push(options.timeout * 1000);
|
||||
|
||||
// Make unique values
|
||||
flags = flags.filter(function(v, i, a) {
|
||||
if(F.is4 && v.toString().toLowerCase() === options.method.toLowerCase())
|
||||
return false; // remove method
|
||||
return a.indexOf(v) === i;
|
||||
});
|
||||
|
||||
options.flags = flags;
|
||||
var handler = function() {
|
||||
|
||||
if (instance.paused || (instance.isDisabled && (instance.isDisabled('output', 0) || instance.isDisabled('output', 1)))) {
|
||||
instance.status('503 Service Unavailable');
|
||||
this.status = 503;
|
||||
this.json();
|
||||
return;
|
||||
}
|
||||
|
||||
var key;
|
||||
var self = this;
|
||||
|
||||
if (instance.options.emptyresponse) {
|
||||
instance.status('200 OK');
|
||||
setTimeout(instance.custom.emptyresponse, 100, self);
|
||||
|
||||
if (instance.hasConnection(0)) {
|
||||
var data = instance.make({
|
||||
query: self.query,
|
||||
body: self.body,
|
||||
session: self.session,
|
||||
user: self.user,
|
||||
files: self.files,
|
||||
headers: self.req.headers,
|
||||
url: self.url,
|
||||
params: self.params
|
||||
});
|
||||
instance.send2(0, data);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (instance.options.cachepolicy) {
|
||||
case 1: // URL
|
||||
key = 'rro' + instance.id + self.url.hash();
|
||||
break;
|
||||
case 2: // URL + query
|
||||
case 3: // URL + query + user
|
||||
key = self.url;
|
||||
var keys = Object.keys(self.query);
|
||||
keys.sort();
|
||||
for (var i = 0, length = keys.length; i < length; i++)
|
||||
key += keys[i] + self.query[keys[i]] + '&';
|
||||
if (instance.options.cachepolicy === 3 && self.user)
|
||||
key += 'iduser' + self.user.id;
|
||||
key = 'rro' + instance.id + key.hash();
|
||||
break;
|
||||
}
|
||||
|
||||
if (key && F.cache.get2(key)) {
|
||||
var data = instance.make(F.cache.get2(key));
|
||||
data.repository.controller = self;
|
||||
instance.send2(1, data);
|
||||
} else {
|
||||
|
||||
var data = instance.make({
|
||||
query: self.query,
|
||||
body: self.body,
|
||||
session: self.session,
|
||||
user: self.user,
|
||||
files: self.files,
|
||||
headers: self.req.headers,
|
||||
url: self.url,
|
||||
params: self.params,
|
||||
mobile: self.mobile,
|
||||
robot: self.robot,
|
||||
referrer: self.referrer,
|
||||
language: self.language
|
||||
});
|
||||
|
||||
data.repository.controller = self;
|
||||
instance.send2(0, data);
|
||||
key && FINISHED(self.res, () => F.cache.set(key, self.$flowdata.data, instance.options.cacheexpire));
|
||||
}
|
||||
};
|
||||
|
||||
if (F.is4)
|
||||
route = ROUTE(options.method.toUpperCase() + ' ' + options.url, handler, flags, options.size || 5);
|
||||
else
|
||||
F.route(options.url, handler, flags, options.size || 5);
|
||||
|
||||
instance.status('Listening', 'green');
|
||||
};
|
||||
|
||||
instance.reconfigure();
|
||||
instance.on('options', instance.reconfigure);
|
||||
instance.on('close', function(){
|
||||
uninstall('id:' + instance.id);
|
||||
});
|
||||
};
|
||||
|
||||
// check url exists
|
||||
FLOW.trigger('httproutecheckurl', function(next, data) {
|
||||
var url = data.url;
|
||||
var method = data.method;
|
||||
if (url[url.length - 1] !== '/')
|
||||
url += '/';
|
||||
var exists = F.routes.web.findItem(r => r.urlraw === url && r.method === method);
|
||||
next(exists != null);
|
||||
});
|
||||
|
||||
exports.uninstall = function() {
|
||||
FLOW.trigger('httproutecheckurl', null);
|
||||
};
|
||||
81
RVO35/flow/infosender.js
Executable file
81
RVO35/flow/infosender.js
Executable file
|
|
@ -0,0 +1,81 @@
|
|||
exports.id = 'infosender';
|
||||
exports.title = 'Info sender';
|
||||
exports.version = '1.0.0';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#2134B0';
|
||||
exports.input = 2;
|
||||
exports.output = 1
|
||||
exports.icon = 'bolt';
|
||||
|
||||
const { networkInterfaces } = require('os');
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">CSV Import</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# send all data to projects.worksys.io, required to monitor status of controller(unipi)`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
let id;
|
||||
let allValues = {};
|
||||
let sendAllValuesInterval;
|
||||
let configured = false;
|
||||
|
||||
let now = new Date();
|
||||
console.log(exports.title, "INSTALLED", now.toLocaleString("sk-SK"));
|
||||
|
||||
const nets = networkInterfaces();
|
||||
let ipAddresses = Object.create(null); // Or just '{}', an empty object
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
if (!ipAddresses[name]) {
|
||||
ipAddresses[name] = [];
|
||||
}
|
||||
ipAddresses[name].push(net.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendValues() {
|
||||
if (!configured) return;
|
||||
|
||||
if (Object.keys(allValues).length > 0) {
|
||||
let dataToSend = { ...allValues };
|
||||
dataToSend.id = id;
|
||||
dataToSend.ipAddresses = ipAddresses;
|
||||
|
||||
instance.send(0, dataToSend);
|
||||
|
||||
allValues = {};
|
||||
}
|
||||
}
|
||||
|
||||
instance.on("close", () => {
|
||||
clearInterval(sendAllValuesInterval);
|
||||
})
|
||||
|
||||
instance.on("0", _ => {
|
||||
id = FLOW.GLOBALS.settings.project_id;
|
||||
if (id) configured = true;
|
||||
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);
|
||||
})
|
||||
|
||||
sendAllValuesInterval = setInterval(() => {
|
||||
sendValues();
|
||||
}, 60000 * 3);
|
||||
|
||||
}
|
||||
|
||||
346
RVO35/flow/modbus_reader.js
Executable file
346
RVO35/flow/modbus_reader.js
Executable file
|
|
@ -0,0 +1,346 @@
|
|||
exports.id = 'modbus_reader';
|
||||
exports.title = 'Modbus reader';
|
||||
exports.version = '2.0.0';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#2134B0';
|
||||
exports.input = 1;
|
||||
exports.output = ["red", "white", "yellow"];
|
||||
exports.click = false;
|
||||
exports.author = 'Rastislav Kovac';
|
||||
exports.icon = 'bolt';
|
||||
exports.readme = `
|
||||
Modbus requests to modbus devices (electromer, twilight sensor, thermometer.
|
||||
Component keeps running arround deviceConfig array in "timeoutInterval" intervals. Array items are objects with single modbus devices.
|
||||
Everything is sent to dido_controller. If requests to device fail (all registers must fail to send NOK status) , we send "NOK-'device'" status to dido_controller.
|
||||
This device needs to be configured in dido_controller!!! Double check if it is. In dido_controller we calculate final status and all values with status are pushed to tb.
|
||||
`;
|
||||
|
||||
const modbus = require('jsmodbus');
|
||||
const SerialPort = require('serialport');
|
||||
const { timeoutInterval, deviceConfig } = require("../databases/modbus_config");
|
||||
const { sendNotification } = require('./helper/notification_reporter');
|
||||
|
||||
const DELAY_BETWEEN_DEVICES = 10000;
|
||||
|
||||
const SEND_TO = {
|
||||
debug: 0,
|
||||
dido_controller: 1,
|
||||
tb: 2
|
||||
};
|
||||
|
||||
//to handle NOK and OK sendNotifications s
|
||||
const numberOfNotResponding = {};
|
||||
let tbName = null;
|
||||
let mainSocket;
|
||||
//number of phases inRVO
|
||||
let phases;
|
||||
//phases where voltage is 0 (set)
|
||||
let noVoltage;
|
||||
let energyToSwitchLamps;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
class SocketWithClients {
|
||||
|
||||
constructor() {
|
||||
this.stream = null;
|
||||
this.socket = null;
|
||||
this.clients = {};
|
||||
this.allValues = {};
|
||||
this.errors = 0;
|
||||
this.index = 0;
|
||||
this.timeoutInterval = 5000;
|
||||
|
||||
// we need to go always around for all devices. So we need index value, device address, as well as number of registers for single device
|
||||
this.deviceAddress = null; // device address (1 - EM340 and 2 for twilight_sensor)
|
||||
this.indexInDeviceConfig = 0; // first item in deviceConfig
|
||||
this.lengthOfActualDeviceStream = null;
|
||||
this.device = null;
|
||||
|
||||
// lampSwitchNotification helper variables
|
||||
this.onNotificationSent = false;
|
||||
this.offNotificationSent = false;
|
||||
this.phases = this.buildPhases();
|
||||
|
||||
this.startSocket();
|
||||
}
|
||||
|
||||
buildPhases = () => {
|
||||
let a = [];
|
||||
for (let i = 1; i <= phases; i++) {
|
||||
a.push(`Phase_${i}_voltage`)
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
startSocket = () => {
|
||||
|
||||
let obj = this;
|
||||
|
||||
if (this.socket) {
|
||||
this.socket.removeAllListeners();
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
this.socket = new SerialPort("/dev/ttymxc0", {
|
||||
baudRate: 9600,
|
||||
})
|
||||
|
||||
// we create a client for every deviceAddress ( = address) in list and push them into dictionary
|
||||
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.socket.on('error', function(e) {
|
||||
console.log('Modbus_reader: Socket connection error', e); //'ECONNREFUSED' or 'ECONNRESET' ??
|
||||
});
|
||||
|
||||
this.socket.on('close', function() {
|
||||
console.log('Modbus_reader: Socket connection closed - Waiting 10 seconds before connecting again');
|
||||
setTimeout(obj.startSocket, 10000);
|
||||
});
|
||||
|
||||
this.socket.on('open', function() {
|
||||
console.log("socket connected");
|
||||
obj.getActualStreamAndDevice();
|
||||
obj.timeoutInterval = timeoutInterval - DELAY_BETWEEN_DEVICES; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
getActualStreamAndDevice = () => {
|
||||
const dev = deviceConfig[this.indexInDeviceConfig];
|
||||
this.index = 0;
|
||||
this.errors = 0;
|
||||
this.stream = dev.stream;
|
||||
this.lengthOfActualDeviceStream = dev.stream.length;
|
||||
this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number
|
||||
this.device = dev.device; //em340, twilight_sensor
|
||||
|
||||
//if we just start to loop devices from the beginning, or there is just 1 device in config, we wait whole timeoutInterval
|
||||
if (this.indexInDeviceConfig == 0 || deviceConfig.length === 1) setTimeout(this.readRegisters, this.timeoutInterval);
|
||||
else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES);
|
||||
}
|
||||
|
||||
readRegisters = () => {
|
||||
|
||||
const str = this.stream[this.index];
|
||||
const register = str.register;
|
||||
const size = str.size;
|
||||
const tbAttribute = str.tbAttribute;
|
||||
|
||||
let obj = this;
|
||||
|
||||
this.clients[this.deviceAddress].readHoldingRegisters(register, size)
|
||||
.then(function(resp) {
|
||||
|
||||
resp = resp.response._body.valuesAsArray; //resp is array of length 1 or 2, f.e. [2360,0]
|
||||
// console.log(deviceAddress, register, tbAttribute, resp);
|
||||
|
||||
//device is responding again after NOK status
|
||||
if (numberOfNotResponding.hasOwnProperty(obj.device)) {
|
||||
let message = "";
|
||||
if (obj.device == "em340") {
|
||||
message = "electrometer_ok";
|
||||
}
|
||||
else if (obj.device == "twilight_sensor") {
|
||||
message = "twilight_sensor_ok";
|
||||
}
|
||||
message && sendNotification("modbus_reader: readRegisters", tbName, message, {}, "", SEND_TO.tb, instance);
|
||||
delete numberOfNotResponding[obj.device];
|
||||
}
|
||||
|
||||
obj.transformResponse(resp, register);
|
||||
|
||||
//obj.errors = 0;
|
||||
obj.index++;
|
||||
obj.readAnotherRegister();
|
||||
|
||||
}).catch(function() {
|
||||
|
||||
//console.log("errors pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute);
|
||||
|
||||
obj.errors++;
|
||||
if (obj.errors == obj.lengthOfActualDeviceStream) {
|
||||
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 ??
|
||||
if (!numberOfNotResponding.hasOwnProperty(obj.device)) {
|
||||
let message = "";
|
||||
if (obj.device == "twilight_sensor") {
|
||||
message = "twilight_sensor_nok";
|
||||
}
|
||||
else if (obj.device == "em340") {
|
||||
message = "electrometer_nok";
|
||||
}
|
||||
message && sendNotification("modbus_reader: readingTimeouted", tbName, message, {}, "", SEND_TO.tb, instance);
|
||||
numberOfNotResponding[obj.device] = 1;
|
||||
}
|
||||
|
||||
obj.errors = 0;
|
||||
numberOfNotResponding[obj.device] += 1;
|
||||
}
|
||||
|
||||
// console.error(require('util').inspect(arguments, {
|
||||
// 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 (obj.index + 1 >= obj.lengthOfActualDeviceStream) {
|
||||
if (!isObjectEmpty(obj.allValues)) instance.send(SEND_TO.dido_controller, { values: obj.allValues });
|
||||
obj.allValues = {};
|
||||
}
|
||||
obj.index++;
|
||||
obj.readAnotherRegister();
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
readAnotherRegister = () => {
|
||||
if (this.index < this.lengthOfActualDeviceStream) setTimeout(this.readRegisters, 0);
|
||||
else this.setNewStream();
|
||||
}
|
||||
|
||||
transformResponse = (response, register) => {
|
||||
|
||||
for (let i = 0; i < this.lengthOfActualDeviceStream; i++) {
|
||||
|
||||
let a = this.stream[i];
|
||||
if (a.register === register) {
|
||||
let tbAttribute = a.tbAttribute;
|
||||
let multiplier = a.multiplier;
|
||||
|
||||
let value = this.calculateValue(response, multiplier);
|
||||
// console.log(register, tbName, tbAttribute, response, a.multiplier, value);
|
||||
|
||||
// if(tbName == undefined) return;
|
||||
|
||||
if (this.index + 1 < this.lengthOfActualDeviceStream) {
|
||||
this.allValues[tbAttribute] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const values = {
|
||||
...this.allValues,
|
||||
[tbAttribute]: value,
|
||||
};
|
||||
|
||||
this.checkNullVoltage(values);
|
||||
this.lampSwitchNotification(values);
|
||||
|
||||
instance.send(SEND_TO.dido_controller, { values: values });
|
||||
|
||||
this.allValues = {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNewStream = () => {
|
||||
if (this.lengthOfActualDeviceStream == this.index) {
|
||||
if (this.indexInDeviceConfig + 1 == deviceConfig.length) {
|
||||
this.indexInDeviceConfig = 0;
|
||||
}
|
||||
else {
|
||||
this.indexInDeviceConfig += 1;
|
||||
}
|
||||
|
||||
this.getActualStreamAndDevice();
|
||||
}
|
||||
}
|
||||
|
||||
calculateValue = (response, multiplier) => {
|
||||
let value = 0;
|
||||
|
||||
let l = response.length;
|
||||
if (l === 2) {
|
||||
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
|
||||
{
|
||||
value = value - "0xFFFFFFFF" + 1;
|
||||
}
|
||||
}
|
||||
else if (l === 1) {
|
||||
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
|
||||
{
|
||||
value = value - "0xFFFF" + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.round(value * multiplier * 10) / 10;
|
||||
}
|
||||
|
||||
checkNullVoltage = (values) => {
|
||||
|
||||
if (!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return;
|
||||
|
||||
Object.keys(values).map(singleValue => {
|
||||
if (this.phases.includes(singleValue)) {
|
||||
let l = singleValue.split("_");
|
||||
let phase = parseInt(l[1]);
|
||||
|
||||
// console.log(values[singleValue], tbName);
|
||||
|
||||
if (values[singleValue] == 0) {
|
||||
noVoltage.add(phase);
|
||||
sendNotification("modbus_reader: checkNullVoltage", tbName, "no_voltage_on_phase", { phase: phase }, "", SEND_TO.tb, instance, "voltage" + phase);
|
||||
// console.log('no voltage')
|
||||
}
|
||||
else {
|
||||
noVoltage.delete(phase);
|
||||
// console.log('voltage detected')
|
||||
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
|
||||
*/
|
||||
lampSwitchNotification = (values) => {
|
||||
|
||||
if (!values.hasOwnProperty("total_power")) return;
|
||||
|
||||
const actualTotalPower = values.total_power;
|
||||
|
||||
if (actualTotalPower > energyToSwitchLamps && this.onNotificationSent == false) {
|
||||
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_on", {}, "", SEND_TO.tb, instance);
|
||||
this.onNotificationSent = true;
|
||||
this.offNotificationSent = false;
|
||||
}
|
||||
else if (actualTotalPower <= energyToSwitchLamps && this.offNotificationSent == false) {
|
||||
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_off", {}, "", SEND_TO.tb, instance);
|
||||
this.onNotificationSent = false;
|
||||
this.offNotificationSent = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const isObjectEmpty = (objectName) => {
|
||||
return Object.keys(objectName).length === 0 && objectName.constructor === Object;
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
phases = FLOW.GLOBALS.settings.phases;
|
||||
tbName = FLOW.GLOBALS.settings.rvoTbName;
|
||||
noVoltage = FLOW.GLOBALS.settings.no_voltage;
|
||||
energyToSwitchLamps = FLOW.GLOBALS.settings.energy_to_switch_lamps / 2.5; //half value is enought to show if lamps are turned on or off
|
||||
if (deviceConfig.length) mainSocket = new SocketWithClients();
|
||||
else console.log("Modbus_reader: no modbus device in configuration");
|
||||
|
||||
// this notification is to show, that flow (unipi) has been restarted
|
||||
sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance);
|
||||
}
|
||||
|
||||
instance.on("0", function(_) {
|
||||
main();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
156
RVO35/flow/monitorconsumption.js
Executable file
156
RVO35/flow/monitorconsumption.js
Executable file
|
|
@ -0,0 +1,156 @@
|
|||
exports.id = 'monitorconsumption';
|
||||
exports.title = 'Consumption';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Peter Širka';
|
||||
exports.group = 'Monitoring';
|
||||
exports.color = '#967ADC';
|
||||
exports.input = 0;
|
||||
exports.output = 1;
|
||||
exports.icon = 'bug';
|
||||
exports.options = { interval: 5000, enabled: true, monitorconsumption: true, monitorsize: true, monitorconnections: true, monitorfiles: true };
|
||||
exports.click = true;
|
||||
exports.readme = `# Consumption monitoring
|
||||
|
||||
This component measure CPU and memory consumption, open files and open connections of this application. It uses these Linux commands: \`ps\`, \`lsof\`, \`netstat\` and \`df\`.
|
||||
|
||||
__Data Example__:
|
||||
|
||||
\`\`\`javascript
|
||||
{
|
||||
cpu: 0, // percentage
|
||||
memory: 4096, // in bytes
|
||||
size: 34303, // directory size in bytes
|
||||
files: 34, // count of open files
|
||||
connections: 343, // count of connections
|
||||
uptime: '1-12:34:00'
|
||||
}
|
||||
\`\`\``;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div data-jc="checkbox" data-jc-path="monitorconsumption">Monitor: Consumption + uptime</div>
|
||||
<div data-jc="checkbox" data-jc-path="monitorfiles">Monitor: Count of open files</div>
|
||||
<div data-jc="checkbox" data-jc-path="monitorconnections">Monitor: Count of open connections</div>
|
||||
<div data-jc="checkbox" data-jc-path="monitorsize">Monitor: Directory size</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var current = { cpu: 0, memory: 0, files: 0, connections: 0, size: 0, uptime: '', counter: 0 };
|
||||
var tproc = null;
|
||||
var Exec = require('child_process').exec;
|
||||
var reg_empty = /\s{2,}/g;
|
||||
var reg_appdisksize = /^[\d.,]+/;
|
||||
|
||||
instance.custom.run = function() {
|
||||
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
|
||||
var arr = [];
|
||||
|
||||
// Get CPU and Memory consumption
|
||||
instance.options.monitorconsumption && arr.push(function(next) {
|
||||
Exec('ps -p {0} -o %cpu,rss,etime'.format(process.pid), function(err, response) {
|
||||
|
||||
if (err) {
|
||||
instance.throw(err);
|
||||
} else {
|
||||
var line = response.split('\n')[1];
|
||||
line = line.trim().replace(reg_empty, ' ').split(' ');
|
||||
var cpu = line[0].parseFloat();
|
||||
current.cpu = cpu.floor(1);
|
||||
current.memory = line[1].parseInt2() * 1024; // kB to bytes
|
||||
current.uptime = line[2];
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
// Get count of open files
|
||||
instance.options.monitorfiles && arr.push(function(next) {
|
||||
Exec('lsof -a -p {0} | wc -l'.format(process.pid), function(err, response) {
|
||||
if (err)
|
||||
instance.throw(err);
|
||||
else
|
||||
current.files = response.trim().parseInt2();
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
// Get count of opened network connections
|
||||
instance.options.monitorconnections && arr.push(function(next) {
|
||||
Exec('netstat -an | grep :{0} | wc -l'.format(F.port), function(err, response) {
|
||||
if (err) {
|
||||
instance.throw(err);
|
||||
} else {
|
||||
current.connections = response.trim().parseInt2() - 1;
|
||||
if (current.connections < 0)
|
||||
current.connections = 0;
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
// Get directory size
|
||||
instance.options.monitorsize && current.counter % 5 !== 0 && arr.push(function(next) {
|
||||
Exec('du -hsb ' + process.cwd(), function(err, response) {
|
||||
if (err) {
|
||||
instance.throw(err);
|
||||
} else {
|
||||
var match = response.trim().match(reg_appdisksize);
|
||||
match && (current.size = match.toString().trim().parseInt2());
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
arr.async(function() {
|
||||
|
||||
tproc && clearTimeout(tproc);
|
||||
|
||||
if (instance.options.enabled) {
|
||||
tproc = setTimeout(instance.custom.run, instance.options.interval);
|
||||
instance.send2(current);
|
||||
}
|
||||
|
||||
instance.custom.status();
|
||||
current.counter++;
|
||||
});
|
||||
};
|
||||
|
||||
instance.custom.status = function() {
|
||||
if (instance.options.enabled)
|
||||
instance.status('{0}% / {1}'.format(current.cpu, current.memory.filesize()));
|
||||
else
|
||||
instance.status('Disabled', 'red');
|
||||
};
|
||||
|
||||
instance.on('click', function() {
|
||||
instance.options.enabled = !instance.options.enabled;
|
||||
instance.custom.status();
|
||||
|
||||
if (instance.options.enabled) {
|
||||
current.counter = 0;
|
||||
instance.custom.run();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
instance.on('close', function() {
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(instance.custom.run, 1000);
|
||||
};
|
||||
96
RVO35/flow/monitordisk.js
Executable file
96
RVO35/flow/monitordisk.js
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
exports.id = 'monitordisk';
|
||||
exports.title = 'Disk';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Peter Širka';
|
||||
exports.group = 'Monitoring';
|
||||
exports.color = '#F6BB42';
|
||||
exports.output = 1;
|
||||
exports.icon = 'hdd-o';
|
||||
exports.click = true;
|
||||
exports.options = { interval: 8000, path: '/', enabled: true };
|
||||
exports.readme = `# Disk monitoring
|
||||
|
||||
This component monitors disk \`bytes\` consumption in Linux systems. It uses \`df\` command.
|
||||
|
||||
__Data Example__:
|
||||
|
||||
\`\`\`javascript
|
||||
{
|
||||
total: 474549649408,
|
||||
used: 39125245952,
|
||||
free: 411294994432
|
||||
}
|
||||
\`\`\``;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-3 m">
|
||||
<div data---="textbox__interval__placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
|
||||
</div>
|
||||
<div class="col-md-3 m">
|
||||
<div data---="textbox__path__placeholder:/;required:true">@(Path)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var current = { total: 0, used: 0, free: 0, path: '', type: '', percentUsed: 0 };
|
||||
var tproc = null;
|
||||
|
||||
instance.custom.run = function() {
|
||||
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
|
||||
if (!instance.options.enabled)
|
||||
return;
|
||||
|
||||
require('child_process').exec('df -hTB1 ' + instance.options.path, function(err, response) {
|
||||
|
||||
tproc = setTimeout(instance.custom.run, instance.options.interval);
|
||||
|
||||
if (err) {
|
||||
instance.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
response.parseTerminal(function(line) {
|
||||
if (line[0][0] !== '/')
|
||||
return;
|
||||
current.total = line[2].parseInt();
|
||||
current.free = line[4].parseInt();
|
||||
current.used = line[3].parseInt();
|
||||
current.path = instance.options.path || '/';
|
||||
current.type = line[1];
|
||||
current.percentUsed = line[5];
|
||||
instance.custom.status();
|
||||
instance.send2(current);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
instance.custom.status = function() {
|
||||
if (instance.options.enabled)
|
||||
instance.status(current.free.filesize() + ' / ' + current.total.filesize());
|
||||
else
|
||||
instance.status('Disabled', 'red');
|
||||
};
|
||||
|
||||
instance.on('click', function() {
|
||||
instance.options.enabled = !instance.options.enabled;
|
||||
instance.custom.status();
|
||||
instance.options.enabled && instance.custom.run();
|
||||
});
|
||||
|
||||
instance.on('close', function() {
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(instance.custom.run, 1000);
|
||||
};
|
||||
87
RVO35/flow/monitormemory.js
Executable file
87
RVO35/flow/monitormemory.js
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
exports.id = 'monitormemory';
|
||||
exports.title = 'Memory';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Peter Širka';
|
||||
exports.group = 'Monitoring';
|
||||
exports.color = '#F6BB42';
|
||||
exports.output = 1;
|
||||
exports.click = true;
|
||||
exports.icon = 'microchip';
|
||||
exports.options = { interval: 8000, enabled: true };
|
||||
exports.readme = `# Memory monitoring
|
||||
|
||||
This component monitors memory \`bytes\` consumption in Linux systems. It uses \`free\` command.
|
||||
|
||||
__Data Example__:
|
||||
|
||||
\`\`\`javascript
|
||||
{
|
||||
total: 33558769664,
|
||||
used: 1998868480,
|
||||
free: 2653708288
|
||||
}
|
||||
\`\`\``;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var current = { total: 0, used: 0, free: 0 };
|
||||
var tproc = null;
|
||||
|
||||
instance.custom.run = function() {
|
||||
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
|
||||
if (!instance.options.enabled)
|
||||
return;
|
||||
|
||||
require('child_process').exec('free -b -t', function(err, response) {
|
||||
|
||||
tproc = setTimeout(instance.custom.run, instance.options.interval);
|
||||
|
||||
if (err) {
|
||||
instance.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var memory = response.split('\n')[1].match(/\d+/g);
|
||||
current.total = memory[0].parseInt();
|
||||
current.used = memory[1].parseInt() - memory[3].parseInt();
|
||||
current.free = current.total - current.used;
|
||||
instance.custom.status();
|
||||
instance.send2(current);
|
||||
});
|
||||
};
|
||||
|
||||
instance.custom.status = function() {
|
||||
if (instance.options.enabled)
|
||||
instance.status(current.free.filesize() + ' / ' + current.total.filesize());
|
||||
else
|
||||
instance.status('Disabled', 'red');
|
||||
};
|
||||
|
||||
instance.on('click', function() {
|
||||
instance.options.enabled = !instance.options.enabled;
|
||||
instance.custom.status();
|
||||
instance.options.enabled && instance.custom.run();
|
||||
});
|
||||
|
||||
instance.on('close', function() {
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(instance.custom.run, 1000);
|
||||
};
|
||||
77
RVO35/flow/nodesdb_changecheck.js
Executable file
77
RVO35/flow/nodesdb_changecheck.js
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
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 = 2;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//kedze do slacku posielame stringy, na cloud musime previest vsetky stringy spat na objekty:
|
||||
// Funkcia na parsovanie všetkých reťazcov v poli
|
||||
const parseArray = (arr) => {
|
||||
return arr.map(jsonString => JSON.parse(jsonString));
|
||||
};
|
||||
|
||||
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);
|
||||
if (FLOW.GLOBALS.settings.send_changed_node_numbers) instance.send(1, { aktualneNody: parseArray(zmenene), originalne: parseArray(Array.from(set2)) });
|
||||
}
|
||||
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);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
243
RVO35/flow/show_dbdata.js
Executable file
243
RVO35/flow/show_dbdata.js
Executable file
|
|
@ -0,0 +1,243 @@
|
|||
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
|
||||
}
|
||||
188
RVO35/flow/slack_filter.js
Executable file
188
RVO35/flow/slack_filter.js
Executable file
|
|
@ -0,0 +1,188 @@
|
|||
exports.id = 'slack_filter';
|
||||
exports.title = 'Slack Filter';
|
||||
exports.group = 'Citysys';
|
||||
exports.color = '#30E193';
|
||||
exports.input = 2;
|
||||
exports.output = 1;
|
||||
exports.author = 'Jakub Klena';
|
||||
exports.icon = 'plug';
|
||||
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.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<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>
|
||||
<div class="col-md-12">
|
||||
<div data-jc="textbox" data-jc-path="types" data-jc-config="required:false">@(Watch these types, comma separated names)</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div data-jc="textbox" data-jc-path="message_includes" data-jc-config="required:false">@(Watch messages that include any of the following strings)</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div data-jc="textbox" data-jc-path="tag_on_include" data-jc-config="required:false">@(Tag people if message includes something)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# Slack Filter`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
var running = false;
|
||||
instance["savedSlackMessages"] = [];
|
||||
var timer = null;
|
||||
|
||||
instance.on('0', function(response) {
|
||||
if (!running) return;
|
||||
let value = response.data;
|
||||
if (typeof value !== 'object') return;
|
||||
|
||||
let can = false
|
||||
var k = Object.keys(value);
|
||||
var interested = JSON.parse(instance.options.types);
|
||||
var msg_incl = JSON.parse(instance.options.message_includes);
|
||||
var tags = JSON.parse(instance.options.tag_on_include);
|
||||
|
||||
if (k.length <= 0) return;
|
||||
if (value[k[0]].length <= 0) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0], 'values')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values'], '_event')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'type')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'source')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event']['source'], 'func')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'message')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'message_data')) return;
|
||||
|
||||
let icon = ':totaljs:';
|
||||
let type = value[k[0]][0]['values']['_event']['type'];
|
||||
let source = value[k[0]][0]['values']['_event']['source']['func'];
|
||||
let message = value[k[0]][0]['values']['_event']['message']['en'];
|
||||
let message_data = value[k[0]][0]['values']['_event']['message_data'];
|
||||
let tag = '';
|
||||
|
||||
switch (type) {
|
||||
case 'debug':
|
||||
icon = ':beetle:';
|
||||
break;
|
||||
case 'info':
|
||||
icon = ':speech_balloon:';
|
||||
break;
|
||||
case 'notice':
|
||||
icon = ':speech_balloon:';
|
||||
break;
|
||||
case 'warning':
|
||||
icon = ':exclamation:';
|
||||
break;
|
||||
case 'alert':
|
||||
icon = ':warning:';
|
||||
break;
|
||||
case 'error':
|
||||
icon = ':no_entry:';
|
||||
break;
|
||||
case 'emergency':
|
||||
icon = ':fire:';
|
||||
break;
|
||||
case 'critical':
|
||||
icon = ':fire:';
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this message includes one of the strings we are watching for
|
||||
for (const msg of msg_incl) {
|
||||
if (message.includes(msg)) {
|
||||
if (msg == 'is responding again') icon = ':large_green_circle:';
|
||||
can = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if message is one of the types we are watching for
|
||||
if (interested.includes(type)) {
|
||||
can = true;
|
||||
}
|
||||
|
||||
if (!can) return;
|
||||
|
||||
|
||||
// Check for each person tags based on what the message includes
|
||||
for (const person of tags) {
|
||||
for (const msg of person.includes) {
|
||||
if (message.includes(msg)) {
|
||||
tag += '<@' + person.user_id + '> ';
|
||||
break; // Break out from this person checks as they are already tagged now
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now that all people are tagged add new line symbol
|
||||
if (tag != '') tag += '\n';
|
||||
|
||||
let send_data = tag + instance.options.name + ' ' + type.toUpperCase() + '\n*Source*: ' + source + '\n*Message*: ' + message;
|
||||
if (message_data) {
|
||||
send_data += '\nData: ' + message_data;
|
||||
}
|
||||
|
||||
let ignore_msg = false
|
||||
if (message.includes('Configuration of dimming profile to node no')) {
|
||||
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++) {
|
||||
if (FLOW["savedSlackMessages"][i].message == message) {
|
||||
ignore_msg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ignore_msg) {
|
||||
FLOW["savedSlackMessages"].push({ message, 'dateandtime': Date.now() });
|
||||
if (timer === null) {
|
||||
timer = setTimeout(checkSavedMessages, 60 * 60000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignore_msg) {
|
||||
instance.send2({ 'msg': send_data, 'bot_name': instance.options.name + ' ' + type.toUpperCase(), 'bot_icon': icon, 'channel': instance.options.slack_channel });
|
||||
}
|
||||
});
|
||||
|
||||
function checkSavedMessages() {
|
||||
var d = Date.now();
|
||||
d = d - 86400000; // older then 24hr
|
||||
var a = [];
|
||||
//Remove msgs older then 24hr
|
||||
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++) {
|
||||
if (FLOW["savedSlackMessages"][i].dateandtime > d) {
|
||||
a.push(FLOW["savedSlackMessages"][i]);
|
||||
}
|
||||
}
|
||||
FLOW["savedSlackMessages"] = a;
|
||||
|
||||
if (FLOW["savedSlackMessages"].length > 0) {
|
||||
timer = setTimeout(checkSavedMessages, 60 * 60000);
|
||||
} else {
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
instance.reconfigure = function() {
|
||||
try {
|
||||
if (!FLOW["savedSlackMessages"]) {
|
||||
FLOW["savedSlackMessages"] = [];
|
||||
}
|
||||
|
||||
instance.options.name = FLOW.GLOBALS.settings.rvo_name;
|
||||
if (instance.options.name) {
|
||||
instance.status('Running');
|
||||
running = true;
|
||||
} else {
|
||||
instance.status('Please run options again', 'red');
|
||||
running = false;
|
||||
}
|
||||
} catch (e) {
|
||||
instance.error('Citysys connector: ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
instance.on('options', instance.reconfigure);
|
||||
|
||||
instance.on("1", _ => {
|
||||
instance.reconfigure();
|
||||
})
|
||||
};
|
||||
98
RVO35/flow/thermometer.js
Executable file
98
RVO35/flow/thermometer.js
Executable file
|
|
@ -0,0 +1,98 @@
|
|||
exports.id = 'thermometer';
|
||||
exports.title = 'Thermometer';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#5CB36D';
|
||||
exports.input = 1;
|
||||
exports.version = '1.0.3';
|
||||
exports.output = ["red", "white", "blue"];
|
||||
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`;
|
||||
|
||||
const { errLogger, logger, monitor } = require('./helper/logger');
|
||||
|
||||
const SEND_TO = {
|
||||
debug: 0,
|
||||
tb: 1,
|
||||
dido_controller: 2
|
||||
}
|
||||
|
||||
//read temperature - frequency
|
||||
let timeoutMin = 5;//minutes
|
||||
let NUMBER_OF_FAILURES_TO_SEND_ERROR = 13;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
const { exec } = require('child_process');
|
||||
const { sendNotification } = require('./helper/notification_reporter');
|
||||
|
||||
let startRead;
|
||||
let counter = 0;
|
||||
let rvoTbName = "";
|
||||
let temperatureAddress = "";
|
||||
|
||||
logger.debug(exports.title, "installed");
|
||||
|
||||
instance.on("close", function(){
|
||||
clearInterval(startRead);
|
||||
})
|
||||
|
||||
|
||||
const main = function() {
|
||||
|
||||
try {
|
||||
|
||||
if(temperatureAddress === "") throw "Thermometer: temperatureAddress is not defined";
|
||||
|
||||
exec(`owread -C ${temperatureAddress}/temperature`, (error, stdout, stderr) => {
|
||||
|
||||
if(!error)
|
||||
{
|
||||
parseData(stdout)
|
||||
return;
|
||||
}
|
||||
|
||||
counter++;
|
||||
if(counter == NUMBER_OF_FAILURES_TO_SEND_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) {
|
||||
errLogger.error(exports.title, err);
|
||||
clearInterval(startRead);
|
||||
}
|
||||
}
|
||||
|
||||
const parseData = function(data) {
|
||||
|
||||
data = parseFloat(data);
|
||||
//logger.debug("Thermometer", data);
|
||||
|
||||
if(isNaN(data)) {
|
||||
errLogger.error("Thermometer sends invalid data");
|
||||
return;
|
||||
}
|
||||
|
||||
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", _ => {
|
||||
temperatureAddress = FLOW.GLOBALS.settings.temperature_address;
|
||||
rvoTbName = FLOW.GLOBALS.settings.rvoTbName;
|
||||
startRead = setInterval(main, timeoutMin * 1000 * 60);
|
||||
setTimeout(main, 20000);
|
||||
})
|
||||
};
|
||||
79
RVO35/flow/trigger.js
Executable file
79
RVO35/flow/trigger.js
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
exports.id = 'trigger';
|
||||
exports.title = 'Trigger';
|
||||
exports.group = 'Inputs';
|
||||
exports.color = '#F6BB42';
|
||||
exports.click = true;
|
||||
exports.output = 1;
|
||||
exports.version = '1.1.0';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.icon = 'play';
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="dropdown__datatype__items:,String|string,Integer|integer,Float|float,Boolean|boolean,Date|date,Object|object,Base64 as Buffer|buffer" class="m">@(Data type (String by default))</div>
|
||||
<div data-jc="textbox__data__placeholder:@(e.g. Hello world or { hello: 'world'} or ['hello', 'world']))" class="m">@(Data)</div>
|
||||
<div data-jc="checkbox__restart">Trigger 5s after initialization.</div>
|
||||
<div class="help">@(Useful when there's a need to run certain flow when the app restarts, etc.)</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# Trigger
|
||||
|
||||
- Clicking on the component starts the chain
|
||||
- Settings allows to set a data-type and a value`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var value;
|
||||
|
||||
instance.on('click', () => instance.send2(value));
|
||||
|
||||
instance.reconfigure = function() {
|
||||
var options = instance.options;
|
||||
value = null;
|
||||
switch (options.datatype) {
|
||||
case 'integer':
|
||||
value = options.data.parseInt2('error');
|
||||
value = value === 'error' ? NaN : value;
|
||||
break;
|
||||
case 'float':
|
||||
value = options.data.parseFloat2('error');
|
||||
value = value === 'error' ? NaN : value;
|
||||
break;
|
||||
case 'date':
|
||||
options.data = options.data.toString();
|
||||
var num = options.data.parseInt('error');
|
||||
num === 'error' && (num = options.data.parseDate('error'));
|
||||
num === 'error' && (num = null);
|
||||
value = num ? new Date(num).toUTCString() : num;
|
||||
break;
|
||||
case 'object':
|
||||
try {
|
||||
value = (new Function('return ' + options.data))();
|
||||
} catch (e) {
|
||||
instance.error(e);
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
value = options.data.parseBoolean();
|
||||
break;
|
||||
case 'buffer':
|
||||
try {
|
||||
value = U.createBuffer(options.data);
|
||||
} catch (e) {
|
||||
instance.error(e);
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
default:
|
||||
value = '' + (options.data || '');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
instance.on('options', instance.reconfigure);
|
||||
instance.reconfigure();
|
||||
|
||||
if (instance.options.restart)
|
||||
setTimeout(function(){
|
||||
instance.send2(value);
|
||||
}, 5000);
|
||||
};
|
||||
0
RVO35/flow/variables.txt
Executable file
0
RVO35/flow/variables.txt
Executable file
43
RVO35/flow/virtualwirein.js
Executable file
43
RVO35/flow/virtualwirein.js
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
exports.id = 'virtualwirein';
|
||||
exports.title = 'Virtual wire in';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.color = '#303E4D';
|
||||
exports.icon = 'sign-in';
|
||||
exports.input = false;
|
||||
exports.output = 1;
|
||||
exports.options = {};
|
||||
exports.readme = `# Virtual wire in
|
||||
|
||||
When the wires between the components are mess it's time to use Virtual wire.`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="textbox" data-jc-path="wirename" data-jc-config="required:true;placeholder:@(some identifier)" class="m">@(Wire name)</div>
|
||||
</div>
|
||||
<script>
|
||||
ON('save.virtualwirein', function(component, options) {
|
||||
!component.name && (component.name = options.wirename);
|
||||
});
|
||||
WATCH('settings.virtualwirein.wirename', function(path, value, type){
|
||||
if (type === 2)
|
||||
SET('settings.virtualwirein.wirename', value.slug());
|
||||
});
|
||||
</script>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
instance.custom.reconfigure = function(options){
|
||||
if (instance.options.wirename) {
|
||||
instance.status(instance.options.wirename);
|
||||
} else
|
||||
instance.status('Not configured', 'red');
|
||||
};
|
||||
|
||||
ON('virtualwire', function(wirename, flowdata){
|
||||
if (instance.options.wirename && instance.options.wirename === wirename)
|
||||
instance.send(flowdata);
|
||||
});
|
||||
|
||||
instance.on('options', instance.custom.reconfigure);
|
||||
instance.custom.reconfigure();
|
||||
};
|
||||
41
RVO35/flow/virtualwireout.js
Executable file
41
RVO35/flow/virtualwireout.js
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
exports.id = 'virtualwireout';
|
||||
exports.title = 'Virtual wire out';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.color = '#303E4D';
|
||||
exports.icon = 'sign-out';
|
||||
exports.input = true;
|
||||
exports.options = {};
|
||||
exports.readme = `# Virtual wire out
|
||||
|
||||
When the wires between the components are mess it's time to use Virtual wire.`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="textbox" data-jc-path="wirename" class="m" data-jc-config="required:true;placeholder:@(some identifier)">@(Wire name)</div>
|
||||
</div>
|
||||
<script>
|
||||
ON('save.virtualwireout', function(component, options) {
|
||||
!component.name && (component.name = options.wirename);
|
||||
});
|
||||
WATCH('settings.virtualwireout.wirename', function(path, value, type){
|
||||
if (type === 2)
|
||||
SET('settings.virtualwireout.wirename', value.slug());
|
||||
});
|
||||
</script>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
instance.custom.reconfigure = function(){
|
||||
if (instance.options.wirename) {
|
||||
instance.status(instance.options.wirename);
|
||||
} else
|
||||
instance.status('Not configured', 'red');
|
||||
};
|
||||
|
||||
instance.on('data', function(flowdata) {
|
||||
EMIT('virtualwire', instance.options.wirename, flowdata);
|
||||
});
|
||||
|
||||
instance.on('options', instance.custom.reconfigure);
|
||||
instance.custom.reconfigure();
|
||||
};
|
||||
448
RVO35/flow/wsmqttpublish.js
Executable file
448
RVO35/flow/wsmqttpublish.js
Executable file
|
|
@ -0,0 +1,448 @@
|
|||
exports.id = 'wsmqttpublish';
|
||||
exports.title = 'WS MQTT publish';
|
||||
exports.group = 'MQTT';
|
||||
exports.color = '#888600';
|
||||
exports.version = '1.0.2';
|
||||
exports.icon = 'sign-out';
|
||||
exports.input = 2;
|
||||
exports.output = 3;
|
||||
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="username" class="m">@(Username)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
exports.readme = `
|
||||
# WS MQTT Publish
|
||||
|
||||
Version 1.0.3.
|
||||
|
||||
Added:
|
||||
- database collections,
|
||||
- rpc response
|
||||
`;
|
||||
|
||||
const { promisifyBuilder } = require('./helper/db_helper');
|
||||
const { errLogger, monitor } = require('./helper/logger');
|
||||
const fs = require('fs');
|
||||
const mqtt = require('mqtt');
|
||||
|
||||
const SEND_TO = {
|
||||
debug: 0,
|
||||
rpcCall: 1,
|
||||
services: 2
|
||||
}
|
||||
|
||||
//CONFIG
|
||||
let createTelemetryBackup = true;
|
||||
let saveTelemetryOnError = true;//backup_on_failure overrides this value
|
||||
//------------------------
|
||||
|
||||
let rollers;
|
||||
if (createTelemetryBackup) rollers = require('streamroller');
|
||||
|
||||
const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304
|
||||
let insertNoSqlCounter = 0;
|
||||
let insertBackupNoSqlCounter = 0;
|
||||
let processingData = false;
|
||||
|
||||
let backup_on_failure = false;//== saveTelemetryOnError - create backup client send failure
|
||||
let restore_from_backup = 0; //how many rows process at once?
|
||||
let restore_backup_wait = 0;//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;
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
|
||||
errLogger.error('uncaughtException:', err.message)
|
||||
errLogger.error(err.stack);
|
||||
|
||||
//TODO
|
||||
//send to service
|
||||
|
||||
//process.exit(1);
|
||||
})
|
||||
|
||||
const nosql = NOSQL('tbdata');
|
||||
const nosqlBackup = NOSQL('/backup/tbdata');
|
||||
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var client;
|
||||
var opts;
|
||||
var clientReady = false;
|
||||
|
||||
// wsmqtt status for notification purposes on projects.worksys.io database
|
||||
let wsmqttName = null;
|
||||
let sendWsStatusVar = null;
|
||||
let wsmqtt_status = 'disconnected';
|
||||
|
||||
function getWsmqttName(host) {
|
||||
if (host == "tb-demo.worksys.io" || host == '192.168.252.4') return 'wsmqtt_demo';
|
||||
else if (host == "tb-qas01.worksys.io" || host == '192.168.252.5') return 'wsmqtt_qas01';
|
||||
else if (host == "tb-prod01.worksys.io" || host == '192.168.252.1') return 'wsmqtt_prod01';
|
||||
}
|
||||
|
||||
function sendWsStatus() {
|
||||
instance.send(SEND_TO.services, { [wsmqttName]: wsmqtt_status });
|
||||
}
|
||||
|
||||
|
||||
function main() {
|
||||
if (!FLOW.dbLoaded) return;
|
||||
|
||||
loadSettings();
|
||||
clearInterval(sendWsStatus);
|
||||
sendWsStatusVar = setInterval(sendWsStatus, 180000);
|
||||
}
|
||||
|
||||
//set opts according to db settings
|
||||
function loadSettings() {
|
||||
|
||||
if (instance.options.host !== "") {
|
||||
//override settings from database
|
||||
var o = instance.options;
|
||||
opts = {
|
||||
host: o.host,
|
||||
port: o.port,
|
||||
clientId: o.clientid,
|
||||
username: o.username,
|
||||
rejectUnauthorized: false,
|
||||
resubscribe: false
|
||||
};
|
||||
|
||||
wsmqttName = getWsmqttName(o.host);
|
||||
|
||||
console.log("wsmqttpublich -> loadSettings from instance.options", instance.options);
|
||||
}
|
||||
else {
|
||||
|
||||
const SETTINGS = FLOW.GLOBALS.settings;
|
||||
backup_on_failure = SETTINGS.backup_on_failure;
|
||||
saveTelemetryOnError = backup_on_failure;
|
||||
|
||||
restore_from_backup = SETTINGS.restore_from_backup;
|
||||
restore_backup_wait = SETTINGS.restore_backup_wait;
|
||||
|
||||
let mqtt_host = SETTINGS.mqtt_host;
|
||||
let mqtt_clientid = SETTINGS.mqtt_clientid;
|
||||
let mqtt_username = SETTINGS.mqtt_username;
|
||||
let mqtt_port = SETTINGS.mqtt_port;
|
||||
|
||||
opts = {
|
||||
host: mqtt_host,
|
||||
port: mqtt_port,
|
||||
keepalive: 10,
|
||||
clientId: mqtt_clientid,
|
||||
username: mqtt_username,
|
||||
rejectUnauthorized: false,
|
||||
resubscribe: false
|
||||
};
|
||||
|
||||
wsmqttName = getWsmqttName(mqtt_host);
|
||||
}
|
||||
|
||||
connectToTbServer();
|
||||
}
|
||||
|
||||
function connectToTbServer() {
|
||||
var url = "mqtt://" + opts.host + ":" + opts.port;
|
||||
console.log("MQTT URL: ", url);
|
||||
|
||||
client = mqtt.connect(url, opts);
|
||||
|
||||
client.on('connect', function() {
|
||||
instance.status("Connected", "green");
|
||||
//monitor.info("MQTT client connected");
|
||||
|
||||
sendClientError = true;
|
||||
clientReady = true;
|
||||
wsmqtt_status = 'connected';
|
||||
});
|
||||
|
||||
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(function() {
|
||||
|
||||
message = JSON.parse(message);
|
||||
if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) {
|
||||
client.publish(topic, `{"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 } });
|
||||
}
|
||||
|
||||
}, () => instance.debug('MQTT: Error parsing data', message));
|
||||
}
|
||||
|
||||
instance.send(SEND_TO.rpcCall, { "topic": topic, "content": message });
|
||||
});
|
||||
|
||||
client.on('close', function() {
|
||||
clientReady = false;
|
||||
wsmqtt_status = 'disconnected';
|
||||
|
||||
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) {
|
||||
monitor.info('MQTT client error', err);
|
||||
sendClientError = false;
|
||||
}
|
||||
clientReady = false;
|
||||
wsmqtt_status = 'disconnected';
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
instance.on("0", _ => {
|
||||
main();
|
||||
})
|
||||
|
||||
|
||||
instance.on('1', 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("v1/gateway/telemetry", stringifiedJson, { qos: 1 });
|
||||
|
||||
//backup telemetry
|
||||
if (createTelemetryBackup) {
|
||||
data.data.id = UID();
|
||||
nosqlBackup.insert(data.data);
|
||||
|
||||
insertBackupNoSqlCounter++;
|
||||
if (insertBackupNoSqlCounter > 150) {
|
||||
let options = { compress: true };
|
||||
let path = __dirname + "/../databases/backup/tbdata.nosql";
|
||||
var stream = new rollers.RollingFileStream(path, noSqlFileSizeLimit, 150, options);
|
||||
stream.write("");
|
||||
stream.end();
|
||||
|
||||
insertBackupNoSqlCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
//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.close = function(done) {
|
||||
if (clientReady) {
|
||||
client.end();
|
||||
clearInterval(sendWsStatusVar);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function getDbBackupFileCounter(type) {
|
||||
var files = fs.readdirSync(__dirname + "/../databases");
|
||||
|
||||
let counter = 0;
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
|
||||
if (files[i] == "tbdata.nosql") continue;
|
||||
|
||||
if (files[i].endsWith(".nosql")) {
|
||||
|
||||
let pos = files[i].indexOf(".");
|
||||
if (pos > -1) {
|
||||
|
||||
let fileCounter = counter;
|
||||
let firstDigit = files[i].slice(0, pos);
|
||||
|
||||
fileCounter = parseInt(firstDigit);
|
||||
if (isNaN(fileCounter)) fileCounter = 0;
|
||||
//console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type);
|
||||
|
||||
if (type == "max") {
|
||||
if (fileCounter > counter) {
|
||||
counter = fileCounter;
|
||||
}
|
||||
}
|
||||
else if (type == "min") {
|
||||
if (counter == 0) counter = fileCounter;
|
||||
|
||||
if (fileCounter < counter) {
|
||||
counter = fileCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (type == "max") counter++;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
const makeBackupFromDbFile = async () => {
|
||||
|
||||
if (!saveTelemetryOnError) return;
|
||||
|
||||
//to avoid large file: tbdata.nosql
|
||||
|
||||
//init value is 0!
|
||||
if (insertNoSqlCounter > 0) {
|
||||
--insertNoSqlCounter;
|
||||
return;
|
||||
}
|
||||
|
||||
insertNoSqlCounter = 100;
|
||||
|
||||
let source = __dirname + "/../databases/tbdata.nosql";
|
||||
|
||||
var stats = fs.statSync(source);
|
||||
var fileSizeInBytes = stats.size;
|
||||
|
||||
if (fileSizeInBytes > noSqlFileSizeLimit) {
|
||||
|
||||
let counter = 1;
|
||||
counter = getDbBackupFileCounter("max");
|
||||
|
||||
let destination = __dirname + "/../databases/" + counter + "." + "tbdata.nosql";
|
||||
|
||||
//make backup file
|
||||
fs.copyFileSync(source, destination);
|
||||
//fs.renameSync(p, p + "." + counter);
|
||||
|
||||
//clear tbdata.nosql
|
||||
fs.writeFileSync(source, "");
|
||||
fs.truncateSync(source, 0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const processDataFromDatabase = async () => {
|
||||
|
||||
if (restore_from_backup <= 0) return;
|
||||
|
||||
//calculate diff
|
||||
const now = new Date();
|
||||
let currentTime = now.getTime();
|
||||
let diff = currentTime - lastRestoreTime;
|
||||
|
||||
if ((diff / 1000) < restore_backup_wait) {
|
||||
//console.log("*********restore_backup_wait", diff, restore_backup_wait);
|
||||
return;
|
||||
}
|
||||
|
||||
processingData = true;
|
||||
|
||||
//get filename to process
|
||||
let counter = getDbBackupFileCounter("min");
|
||||
|
||||
//we have some backup files
|
||||
let dataBase = 'tbdata';
|
||||
|
||||
var nosql;
|
||||
if (counter == 0) dataBase = 'tbdata';
|
||||
else dataBase = counter + "." + 'tbdata';
|
||||
|
||||
nosql = NOSQL(dataBase);
|
||||
|
||||
//select all data - use limit restore_from_backup
|
||||
let records = await promisifyBuilder(nosql.find().take(restore_from_backup));
|
||||
|
||||
for (let i = 0; i < records.length; i++) {
|
||||
if (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("v1/gateway/telemetry", 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);
|
||||
//instance.reconfigure();
|
||||
};
|
||||
48
RVO35/flow_old/audit_test_panel.csv
Executable file
48
RVO35/flow_old/audit_test_panel.csv
Executable 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
|
||||
|
374
RVO35/flow_old/cloudmqttconnect.js
Executable file
374
RVO35/flow_old/cloudmqttconnect.js
Executable file
|
|
@ -0,0 +1,374 @@
|
|||
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(function() {
|
||||
|
||||
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}});
|
||||
}
|
||||
|
||||
}, () => 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);
|
||||
|
||||
};
|
||||
2890
RVO35/flow_old/cmd_manager.js
Executable file
2890
RVO35/flow_old/cmd_manager.js
Executable file
File diff suppressed because it is too large
Load diff
90
RVO35/flow_old/code.js
Executable file
90
RVO35/flow_old/code.js
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
exports.id = 'code';
|
||||
exports.title = 'Code';
|
||||
exports.group = 'Common';
|
||||
exports.color = '#656D78';
|
||||
exports.input = true;
|
||||
exports.output = 1;
|
||||
exports.author = 'Peter Širka';
|
||||
exports.icon = 'code';
|
||||
exports.version = '1.2.0';
|
||||
exports.options = { outputs: 1, code: 'send(0, value);', keepmessage: true };
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div data-jc="textbox" data-jc-path="outputs" data-jc-config="type:number;validation:value > 0;increment:true;maxlength:3">@(Number of outputs)</div>
|
||||
<div class="help m">@(Minimum is 1)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-jc="codemirror" data-jc-path="code" data-jc-config="type:javascript;required:true;height:500;tabs:true;trim:true" class="m">@(Code)</div>
|
||||
<div data-jc="checkbox" data-jc-path="keepmessage">@(Keep message instance)</div>
|
||||
</div>
|
||||
<script>
|
||||
var code_outputs_count;
|
||||
|
||||
ON('open.code', function(component, options) {
|
||||
code_outputs_count = options.outputs = options.outputs || 1;
|
||||
});
|
||||
|
||||
ON('save.code', function(component, options) {
|
||||
if (code_outputs_count !== options.outputs) {
|
||||
if (flow.version < 511) {
|
||||
component.connections = {};
|
||||
setState(MESSAGES.apply);
|
||||
}
|
||||
component.output = options.outputs || 1;
|
||||
}
|
||||
});
|
||||
</script>`;
|
||||
|
||||
exports.readme = `# Code
|
||||
|
||||
This component executes custom JavaScript code as it is and it doesn't contain any secure scope.
|
||||
|
||||
\`\`\`javascript
|
||||
// value {Object} contains received data
|
||||
// send(outputIndex, newValue) sends a new value
|
||||
// error(value) sends an error
|
||||
// instance {Object} a current component instance
|
||||
// flowdata {Object} a current flowdata
|
||||
// repository {Object} a current repository of flowdata
|
||||
// Example:
|
||||
|
||||
// send() can be execute multiple times
|
||||
send(0, value);
|
||||
\`\`\``;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var fn;
|
||||
|
||||
instance.on('data', function(response) {
|
||||
if (fn) {
|
||||
try {
|
||||
fn(response.data, instance, response, instance.options, response.repository, require);
|
||||
} catch (e) {
|
||||
response.data = e;
|
||||
instance.throw(response);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
instance.reconfigure = function() {
|
||||
try {
|
||||
if (instance.options.code) {
|
||||
instance.status('');
|
||||
var code = 'var send = function(index, value) { if (options.keepmessage) { flowdata.data = value; instance.send2(index, flowdata); } else instance.send2(index, value);}; var error = function(err) { instance.throw(err); }; ' + instance.options.code;
|
||||
fn = new Function('value', 'instance', 'flowdata', 'options', 'repository', 'require', code);
|
||||
} else {
|
||||
instance.status('Not configured', 'red');
|
||||
fn = null;
|
||||
}
|
||||
} catch (e) {
|
||||
fn = null;
|
||||
instance.error('Code: ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
instance.on('options', instance.reconfigure);
|
||||
instance.reconfigure();
|
||||
};
|
||||
11
RVO35/flow_old/comment.js
Executable file
11
RVO35/flow_old/comment.js
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
exports.id = 'comment';
|
||||
exports.title = 'Comment';
|
||||
exports.group = 'Common';
|
||||
exports.color = '#704cff';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.icon = 'comment';
|
||||
exports.traffic = false;
|
||||
exports.version = '1.0.0';
|
||||
exports.readme = '# Comment';
|
||||
|
||||
exports.install = function() {};
|
||||
60
RVO35/flow_old/count.js
Executable file
60
RVO35/flow_old/count.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
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
RVO35/flow_old/csv_import.js
Executable file
175
RVO35/flow_old/csv_import.js
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
exports.id = 'csv_import';
|
||||
exports.title = 'CsvImport';
|
||||
exports.version = '1.0.0';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#2134B0';
|
||||
exports.input = 1;
|
||||
exports.output = ["red", "white"];
|
||||
exports.click = false;
|
||||
exports.author = 'Daniel Segeš';
|
||||
exports.icon = 'file-import';
|
||||
exports.options = { edge: "undefined" };
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">CSV Import</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# load csv to table db`;
|
||||
|
||||
//config
|
||||
let delimiter = ";";
|
||||
let uniqueColumn = "node";
|
||||
let path = "flow/audit_test_panel.csv";
|
||||
let startFrom = 1;
|
||||
let table = "nodes";
|
||||
let mapImport = {
|
||||
2: "node",
|
||||
4: "tbname",
|
||||
3: "line"
|
||||
};
|
||||
|
||||
//10.0.0.62
|
||||
delimiter = ";";
|
||||
uniqueColumn = "node";
|
||||
path = "flow/audit_rvo14_lampy.csv";
|
||||
startFrom = 1;
|
||||
table = "nodes";
|
||||
mapImport = {
|
||||
1: "node",
|
||||
3: "tbname",
|
||||
2: "line"
|
||||
};
|
||||
|
||||
//notification
|
||||
delimiter = ";";
|
||||
uniqueColumn = undefined;
|
||||
path = "flow/notifikacie.csv";
|
||||
startFrom = 1;
|
||||
table = "notifications";
|
||||
mapImport = {
|
||||
0: "key",
|
||||
1: "weight",
|
||||
2: "en",
|
||||
3: "sk"
|
||||
};
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
//console.log("csv import installed");
|
||||
|
||||
instance.on("close", () => {
|
||||
|
||||
})
|
||||
|
||||
|
||||
instance.on("data", (flowdata) => {
|
||||
|
||||
instance.send(0, "start import");
|
||||
console.log("csv import", flowdata.data);
|
||||
|
||||
//{table: "nodes", startFrom: 1, delimiter: ";", uniqueColumn: "node", path: "flow/audit_rvo14_lampy.csv", mapImport: {1: "node", 3: "tbname", 2: "line"}}
|
||||
|
||||
|
||||
if(typeof flowdata.data === 'object')
|
||||
{
|
||||
console.log("*******************", flowdata.data);
|
||||
|
||||
if(!flowdata.data.hasOwnProperty("table"))
|
||||
{
|
||||
instance.send(0, "!!!!csv import - nedefinovana tabulka");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!flowdata.data.hasOwnProperty("uniqueColumn"))
|
||||
{
|
||||
//instance.send(0, "!!!!csv import - nedefinovane uniqueColumn");
|
||||
//return;
|
||||
}
|
||||
|
||||
if(!flowdata.data.hasOwnProperty("path"))
|
||||
{
|
||||
instance.send(0, "!!!!csv import - nedefinovana cesta k suboru");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!flowdata.data.hasOwnProperty("mapImport"))
|
||||
{
|
||||
instance.send(0, "!!!!csv import - nedefinovany mapImport");
|
||||
return;
|
||||
}
|
||||
|
||||
table = flowdata.data.table;
|
||||
uniqueColumn = flowdata.data.uniqueColumn;
|
||||
if(uniqueColumn === "") uniqueColumn = undefined;
|
||||
|
||||
path = flowdata.data.path;
|
||||
mapImport = flowdata.data.mapImport;
|
||||
|
||||
if(flowdata.data.hasOwnProperty("delimiter")) delimiter = flowdata.data.delimiter;
|
||||
if(flowdata.data.hasOwnProperty("startFrom")) startFrom = flowdata.data.startFrom;
|
||||
}
|
||||
|
||||
|
||||
var db = TABLE(table);
|
||||
db.clear();
|
||||
|
||||
let keys = Object.keys(mapImport);
|
||||
|
||||
try {
|
||||
const data = fs.readFileSync(path, 'utf8')
|
||||
|
||||
let lines = data.split("\n");
|
||||
|
||||
for(let i = startFrom; i < lines.length; i++)
|
||||
{
|
||||
let line = lines[i];
|
||||
if(line === "") continue;
|
||||
|
||||
let data = line.split(delimiter);
|
||||
if(data.length == 0) continue;
|
||||
|
||||
let insertData = {};
|
||||
|
||||
keys.map(function(key){
|
||||
let k = mapImport[key];
|
||||
|
||||
//console.log("importineg", i, key, k);
|
||||
|
||||
if(data[key] != undefined) insertData[k] = data[key].trim();
|
||||
else{
|
||||
console.log("undefined", key, data);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("insertData", insertData);
|
||||
|
||||
if(uniqueColumn != undefined)
|
||||
{
|
||||
db.insert(insertData, true).where(uniqueColumn, insertData[uniqueColumn]);
|
||||
}
|
||||
else
|
||||
{
|
||||
db.insert(insertData);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
console.log("csv import finished");
|
||||
instance.send(0, "csv import finished");
|
||||
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
instance.send(0, err);
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
286
RVO35/flow_old/db_connector.js
Executable file
286
RVO35/flow_old/db_connector.js
Executable file
|
|
@ -0,0 +1,286 @@
|
|||
exports.id = 'db_connector';
|
||||
exports.title = 'DbConnector';
|
||||
exports.version = '1.0.0';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#2134B0';
|
||||
exports.input = 1;
|
||||
exports.output = ["red", "white"];
|
||||
exports.click = false;
|
||||
exports.author = 'Daniel Segeš';
|
||||
exports.icon = 'bolt';
|
||||
exports.options = { edge: "undefined" };
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">DbConnector</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# read/write data to tables`;
|
||||
|
||||
const instanceSendTo = {
|
||||
debug: 0,
|
||||
http_response: 1,
|
||||
}
|
||||
|
||||
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
|
||||
|
||||
function extractWhereParams(params)
|
||||
{
|
||||
let name = params[0];
|
||||
let operator = '=';
|
||||
let value = params[1];
|
||||
|
||||
if(params.length == 3)
|
||||
{
|
||||
operator = params[1];
|
||||
value = params[2];
|
||||
}
|
||||
|
||||
return {name: name, operator: operator, value: value};
|
||||
}
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
let refFlowdata = null;//holds reference to httprequest flowdata
|
||||
|
||||
instance.on("close", () => {
|
||||
|
||||
})
|
||||
|
||||
|
||||
instance.on("data", async function(flowdata) {
|
||||
|
||||
let params = flowdata.data.body;
|
||||
console.log("DbConnector", params);
|
||||
|
||||
refFlowdata = flowdata;
|
||||
|
||||
if(refFlowdata != undefined)
|
||||
{
|
||||
//make http response
|
||||
let responseObj = {};
|
||||
responseObj["type"] = "SUCESS";
|
||||
|
||||
try{
|
||||
|
||||
let table = params.table;
|
||||
let action = params.action;
|
||||
|
||||
|
||||
if(params.data != undefined)
|
||||
{
|
||||
let className = params.data.className;
|
||||
|
||||
if(className == "SqlQueryBuilder")
|
||||
{
|
||||
let type = params.data.type;
|
||||
|
||||
console.log("SqlQueryBuilder---->", params.data);
|
||||
|
||||
if(type == "SELECT")
|
||||
{
|
||||
let table = params.data.queryData.tables[0].table;
|
||||
|
||||
const db = TABLE(table);
|
||||
var builder = db.find();
|
||||
|
||||
let result = await promisifyBuilder(builder);
|
||||
|
||||
let response = {};
|
||||
response["result"] = result;
|
||||
response["success"] = true;
|
||||
|
||||
responseObj["data"] = response;
|
||||
|
||||
//console.log(responseObj);
|
||||
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log("db_connector---->", table, action);
|
||||
|
||||
//actions: read, replace, insert, delete, update...
|
||||
|
||||
if(action == "read")
|
||||
{
|
||||
const db = TABLE(params.table);
|
||||
|
||||
//where builder.where('age', '<', 15);
|
||||
//builder.where('id', 3403);
|
||||
|
||||
var builder = db.find();
|
||||
|
||||
//https://docs.totaljs.com/latest/en.html#api~DatabaseBuilder~builder.where
|
||||
if(params.hasOwnProperty("where"))
|
||||
{
|
||||
//optionalCan contain "=", "<=", "<", ">=", ">".
|
||||
//Default value: '='
|
||||
|
||||
//1.["production_line", 1]
|
||||
//2. ["or", ["production_line", 1], ["production_line", 2], "end"]
|
||||
|
||||
if (Array.isArray(params.where)) {
|
||||
|
||||
let multipleConditions = false;
|
||||
|
||||
if(params.where[0] == "or") multipleConditions = true;
|
||||
if (Array.isArray(params.where[0])) multipleConditions = true;
|
||||
|
||||
if(multipleConditions)
|
||||
{
|
||||
|
||||
for(var i = 0; i < params.where.length; i++)
|
||||
{
|
||||
const item = params.where[i];
|
||||
|
||||
if(item === "or") builder.or();
|
||||
|
||||
if (Array.isArray(item))
|
||||
{
|
||||
const { name, operator, value } = extractWhereParams(item);
|
||||
builder.where(name, operator, value);
|
||||
}
|
||||
|
||||
if(item === "end") builder.end();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
const { name, operator, value } = extractWhereParams(params.where);
|
||||
builder.where(name, operator, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
if(params.where.length >=2 )
|
||||
{
|
||||
let name = params.where[0];
|
||||
let operator = '=';
|
||||
let value = params.where[1];
|
||||
|
||||
if(params.where.length == 3)
|
||||
{
|
||||
operator = params.where[1];
|
||||
value = params.where[2];
|
||||
}
|
||||
|
||||
builder.where(name, operator, value);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if(params.hasOwnProperty("between"))
|
||||
{
|
||||
builder.between(params.between[0], params.between[1], params.between[2]);
|
||||
}
|
||||
|
||||
let response = await promisifyBuilder(builder);
|
||||
responseObj["data"] = response;
|
||||
|
||||
//console.log(responseObj);
|
||||
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(action == "delete")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
if(action == "update")
|
||||
{
|
||||
//! data receiving from terminal (params)
|
||||
// {
|
||||
// hostname: 'localhost',
|
||||
// table: 'settings',
|
||||
// action: 'update',
|
||||
// body: {
|
||||
// rvo_name: 'terrrr',
|
||||
// lang: 'en',
|
||||
// temperature_adress: '28.427B45920702',
|
||||
// latitude: 48.70826502,
|
||||
// longitude: 17.28455203,
|
||||
// mqtt_host: '192.168.252.4',
|
||||
// mqtt_clientid: 'showroom_test_panel_led',
|
||||
// mqtt_username: 'xmRd6RJxW53WZe4vMFLU',
|
||||
// mqtt_port: 1883,
|
||||
// maintanace_mode: false
|
||||
// }
|
||||
// }
|
||||
const tableToModify = TABLE(params.table);
|
||||
const newValues = params.body;
|
||||
|
||||
tableToModify.modify(newValues).make(function(builder) {
|
||||
|
||||
builder.callback(function(err, response) {
|
||||
|
||||
if(!err)
|
||||
{
|
||||
responseObj["data"] = response;
|
||||
responseObj["tableUpdated"] = true;
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if(action == "replace")
|
||||
{
|
||||
//truncate table
|
||||
const db = TABLE(params.table);
|
||||
var builder = db.remove();
|
||||
db.clean();
|
||||
|
||||
//insert data
|
||||
let data = params.data;
|
||||
|
||||
for(let i = 0; i < data.length; i++)
|
||||
{
|
||||
//console.log(data[i]);
|
||||
db.insert(data[i]);
|
||||
}
|
||||
|
||||
console.log("insert done");
|
||||
|
||||
let responseObj = {};
|
||||
responseObj["type"] = "SUCESS";
|
||||
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
} catch (error) {
|
||||
//console.log(error);
|
||||
responseObj["type"] = "ERROR";
|
||||
responseObj["message"] = error;
|
||||
|
||||
refFlowdata.data = responseObj;
|
||||
instance.send(instanceSendTo.http_response, refFlowdata);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
106
RVO35/flow_old/db_init.js
Executable file
106
RVO35/flow_old/db_init.js
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
exports.id = 'db_init';
|
||||
exports.title = 'DB Initialization';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#888600';
|
||||
exports.version = '1.0.2';
|
||||
exports.icon = 'sign-out';
|
||||
exports.input = 1;
|
||||
exports.output = ["blue"];
|
||||
|
||||
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');
|
||||
|
||||
|
||||
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"];
|
||||
|
||||
dbs.settings = {
|
||||
edge_fw_version: "2025-01-21", //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"],
|
||||
|
||||
//dynamic values
|
||||
masterNodeIsResponding: true, //cmd_manager
|
||||
maintenance_mode: false,
|
||||
}
|
||||
|
||||
FLOW.dbLoaded = true;
|
||||
initNotification();
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("DB_INIT - data loaded");
|
||||
instance.send(0, "_")
|
||||
}, 5000)
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
100
RVO35/flow_old/debug.js
Executable file
100
RVO35/flow_old/debug.js
Executable file
|
|
@ -0,0 +1,100 @@
|
|||
exports.id = 'debug';
|
||||
exports.title = 'Debug';
|
||||
exports.author = 'Peter Širka';
|
||||
exports.color = '#967ADC';
|
||||
exports.click = true;
|
||||
exports.input = true;
|
||||
exports.icon = 'bug';
|
||||
exports.version = '2.0.4';
|
||||
exports.options = { enabled: true, repository: false, type: 'data' };
|
||||
exports.readme = `# Debug
|
||||
|
||||
Prints data to the debug tab.`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div data-jc="dropdown" data-jc-path="type" data-jc-config="items:Message data|data,Message repository|repository,Message data + Message repository|both;required:true" class="m">@(Output type)</div>
|
||||
<div data-jc="textbox" data-jc-path="property" data-jc-config="placeholder: @(e.g. address.street)" class="m">@(Path to the property (leave empty to show the whole data object))</div>
|
||||
<div data-jc="textbox" data-jc-path="group" data-jc-config="placeholder: @(e.g. Temperature)" class="m">@(A group name)</div>
|
||||
<div data-jc="checkbox" data-jc-path="enabled">@(Enabled)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
instance.on('data', function(response) {
|
||||
if (instance.options.enabled) {
|
||||
|
||||
var opt = instance.options;
|
||||
var rep = response.repository;
|
||||
var val = response.data;
|
||||
var id = response.id;
|
||||
|
||||
switch (instance.options.type){
|
||||
case 'both':
|
||||
var data = {};
|
||||
data.repository = rep;
|
||||
data.data = val instanceof Error ? { error: val.message, stack: val.stack } : val;
|
||||
instance.debug(safeparse(opt.property ? U.get(data, opt.property) : data), undefined, opt.group, id);
|
||||
break;
|
||||
case 'repository':
|
||||
instance.debug(safeparse(opt.property ? U.get(rep, opt.property) : rep), undefined, opt.group, id);
|
||||
break;
|
||||
case 'data':
|
||||
default:
|
||||
if (val instanceof Error)
|
||||
instance.debug({ error: val.message, stack: val.stack }, undefined, opt.group, id);
|
||||
else
|
||||
instance.debug(safeparse(opt.property ? U.get(val, opt.property) : val), undefined, opt.group, id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
instance.on('click', function() {
|
||||
instance.options.enabled = !instance.options.enabled;
|
||||
instance.custom.status();
|
||||
instance.save();
|
||||
});
|
||||
|
||||
instance.on('options', function() {
|
||||
instance.custom.status();
|
||||
});
|
||||
|
||||
instance.custom.status = function() {
|
||||
instance.status(instance.options.enabled ? 'Enabled' : 'Disabled');
|
||||
};
|
||||
|
||||
instance.custom.status();
|
||||
|
||||
function safeparse(o) {
|
||||
|
||||
if (o instanceof Buffer)
|
||||
return o;
|
||||
|
||||
if (o === undefined)
|
||||
return 'undefined';
|
||||
|
||||
if (o === null)
|
||||
return 'null';
|
||||
|
||||
var cache = [];
|
||||
var str = JSON.stringify(o, function(key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.indexOf(value) !== -1) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
cache.push(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
cache = null;
|
||||
return JSON.parse(str);
|
||||
}
|
||||
};
|
||||
2846
RVO35/flow_old/designer.json
Executable file
2846
RVO35/flow_old/designer.json
Executable file
File diff suppressed because it is too large
Load diff
1473
RVO35/flow_old/dido_controller.js
Executable file
1473
RVO35/flow_old/dido_controller.js
Executable file
File diff suppressed because it is too large
Load diff
156
RVO35/flow_old/helper/DataToTbHandler.js
Executable file
156
RVO35/flow_old/helper/DataToTbHandler.js
Executable file
|
|
@ -0,0 +1,156 @@
|
|||
class DataToTbHandler {
|
||||
|
||||
constructor(index) {
|
||||
this.index = index;
|
||||
|
||||
// time, after new value for the given key will be resend to tb (e.g. {status: "OK"})
|
||||
this.timeToHoldTbValue = 30 * 60; //30 minutes
|
||||
|
||||
this.previousValues = {};
|
||||
this.debug = false;
|
||||
this.messageCounter = 0;
|
||||
|
||||
this.sender = "";
|
||||
|
||||
// if attribute change difference is less than limit value, we do not send to tb.
|
||||
this.attributeChangeLimit = {
|
||||
temperature: 0.5,
|
||||
Phase_1_voltage: 2,
|
||||
Phase_2_voltage: 2,
|
||||
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,
|
||||
Phase_1_pow_factor: 0.1,
|
||||
Phase_2_pow_factor: 0.1,
|
||||
Phase_3_pow_factor: 0.1,
|
||||
power_factor: 0.1,
|
||||
lifetime: 0.5,
|
||||
voltage: 2,
|
||||
power: 2,
|
||||
frequency: 3,
|
||||
energy: 0.1,
|
||||
current: 2,
|
||||
inclination_x: 10,
|
||||
inclination_y: 10,
|
||||
inclination_z: 10
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
dump() {
|
||||
console.log("----------------------------");
|
||||
console.log("previousValues", this.previousValues);
|
||||
console.log("----------------------------");
|
||||
}
|
||||
|
||||
setSender(sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
isEmptyObject(obj) {
|
||||
for (var _ in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
sendToTb(dataToTb, instance) {
|
||||
|
||||
let keys = Object.keys(dataToTb);
|
||||
|
||||
if (keys.length == 0) {
|
||||
if (this.debug) console.log("sendToTb received empty object", dataToTb);
|
||||
return;
|
||||
}
|
||||
|
||||
let tbname = keys[0];
|
||||
let ts;
|
||||
|
||||
let arrayOfValues = dataToTb[tbname];
|
||||
let arrayOfValuesToSend = [];
|
||||
|
||||
for (let i = 0; i < arrayOfValues.length; i++) {
|
||||
ts = arrayOfValues[i].ts;
|
||||
let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values);
|
||||
|
||||
if (!this.isEmptyObject(values)) {
|
||||
arrayOfValuesToSend.push({ ts: ts, values: values });
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayOfValuesToSend.length == 0) {
|
||||
//if(this.debug) console.log("data not sent - empty array");
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageCounter++;
|
||||
|
||||
let dataToTbModified = {
|
||||
[tbname]: arrayOfValuesToSend
|
||||
}
|
||||
|
||||
//console.log(this.sender + " DATA SEND TO TB ", tbname, this.messageCounter, new Date(ts), dataToTbModified[tbname][0].values, this.instance);
|
||||
//if(this.debug) console.log(this.sender + " DATA SEND TO TB ", this.index, tbname, arrayOfValuesToSend);
|
||||
instance.send(this.index, dataToTbModified);
|
||||
}
|
||||
|
||||
getDiffTimestamp(key) {
|
||||
//TODO set different value for given key!!!
|
||||
//if(key == "status") this.timeToHoldTbValue = 2*60*60;//2h
|
||||
return this.timeToHoldTbValue * 1000;
|
||||
}
|
||||
|
||||
prepareValuesForTb(tbname, timestamp, values) {
|
||||
|
||||
let keys = Object.keys(values);
|
||||
if (!this.previousValues.hasOwnProperty(tbname)) {
|
||||
this.previousValues[tbname] = {};
|
||||
}
|
||||
|
||||
//if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
let value = values[key];
|
||||
|
||||
if (!this.previousValues[tbname].hasOwnProperty(key)) {
|
||||
this.previousValues[tbname][key] = { ts: timestamp, value: value };
|
||||
continue;
|
||||
}
|
||||
|
||||
// attributeData ==> voltage: {ts:333333, value:5}
|
||||
let attributeData = this.previousValues[tbname][key];
|
||||
let attributeToChange = false;
|
||||
if (key in this.attributeChangeLimit) attributeToChange = true;
|
||||
let limit = this.attributeChangeLimit[key];
|
||||
|
||||
if (attributeData.value === value || attributeToChange && Math.abs(attributeData.value - value) < limit) {
|
||||
let diff = timestamp - attributeData.ts;
|
||||
let 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;
|
||||
|
||||
126
RVO35/flow_old/helper/ErrorToServiceHandler.js
Executable file
126
RVO35/flow_old/helper/ErrorToServiceHandler.js
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
const { MD5 } = require('./md5.js');
|
||||
const { networkInterfaces } = require('os');
|
||||
|
||||
class ErrorToServiceHandler
|
||||
{
|
||||
constructor() {
|
||||
this.previousValues = {};
|
||||
|
||||
this.projects_id = undefined;
|
||||
|
||||
const nets = networkInterfaces();
|
||||
this.ipAddresses = Object.create(null); // Or just '{}', an empty object
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
if (!this.ipAddresses[name]) {
|
||||
this.ipAddresses[name] = [];
|
||||
}
|
||||
this.ipAddresses[name].push(net.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//console.log(this.ipAddresses);
|
||||
|
||||
}
|
||||
|
||||
setProjectsId(projects_id)
|
||||
{
|
||||
this.projects_id = projects_id;
|
||||
}
|
||||
|
||||
processMessage(message, seconds, message_type)
|
||||
{
|
||||
if(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 timestamp = new Date().getTime();
|
||||
|
||||
//keep in memory
|
||||
if (seconds === undefined) seconds = 60*60;
|
||||
|
||||
if(!this.previousValues.hasOwnProperty(key))
|
||||
{
|
||||
this.previousValues[key] = {ts: timestamp, duration: seconds};
|
||||
}
|
||||
|
||||
let diff = (timestamp - this.previousValues[key].ts);
|
||||
if(diff < this.previousValues[key].duration*1000) return;
|
||||
|
||||
this.previousValues[key].ts = timestamp;
|
||||
*/
|
||||
|
||||
//-------------------------
|
||||
|
||||
//send to service
|
||||
|
||||
let dataToInfoSender = {id: this.projects_id};
|
||||
|
||||
//js_error || error_message
|
||||
dataToInfoSender[message_type] = message;
|
||||
dataToInfoSender.ipAddresses = this.ipAddresses;
|
||||
|
||||
console.log("ErrorToServiceHandler------------------------>send to service", dataToInfoSender);
|
||||
|
||||
//TODO UGLY!!!
|
||||
// if error occures too early FLOW.GLOBALs.settings.project_id is still undefined
|
||||
// if(this.projects_id === undefined) this.projects_id = FLOW.GLOBALS.settings.project_id;
|
||||
if(this.projects_id === undefined) return;
|
||||
|
||||
/*
|
||||
if(this.projects_id === undefined)
|
||||
{
|
||||
console.log("this.projects_id is undefined");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
RESTBuilder.make(function(builder) {
|
||||
builder.method('POST');
|
||||
builder.post(dataToInfoSender);
|
||||
builder.url('http://192.168.252.2:8004/sentmessage');
|
||||
|
||||
builder.callback(function(err, response, output) {
|
||||
console.log("process.on error send", err, response, output, dataToInfoSender);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ErrorToServiceHandler;
|
||||
44
RVO35/flow_old/helper/db_helper.js
Executable file
44
RVO35/flow_old/helper/db_helper.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
function promisifyBuilder(builder)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
try{
|
||||
|
||||
builder.callback(function(err, response) {
|
||||
|
||||
if(err != null) reject(err);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function makeMapFromDbResult(response, ...keys)
|
||||
{
|
||||
let s = "-";
|
||||
let data = {};
|
||||
|
||||
for(let i = 0; i < response.length; i++)
|
||||
{
|
||||
let record = response[i];
|
||||
|
||||
let k = [];
|
||||
for(let j = 0; j < keys.length; j++)
|
||||
{
|
||||
k.push( record[keys[j]] );
|
||||
}
|
||||
|
||||
let key = k.join(s);
|
||||
data[ key ] = record;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
promisifyBuilder,
|
||||
makeMapFromDbResult
|
||||
}
|
||||
30
RVO35/flow_old/helper/logger.js
Executable file
30
RVO35/flow_old/helper/logger.js
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
//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 };
|
||||
5
RVO35/flow_old/helper/md5.js
Executable file
5
RVO35/flow_old/helper/md5.js
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
function MD5(d){var r = M(V(Y(X(d),8*d.length)));return r.toLowerCase()};function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
|
||||
|
||||
module.exports = {
|
||||
MD5
|
||||
}
|
||||
131
RVO35/flow_old/helper/notification_reporter.js
Executable file
131
RVO35/flow_old/helper/notification_reporter.js
Executable file
|
|
@ -0,0 +1,131 @@
|
|||
//key is device, value = str
|
||||
let sentValues= {};
|
||||
let notificationsData = null;
|
||||
|
||||
let ERRWEIGHT = {
|
||||
EMERGENCY: "emergency", // System unusable
|
||||
ALERT: "alert", // Action must be taken immidiately
|
||||
CRITICAL: "critical", // Component unable to function
|
||||
ERROR: "error", // Error, but component able to recover from it
|
||||
WARNING: "warning", // Possibility of error, system running futher
|
||||
NOTICE: "notice", // Significant message but not an error, things user might want to know about
|
||||
INFO: "informational", // Info
|
||||
DEBUG: "debug" // Debug - only if CONFIG.debug is enabled
|
||||
};
|
||||
|
||||
function getKey(map, val) {
|
||||
return Object.keys(map).findItem(key => map[key] === val);
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/41117799/string-interpolation-on-variable
|
||||
var template = (tpl, args) => tpl.replace(/\${(\w+)}/g, (_, v) => args[v]);
|
||||
|
||||
|
||||
function initNotification() {
|
||||
notificationsData = FLOW.GLOBALS.notificationsData;
|
||||
}
|
||||
|
||||
function sendNotification(func, device, key, params, extra, tb_output, instance, saveKey) {
|
||||
|
||||
// return;
|
||||
|
||||
let storeToSendValues = true;
|
||||
if(saveKey == undefined) storeToSendValues = false;
|
||||
|
||||
let lang = FLOW.GLOBALS.settings.language;
|
||||
if(lang != "en" || lang != "sk") lang = "en";
|
||||
|
||||
let tpl = key;
|
||||
let weight = "";
|
||||
|
||||
if(notificationsData[key])
|
||||
{
|
||||
weight = notificationsData[key].weight;
|
||||
weight = weight.toLowerCase();
|
||||
|
||||
tpl = notificationsData[key][lang];
|
||||
tpl = template(tpl, params);
|
||||
}
|
||||
else
|
||||
{
|
||||
//console.error("sendNotification: Notifications: undefined key", key, func, notificationsData);
|
||||
console.error("sendNotification: Notifications: undefined key", key, func );
|
||||
return false;
|
||||
}
|
||||
|
||||
//detect invalid err weight
|
||||
if(getKey(ERRWEIGHT, weight) == undefined)
|
||||
{
|
||||
console.error("sendNotification: Notifications: undefined weight", weight, key, func);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(sentValues.hasOwnProperty(saveKey))
|
||||
{
|
||||
if(sentValues[saveKey] == tpl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(sentValues[saveKey] == undefined)
|
||||
{
|
||||
if(storeToSendValues)
|
||||
{
|
||||
//do not send - flow is was started
|
||||
sentValues[saveKey] = tpl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(saveKey == "rvo_door")
|
||||
{
|
||||
//console.log("******", saveKey, sentValues[saveKey], tpl);
|
||||
}
|
||||
|
||||
if(storeToSendValues) sentValues[saveKey] = tpl;
|
||||
|
||||
let str = FLOW.GLOBALS.settings.rvo_name;
|
||||
if(str != "") str = str + ": ";
|
||||
str = str + tpl;
|
||||
|
||||
let content = {
|
||||
"type": weight,
|
||||
"status": "new",
|
||||
"source": {
|
||||
"func":func,
|
||||
"component":instance.id,
|
||||
"component_name":instance.name,
|
||||
"edge":device
|
||||
},
|
||||
"message":str,
|
||||
"message_data": extra
|
||||
};
|
||||
|
||||
let msg = {};
|
||||
msg[device] = [
|
||||
{
|
||||
"ts": Date.now(),
|
||||
"values": {
|
||||
"_event":content
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Msg can be outputted from components only after configuration
|
||||
/*if (canSendErrData()){
|
||||
sendBufferedErrors();
|
||||
} else {
|
||||
bufferError(msg);
|
||||
}*/
|
||||
instance.send(tb_output, msg); // Even if error server is unavailable, send this message to output, for other possible component connections
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendNotification,
|
||||
ERRWEIGHT,
|
||||
initNotification
|
||||
}
|
||||
144
RVO35/flow_old/helper/register.js
Executable file
144
RVO35/flow_old/helper/register.js
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
0 - cislo registra / prikaz
|
||||
1 - recepient - 0 = master, 1 = slave (slave je automaticky group a broadcast)
|
||||
2 - r/rw - read/write
|
||||
3- register name - nazov registra / prikazu (len pre info - zobrazenie v aplikacii)
|
||||
|
||||
4,5,6,7, - jednotlive byte-y - nazov byte-u
|
||||
4-7 - RES. a prazdny string "" sa nezobrazia!!!
|
||||
*/
|
||||
|
||||
//124 zaznamov
|
||||
const registers = [
|
||||
["0","1","R","Status","","",""],
|
||||
["1","1","RW","Dimming","R-Channel ","G-Channel","B-Channel ","W - Channel"],
|
||||
["2","1","R","Device types","","",".",""],
|
||||
["3","1","RW","Group addresses 1-4","Groups Add. 4","Groups Add. 3","Groups Add. 2","Groups Add. 1"],
|
||||
["4","1","RW","Group addresses 5-8","Groups Add. 8","Groups Add. 7","Groups Add. 6","Groups Add. 5"],
|
||||
["5","1","RW","Serial number (MAC)","","","",""],
|
||||
["6","1","RW","Time of dusk","HH","MM","SS","EXTRA"],
|
||||
["7","1","RW","Time of dawn","HH","MM","SS","EXTRA"],
|
||||
["8","1","RW","Time Schedule settings","TBD","TBD","Movement sensor","Time Schedule"],
|
||||
["9","1","RW","TS1 Time point 1","HH","MM","SS","Ext. Device"],
|
||||
["10","1","RW","TS1 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["11","1","RW","TS1 Time point 2","HH","MM","SS","Ext. Device"],
|
||||
["12","1","RW","TS1 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["13","1","RW","TS1 Time point 3","HH","MM","SS","Ext. Device"],
|
||||
["14","1","RW","TS1 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["15","1","RW","TS1 Time point 4","HH","MM","SS","Ext. Device"],
|
||||
["16","1","RW","TS1 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["17","1","RW","TS1 Time point 5","HH","MM","SS","Ext. Device"],
|
||||
["18","1","RW","TS1 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["19","1","RW","TS1 Time point 6","HH","MM","SS","Ext. Device"],
|
||||
["20","1","RW","TS1 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["21","1","RW","TS1 Time point 7","HH","MM","SS","Ext. Device"],
|
||||
["22","1","RW","TS1 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["23","1","RW","TS1 Time point 8","HH","MM","SS","Ext. Device"],
|
||||
["24","1","RW","TS1 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["25","1","RW","TS1 Time point 9","HH","MM","SS","Ext. Device"],
|
||||
["26","1","RW","TS1 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["27","1","RW","TS1 Time point 10","HH","MM","SS","Ext. Device"],
|
||||
["28","1","RW","TS1 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["29","1","RW","TS1 Time point 11","HH","MM","SS","Ext. Device"],
|
||||
["30","1","RW","TS1 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["31","1","RW","TS1 Time point 12","HH","MM","SS","Ext. Device"],
|
||||
["32","1","RW","TS1 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["33","1","RW","TS1 Time point 13","HH","MM","SS","Ext. Device"],
|
||||
["34","1","RW","TS1 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["35","1","RW","TS1 Time point 14","HH","MM","SS","Ext. Device"],
|
||||
["36","1","RW","TS1 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["37","1","RW","TS1 Time point 15","HH","MM","SS","Ext. Device"],
|
||||
["38","1","RW","TS1 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["39","1","RW","TS1 Time point 16","HH","MM","SS","Ext. Device"],
|
||||
["40","1","RW","TS1 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["41","1","RW","TS2 Time point 1","HH","MM","SS","Ext. Device"],
|
||||
["42","1","RW","TS2 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["43","1","RW","TS2 Time point 2","HH","MM","SS","Ext. Device"],
|
||||
["44","1","RW","TS2 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["45","1","RW","TS2 Time point 3","HH","MM","SS","Ext. Device"],
|
||||
["46","1","RW","TS2 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["47","1","RW","TS2 Time point 4","HH","MM","SS","Ext. Device"],
|
||||
["48","1","RW","TS2 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["49","1","RW","TS2 Time point 5","HH","MM","SS","Ext. Device"],
|
||||
["50","1","RW","TS2 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["51","1","RW","TS2 Time point 6","HH","MM","SS","Ext. Device"],
|
||||
["52","1","RW","TS2 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["53","1","RW","TS2 Time point 7","HH","MM","SS","Ext. Device"],
|
||||
["54","1","RW","TS2 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["55","1","RW","TS2 Time point 8","HH","MM","SS","Ext. Device"],
|
||||
["56","1","RW","TS2 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["57","1","RW","TS2 Time point 9","HH","MM","SS","Ext. Device"],
|
||||
["58","1","RW","TS2 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["59","1","RW","TS2 Time point 10","HH","MM","SS","Ext. Device"],
|
||||
["60","1","RW","TS2 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["61","1","RW","TS2 Time point 11","HH","MM","SS","Ext. Device"],
|
||||
["62","1","RW","TS2 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["63","1","RW","TS2 Time point 12","HH","MM","SS","Ext. Device"],
|
||||
["64","1","RW","TS2 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["65","1","RW","TS2 Time point 13","HH","MM","SS","Ext. Device"],
|
||||
["66","1","RW","TS2 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["67","1","RW","TS2 Time point 14","HH","MM","SS","Ext. Device"],
|
||||
["68","1","RW","TS2 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["69","1","RW","TS2 Time point 15","HH","MM","SS","Ext. Device"],
|
||||
["70","1","RW","TS2 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["71","1","RW","TS2 Time point 16","HH","MM","SS","Ext. Device"],
|
||||
["72","1","RW","TS2 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
|
||||
["73","1","RW","Power meter status","TBD","","",""],
|
||||
["74","1","R","Input Voltage","","","",""],
|
||||
["75","1","R","Input Current","","","",""],
|
||||
["76","1","R","Input Power","","","",""],
|
||||
["77","1","R","Cos phi","","","",""],
|
||||
["78","1","R","Frequency","","","",""],
|
||||
["79","1","RW","Energy","","","",""],
|
||||
["80","1","RW","Lifetime","","","",""],
|
||||
["81","1","RW","Power on cycles (input)","","","",""],
|
||||
["82","1","RW","Power on cycles (relay)","","","",""],
|
||||
["83","1","R","Time since last power on","","","",""],
|
||||
["84","1","R","Accelerometer data","","","",""],
|
||||
["85","1","RW","GPS latitude","pos/neg","deg","min ","sec"],
|
||||
["86","1","RW","GPS longitude","pos/neg","deg","min ","sec"],
|
||||
["87","1","RW","Actual time","HH","MM","SS","RES."],
|
||||
["88","1","RW","Actual date","Day of week","Day","Month","Year"],
|
||||
["89","1","R","Production data 1","","","",""],
|
||||
["90","1","R","Production data 2","","","",""],
|
||||
["91","1","RW","Network ID","NID3","NID2","NID1","NID0"],
|
||||
["95","1","RW","Actual Lux level from cabinet","RES.","RES.","HB","LB"],
|
||||
["96","1","RW","Threshold lux level","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
|
||||
["97","1","RW","Adjust period","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
|
||||
["98","1","RW","Offset","RES.","RES.","Dusk","Dawn"],
|
||||
["99","1","RW","CCT min/max range","max-H","max-L","min-H","min-L"],
|
||||
["100","1","RW","DALI interface","Cmd ID","Add","Cmd","Resp"],
|
||||
["101","1","RW","Module FW ver","v1","v2","v3","v4"],
|
||||
["102","1","RW","Module MAC-H","unused","unused","M1","M2"],
|
||||
["103","1","RW","Module MAC-L","M3","M4","M5","M6"],
|
||||
["122","1","R","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["123","1","R","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
|
||||
["124","1","R","FW update - data","Byte3","Byte2","Byte1","Byte0"],
|
||||
["0","0","R","Status","","","",""],
|
||||
["1","0","RW","Control register","RES.","RES.","RES.","init mode enable"],
|
||||
["2","0","R","Device types","","","",""],
|
||||
["3","0","R","Serial number (MAC)","","","",""],
|
||||
["4","0","R","Production data 1","","","",""],
|
||||
["5","0","R","Production data 2","","","",""],
|
||||
["6","0","RW","Network ID","NID3","NID2","NID1","NID0"],
|
||||
["7","0","RW","RS232 settings","param.","param.","Baudrate H","Baudrate L"],
|
||||
["8","0","R","Accelerometer data","","","",""],
|
||||
["9","0","RW","Module FW ver","v1","v2","v3","v4"],
|
||||
["10","0","RW","Module MAC-H","unused","unused","M1","M2"],
|
||||
["11","0","RW","Module MAC-L","M3","M4","M5","M6"],
|
||||
["32","0","RW","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["33","0","RW","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
|
||||
["34","0","RW","FW update - data","Byte3","Byte2","Byte1","Byte0"],
|
||||
["125","0","RW","Debug Register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["126","0","RW","Network Control Register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["127","0","R","Network Status Register","Byte3","Byte2","Byte1","Byte0"],
|
||||
["128","0","RW","Node XX Serial Number Register","SER3","SER2","SER1","SER0"],
|
||||
["256","0","R","Node XX Network Status Register","","","",""]
|
||||
];
|
||||
|
||||
let register = {};
|
||||
|
||||
module.exports = {
|
||||
registers,
|
||||
register
|
||||
}
|
||||
100
RVO35/flow_old/helper/serialport_helper.js
Executable file
100
RVO35/flow_old/helper/serialport_helper.js
Executable file
|
|
@ -0,0 +1,100 @@
|
|||
const { exec } = require('child_process');
|
||||
|
||||
function openPort(port){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
var callbackError = function(err) {
|
||||
port.removeListener('error', callbackError);
|
||||
port.removeListener('open', callbackError);
|
||||
|
||||
reject(err.message);
|
||||
};
|
||||
|
||||
var callbackOpen = function(data) {
|
||||
port.removeListener('error', callbackError);
|
||||
port.removeListener('open', callbackOpen);
|
||||
|
||||
resolve("port open: ok");
|
||||
};
|
||||
|
||||
port.on('error', callbackError);
|
||||
port.on('open', callbackOpen);
|
||||
|
||||
port.open();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function runSyncExec(command){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
if(error == null) resolve(stdout);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async function writeData(port, data, readbytes, timeout){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// If first item in data array is 255, we just write broadcast command to rsPort
|
||||
// We wait 3 seconds and resolve(["broadcast"])
|
||||
// It is important to resolve with array
|
||||
if(data[0] == 255) {
|
||||
|
||||
port.write(Buffer.from(data), function(err) {
|
||||
if (err) {
|
||||
reject(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
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 = {
|
||||
openPort,
|
||||
runSyncExec,
|
||||
writeData
|
||||
}
|
||||
317
RVO35/flow_old/helper/suncalc.js
Executable file
317
RVO35/flow_old/helper/suncalc.js
Executable file
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
(c) 2011-2015, Vladimir Agafonkin
|
||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
||||
https://github.com/mourner/suncalc
|
||||
*/
|
||||
|
||||
(function () { 'use strict';
|
||||
|
||||
// shortcuts for easier to read formulas
|
||||
|
||||
var PI = Math.PI,
|
||||
sin = Math.sin,
|
||||
cos = Math.cos,
|
||||
tan = Math.tan,
|
||||
asin = Math.asin,
|
||||
atan = Math.atan2,
|
||||
acos = Math.acos,
|
||||
rad = PI / 180;
|
||||
|
||||
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
|
||||
|
||||
|
||||
// date/time constants and conversions
|
||||
|
||||
var dayMs = 1000 * 60 * 60 * 24,
|
||||
J1970 = 2440588,
|
||||
J2000 = 2451545;
|
||||
|
||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
||||
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
|
||||
function toDays(date) { return toJulian(date) - J2000; }
|
||||
|
||||
|
||||
// general calculations for position
|
||||
|
||||
var e = rad * 23.4397; // obliquity of the Earth
|
||||
|
||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
||||
|
||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
||||
|
||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
||||
|
||||
function astroRefraction(h) {
|
||||
if (h < 0) // the following formula works for positive altitudes only.
|
||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
||||
|
||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
||||
}
|
||||
|
||||
// general sun calculations
|
||||
|
||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
||||
|
||||
function eclipticLongitude(M) {
|
||||
|
||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
||||
P = rad * 102.9372; // perihelion of the Earth
|
||||
|
||||
return M + C + P + PI;
|
||||
}
|
||||
|
||||
function sunCoords(d) {
|
||||
|
||||
var M = solarMeanAnomaly(d),
|
||||
L = eclipticLongitude(M);
|
||||
|
||||
return {
|
||||
dec: declination(L, 0),
|
||||
ra: rightAscension(L, 0)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var SunCalc = {};
|
||||
|
||||
|
||||
// calculates sun position for a given date and latitude/longitude
|
||||
|
||||
SunCalc.getPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = sunCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra;
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: altitude(H, phi, c.dec)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// sun times configuration (angle, morning name, evening name)
|
||||
|
||||
var times = SunCalc.times = [
|
||||
[-0.833, 'sunrise', 'sunset' ],
|
||||
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
|
||||
[ -6, 'dawn', 'dusk' ],
|
||||
[ -12, 'nauticalDawn', 'nauticalDusk'],
|
||||
[ -18, 'nightEnd', 'night' ],
|
||||
[ 6, 'goldenHourEnd', 'goldenHour' ]
|
||||
];
|
||||
|
||||
// adds a custom time to the times config
|
||||
|
||||
SunCalc.addTime = function (angle, riseName, setName) {
|
||||
times.push([angle, riseName, setName]);
|
||||
};
|
||||
|
||||
|
||||
// calculations for sun times
|
||||
|
||||
var J0 = 0.0009;
|
||||
|
||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
||||
|
||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
||||
|
||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
||||
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
|
||||
|
||||
// returns set time for the given sun altitude
|
||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
||||
|
||||
var w = hourAngle(h, phi, dec),
|
||||
a = approxTransit(w, lw, n);
|
||||
return solarTransitJ(a, M, L);
|
||||
}
|
||||
|
||||
|
||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
||||
// the observer height (in meters) relative to the horizon
|
||||
|
||||
SunCalc.getTimes = function (date, lat, lng, height) {
|
||||
|
||||
height = height || 0;
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
|
||||
dh = observerAngle(height),
|
||||
|
||||
d = toDays(date),
|
||||
n = julianCycle(d, lw),
|
||||
ds = approxTransit(0, lw, n),
|
||||
|
||||
M = solarMeanAnomaly(ds),
|
||||
L = eclipticLongitude(M),
|
||||
dec = declination(L, 0),
|
||||
|
||||
Jnoon = solarTransitJ(ds, M, L),
|
||||
|
||||
i, len, time, h0, Jset, Jrise;
|
||||
|
||||
|
||||
var result = {
|
||||
solarNoon: fromJulian(Jnoon),
|
||||
nadir: fromJulian(Jnoon - 0.5)
|
||||
};
|
||||
|
||||
for (i = 0, len = times.length; i < len; i += 1) {
|
||||
time = times[i];
|
||||
h0 = (time[0] + dh) * rad;
|
||||
|
||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
||||
Jrise = Jnoon - (Jset - Jnoon);
|
||||
|
||||
result[time[1]] = fromJulian(Jrise);
|
||||
result[time[2]] = fromJulian(Jset);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
||||
|
||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
||||
|
||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
||||
|
||||
l = L + rad * 6.289 * sin(M), // longitude
|
||||
b = rad * 5.128 * sin(F), // latitude
|
||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
||||
|
||||
return {
|
||||
ra: rightAscension(l, b),
|
||||
dec: declination(l, b),
|
||||
dist: dt
|
||||
};
|
||||
}
|
||||
|
||||
SunCalc.getMoonPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = moonCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra,
|
||||
h = altitude(H, phi, c.dec),
|
||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
||||
|
||||
h = h + astroRefraction(h); // altitude correction for refraction
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: h,
|
||||
distance: c.dist,
|
||||
parallacticAngle: pa
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// calculations for illumination parameters of the moon,
|
||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
|
||||
SunCalc.getMoonIllumination = function (date) {
|
||||
|
||||
var d = toDays(date || new Date()),
|
||||
s = sunCoords(d),
|
||||
m = moonCoords(d),
|
||||
|
||||
sdist = 149598000, // distance from Earth to Sun in km
|
||||
|
||||
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
|
||||
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
|
||||
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
|
||||
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
|
||||
|
||||
return {
|
||||
fraction: (1 + cos(inc)) / 2,
|
||||
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
|
||||
angle: angle
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function hoursLater(date, h) {
|
||||
return new Date(date.valueOf() + h * dayMs / 24);
|
||||
}
|
||||
|
||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
||||
|
||||
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
|
||||
var t = new Date(date);
|
||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
||||
else t.setHours(0, 0, 0, 0);
|
||||
|
||||
var hc = 0.133 * rad,
|
||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
||||
|
||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
||||
for (var i = 1; i <= 24; i += 2) {
|
||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
||||
|
||||
a = (h0 + h2) / 2 - h1;
|
||||
b = (h2 - h0) / 2;
|
||||
xe = -b / (2 * a);
|
||||
ye = (a * xe + b) * xe + h1;
|
||||
d = b * b - 4 * a * h1;
|
||||
roots = 0;
|
||||
|
||||
if (d >= 0) {
|
||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
||||
x1 = xe - dx;
|
||||
x2 = xe + dx;
|
||||
if (Math.abs(x1) <= 1) roots++;
|
||||
if (Math.abs(x2) <= 1) roots++;
|
||||
if (x1 < -1) x1 = x2;
|
||||
}
|
||||
|
||||
if (roots === 1) {
|
||||
if (h0 < 0) rise = i + x1;
|
||||
else set = i + x1;
|
||||
|
||||
} else if (roots === 2) {
|
||||
rise = i + (ye < 0 ? x2 : x1);
|
||||
set = i + (ye < 0 ? x1 : x2);
|
||||
}
|
||||
|
||||
if (rise && set) break;
|
||||
|
||||
h0 = h2;
|
||||
}
|
||||
|
||||
var result = {};
|
||||
|
||||
if (rise) result.rise = hoursLater(t, rise);
|
||||
if (set) result.set = hoursLater(t, set);
|
||||
|
||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// export as Node module / AMD module / browser variable
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
|
||||
else if (typeof define === 'function' && define.amd) define(SunCalc);
|
||||
else window.SunCalc = SunCalc;
|
||||
|
||||
}());
|
||||
124
RVO35/flow_old/helper/utils.js
Executable file
124
RVO35/flow_old/helper/utils.js
Executable file
|
|
@ -0,0 +1,124 @@
|
|||
function bytesToInt(bytes, numberOfBytes)
|
||||
{
|
||||
let buffer = [];
|
||||
if(Array.isArray(bytes))
|
||||
{
|
||||
buffer = bytes.slice(0);
|
||||
if(numberOfBytes != undefined)
|
||||
{
|
||||
buffer = bytes.slice(bytes.length - numberOfBytes);
|
||||
}
|
||||
}
|
||||
else buffer.push(bytes);
|
||||
|
||||
//var decimal = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
|
||||
let l = (buffer.length - 1) * 8;
|
||||
|
||||
let decimal = 0;
|
||||
for(let i = 0; i < buffer.length; i++)
|
||||
{
|
||||
var s = buffer[i] << l;
|
||||
if(l < 8) s = buffer[i]
|
||||
decimal = decimal + s;
|
||||
|
||||
l = l - 8;
|
||||
|
||||
}
|
||||
|
||||
return decimal;
|
||||
}
|
||||
|
||||
function resizeArray(arr, newSize, defaultValue) {
|
||||
while(newSize > arr.length)
|
||||
arr.push(defaultValue);
|
||||
arr.length = newSize;
|
||||
}
|
||||
|
||||
longToByteArray = function(/*long*/long) {
|
||||
// we want to represent the input as a 8-bytes array
|
||||
var byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
for ( var index = 0; index < byteArray.length; index ++ ) {
|
||||
var byte = long & 0xff;
|
||||
byteArray [ index ] = byte;
|
||||
long = (long - byte) / 256 ;
|
||||
}
|
||||
|
||||
return byteArray;
|
||||
};
|
||||
|
||||
function addDays(date, days) {
|
||||
var result = new Date(date);
|
||||
result.setDate(result.getDate() + days);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
sleep(2000).then(() => {
|
||||
// Do something after the sleep!
|
||||
|
||||
|
||||
});
|
||||
*/
|
||||
|
||||
function sleep (time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
function isEmptyObject( obj ) {
|
||||
for ( var name in obj ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function convertUTCDateToLocalDate(date) {
|
||||
var newDate = new Date(date);
|
||||
newDate.setMinutes(date.getMinutes() + date.getTimezoneOffset());
|
||||
return newDate;
|
||||
}
|
||||
|
||||
function addZeroBefore(n) {
|
||||
return (n < 10 ? '0' : '') + n;
|
||||
}
|
||||
|
||||
var convertBase = function () {
|
||||
|
||||
function convertBase(baseFrom, baseTo) {
|
||||
return function (num) {
|
||||
return parseInt(num, baseFrom).toString(baseTo);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// binary to decimal
|
||||
convertBase.bin2dec = convertBase(2, 10);
|
||||
|
||||
// binary to hexadecimal
|
||||
convertBase.bin2hex = convertBase(2, 16);
|
||||
|
||||
// decimal to binary
|
||||
convertBase.dec2bin = convertBase(10, 2);
|
||||
|
||||
// decimal to hexadecimal
|
||||
convertBase.dec2hex = convertBase(10, 16);
|
||||
|
||||
// hexadecimal to binary
|
||||
convertBase.hex2bin = convertBase(16, 2);
|
||||
|
||||
// hexadecimal to decimal
|
||||
convertBase.hex2dec = convertBase(16, 10);
|
||||
|
||||
return convertBase;
|
||||
}();
|
||||
|
||||
module.exports = {
|
||||
bytesToInt,
|
||||
longToByteArray,
|
||||
addDays,
|
||||
addZeroBefore,
|
||||
resizeArray,
|
||||
isEmptyObject,
|
||||
sleep,
|
||||
convertUTCDateToLocalDate
|
||||
}
|
||||
137
RVO35/flow_old/httprequest.js
Executable file
137
RVO35/flow_old/httprequest.js
Executable file
|
|
@ -0,0 +1,137 @@
|
|||
exports.id = 'httprequest';
|
||||
exports.title = 'HTTP Request';
|
||||
exports.group = 'HTTP';
|
||||
exports.color = '#5D9CEC';
|
||||
exports.input = true;
|
||||
exports.version = '2.0.1';
|
||||
exports.output = 1;
|
||||
exports.author = 'Peter Širka';
|
||||
exports.icon = 'cloud-upload';
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="textbox" data-jc-path="url" class="m" data-jc-config="required:true;maxlength:500;placeholder:@(E.g. https\\://www.totaljs.com)">@(URL address)</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE">@(HTTP method)</div>
|
||||
</div>
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="dropdown" data-jc-path="stringify" data-jc-config="required:true;items:,URL encoded|encoded,JSON|json,RAW|raw,None|none">@(Serialization)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-jc="checkbox" data-jc-path="chunks">@(Download the content <b>in chunks</b>)</div>
|
||||
<div data-jc="checkbox" data-jc-path="persistentcookies">@(Keep persistent cookies)</div>
|
||||
<div data-jc="checkbox" data-jc-path="nodns">@(Disable DNS cache)</div>
|
||||
<div data-jc="checkbox" data-jc-path="keepalive">@(Keep alive connection)</div>
|
||||
</div>
|
||||
<hr class="nmt nmb" />
|
||||
<div class="padding">
|
||||
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
|
||||
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
|
||||
</div>
|
||||
<div class="padding bg-smoke">
|
||||
<section>
|
||||
<label><i class="fa fa-lock"></i>@(HTTP basic access authentication)</label>
|
||||
<div class="padding npb">
|
||||
<div class="row">
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="textbox" data-jc-path="username">@(User)</div>
|
||||
</div>
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="textbox" data-jc-path="userpassword">@(Password)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# Request
|
||||
|
||||
This component creates a request with received data.
|
||||
|
||||
__Response:__
|
||||
\`\`\`javascript
|
||||
{
|
||||
data: String,
|
||||
headers: Object,
|
||||
status: Number,
|
||||
host: String
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
__Dynamic arguments__:
|
||||
Are performed via FlowData repository and can be used for URL address or for custom headers/cookies/auth. Use \`repository\` component for creating of dynamic arguments. Dynamic values are replaced in the form \`{key}\`:
|
||||
|
||||
- url address e.g. \`https://.../{key}/\`
|
||||
- headers values e.g. \`{token}\`
|
||||
- cookies values e.g. \`{token}\``;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var can = false;
|
||||
var flags = null;
|
||||
var cookies2 = null;
|
||||
|
||||
instance.on('data', function(response) {
|
||||
can && instance.custom.send(response);
|
||||
});
|
||||
|
||||
instance.custom.send = function(response) {
|
||||
var options = instance.options;
|
||||
|
||||
var headers = null;
|
||||
var cookies = null;
|
||||
|
||||
options.headers && Object.keys(options.headers).forEach(function(key) {
|
||||
!headers && (headers = {});
|
||||
headers[key] = response.arg(options.headers[key]);
|
||||
});
|
||||
|
||||
if (options.username && options.userpassword) {
|
||||
!headers && (headers = {});
|
||||
headers['Authorization'] = 'Basic ' + U.createBuffer(response.arg(options.username + ':' + options.userpassword)).toString('base64');
|
||||
}
|
||||
|
||||
options.cookies && Object.keys(options.cookies).forEach(function(key) {
|
||||
!cookies && (cookies = {});
|
||||
cookies[key] = response.arg(options.cookies[key]);
|
||||
});
|
||||
|
||||
if (options.chunks) {
|
||||
U.download(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, response) {
|
||||
response.on('data', (chunks) => instance.send2(chunks));
|
||||
}, cookies || cookies2, headers);
|
||||
} else {
|
||||
U.request(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, data, status, headers, host) {
|
||||
if (response && !err) {
|
||||
response.data = { data: data, status: status, headers: headers, host: host };
|
||||
instance.send2(response);
|
||||
} else if (err)
|
||||
instance.error(err, response);
|
||||
}, cookies || cookies2, headers);
|
||||
}
|
||||
};
|
||||
|
||||
instance.reconfigure = function() {
|
||||
var options = instance.options;
|
||||
can = options.url && options.url && options.method && options.stringify ? true : false;
|
||||
instance.status(can ? '' : 'Not configured', can ? undefined : 'red');
|
||||
|
||||
if (!can)
|
||||
return;
|
||||
|
||||
flags = [];
|
||||
flags.push(options.method.toLowerCase());
|
||||
options.stringify === 'json' && flags.push('json');
|
||||
options.stringify === 'raw' && flags.push('raw');
|
||||
options.keepalive && flags.push('keepalive');
|
||||
!options.nodns && flags.push('dnscache');
|
||||
if (options.persistentcookies) {
|
||||
flags.push('cookies');
|
||||
cookies2 = {};
|
||||
} else
|
||||
cookies2 = null;
|
||||
};
|
||||
|
||||
instance.on('options', instance.reconfigure);
|
||||
instance.reconfigure();
|
||||
};
|
||||
76
RVO35/flow_old/httpresponse.js
Executable file
76
RVO35/flow_old/httpresponse.js
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
exports.id = 'httpresponse';
|
||||
exports.title = 'HTTP Response';
|
||||
exports.group = 'HTTP';
|
||||
exports.color = '#5D9CEC';
|
||||
exports.icon = 'arrow-right';
|
||||
exports.input = true;
|
||||
exports.output = ['#666D76'];
|
||||
exports.version = '2.0.0';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.readme = `# HTTP response
|
||||
|
||||
HTTP response will respond with data recieved using data-type set in Settings form or plain text if not set. Output is the message duration \`Number\` in seconds.`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="dropdown" data-jc-path="datatype" data-jc-config="required:true;items:,Empty response|emptyresponse,JSON|json,HTML|html,Plain text|plain,XML|xml">@(Response data-type)</div>
|
||||
<div class="help"><code>JSON</code> is by default.</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var dursum = 0;
|
||||
var durcount = 0;
|
||||
|
||||
instance.on('data', function(flowdata) {
|
||||
|
||||
var ctrl = flowdata.repository.controller;
|
||||
var data = flowdata.data;
|
||||
|
||||
if (!ctrl) {
|
||||
instance.throw('No controller to use for response!');
|
||||
return;
|
||||
}
|
||||
|
||||
durcount++;
|
||||
dursum += ((new Date() - flowdata.begin) / 1000).floor(2);
|
||||
setTimeout2(instance.id, instance.custom.duration, 500, 10);
|
||||
|
||||
ctrl.$flowdata = flowdata;
|
||||
|
||||
var datatype = instance.options.datatype || 'json';
|
||||
if (datatype === 'emptyresponse')
|
||||
return ctrl.plain('');
|
||||
|
||||
if (datatype !== 'json' && typeof(data) !== 'string') {
|
||||
instance.throw('Incorect type of data, expected string, got ' + typeof(data));
|
||||
ctrl.plain(data == null ? '' : data.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
switch(datatype) {
|
||||
case 'html':
|
||||
ctrl.content(data, 'text/html');
|
||||
break;
|
||||
case 'plain':
|
||||
ctrl.plain(data);
|
||||
break;
|
||||
case 'xml':
|
||||
ctrl.content(data, 'text/xml');
|
||||
break;
|
||||
default:
|
||||
ctrl.json(data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
instance.on('service', function() {
|
||||
dursum = 0;
|
||||
durcount = 0;
|
||||
});
|
||||
|
||||
instance.custom.duration = function() {
|
||||
var avg = (dursum / durcount).floor(2);
|
||||
instance.status(avg + ' sec.');
|
||||
instance.send2(0, avg);
|
||||
};
|
||||
};
|
||||
326
RVO35/flow_old/httproute.js
Executable file
326
RVO35/flow_old/httproute.js
Executable file
|
|
@ -0,0 +1,326 @@
|
|||
exports.id = 'httproute';
|
||||
exports.title = 'HTTP Route';
|
||||
exports.group = 'HTTP';
|
||||
exports.color = '#5D9CEC';
|
||||
exports.icon = 'globe';
|
||||
exports.input = false;
|
||||
exports.output = ['#6CAC5A', '#37BC9B'];
|
||||
exports.version = '1.2.3';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.cloning = false;
|
||||
exports.options = { method: 'GET', url: '', size: 5, cacheexpire: '5 minutes', cachepolicy: 0, timeout: 5 };
|
||||
exports.dateupdated = '2021-01-21 18:30d';
|
||||
exports.readme = `# HTTP route
|
||||
|
||||
__Outputs__:
|
||||
- first output: raw data (cache is empty or is disabled)
|
||||
- second output: cached data
|
||||
|
||||
If one of the outputs is disabled then automatic responce with code "503 service unavailable" is sent.
|
||||
|
||||
When a request comes in bellow object is available at \`flowdata.data\`:
|
||||
|
||||
\`\`\`javascript
|
||||
{
|
||||
params: { id: '1' }, // params for dynamic routes, e.g. /test/{id}
|
||||
query: { msg: 'Hello' }, // parsed query string, e.g. /test/1?msg=Hello
|
||||
body: { test: 'OK' }, // object if json requests otherwise string
|
||||
headers: {}, // headers data
|
||||
session: {}, // session data
|
||||
user: {}, // user data
|
||||
files: [], // uploaded files
|
||||
url: '/users/', // a relative URL address
|
||||
referrer: '/', // referrer
|
||||
mobile: false, // determines mobile device
|
||||
robot: false, // determines search robots/crawlsers
|
||||
language: 'en' // determines language
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
See [documentation for flags](https://docs.totaljs.com/latest/en.html#api~HttpRouteOptionsFlags~unauthorize). These method flags are set automatically e.g. \`get, post, put, patch or delete\`
|
||||
|
||||
---
|
||||
|
||||
\`id:ROUTE_ID\` flag cannot be used since it's already used by this component internally`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<section>
|
||||
<label>@(Main settings)</label>
|
||||
<div class="padding npb">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE,PATCH,OPTIONS">@(HTTP method)</div>
|
||||
</div>
|
||||
<div class="col-md-9 m">
|
||||
<div data-jc="textbox" data-jc-path="url" data-jc-config="required:true;maxlength:500;placeholder:/api/test;error:URL already in use">@(URL address)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 m">
|
||||
<div data-jc="textbox" data-jc-path="flags" data-jc-config="placeholder:json">@(Additional flags)</div>
|
||||
<div class="help m">@(Separate flags by comma e.g. <code>json, authorize</code>)</div>
|
||||
</div>
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="size" data-jc-config="placeholder:@(in kB);increment:true;type:number;maxlength:10;align:center">@(Max. request size)</div>
|
||||
<div class="help m">@(In <code>kB</code> kilobytes)</div>
|
||||
</div>
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="timeout" data-jc-config="placeholder:@(in seconds);increment:true;type:number;maxlength:5;align:center">@(Timeout)</div>
|
||||
<div class="help m">@(In seconds.)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<br />
|
||||
<div data-jc="checkbox" data-jc-path="emptyresponse" class="b black">@(Automatically respond with 200 OK?)</div>
|
||||
<div class="help m">@(If not checked you need to use HTTP response component to respond to the request.)</div>
|
||||
<hr />
|
||||
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
|
||||
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
|
||||
</div>
|
||||
<hr class="nmt" />
|
||||
<div class="padding npt">
|
||||
<div class="row">
|
||||
<div class="col-md-9 m">
|
||||
<div data-jc="dropdown" data-jc-path="cachepolicy" data-jc-config="type:number;items:@(no cache)|0,@(URL)|1,@(URL + query string)|2,@(URL + query string + user instance)|3">@(Cache policy)</div>
|
||||
<div class="help">@(User instance must contain <code>id</code> property.)</div>
|
||||
</div>
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="cacheexpire" data-jc-config="maxlength:20;align:center">@(Expiration)</div>
|
||||
<div class="help">@(E.g. <code>5 minutes</code>)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
var httproute_currenturl = '';
|
||||
var httproute_currentmethod = 'GET';
|
||||
|
||||
ON('open.httproute', function(component, options) {
|
||||
if (options.flags instanceof Array) {
|
||||
var method = options.method.toLowerCase();
|
||||
options.flags = options.flags.remove(function(item) {
|
||||
switch (typeof(item)) {
|
||||
case 'string':
|
||||
return item.substring(0, 3) === 'id:' || item === method;
|
||||
case 'number': // timeout
|
||||
return true;
|
||||
}
|
||||
}).join(', ');
|
||||
}
|
||||
if (component.isnew) {
|
||||
options.url = '';
|
||||
options.name = '';
|
||||
} else {
|
||||
httproute_currenturl = options.url;
|
||||
httproute_currentmethod = options.method;
|
||||
}
|
||||
});
|
||||
|
||||
WATCH('settings.httproute.url', httproutecheckurl);
|
||||
WATCH('settings.httproute.method', httproutecheckurl);
|
||||
|
||||
function httproutecheckurl() {
|
||||
if (httproute_currenturl !== settings.httproute.url || httproute_currentmethod !== settings.httproute.method) {
|
||||
TRIGGER('httproutecheckurl', { url: settings.httproute.url, method: settings.httproute.method }, function(e) {
|
||||
var p = 'settings.httproute.url';
|
||||
if (e) {
|
||||
// invalid
|
||||
INVALID(p);
|
||||
} else {
|
||||
if (!CAN(p))
|
||||
RESET(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ON('save.httproute', function(component, options) {
|
||||
!component.name && (component.name = options.method + ' ' + options.url);
|
||||
|
||||
var builder = [];
|
||||
builder.push('### @(Configuration)');
|
||||
builder.push('');
|
||||
builder.push('- __' + options.method + ' ' + options.url + '__');
|
||||
builder.push('- @(flags): ' + options.flags);
|
||||
builder.push('- @(maximum request data length): __' + options.size + ' kB__');
|
||||
builder.push('- @(empty response): __' + options.emptyresponse + '__');
|
||||
|
||||
if (options.headers) {
|
||||
var headers = [];
|
||||
Object.keys(options.headers).forEach(function(key){
|
||||
headers.push(key + ': ' + options.headers[key]);
|
||||
});
|
||||
headers.length && builder.push('- @(headers):\\n\`\`\`' + headers.join('\\n') + '\`\`\`');
|
||||
};
|
||||
|
||||
var cp = '@(no cache)';
|
||||
if (options.cachepolicy === 1)
|
||||
cp = '@(URL)';
|
||||
if (options.cachepolicy === 2)
|
||||
cp = '@(URL + query string)';
|
||||
|
||||
if (options.cachepolicy === 3)
|
||||
cp = '@(URL + query string + user instance)';
|
||||
|
||||
builder.push('- @(cache policy): __' + cp + '__');
|
||||
|
||||
options.cacheexpire && builder.push('- @(cache expire): __' + options.cacheexpire + '__');
|
||||
|
||||
component.notes = builder.join('\\n');
|
||||
});
|
||||
</script>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var route;
|
||||
|
||||
var uninstall = function(id) {
|
||||
if (F.is4) {
|
||||
route && route.remove();
|
||||
route = null;
|
||||
} else
|
||||
UNINSTALL('route', id);
|
||||
};
|
||||
|
||||
instance.custom.emptyresponse = function(self) {
|
||||
self.plain();
|
||||
};
|
||||
|
||||
instance.reconfigure = function() {
|
||||
|
||||
var options = instance.options;
|
||||
|
||||
if (!options.url) {
|
||||
instance.status('Not configured', 'red');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(options.flags) === 'string')
|
||||
options.flags = options.flags.split(',').trim();
|
||||
|
||||
uninstall('id:' + instance.id);
|
||||
|
||||
var flags = options.flags || [];
|
||||
|
||||
flags.push('id:' + instance.id);
|
||||
|
||||
if (!F.is4)
|
||||
flags.push(options.method.toLowerCase());
|
||||
|
||||
options.timeout && flags.push(options.timeout * 1000);
|
||||
|
||||
// Make unique values
|
||||
flags = flags.filter(function(v, i, a) {
|
||||
if(F.is4 && v.toString().toLowerCase() === options.method.toLowerCase())
|
||||
return false; // remove method
|
||||
return a.indexOf(v) === i;
|
||||
});
|
||||
|
||||
options.flags = flags;
|
||||
var handler = function() {
|
||||
|
||||
if (instance.paused || (instance.isDisabled && (instance.isDisabled('output', 0) || instance.isDisabled('output', 1)))) {
|
||||
instance.status('503 Service Unavailable');
|
||||
this.status = 503;
|
||||
this.json();
|
||||
return;
|
||||
}
|
||||
|
||||
var key;
|
||||
var self = this;
|
||||
|
||||
if (instance.options.emptyresponse) {
|
||||
instance.status('200 OK');
|
||||
setTimeout(instance.custom.emptyresponse, 100, self);
|
||||
|
||||
if (instance.hasConnection(0)) {
|
||||
var data = instance.make({
|
||||
query: self.query,
|
||||
body: self.body,
|
||||
session: self.session,
|
||||
user: self.user,
|
||||
files: self.files,
|
||||
headers: self.req.headers,
|
||||
url: self.url,
|
||||
params: self.params
|
||||
});
|
||||
instance.send2(0, data);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (instance.options.cachepolicy) {
|
||||
case 1: // URL
|
||||
key = 'rro' + instance.id + self.url.hash();
|
||||
break;
|
||||
case 2: // URL + query
|
||||
case 3: // URL + query + user
|
||||
key = self.url;
|
||||
var keys = Object.keys(self.query);
|
||||
keys.sort();
|
||||
for (var i = 0, length = keys.length; i < length; i++)
|
||||
key += keys[i] + self.query[keys[i]] + '&';
|
||||
if (instance.options.cachepolicy === 3 && self.user)
|
||||
key += 'iduser' + self.user.id;
|
||||
key = 'rro' + instance.id + key.hash();
|
||||
break;
|
||||
}
|
||||
|
||||
if (key && F.cache.get2(key)) {
|
||||
var data = instance.make(F.cache.get2(key));
|
||||
data.repository.controller = self;
|
||||
instance.send2(1, data);
|
||||
} else {
|
||||
|
||||
var data = instance.make({
|
||||
query: self.query,
|
||||
body: self.body,
|
||||
session: self.session,
|
||||
user: self.user,
|
||||
files: self.files,
|
||||
headers: self.req.headers,
|
||||
url: self.url,
|
||||
params: self.params,
|
||||
mobile: self.mobile,
|
||||
robot: self.robot,
|
||||
referrer: self.referrer,
|
||||
language: self.language
|
||||
});
|
||||
|
||||
data.repository.controller = self;
|
||||
instance.send2(0, data);
|
||||
key && FINISHED(self.res, () => F.cache.set(key, self.$flowdata.data, instance.options.cacheexpire));
|
||||
}
|
||||
};
|
||||
|
||||
if (F.is4)
|
||||
route = ROUTE(options.method.toUpperCase() + ' ' + options.url, handler, flags, options.size || 5);
|
||||
else
|
||||
F.route(options.url, handler, flags, options.size || 5);
|
||||
|
||||
instance.status('Listening', 'green');
|
||||
};
|
||||
|
||||
instance.reconfigure();
|
||||
instance.on('options', instance.reconfigure);
|
||||
instance.on('close', function(){
|
||||
uninstall('id:' + instance.id);
|
||||
});
|
||||
};
|
||||
|
||||
// check url exists
|
||||
FLOW.trigger('httproutecheckurl', function(next, data) {
|
||||
var url = data.url;
|
||||
var method = data.method;
|
||||
if (url[url.length - 1] !== '/')
|
||||
url += '/';
|
||||
var exists = F.routes.web.findItem(r => r.urlraw === url && r.method === method);
|
||||
next(exists != null);
|
||||
});
|
||||
|
||||
exports.uninstall = function() {
|
||||
FLOW.trigger('httproutecheckurl', null);
|
||||
};
|
||||
101
RVO35/flow_old/infosender.js
Executable file
101
RVO35/flow_old/infosender.js
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
exports.id = 'infosender';
|
||||
exports.title = 'Info sender';
|
||||
exports.version = '1.0.0';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#2134B0';
|
||||
exports.input = 2;
|
||||
exports.output = 1
|
||||
exports.icon = 'bolt';
|
||||
|
||||
const { networkInterfaces } = require('os');
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">CSV Import</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# send all data to projects.worksys.io, required to monitor status of controller(unipi)`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
let id;
|
||||
let allValues = {};
|
||||
let sendAllValuesInterval;
|
||||
let configured = false;
|
||||
|
||||
let now = new Date();
|
||||
console.log(exports.title, "INSTALLED", now.toLocaleString("sk-SK"));
|
||||
|
||||
const nets = networkInterfaces();
|
||||
let ipAddresses = Object.create(null); // Or just '{}', an empty object
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
if (!ipAddresses[name]) {
|
||||
ipAddresses[name] = [];
|
||||
}
|
||||
ipAddresses[name].push(net.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendValues()
|
||||
{
|
||||
if(!configured) return;
|
||||
|
||||
if(Object.keys(allValues).length > 0)
|
||||
{
|
||||
if(id)
|
||||
{
|
||||
delete allValues.__force__;
|
||||
let dataToSend = {...allValues};
|
||||
dataToSend.id = id;
|
||||
dataToSend.ipAddresses = ipAddresses;
|
||||
|
||||
instance.send(0, dataToSend);
|
||||
|
||||
allValues = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(exports.title, "unable to send data, no id");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
instance.on("close", () => {
|
||||
clearInterval(sendAllValuesInterval);
|
||||
})
|
||||
|
||||
instance.on("0", _ => {
|
||||
id = FLOW.GLOBALS.settings.project_id;
|
||||
configured = true;
|
||||
})
|
||||
|
||||
instance.on("1", flowdata => {
|
||||
|
||||
allValues = { ...allValues, ...flowdata.data};
|
||||
//console.log("DATA RECEIVED", flowdata.data);
|
||||
|
||||
//__force__
|
||||
if(flowdata.data.hasOwnProperty("__force__"))
|
||||
{
|
||||
if(flowdata.data.__force__)
|
||||
{
|
||||
sendValues();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
sendAllValuesInterval = setInterval(() => {
|
||||
sendValues();
|
||||
}, 60000*3);
|
||||
|
||||
}
|
||||
|
||||
367
RVO35/flow_old/modbus_reader.js
Executable file
367
RVO35/flow_old/modbus_reader.js
Executable file
|
|
@ -0,0 +1,367 @@
|
|||
exports.id = 'modbus_reader';
|
||||
exports.title = 'Modbus reader';
|
||||
exports.version = '2.0.0';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#2134B0';
|
||||
exports.input = 1;
|
||||
exports.output = ["red", "white", "yellow"];
|
||||
exports.click = false;
|
||||
exports.author = 'Rastislav Kovac';
|
||||
exports.icon = 'bolt';
|
||||
exports.readme = `
|
||||
Modbus requests to modbus devices (electromer, twilight sensor, thermometer.
|
||||
Component keeps running arround deviceConfig array in "timeoutInterval" intervals. Array items are objects with single modbus devices.
|
||||
Everything is sent to dido_controller. If requests to device fail (all registers must fail to send NOK status) , we send "NOK-'device'" status to dido_controller.
|
||||
This device needs to be configured in dido_controller!!! Double check if it is. In dido_controller we calculate final status and all values with status are pushed to tb.
|
||||
`;
|
||||
|
||||
const modbus = require('jsmodbus');
|
||||
const SerialPort = require('serialport');
|
||||
const { timeoutInterval, deviceConfig } = require("../databases/modbus_config");
|
||||
const { sendNotification } = require('./helper/notification_reporter');
|
||||
|
||||
const DELAY_BETWEEN_DEVICES = 10000;
|
||||
|
||||
const SEND_TO = {
|
||||
debug: 0,
|
||||
dido_controller: 1,
|
||||
tb: 2
|
||||
};
|
||||
|
||||
//to handle NOK and OK sendNotifications s
|
||||
const numberOfNotResponding = {};
|
||||
let tbName = null;
|
||||
let mainSocket;
|
||||
//number of phases inRVO
|
||||
let phases;
|
||||
//phases where voltage is 0 (set)
|
||||
let noVoltage;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
class SocketWithClients {
|
||||
|
||||
constructor () {
|
||||
this.stream = null;
|
||||
this.socket = null;
|
||||
this.clients = {};
|
||||
this.allValues = {};
|
||||
this.errors = 0;
|
||||
this.index = 0;
|
||||
this.timeoutInterval = 5000;
|
||||
|
||||
// we need to go always around for all devices. So we need index value, device address, as well as number of registers for single device
|
||||
this.deviceAddress = null; // device address (1 - EM340 and 2 for twilight_sensor)
|
||||
this.indexInDeviceConfig = 0; // first item in deviceConfig
|
||||
this.lengthOfActualDeviceStream = null;
|
||||
this.device = null;
|
||||
|
||||
// lampSwitchNotification helper variables
|
||||
this.onNotificationSent = false;
|
||||
this.offNotificationSent = false;
|
||||
this.phases = this.buildPhases();
|
||||
|
||||
this.startSocket();
|
||||
}
|
||||
|
||||
buildPhases = () => {
|
||||
let a = [];
|
||||
for (let i = 1; i<= phases; i++) {
|
||||
a.push(`Phase_${i}_voltage`)
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
startSocket = () => {
|
||||
|
||||
let obj = this;
|
||||
|
||||
this.socket = new SerialPort("/dev/ttymxc0", {
|
||||
baudRate: 9600,
|
||||
})
|
||||
|
||||
// we create a client for every deviceAddress ( = address) in list and push them into dictionary
|
||||
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.socket.on('error', function(e) {
|
||||
console.log('socket connection error', e);
|
||||
if(e.code == 'ECONNREFUSED' || e.code == 'ECONNRESET') {
|
||||
console.log(exports.title + ' Waiting 10 seconds before trying to connect again');
|
||||
setTimeout(obj.startSocket, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('close', function() {
|
||||
console.log('Socket connection closed ' + exports.title + ' Waiting 10 seconds before trying to connect again');
|
||||
setTimeout(obj.startSocket, 10000);
|
||||
});
|
||||
|
||||
this.socket.on('open', function () {
|
||||
console.log("socket connected");
|
||||
obj.getActualStreamAndDevice();
|
||||
obj.timeoutInterval = timeoutInterval - DELAY_BETWEEN_DEVICES; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
getActualStreamAndDevice = () => {
|
||||
const dev = deviceConfig[this.indexInDeviceConfig];
|
||||
this.index = 0;
|
||||
this.errors = 0;
|
||||
this.stream = dev.stream;
|
||||
this.lengthOfActualDeviceStream = dev.stream.length;
|
||||
this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number
|
||||
this.device = dev.device; //em340, twilight_sensor
|
||||
|
||||
if(this.indexInDeviceConfig == 0) setTimeout(this.readRegisters, this.timeoutInterval);
|
||||
else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES);
|
||||
}
|
||||
|
||||
readRegisters = () => {
|
||||
|
||||
const str = this.stream[this.index];
|
||||
const register = str.register;
|
||||
const size = str.size;
|
||||
const tbAttribute = str.tbAttribute;
|
||||
|
||||
let obj = this;
|
||||
|
||||
this.clients[this.deviceAddress].readHoldingRegisters(register, size)
|
||||
.then( function (resp) {
|
||||
|
||||
resp = resp.response._body.valuesAsArray; //resp is array of length 1 or 2, f.e. [2360,0]
|
||||
// console.log(deviceAddress, register, tbAttribute, resp);
|
||||
|
||||
//device is responding again after NOK status
|
||||
if(numberOfNotResponding.hasOwnProperty(obj.device))
|
||||
{
|
||||
let message = "";
|
||||
if(obj.device == "em340")
|
||||
{
|
||||
message = "electrometer_ok";
|
||||
}
|
||||
else if(obj.device == "twilight_sensor")
|
||||
{
|
||||
message = "twilight_sensor_ok";
|
||||
}
|
||||
message && sendNotification("modbus_reader: readRegisters", tbName, message, {}, "", SEND_TO.tb, instance);
|
||||
delete numberOfNotResponding[obj.device];
|
||||
}
|
||||
|
||||
obj.transformResponse(resp, register);
|
||||
|
||||
//obj.errors = 0;
|
||||
obj.index++;
|
||||
obj.readAnotherRegister();
|
||||
|
||||
}).catch (function () {
|
||||
|
||||
//console.log("errors pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute);
|
||||
|
||||
obj.errors++;
|
||||
if(obj.errors == obj.lengthOfActualDeviceStream)
|
||||
{
|
||||
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 ??
|
||||
if(!numberOfNotResponding.hasOwnProperty(obj.device))
|
||||
{
|
||||
let message = "";
|
||||
if(obj.device == "twilight_sensor")
|
||||
{
|
||||
message = "twilight_sensor_nok";
|
||||
}
|
||||
else if(obj.device == "em340")
|
||||
{
|
||||
message = "electrometer_nok";
|
||||
}
|
||||
message && sendNotification("modbus_reader: readingTimeouted", tbName, message, {}, "", SEND_TO.tb, instance);
|
||||
numberOfNotResponding[obj.device] = 1;
|
||||
}
|
||||
|
||||
obj.errors = 0;
|
||||
numberOfNotResponding[obj.device] += 1;
|
||||
}
|
||||
|
||||
// console.error(require('util').inspect(arguments, {
|
||||
// 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(obj.index + 1 >= obj.lengthOfActualDeviceStream)
|
||||
{
|
||||
if(!isObjectEmpty(obj.allValues)) instance.send(SEND_TO.dido_controller, {values: obj.allValues});
|
||||
obj.allValues = {};
|
||||
}
|
||||
obj.index++;
|
||||
obj.readAnotherRegister();
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
readAnotherRegister = () => {
|
||||
if(this.index < this.lengthOfActualDeviceStream) setTimeout(this.readRegisters, 0);
|
||||
else this.setNewStream();
|
||||
}
|
||||
|
||||
transformResponse = (response, register) => {
|
||||
|
||||
for (let i = 0; i < this.lengthOfActualDeviceStream; i++) {
|
||||
|
||||
let a = this.stream[i];
|
||||
if (a.register === register)
|
||||
{
|
||||
let tbAttribute = a.tbAttribute;
|
||||
let multiplier = a.multiplier;
|
||||
|
||||
let value = this.calculateValue(response, multiplier);
|
||||
// console.log(register, tbName, tbAttribute, response, a.multiplier, value);
|
||||
|
||||
// if(tbName == undefined) return;
|
||||
|
||||
if(this.index + 1 < this.lengthOfActualDeviceStream)
|
||||
{
|
||||
this.allValues[tbAttribute] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const values = {
|
||||
...this.allValues,
|
||||
[tbAttribute]: value,
|
||||
};
|
||||
|
||||
this.checkNullVoltage(values);
|
||||
this.lampSwitchNotification(values);
|
||||
|
||||
instance.send(SEND_TO.dido_controller, {values: values});
|
||||
|
||||
this.allValues = {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNewStream = () =>
|
||||
{
|
||||
if(this.lengthOfActualDeviceStream == this.index)
|
||||
{
|
||||
if(this.indexInDeviceConfig + 1 == deviceConfig.length)
|
||||
{
|
||||
this.indexInDeviceConfig = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.indexInDeviceConfig += 1;
|
||||
}
|
||||
|
||||
this.getActualStreamAndDevice();
|
||||
}
|
||||
}
|
||||
|
||||
calculateValue = (response, multiplier) =>
|
||||
{
|
||||
let value = 0;
|
||||
|
||||
let l = response.length;
|
||||
if (l === 2)
|
||||
{
|
||||
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
|
||||
{
|
||||
value = value - "0xFFFFFFFF" + 1;
|
||||
}
|
||||
}
|
||||
else if (l === 1)
|
||||
{
|
||||
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
|
||||
{
|
||||
value = value - "0xFFFF" + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.round(value * multiplier * 10) / 10;
|
||||
}
|
||||
|
||||
checkNullVoltage = (values) => {
|
||||
|
||||
if(!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return;
|
||||
|
||||
Object.keys(values).map(singleValue => {
|
||||
if (this.phases.includes(singleValue))
|
||||
{
|
||||
let l = singleValue.split("_");
|
||||
let phase = parseInt(l[1]);
|
||||
|
||||
// console.log(values[singleValue], tbName);
|
||||
|
||||
if(values[singleValue] == 0)
|
||||
{
|
||||
noVoltage.add(phase);
|
||||
sendNotification("modbus_reader: checkNullVoltage", tbName, "no_voltage_on_phase", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase );
|
||||
// console.log('no voltage')
|
||||
}
|
||||
else
|
||||
{
|
||||
noVoltage.delete(phase);
|
||||
// console.log('voltage detected')
|
||||
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
|
||||
*/
|
||||
lampSwitchNotification = (values) => {
|
||||
|
||||
if(!values.hasOwnProperty("total_power")) return;
|
||||
|
||||
const actualTotalPower = values.total_power;
|
||||
|
||||
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);
|
||||
this.onNotificationSent = true;
|
||||
this.offNotificationSent = false;
|
||||
}
|
||||
else if(actualTotalPower <= numberOfNodes * 15 && this.offNotificationSent == false)
|
||||
{
|
||||
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_off", {}, "", SEND_TO.tb, instance);
|
||||
this.onNotificationSent = false;
|
||||
this.offNotificationSent = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const isObjectEmpty = (objectName) => {
|
||||
return Object.keys(objectName).length === 0 && objectName.constructor === Object;
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
phases = FLOW.GLOBALS.settings.phases;
|
||||
tbName = FLOW.GLOBALS.settings.rvoTbName;
|
||||
noVoltage = FLOW.GLOBALS.settings.no_voltage;
|
||||
mainSocket = new SocketWithClients();
|
||||
|
||||
// this notification is to show, that flow (unipi) has been restarted
|
||||
sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance);
|
||||
}
|
||||
|
||||
instance.on("0", function(_) {
|
||||
main();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
156
RVO35/flow_old/monitorconsumption.js
Executable file
156
RVO35/flow_old/monitorconsumption.js
Executable file
|
|
@ -0,0 +1,156 @@
|
|||
exports.id = 'monitorconsumption';
|
||||
exports.title = 'Consumption';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Peter Širka';
|
||||
exports.group = 'Monitoring';
|
||||
exports.color = '#967ADC';
|
||||
exports.input = 0;
|
||||
exports.output = 1;
|
||||
exports.icon = 'bug';
|
||||
exports.options = { interval: 5000, enabled: true, monitorconsumption: true, monitorsize: true, monitorconnections: true, monitorfiles: true };
|
||||
exports.click = true;
|
||||
exports.readme = `# Consumption monitoring
|
||||
|
||||
This component measure CPU and memory consumption, open files and open connections of this application. It uses these Linux commands: \`ps\`, \`lsof\`, \`netstat\` and \`df\`.
|
||||
|
||||
__Data Example__:
|
||||
|
||||
\`\`\`javascript
|
||||
{
|
||||
cpu: 0, // percentage
|
||||
memory: 4096, // in bytes
|
||||
size: 34303, // directory size in bytes
|
||||
files: 34, // count of open files
|
||||
connections: 343, // count of connections
|
||||
uptime: '1-12:34:00'
|
||||
}
|
||||
\`\`\``;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div data-jc="checkbox" data-jc-path="monitorconsumption">Monitor: Consumption + uptime</div>
|
||||
<div data-jc="checkbox" data-jc-path="monitorfiles">Monitor: Count of open files</div>
|
||||
<div data-jc="checkbox" data-jc-path="monitorconnections">Monitor: Count of open connections</div>
|
||||
<div data-jc="checkbox" data-jc-path="monitorsize">Monitor: Directory size</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var current = { cpu: 0, memory: 0, files: 0, connections: 0, size: 0, uptime: '', counter: 0 };
|
||||
var tproc = null;
|
||||
var Exec = require('child_process').exec;
|
||||
var reg_empty = /\s{2,}/g;
|
||||
var reg_appdisksize = /^[\d.,]+/;
|
||||
|
||||
instance.custom.run = function() {
|
||||
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
|
||||
var arr = [];
|
||||
|
||||
// Get CPU and Memory consumption
|
||||
instance.options.monitorconsumption && arr.push(function(next) {
|
||||
Exec('ps -p {0} -o %cpu,rss,etime'.format(process.pid), function(err, response) {
|
||||
|
||||
if (err) {
|
||||
instance.throw(err);
|
||||
} else {
|
||||
var line = response.split('\n')[1];
|
||||
line = line.trim().replace(reg_empty, ' ').split(' ');
|
||||
var cpu = line[0].parseFloat();
|
||||
current.cpu = cpu.floor(1);
|
||||
current.memory = line[1].parseInt2() * 1024; // kB to bytes
|
||||
current.uptime = line[2];
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
// Get count of open files
|
||||
instance.options.monitorfiles && arr.push(function(next) {
|
||||
Exec('lsof -a -p {0} | wc -l'.format(process.pid), function(err, response) {
|
||||
if (err)
|
||||
instance.throw(err);
|
||||
else
|
||||
current.files = response.trim().parseInt2();
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
// Get count of opened network connections
|
||||
instance.options.monitorconnections && arr.push(function(next) {
|
||||
Exec('netstat -an | grep :{0} | wc -l'.format(F.port), function(err, response) {
|
||||
if (err) {
|
||||
instance.throw(err);
|
||||
} else {
|
||||
current.connections = response.trim().parseInt2() - 1;
|
||||
if (current.connections < 0)
|
||||
current.connections = 0;
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
// Get directory size
|
||||
instance.options.monitorsize && current.counter % 5 !== 0 && arr.push(function(next) {
|
||||
Exec('du -hsb ' + process.cwd(), function(err, response) {
|
||||
if (err) {
|
||||
instance.throw(err);
|
||||
} else {
|
||||
var match = response.trim().match(reg_appdisksize);
|
||||
match && (current.size = match.toString().trim().parseInt2());
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
arr.async(function() {
|
||||
|
||||
tproc && clearTimeout(tproc);
|
||||
|
||||
if (instance.options.enabled) {
|
||||
tproc = setTimeout(instance.custom.run, instance.options.interval);
|
||||
instance.send2(current);
|
||||
}
|
||||
|
||||
instance.custom.status();
|
||||
current.counter++;
|
||||
});
|
||||
};
|
||||
|
||||
instance.custom.status = function() {
|
||||
if (instance.options.enabled)
|
||||
instance.status('{0}% / {1}'.format(current.cpu, current.memory.filesize()));
|
||||
else
|
||||
instance.status('Disabled', 'red');
|
||||
};
|
||||
|
||||
instance.on('click', function() {
|
||||
instance.options.enabled = !instance.options.enabled;
|
||||
instance.custom.status();
|
||||
|
||||
if (instance.options.enabled) {
|
||||
current.counter = 0;
|
||||
instance.custom.run();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
instance.on('close', function() {
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(instance.custom.run, 1000);
|
||||
};
|
||||
96
RVO35/flow_old/monitordisk.js
Executable file
96
RVO35/flow_old/monitordisk.js
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
exports.id = 'monitordisk';
|
||||
exports.title = 'Disk';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Peter Širka';
|
||||
exports.group = 'Monitoring';
|
||||
exports.color = '#F6BB42';
|
||||
exports.output = 1;
|
||||
exports.icon = 'hdd-o';
|
||||
exports.click = true;
|
||||
exports.options = { interval: 8000, path: '/', enabled: true };
|
||||
exports.readme = `# Disk monitoring
|
||||
|
||||
This component monitors disk \`bytes\` consumption in Linux systems. It uses \`df\` command.
|
||||
|
||||
__Data Example__:
|
||||
|
||||
\`\`\`javascript
|
||||
{
|
||||
total: 474549649408,
|
||||
used: 39125245952,
|
||||
free: 411294994432
|
||||
}
|
||||
\`\`\``;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-3 m">
|
||||
<div data---="textbox__interval__placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
|
||||
</div>
|
||||
<div class="col-md-3 m">
|
||||
<div data---="textbox__path__placeholder:/;required:true">@(Path)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var current = { total: 0, used: 0, free: 0, path: '', type: '', percentUsed: 0 };
|
||||
var tproc = null;
|
||||
|
||||
instance.custom.run = function() {
|
||||
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
|
||||
if (!instance.options.enabled)
|
||||
return;
|
||||
|
||||
require('child_process').exec('df -hTB1 ' + instance.options.path, function(err, response) {
|
||||
|
||||
tproc = setTimeout(instance.custom.run, instance.options.interval);
|
||||
|
||||
if (err) {
|
||||
instance.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
response.parseTerminal(function(line) {
|
||||
if (line[0][0] !== '/')
|
||||
return;
|
||||
current.total = line[2].parseInt();
|
||||
current.free = line[4].parseInt();
|
||||
current.used = line[3].parseInt();
|
||||
current.path = instance.options.path || '/';
|
||||
current.type = line[1];
|
||||
current.percentUsed = line[5];
|
||||
instance.custom.status();
|
||||
instance.send2(current);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
instance.custom.status = function() {
|
||||
if (instance.options.enabled)
|
||||
instance.status(current.free.filesize() + ' / ' + current.total.filesize());
|
||||
else
|
||||
instance.status('Disabled', 'red');
|
||||
};
|
||||
|
||||
instance.on('click', function() {
|
||||
instance.options.enabled = !instance.options.enabled;
|
||||
instance.custom.status();
|
||||
instance.options.enabled && instance.custom.run();
|
||||
});
|
||||
|
||||
instance.on('close', function() {
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(instance.custom.run, 1000);
|
||||
};
|
||||
87
RVO35/flow_old/monitormemory.js
Executable file
87
RVO35/flow_old/monitormemory.js
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
exports.id = 'monitormemory';
|
||||
exports.title = 'Memory';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Peter Širka';
|
||||
exports.group = 'Monitoring';
|
||||
exports.color = '#F6BB42';
|
||||
exports.output = 1;
|
||||
exports.click = true;
|
||||
exports.icon = 'microchip';
|
||||
exports.options = { interval: 8000, enabled: true };
|
||||
exports.readme = `# Memory monitoring
|
||||
|
||||
This component monitors memory \`bytes\` consumption in Linux systems. It uses \`free\` command.
|
||||
|
||||
__Data Example__:
|
||||
|
||||
\`\`\`javascript
|
||||
{
|
||||
total: 33558769664,
|
||||
used: 1998868480,
|
||||
free: 2653708288
|
||||
}
|
||||
\`\`\``;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<div class="col-md-3 m">
|
||||
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var current = { total: 0, used: 0, free: 0 };
|
||||
var tproc = null;
|
||||
|
||||
instance.custom.run = function() {
|
||||
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
|
||||
if (!instance.options.enabled)
|
||||
return;
|
||||
|
||||
require('child_process').exec('free -b -t', function(err, response) {
|
||||
|
||||
tproc = setTimeout(instance.custom.run, instance.options.interval);
|
||||
|
||||
if (err) {
|
||||
instance.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var memory = response.split('\n')[1].match(/\d+/g);
|
||||
current.total = memory[0].parseInt();
|
||||
current.used = memory[1].parseInt() - memory[3].parseInt();
|
||||
current.free = current.total - current.used;
|
||||
instance.custom.status();
|
||||
instance.send2(current);
|
||||
});
|
||||
};
|
||||
|
||||
instance.custom.status = function() {
|
||||
if (instance.options.enabled)
|
||||
instance.status(current.free.filesize() + ' / ' + current.total.filesize());
|
||||
else
|
||||
instance.status('Disabled', 'red');
|
||||
};
|
||||
|
||||
instance.on('click', function() {
|
||||
instance.options.enabled = !instance.options.enabled;
|
||||
instance.custom.status();
|
||||
instance.options.enabled && instance.custom.run();
|
||||
});
|
||||
|
||||
instance.on('close', function() {
|
||||
if (tproc) {
|
||||
clearTimeout(tproc);
|
||||
tproc = null;
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(instance.custom.run, 1000);
|
||||
};
|
||||
70
RVO35/flow_old/nodesdb_changecheck.js
Executable file
70
RVO35/flow_old/nodesdb_changecheck.js
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
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
RVO35/flow_old/notifikacie.csv
Executable file
31
RVO35/flow_old/notifikacie.csv
Executable 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é
|
||||
|
241
RVO35/flow_old/show_dbdata.js
Executable file
241
RVO35/flow_old/show_dbdata.js
Executable file
|
|
@ -0,0 +1,241 @@
|
|||
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 = 7;
|
||||
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);
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
187
RVO35/flow_old/slack_filter.js
Executable file
187
RVO35/flow_old/slack_filter.js
Executable file
|
|
@ -0,0 +1,187 @@
|
|||
exports.id = 'slack_filter';
|
||||
exports.title = 'Slack Filter';
|
||||
exports.group = 'Citysys';
|
||||
exports.color = '#30E193';
|
||||
exports.input = 1;
|
||||
exports.output = 1;
|
||||
exports.author = 'Jakub Klena';
|
||||
exports.icon = 'plug';
|
||||
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.html = `<div class="padding">
|
||||
<div class="row">
|
||||
<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>
|
||||
<div class="col-md-12">
|
||||
<div data-jc="textbox" data-jc-path="types" data-jc-config="required:false">@(Watch these types, comma separated names)</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div data-jc="textbox" data-jc-path="message_includes" data-jc-config="required:false">@(Watch messages that include any of the following strings)</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div data-jc="textbox" data-jc-path="tag_on_include" data-jc-config="required:false">@(Tag people if message includes something)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# Slack Filter`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
var running = false;
|
||||
instance["savedSlackMessages"] = [];
|
||||
var timer = null;
|
||||
|
||||
instance.on('data', function(response) {
|
||||
if (!running) return;
|
||||
let value = response.data;
|
||||
if (typeof value !== 'object') return;
|
||||
|
||||
let can = false
|
||||
var k = Object.keys(value);
|
||||
var interested = JSON.parse(instance.options.types);
|
||||
var msg_incl = JSON.parse(instance.options.message_includes);
|
||||
var tags = JSON.parse(instance.options.tag_on_include);
|
||||
|
||||
if (k.length <= 0) return;
|
||||
if (value[k[0]].length <= 0) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0], 'values')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values'], '_event')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'type')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'source')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event']['source'], 'func')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'message')) return;
|
||||
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'message_data')) return;
|
||||
|
||||
let icon = ':totaljs:';
|
||||
let type = value[k[0]][0]['values']['_event']['type'];
|
||||
let source = value[k[0]][0]['values']['_event']['source']['func'];
|
||||
let message = value[k[0]][0]['values']['_event']['message'];
|
||||
let message_data = value[k[0]][0]['values']['_event']['message_data'];
|
||||
let tag = '';
|
||||
|
||||
switch(type){
|
||||
case 'debug':
|
||||
icon = ':beetle:';
|
||||
break;
|
||||
case 'info':
|
||||
icon = ':speech_balloon:';
|
||||
break;
|
||||
case 'notice':
|
||||
icon = ':speech_balloon:';
|
||||
break;
|
||||
case 'warning':
|
||||
icon = ':exclamation:';
|
||||
break;
|
||||
case 'alert':
|
||||
icon = ':warning:';
|
||||
break;
|
||||
case 'error':
|
||||
icon = ':no_entry:';
|
||||
break;
|
||||
case 'emergency':
|
||||
icon = ':fire:';
|
||||
break;
|
||||
case 'critical':
|
||||
icon = ':fire:';
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this message includes one of the strings we are watching for
|
||||
for (const msg of msg_incl){
|
||||
if (message.includes(msg)){
|
||||
if (msg == 'is responding again') icon = ':large_green_circle:';
|
||||
can = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if message is one of the types we are watching for
|
||||
if (interested.includes(type)){
|
||||
can = true;
|
||||
}
|
||||
|
||||
if (!can) return;
|
||||
|
||||
|
||||
// Check for each person tags based on what the message includes
|
||||
for (const person of tags){
|
||||
for (const msg of person.includes){
|
||||
if (message.includes(msg)){
|
||||
tag += '<@'+person.user_id+'> ';
|
||||
break; // Break out from this person checks as they are already tagged now
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now that all people are tagged add new line symbol
|
||||
if (tag != '') tag += '\n';
|
||||
|
||||
let send_data = tag+instance.options.name+' '+type.toUpperCase()+'\n*Source*: '+source+'\n*Message*: '+message;
|
||||
if (message_data) {
|
||||
send_data += '\nData: '+message_data;
|
||||
}
|
||||
|
||||
let ignore_msg = false
|
||||
if (message.includes('Configuration of dimming profile to node no')){
|
||||
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++){
|
||||
if (FLOW["savedSlackMessages"][i].message == message){
|
||||
ignore_msg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ignore_msg){
|
||||
FLOW["savedSlackMessages"].push({message, 'dateandtime': Date.now()});
|
||||
if (timer === null){
|
||||
timer = setTimeout(checkSavedMessages, 60*60000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignore_msg){
|
||||
instance.send2({'msg':send_data,'bot_name':instance.options.name+' '+type.toUpperCase(),'bot_icon':icon,'channel':instance.options.slack_channel});
|
||||
}
|
||||
});
|
||||
|
||||
function checkSavedMessages(){
|
||||
var d = Date.now();
|
||||
d = d - 86400000; // older then 24hr
|
||||
var a = [];
|
||||
//Remove msgs older then 24hr
|
||||
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++){
|
||||
if (FLOW["savedSlackMessages"][i].dateandtime > d){
|
||||
a.push(FLOW["savedSlackMessages"][i]);
|
||||
}
|
||||
}
|
||||
FLOW["savedSlackMessages"] = a;
|
||||
|
||||
if (FLOW["savedSlackMessages"].length > 0) {
|
||||
timer = setTimeout(checkSavedMessages, 60*60000);
|
||||
} else {
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
instance.reconfigure = function() {
|
||||
try {
|
||||
if (!FLOW["savedSlackMessages"]){
|
||||
FLOW["savedSlackMessages"] = [];
|
||||
}
|
||||
|
||||
instance.options.name = FLOW.GLOBALS.settings.rvo_name;
|
||||
if (instance.options.name) {
|
||||
instance.status('Running');
|
||||
running = true;
|
||||
} else {
|
||||
instance.status('Please run options again', 'red');
|
||||
running = false;
|
||||
}
|
||||
} catch (e) {
|
||||
instance.error('Citysys connector: ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
instance.on('options', instance.reconfigure);
|
||||
setTimeout(instance.reconfigure, 10000);
|
||||
|
||||
|
||||
};
|
||||
98
RVO35/flow_old/thermometer.js
Executable file
98
RVO35/flow_old/thermometer.js
Executable file
|
|
@ -0,0 +1,98 @@
|
|||
exports.id = 'thermometer';
|
||||
exports.title = 'Thermometer';
|
||||
exports.group = 'Worksys';
|
||||
exports.color = '#5CB36D';
|
||||
exports.input = 1;
|
||||
exports.version = '1.0.3';
|
||||
exports.output = ["red", "white", "blue"];
|
||||
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`;
|
||||
|
||||
const { errLogger, logger, monitor } = require('./helper/logger');
|
||||
|
||||
const SEND_TO = {
|
||||
debug: 0,
|
||||
tb: 1,
|
||||
dido_controller: 2
|
||||
}
|
||||
|
||||
//read temperature - frequency
|
||||
let timeoutMin = 5;//minutes
|
||||
let NUMBER_OF_FAILURES_TO_SEND_ERROR = 13;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
const { exec } = require('child_process');
|
||||
const { sendNotification } = require('./helper/notification_reporter');
|
||||
|
||||
let startRead;
|
||||
let counter = 0;
|
||||
let rvoTbName = "";
|
||||
let temperatureAddress = "";
|
||||
|
||||
logger.debug(exports.title, "installed");
|
||||
|
||||
instance.on("close", function(){
|
||||
clearInterval(startRead);
|
||||
})
|
||||
|
||||
|
||||
const main = function() {
|
||||
|
||||
try {
|
||||
|
||||
if(temperatureAddress === "") throw "Thermometer: temperatureAddress is not defined";
|
||||
|
||||
exec(`owread -C ${temperatureAddress}/temperature`, (error, stdout, stderr) => {
|
||||
|
||||
if(!error)
|
||||
{
|
||||
parseData(stdout)
|
||||
return;
|
||||
}
|
||||
|
||||
counter++;
|
||||
if(counter == NUMBER_OF_FAILURES_TO_SEND_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) {
|
||||
errLogger.error(exports.title, err);
|
||||
clearInterval(startRead);
|
||||
}
|
||||
}
|
||||
|
||||
const parseData = function(data) {
|
||||
|
||||
data = parseFloat(data);
|
||||
//logger.debug("Thermometer", data);
|
||||
|
||||
if(isNaN(data)) {
|
||||
errLogger.error("Thermometer sends invalid data");
|
||||
return;
|
||||
}
|
||||
|
||||
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", _ => {
|
||||
temperatureAddress = FLOW.GLOBALS.settings.temperature_address;
|
||||
rvoTbName = FLOW.GLOBALS.settings.rvoTbName;
|
||||
startRead = setInterval(main, timeoutMin * 1000 * 60);
|
||||
setTimeout(main, 20000);
|
||||
})
|
||||
};
|
||||
79
RVO35/flow_old/trigger.js
Executable file
79
RVO35/flow_old/trigger.js
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
exports.id = 'trigger';
|
||||
exports.title = 'Trigger';
|
||||
exports.group = 'Inputs';
|
||||
exports.color = '#F6BB42';
|
||||
exports.click = true;
|
||||
exports.output = 1;
|
||||
exports.version = '1.1.0';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.icon = 'play';
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="dropdown__datatype__items:,String|string,Integer|integer,Float|float,Boolean|boolean,Date|date,Object|object,Base64 as Buffer|buffer" class="m">@(Data type (String by default))</div>
|
||||
<div data-jc="textbox__data__placeholder:@(e.g. Hello world or { hello: 'world'} or ['hello', 'world']))" class="m">@(Data)</div>
|
||||
<div data-jc="checkbox__restart">Trigger 5s after initialization.</div>
|
||||
<div class="help">@(Useful when there's a need to run certain flow when the app restarts, etc.)</div>
|
||||
</div>`;
|
||||
|
||||
exports.readme = `# Trigger
|
||||
|
||||
- Clicking on the component starts the chain
|
||||
- Settings allows to set a data-type and a value`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var value;
|
||||
|
||||
instance.on('click', () => instance.send2(value));
|
||||
|
||||
instance.reconfigure = function() {
|
||||
var options = instance.options;
|
||||
value = null;
|
||||
switch (options.datatype) {
|
||||
case 'integer':
|
||||
value = options.data.parseInt2('error');
|
||||
value = value === 'error' ? NaN : value;
|
||||
break;
|
||||
case 'float':
|
||||
value = options.data.parseFloat2('error');
|
||||
value = value === 'error' ? NaN : value;
|
||||
break;
|
||||
case 'date':
|
||||
options.data = options.data.toString();
|
||||
var num = options.data.parseInt('error');
|
||||
num === 'error' && (num = options.data.parseDate('error'));
|
||||
num === 'error' && (num = null);
|
||||
value = num ? new Date(num).toUTCString() : num;
|
||||
break;
|
||||
case 'object':
|
||||
try {
|
||||
value = (new Function('return ' + options.data))();
|
||||
} catch (e) {
|
||||
instance.error(e);
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
value = options.data.parseBoolean();
|
||||
break;
|
||||
case 'buffer':
|
||||
try {
|
||||
value = U.createBuffer(options.data);
|
||||
} catch (e) {
|
||||
instance.error(e);
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
default:
|
||||
value = '' + (options.data || '');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
instance.on('options', instance.reconfigure);
|
||||
instance.reconfigure();
|
||||
|
||||
if (instance.options.restart)
|
||||
setTimeout(function(){
|
||||
instance.send2(value);
|
||||
}, 5000);
|
||||
};
|
||||
0
RVO35/flow_old/variables.txt
Executable file
0
RVO35/flow_old/variables.txt
Executable file
43
RVO35/flow_old/virtualwirein.js
Executable file
43
RVO35/flow_old/virtualwirein.js
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
exports.id = 'virtualwirein';
|
||||
exports.title = 'Virtual wire in';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.color = '#303E4D';
|
||||
exports.icon = 'sign-in';
|
||||
exports.input = false;
|
||||
exports.output = 1;
|
||||
exports.options = {};
|
||||
exports.readme = `# Virtual wire in
|
||||
|
||||
When the wires between the components are mess it's time to use Virtual wire.`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="textbox" data-jc-path="wirename" data-jc-config="required:true;placeholder:@(some identifier)" class="m">@(Wire name)</div>
|
||||
</div>
|
||||
<script>
|
||||
ON('save.virtualwirein', function(component, options) {
|
||||
!component.name && (component.name = options.wirename);
|
||||
});
|
||||
WATCH('settings.virtualwirein.wirename', function(path, value, type){
|
||||
if (type === 2)
|
||||
SET('settings.virtualwirein.wirename', value.slug());
|
||||
});
|
||||
</script>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
instance.custom.reconfigure = function(options){
|
||||
if (instance.options.wirename) {
|
||||
instance.status(instance.options.wirename);
|
||||
} else
|
||||
instance.status('Not configured', 'red');
|
||||
};
|
||||
|
||||
ON('virtualwire', function(wirename, flowdata){
|
||||
if (instance.options.wirename && instance.options.wirename === wirename)
|
||||
instance.send(flowdata);
|
||||
});
|
||||
|
||||
instance.on('options', instance.custom.reconfigure);
|
||||
instance.custom.reconfigure();
|
||||
};
|
||||
41
RVO35/flow_old/virtualwireout.js
Executable file
41
RVO35/flow_old/virtualwireout.js
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
exports.id = 'virtualwireout';
|
||||
exports.title = 'Virtual wire out';
|
||||
exports.version = '1.0.0';
|
||||
exports.author = 'Martin Smola';
|
||||
exports.color = '#303E4D';
|
||||
exports.icon = 'sign-out';
|
||||
exports.input = true;
|
||||
exports.options = {};
|
||||
exports.readme = `# Virtual wire out
|
||||
|
||||
When the wires between the components are mess it's time to use Virtual wire.`;
|
||||
|
||||
exports.html = `<div class="padding">
|
||||
<div data-jc="textbox" data-jc-path="wirename" class="m" data-jc-config="required:true;placeholder:@(some identifier)">@(Wire name)</div>
|
||||
</div>
|
||||
<script>
|
||||
ON('save.virtualwireout', function(component, options) {
|
||||
!component.name && (component.name = options.wirename);
|
||||
});
|
||||
WATCH('settings.virtualwireout.wirename', function(path, value, type){
|
||||
if (type === 2)
|
||||
SET('settings.virtualwireout.wirename', value.slug());
|
||||
});
|
||||
</script>`;
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
instance.custom.reconfigure = function(){
|
||||
if (instance.options.wirename) {
|
||||
instance.status(instance.options.wirename);
|
||||
} else
|
||||
instance.status('Not configured', 'red');
|
||||
};
|
||||
|
||||
instance.on('data', function(flowdata) {
|
||||
EMIT('virtualwire', instance.options.wirename, flowdata);
|
||||
});
|
||||
|
||||
instance.on('options', instance.custom.reconfigure);
|
||||
instance.custom.reconfigure();
|
||||
};
|
||||
477
RVO35/flow_old/wsmqttpublish.js
Executable file
477
RVO35/flow_old/wsmqttpublish.js
Executable file
|
|
@ -0,0 +1,477 @@
|
|||
exports.id = 'wsmqttpublish';
|
||||
exports.title = 'WS MQTT publish';
|
||||
exports.group = 'MQTT';
|
||||
exports.color = '#888600';
|
||||
exports.version = '1.0.2';
|
||||
exports.icon = 'sign-out';
|
||||
exports.input = 2;
|
||||
exports.output = 3;
|
||||
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="username" class="m">@(Username)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
exports.readme = `
|
||||
# WS MQTT Publish
|
||||
|
||||
Version 1.0.3.
|
||||
|
||||
Added:
|
||||
- database collections,
|
||||
- rpc response
|
||||
`;
|
||||
|
||||
const { promisifyBuilder } = require('./helper/db_helper');
|
||||
const { errLogger, monitor } = require('./helper/logger');
|
||||
const fs = require('fs');
|
||||
const mqtt = require('mqtt');
|
||||
|
||||
const SEND_TO = {
|
||||
debug: 0,
|
||||
rpcCall: 1,
|
||||
services: 2
|
||||
}
|
||||
|
||||
//CONFIG
|
||||
let createTelemetryBackup = true;
|
||||
let saveTelemetryOnError = true;//backup_on_failure overrides this value
|
||||
//------------------------
|
||||
|
||||
let rollers;
|
||||
if(createTelemetryBackup) rollers = require('streamroller');
|
||||
|
||||
const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304
|
||||
let insertNoSqlCounter = 0;
|
||||
let insertBackupNoSqlCounter = 0;
|
||||
let processingData = false;
|
||||
|
||||
let backup_on_failure = false;//== saveTelemetryOnError - create backup client send failure
|
||||
let restore_from_backup = 0; //how many rows process at once?
|
||||
let restore_backup_wait = 0;//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;
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
|
||||
errLogger.error('uncaughtException:', err.message)
|
||||
errLogger.error(err.stack);
|
||||
|
||||
//TODO
|
||||
//send to service
|
||||
|
||||
//process.exit(1);
|
||||
})
|
||||
|
||||
const nosql = NOSQL('tbdata');
|
||||
const nosqlBackup = NOSQL('/backup/tbdata');
|
||||
|
||||
|
||||
exports.install = function(instance) {
|
||||
|
||||
var client;
|
||||
var opts;
|
||||
var clientReady = false;
|
||||
|
||||
// wsmqtt status for notification purposes on projects.worksys.io database
|
||||
let wsmqttName = null;
|
||||
let sendWsStatusVar = null;
|
||||
let wsmqtt_status = 'disconnected';
|
||||
|
||||
function getWsmqttName(host)
|
||||
{
|
||||
if(host == "tb-demo.worksys.io" || host == '192.168.252.4') return 'wsmqtt_demo';
|
||||
else if(host == "tb-qas01.worksys.io" || host == '192.168.252.5') return 'wsmqtt_qas01';
|
||||
else if(host == "tb-prod01.worksys.io" || host == '192.168.252.1') return 'wsmqtt_prod01';
|
||||
}
|
||||
|
||||
function sendWsStatus()
|
||||
{
|
||||
instance.send(SEND_TO.services, {[wsmqttName]: wsmqtt_status});
|
||||
}
|
||||
|
||||
|
||||
function main()
|
||||
{
|
||||
if(!FLOW.dbLoaded) return;
|
||||
|
||||
loadSettings();
|
||||
clearInterval(sendWsStatus);
|
||||
sendWsStatusVar = setInterval(sendWsStatus, 180000);
|
||||
}
|
||||
|
||||
//set opts according to db settings
|
||||
function loadSettings()
|
||||
{
|
||||
|
||||
if(instance.options.host !== "")
|
||||
{
|
||||
//override settings from database
|
||||
var o = instance.options;
|
||||
opts = {
|
||||
host: o.host,
|
||||
port: o.port,
|
||||
clientId: o.clientid,
|
||||
username: o.username,
|
||||
rejectUnauthorized: false,
|
||||
resubscribe: false
|
||||
};
|
||||
|
||||
wsmqttName = getWsmqttName(o.host);
|
||||
|
||||
console.log("wsmqttpublich -> loadSettings from instance.options", instance.options);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
const SETTINGS = FLOW.GLOBALS.settings;
|
||||
backup_on_failure = SETTINGS.backup_on_failure;
|
||||
saveTelemetryOnError = backup_on_failure;
|
||||
|
||||
restore_from_backup = SETTINGS.restore_from_backup;
|
||||
restore_backup_wait = SETTINGS.restore_backup_wait;
|
||||
|
||||
let mqtt_host = SETTINGS.mqtt_host;
|
||||
let mqtt_clientid = SETTINGS.mqtt_clientid;
|
||||
let mqtt_username = SETTINGS.mqtt_username;
|
||||
let mqtt_port = SETTINGS.mqtt_port;
|
||||
|
||||
opts = {
|
||||
host: mqtt_host,
|
||||
port: mqtt_port,
|
||||
keepalive: 10,
|
||||
clientId: mqtt_clientid,
|
||||
username: mqtt_username,
|
||||
rejectUnauthorized: false,
|
||||
resubscribe: false
|
||||
};
|
||||
|
||||
wsmqttName = getWsmqttName(mqtt_host);
|
||||
}
|
||||
|
||||
connectToTbServer();
|
||||
}
|
||||
|
||||
function connectToTbServer()
|
||||
{
|
||||
var url = "mqtt://" + opts.host + ":" + opts.port;
|
||||
console.log("MQTT URL: ", url);
|
||||
|
||||
client = mqtt.connect(url, opts);
|
||||
|
||||
client.on('connect', function() {
|
||||
instance.status("Connected", "green");
|
||||
monitor.info("MQTT client connected");
|
||||
|
||||
sendClientError = true;
|
||||
clientReady = true;
|
||||
wsmqtt_status = 'connected';
|
||||
});
|
||||
|
||||
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(function() {
|
||||
|
||||
message = JSON.parse(message);
|
||||
if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) {
|
||||
client.publish(topic, `{"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}});
|
||||
}
|
||||
|
||||
}, () => instance.debug('MQTT: Error parsing data', message));
|
||||
}
|
||||
|
||||
instance.send(SEND_TO.rpcCall, {"topic":topic, "content":message });
|
||||
});
|
||||
|
||||
client.on('close', function() {
|
||||
clientReady = false;
|
||||
wsmqtt_status = 'disconnected';
|
||||
|
||||
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) {
|
||||
monitor.info('MQTT client error', err);
|
||||
sendClientError = false;
|
||||
}
|
||||
clientReady = false;
|
||||
wsmqtt_status = 'disconnected';
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
instance.on("0", _ => {
|
||||
main();
|
||||
})
|
||||
|
||||
|
||||
instance.on('1', 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("v1/gateway/telemetry", stringifiedJson, {qos: 1});
|
||||
|
||||
//backup telemetry
|
||||
if(createTelemetryBackup)
|
||||
{
|
||||
data.data.id = UID();
|
||||
nosqlBackup.insert(data.data);
|
||||
|
||||
insertBackupNoSqlCounter++;
|
||||
if(insertBackupNoSqlCounter > 150)
|
||||
{
|
||||
let options = {compress: true};
|
||||
let path = __dirname + "/../databases/backup/tbdata.nosql";
|
||||
var stream = new rollers.RollingFileStream(path, noSqlFileSizeLimit, 150, options);
|
||||
stream.write("");
|
||||
stream.end();
|
||||
|
||||
insertBackupNoSqlCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//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.close = function(done) {
|
||||
if(clientReady){
|
||||
client.end();
|
||||
clearInterval(sendWsStatusVar);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function getDbBackupFileCounter(type)
|
||||
{
|
||||
var files = fs.readdirSync(__dirname + "/../databases");
|
||||
|
||||
let counter = 0;
|
||||
for(var i = 0; i < files.length; i++)
|
||||
{
|
||||
|
||||
if(files[i] == "tbdata.nosql") continue;
|
||||
|
||||
if(files[i].endsWith(".nosql"))
|
||||
{
|
||||
|
||||
let pos = files[i].indexOf(".");
|
||||
if(pos > -1)
|
||||
{
|
||||
|
||||
let fileCounter = counter;
|
||||
let firstDigit = files[i].slice(0, pos);
|
||||
|
||||
fileCounter = parseInt(firstDigit);
|
||||
if(isNaN(fileCounter)) fileCounter = 0;
|
||||
//console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type);
|
||||
|
||||
if(type == "max")
|
||||
{
|
||||
if(fileCounter > counter)
|
||||
{
|
||||
counter = fileCounter;
|
||||
}
|
||||
}
|
||||
else if(type == "min")
|
||||
{
|
||||
if(counter == 0) counter = fileCounter;
|
||||
|
||||
if(fileCounter < counter)
|
||||
{
|
||||
counter = fileCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(type == "max") counter++;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
const makeBackupFromDbFile = async () => {
|
||||
|
||||
if(!saveTelemetryOnError) return;
|
||||
|
||||
//to avoid large file: tbdata.nosql
|
||||
|
||||
//init value is 0!
|
||||
if(insertNoSqlCounter > 0)
|
||||
{
|
||||
--insertNoSqlCounter;
|
||||
return;
|
||||
}
|
||||
|
||||
insertNoSqlCounter = 100;
|
||||
|
||||
let source = __dirname + "/../databases/tbdata.nosql";
|
||||
|
||||
var stats = fs.statSync(source);
|
||||
var fileSizeInBytes = stats.size;
|
||||
|
||||
if(fileSizeInBytes > noSqlFileSizeLimit)
|
||||
{
|
||||
|
||||
let counter = 1;
|
||||
counter = getDbBackupFileCounter("max");
|
||||
|
||||
let destination = __dirname + "/../databases/" + counter + "." + "tbdata.nosql";
|
||||
|
||||
//make backup file
|
||||
fs.copyFileSync(source, destination);
|
||||
//fs.renameSync(p, p + "." + counter);
|
||||
|
||||
//clear tbdata.nosql
|
||||
fs.writeFileSync(source, "");
|
||||
fs.truncateSync(source, 0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const processDataFromDatabase = async () => {
|
||||
|
||||
if(restore_from_backup <= 0) return;
|
||||
|
||||
//calculate diff
|
||||
const now = new Date();
|
||||
let currentTime = now.getTime();
|
||||
let diff = currentTime - lastRestoreTime;
|
||||
|
||||
if( (diff / 1000) < restore_backup_wait)
|
||||
{
|
||||
//console.log("*********restore_backup_wait", diff, restore_backup_wait);
|
||||
return;
|
||||
}
|
||||
|
||||
processingData = true;
|
||||
|
||||
//get filename to process
|
||||
let counter = getDbBackupFileCounter("min");
|
||||
|
||||
//we have some backup files
|
||||
let dataBase = 'tbdata';
|
||||
|
||||
var nosql;
|
||||
if(counter == 0) dataBase = 'tbdata';
|
||||
else dataBase = counter + "." + 'tbdata';
|
||||
|
||||
nosql = NOSQL(dataBase);
|
||||
|
||||
//select all data - use limit restore_from_backup
|
||||
let records = await promisifyBuilder(nosql.find().take(restore_from_backup));
|
||||
|
||||
for(let i = 0; i < records.length; i++)
|
||||
{
|
||||
if(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("v1/gateway/telemetry", 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);
|
||||
//instance.reconfigure();
|
||||
};
|
||||
1241
RVO35/monitor.txt
Executable file
1241
RVO35/monitor.txt
Executable file
File diff suppressed because it is too large
Load diff
2125
RVO35/package-lock.json
generated
Executable file
2125
RVO35/package-lock.json
generated
Executable file
File diff suppressed because it is too large
Load diff
29
RVO35/package.json
Executable file
29
RVO35/package.json
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "emptyproject",
|
||||
"description": "Total.js empty project.",
|
||||
"version": "1.0.0",
|
||||
"main": "debug.js",
|
||||
"dependencies": {
|
||||
"bitwise": "^2.1.0",
|
||||
"easy-crc": "0.0.2",
|
||||
"jsmodbus": "^4.0.6",
|
||||
"log4js": "^6.3.0",
|
||||
"mqtt": "^4.2.6",
|
||||
"nodemailer": "^6.9.7",
|
||||
"serialport": "^9.2.8",
|
||||
"total.js": "^3.4.13"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/totaljs/emptyproject-flow"
|
||||
},
|
||||
"keywords": [
|
||||
"empty",
|
||||
"project"
|
||||
],
|
||||
"author": "Peter Širka",
|
||||
"license": "MIT"
|
||||
}
|
||||
15
RVO35/release.js
Executable file
15
RVO35/release.js
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
// ===================================================
|
||||
// FOR PRODUCTION
|
||||
// Total.js - framework for Node.js platform
|
||||
// https://www.totaljs.com
|
||||
// ===================================================
|
||||
|
||||
const options = {'ip':'0.0.0.0', 'port':12345};
|
||||
|
||||
// options.ip = '127.0.0.1';
|
||||
// options.port = parseInt(process.argv[2]);
|
||||
// options.config = { name: 'Total.js' };
|
||||
// options.sleep = 3000;
|
||||
|
||||
require('total.js').http('release', options);
|
||||
// require('total.js').cluster.http(5, 'release', options);
|
||||
34
RVO35/release.js.json
Executable file
34
RVO35/release.js.json
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"pid": 397,
|
||||
"stats": [
|
||||
{
|
||||
"id": null,
|
||||
"version": {
|
||||
"node": "v14.18.1",
|
||||
"total": "3.4.13",
|
||||
"app": "1.0.0"
|
||||
},
|
||||
"pid": 397,
|
||||
"thread": "",
|
||||
"mode": "release",
|
||||
"overload": 0,
|
||||
"date": "2025-10-16T00:25:03.493Z",
|
||||
"memory": 24.26,
|
||||
"rm": 0,
|
||||
"fm": 0,
|
||||
"wm": 0,
|
||||
"mm": 0,
|
||||
"om": 7,
|
||||
"em": 1,
|
||||
"dbrm": 0,
|
||||
"dbwm": 0,
|
||||
"usage": 0,
|
||||
"requests": 8,
|
||||
"pending": 0,
|
||||
"errors": 0,
|
||||
"timeouts": 0,
|
||||
"uptime": 908,
|
||||
"online": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
395
RVO35/report_data.log
Executable file
395
RVO35/report_data.log
Executable file
|
|
@ -0,0 +1,395 @@
|
|||
{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T10:18:18.856Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T11:18:18.853Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T12:18:18.854Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T13:18:18.853Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T14:18:18.854Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T15:18:18.854Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T17:59:06.755Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [
|
||||
"35/59_0L_2650_NEMA",
|
||||
"35/23_1L_3076_NEMA",
|
||||
"35/30_1L_3429_NEMA",
|
||||
"35/29_1L_3569_NEMA",
|
||||
"35/32_1L_3646_NENA",
|
||||
"35/70_1L_3649_NEMA",
|
||||
"35/26_1L_3655_NEMA",
|
||||
"35/31_1L_3657_NEMA",
|
||||
"35/22_1L_3658_NEMA",
|
||||
"35/78_1L_3659_NEMA",
|
||||
"35/63_1L_3671_NEMA",
|
||||
"35/65_1L_3672_NEMA",
|
||||
"35/67_1L_3673_NEMA",
|
||||
"35/68_1L_3674_NEMA",
|
||||
"35/72_1L_3677_NEMA",
|
||||
"35/69_1L_3679_NEMA",
|
||||
"35/64_1L_3682_NEMA",
|
||||
"35/66_1L_3683_NEMA",
|
||||
"35/71_1L_3687_NEMA",
|
||||
"35/28_1L_3945_NEMA",
|
||||
"35/27_1L_3962_NEMA",
|
||||
"35/24_1L_3980_NEMA",
|
||||
"35/25_1L_4012_NEMA"
|
||||
],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T18:59:06.756Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [
|
||||
"35/59_0L_2650_NEMA",
|
||||
"35/23_1L_3076_NEMA",
|
||||
"35/30_1L_3429_NEMA",
|
||||
"35/29_1L_3569_NEMA",
|
||||
"35/32_1L_3646_NENA",
|
||||
"35/70_1L_3649_NEMA",
|
||||
"35/26_1L_3655_NEMA",
|
||||
"35/31_1L_3657_NEMA",
|
||||
"35/22_1L_3658_NEMA",
|
||||
"35/78_1L_3659_NEMA",
|
||||
"35/63_1L_3671_NEMA",
|
||||
"35/65_1L_3672_NEMA",
|
||||
"35/67_1L_3673_NEMA",
|
||||
"35/68_1L_3674_NEMA",
|
||||
"35/72_1L_3677_NEMA",
|
||||
"35/69_1L_3679_NEMA",
|
||||
"35/64_1L_3682_NEMA",
|
||||
"35/66_1L_3683_NEMA",
|
||||
"35/71_1L_3687_NEMA",
|
||||
"35/28_1L_3945_NEMA",
|
||||
"35/27_1L_3962_NEMA",
|
||||
"35/24_1L_3980_NEMA",
|
||||
"35/25_1L_4012_NEMA"
|
||||
],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T19:59:06.756Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [
|
||||
"35/59_0L_2650_NEMA",
|
||||
"35/23_1L_3076_NEMA",
|
||||
"35/30_1L_3429_NEMA",
|
||||
"35/29_1L_3569_NEMA",
|
||||
"35/32_1L_3646_NENA",
|
||||
"35/70_1L_3649_NEMA",
|
||||
"35/26_1L_3655_NEMA",
|
||||
"35/31_1L_3657_NEMA",
|
||||
"35/22_1L_3658_NEMA",
|
||||
"35/78_1L_3659_NEMA",
|
||||
"35/63_1L_3671_NEMA",
|
||||
"35/65_1L_3672_NEMA",
|
||||
"35/67_1L_3673_NEMA",
|
||||
"35/68_1L_3674_NEMA",
|
||||
"35/72_1L_3677_NEMA",
|
||||
"35/69_1L_3679_NEMA",
|
||||
"35/64_1L_3682_NEMA",
|
||||
"35/66_1L_3683_NEMA",
|
||||
"35/71_1L_3687_NEMA",
|
||||
"35/28_1L_3945_NEMA",
|
||||
"35/27_1L_3962_NEMA",
|
||||
"35/24_1L_3980_NEMA",
|
||||
"35/25_1L_4012_NEMA",
|
||||
"35/19_1L_4108_NEMA"
|
||||
],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T20:59:06.755Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [
|
||||
"35/59_0L_2650_NEMA",
|
||||
"35/23_1L_3076_NEMA",
|
||||
"35/30_1L_3429_NEMA",
|
||||
"35/29_1L_3569_NEMA",
|
||||
"35/32_1L_3646_NENA",
|
||||
"35/70_1L_3649_NEMA",
|
||||
"35/26_1L_3655_NEMA",
|
||||
"35/31_1L_3657_NEMA",
|
||||
"35/22_1L_3658_NEMA",
|
||||
"35/78_1L_3659_NEMA",
|
||||
"35/63_1L_3671_NEMA",
|
||||
"35/65_1L_3672_NEMA",
|
||||
"35/67_1L_3673_NEMA",
|
||||
"35/68_1L_3674_NEMA",
|
||||
"35/72_1L_3677_NEMA",
|
||||
"35/69_1L_3679_NEMA",
|
||||
"35/64_1L_3682_NEMA",
|
||||
"35/66_1L_3683_NEMA",
|
||||
"35/71_1L_3687_NEMA",
|
||||
"35/28_1L_3945_NEMA",
|
||||
"35/27_1L_3962_NEMA",
|
||||
"35/24_1L_3980_NEMA",
|
||||
"35/25_1L_4012_NEMA",
|
||||
"35/19_1L_4108_NEMA"
|
||||
],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T21:59:06.758Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [
|
||||
"35/59_0L_2650_NEMA",
|
||||
"35/23_1L_3076_NEMA",
|
||||
"35/30_1L_3429_NEMA",
|
||||
"35/29_1L_3569_NEMA",
|
||||
"35/32_1L_3646_NENA",
|
||||
"35/70_1L_3649_NEMA",
|
||||
"35/26_1L_3655_NEMA",
|
||||
"35/31_1L_3657_NEMA",
|
||||
"35/22_1L_3658_NEMA",
|
||||
"35/78_1L_3659_NEMA",
|
||||
"35/63_1L_3671_NEMA",
|
||||
"35/65_1L_3672_NEMA",
|
||||
"35/67_1L_3673_NEMA",
|
||||
"35/68_1L_3674_NEMA",
|
||||
"35/72_1L_3677_NEMA",
|
||||
"35/69_1L_3679_NEMA",
|
||||
"35/64_1L_3682_NEMA",
|
||||
"35/66_1L_3683_NEMA",
|
||||
"35/71_1L_3687_NEMA",
|
||||
"35/28_1L_3945_NEMA",
|
||||
"35/27_1L_3962_NEMA",
|
||||
"35/24_1L_3980_NEMA",
|
||||
"35/25_1L_4012_NEMA",
|
||||
"35/19_1L_4108_NEMA"
|
||||
],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T22:59:06.755Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [
|
||||
"35/59_0L_2650_NEMA",
|
||||
"35/23_1L_3076_NEMA",
|
||||
"35/30_1L_3429_NEMA",
|
||||
"35/29_1L_3569_NEMA",
|
||||
"35/32_1L_3646_NENA",
|
||||
"35/70_1L_3649_NEMA",
|
||||
"35/26_1L_3655_NEMA",
|
||||
"35/31_1L_3657_NEMA",
|
||||
"35/22_1L_3658_NEMA",
|
||||
"35/78_1L_3659_NEMA",
|
||||
"35/63_1L_3671_NEMA",
|
||||
"35/65_1L_3672_NEMA",
|
||||
"35/67_1L_3673_NEMA",
|
||||
"35/68_1L_3674_NEMA",
|
||||
"35/72_1L_3677_NEMA",
|
||||
"35/69_1L_3679_NEMA",
|
||||
"35/64_1L_3682_NEMA",
|
||||
"35/66_1L_3683_NEMA",
|
||||
"35/71_1L_3687_NEMA",
|
||||
"35/28_1L_3945_NEMA",
|
||||
"35/27_1L_3962_NEMA",
|
||||
"35/24_1L_3980_NEMA",
|
||||
"35/25_1L_4012_NEMA",
|
||||
"35/19_1L_4108_NEMA"
|
||||
],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}{
|
||||
"name": "rvo_senica_35_10.0.0.129",
|
||||
"time": "2025-10-15T23:59:06.759Z",
|
||||
"report": {
|
||||
"contactor": {
|
||||
"off": [],
|
||||
"on": []
|
||||
},
|
||||
"night_no_data": [
|
||||
"35/59_0L_2650_NEMA",
|
||||
"35/23_1L_3076_NEMA",
|
||||
"35/30_1L_3429_NEMA",
|
||||
"35/29_1L_3569_NEMA",
|
||||
"35/32_1L_3646_NENA",
|
||||
"35/70_1L_3649_NEMA",
|
||||
"35/26_1L_3655_NEMA",
|
||||
"35/31_1L_3657_NEMA",
|
||||
"35/22_1L_3658_NEMA",
|
||||
"35/78_1L_3659_NEMA",
|
||||
"35/63_1L_3671_NEMA",
|
||||
"35/65_1L_3672_NEMA",
|
||||
"35/67_1L_3673_NEMA",
|
||||
"35/68_1L_3674_NEMA",
|
||||
"35/72_1L_3677_NEMA",
|
||||
"35/69_1L_3679_NEMA",
|
||||
"35/64_1L_3682_NEMA",
|
||||
"35/66_1L_3683_NEMA",
|
||||
"35/71_1L_3687_NEMA",
|
||||
"35/28_1L_3945_NEMA",
|
||||
"35/27_1L_3962_NEMA",
|
||||
"35/24_1L_3980_NEMA",
|
||||
"35/25_1L_4012_NEMA",
|
||||
"35/19_1L_4108_NEMA"
|
||||
],
|
||||
"night_dimming=0": [],
|
||||
"night_power=0": [],
|
||||
"day_24/7_no_data": [
|
||||
"35/59_0L_2650_NEMA"
|
||||
],
|
||||
"day_24/7_dimming>0": [],
|
||||
"day_24/7_power>0": []
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue