Backup senica-RVO35 on 16.10.2025

This commit is contained in:
Jakub Klena 2025-10-16 02:25:46 +02:00
parent 1467900c80
commit 15c2f1eb9c
100 changed files with 32138 additions and 0 deletions

36
RVO35/addSwitch.py Executable file
View 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
View 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
View 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
View 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()
#

File diff suppressed because it is too large Load diff

114
RVO35/databases/modbus_config.js Executable file
View 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
View 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|.............

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

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

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

File diff suppressed because it is too large Load diff

90
RVO35/flow/code.js Executable file
View file

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

11
RVO35/flow/comment.js Executable file
View file

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

60
RVO35/flow/count.js Executable file
View 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
View file

@ -0,0 +1,286 @@
exports.id = 'db_connector';
exports.title = 'DbConnector';
exports.version = '1.0.0';
exports.group = 'Worksys';
exports.color = '#2134B0';
exports.input = 1;
exports.output = ["red", "white"];
exports.click = false;
exports.author = 'Daniel Segeš';
exports.icon = 'bolt';
exports.options = { edge: "undefined" };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">DbConnector</div>
</div>
</div>
</div>`;
exports.readme = `# read/write data to tables`;
const instanceSendTo = {
debug: 0,
http_response: 1,
}
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
function extractWhereParams(params)
{
let name = params[0];
let operator = '=';
let value = params[1];
if(params.length == 3)
{
operator = params[1];
value = params[2];
}
return {name: name, operator: operator, value: value};
}
exports.install = function(instance) {
let refFlowdata = null;//holds reference to httprequest flowdata
instance.on("close", () => {
})
instance.on("data", async function(flowdata) {
let params = flowdata.data.body;
console.log("DbConnector", params);
refFlowdata = flowdata;
if(refFlowdata != undefined)
{
//make http response
let responseObj = {};
responseObj["type"] = "SUCESS";
try{
let table = params.table;
let action = params.action;
if(params.data != undefined)
{
let className = params.data.className;
if(className == "SqlQueryBuilder")
{
let type = params.data.type;
console.log("SqlQueryBuilder---->", params.data);
if(type == "SELECT")
{
let table = params.data.queryData.tables[0].table;
const db = TABLE(table);
var builder = db.find();
let result = await promisifyBuilder(builder);
let response = {};
response["result"] = result;
response["success"] = true;
responseObj["data"] = response;
//console.log(responseObj);
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
}
return;
}
}
console.log("db_connector---->", table, action);
//actions: read, replace, insert, delete, update...
if(action == "read")
{
const db = TABLE(params.table);
//where builder.where('age', '<', 15);
//builder.where('id', 3403);
var builder = db.find();
//https://docs.totaljs.com/latest/en.html#api~DatabaseBuilder~builder.where
if(params.hasOwnProperty("where"))
{
//optionalCan contain "=", "<=", "<", ">=", ">".
//Default value: '='
//1.["production_line", 1]
//2. ["or", ["production_line", 1], ["production_line", 2], "end"]
if (Array.isArray(params.where)) {
let multipleConditions = false;
if(params.where[0] == "or") multipleConditions = true;
if (Array.isArray(params.where[0])) multipleConditions = true;
if(multipleConditions)
{
for(var i = 0; i < params.where.length; i++)
{
const item = params.where[i];
if(item === "or") builder.or();
if (Array.isArray(item))
{
const { name, operator, value } = extractWhereParams(item);
builder.where(name, operator, value);
}
if(item === "end") builder.end();
}
}
else
{
const { name, operator, value } = extractWhereParams(params.where);
builder.where(name, operator, value);
}
}
/*
if(params.where.length >=2 )
{
let name = params.where[0];
let operator = '=';
let value = params.where[1];
if(params.where.length == 3)
{
operator = params.where[1];
value = params.where[2];
}
builder.where(name, operator, value);
}
*/
}
if(params.hasOwnProperty("between"))
{
builder.between(params.between[0], params.between[1], params.between[2]);
}
let response = await promisifyBuilder(builder);
responseObj["data"] = response;
//console.log(responseObj);
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
return;
}
if(action == "delete")
{
}
if(action == "update")
{
//! data receiving from terminal (params)
// {
// hostname: 'localhost',
// table: 'settings',
// action: 'update',
// body: {
// rvo_name: 'terrrr',
// lang: 'en',
// temperature_adress: '28.427B45920702',
// latitude: 48.70826502,
// longitude: 17.28455203,
// mqtt_host: '192.168.252.4',
// mqtt_clientid: 'showroom_test_panel_led',
// mqtt_username: 'xmRd6RJxW53WZe4vMFLU',
// mqtt_port: 1883,
// maintanace_mode: false
// }
// }
const tableToModify = TABLE(params.table);
const newValues = params.body;
tableToModify.modify(newValues).make(function(builder) {
builder.callback(function(err, response) {
if(!err)
{
responseObj["data"] = response;
responseObj["tableUpdated"] = true;
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
}
});
});
}
if(action == "replace")
{
//truncate table
const db = TABLE(params.table);
var builder = db.remove();
db.clean();
//insert data
let data = params.data;
for(let i = 0; i < data.length; i++)
{
//console.log(data[i]);
db.insert(data[i]);
}
console.log("insert done");
let responseObj = {};
responseObj["type"] = "SUCESS";
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
}
} catch (error) {
//console.log(error);
responseObj["type"] = "ERROR";
responseObj["message"] = error;
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
}
}
})
}

113
RVO35/flow/db_init.js Executable file
View 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
View file

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

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

File diff suppressed because it is too large Load diff

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

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

@ -0,0 +1,44 @@
function promisifyBuilder(builder)
{
return new Promise((resolve, reject) => {
try{
builder.callback(function(err, response) {
if(err != null) reject(err);
resolve(response);
});
} catch (error) {
reject(error);
}
})
}
function makeMapFromDbResult(response, ...keys)
{
let s = "-";
let data = {};
for(let i = 0; i < response.length; i++)
{
let record = response[i];
let k = [];
for(let j = 0; j < keys.length; j++)
{
k.push( record[keys[j]] );
}
let key = k.join(s);
data[ key ] = record;
}
return data;
}
module.exports = {
promisifyBuilder,
makeMapFromDbResult
}

30
RVO35/flow/helper/logger.js Executable file
View 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
View file

@ -0,0 +1,5 @@
function MD5(d){var r = M(V(Y(X(d),8*d.length)));return r.toLowerCase()};function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
module.exports = {
MD5
}

View file

@ -0,0 +1,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
View file

@ -0,0 +1,144 @@
/*
0 - cislo registra / prikaz
1 - recepient - 0 = master, 1 = slave (slave je automaticky group a broadcast)
2 - r/rw - read/write
3- register name - nazov registra / prikazu (len pre info - zobrazenie v aplikacii)
4,5,6,7, - jednotlive byte-y - nazov byte-u
4-7 - RES. a prazdny string "" sa nezobrazia!!!
*/
//124 zaznamov
const registers = [
["0","1","R","Status","","",""],
["1","1","RW","Dimming","R-Channel ","G-Channel","B-Channel ","W - Channel"],
["2","1","R","Device types","","",".",""],
["3","1","RW","Group addresses 1-4","Groups Add. 4","Groups Add. 3","Groups Add. 2","Groups Add. 1"],
["4","1","RW","Group addresses 5-8","Groups Add. 8","Groups Add. 7","Groups Add. 6","Groups Add. 5"],
["5","1","RW","Serial number (MAC)","","","",""],
["6","1","RW","Time of dusk","HH","MM","SS","EXTRA"],
["7","1","RW","Time of dawn","HH","MM","SS","EXTRA"],
["8","1","RW","Time Schedule settings","TBD","TBD","Movement sensor","Time Schedule"],
["9","1","RW","TS1 Time point 1","HH","MM","SS","Ext. Device"],
["10","1","RW","TS1 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["11","1","RW","TS1 Time point 2","HH","MM","SS","Ext. Device"],
["12","1","RW","TS1 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["13","1","RW","TS1 Time point 3","HH","MM","SS","Ext. Device"],
["14","1","RW","TS1 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["15","1","RW","TS1 Time point 4","HH","MM","SS","Ext. Device"],
["16","1","RW","TS1 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["17","1","RW","TS1 Time point 5","HH","MM","SS","Ext. Device"],
["18","1","RW","TS1 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["19","1","RW","TS1 Time point 6","HH","MM","SS","Ext. Device"],
["20","1","RW","TS1 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["21","1","RW","TS1 Time point 7","HH","MM","SS","Ext. Device"],
["22","1","RW","TS1 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["23","1","RW","TS1 Time point 8","HH","MM","SS","Ext. Device"],
["24","1","RW","TS1 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["25","1","RW","TS1 Time point 9","HH","MM","SS","Ext. Device"],
["26","1","RW","TS1 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["27","1","RW","TS1 Time point 10","HH","MM","SS","Ext. Device"],
["28","1","RW","TS1 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["29","1","RW","TS1 Time point 11","HH","MM","SS","Ext. Device"],
["30","1","RW","TS1 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["31","1","RW","TS1 Time point 12","HH","MM","SS","Ext. Device"],
["32","1","RW","TS1 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["33","1","RW","TS1 Time point 13","HH","MM","SS","Ext. Device"],
["34","1","RW","TS1 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["35","1","RW","TS1 Time point 14","HH","MM","SS","Ext. Device"],
["36","1","RW","TS1 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["37","1","RW","TS1 Time point 15","HH","MM","SS","Ext. Device"],
["38","1","RW","TS1 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["39","1","RW","TS1 Time point 16","HH","MM","SS","Ext. Device"],
["40","1","RW","TS1 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["41","1","RW","TS2 Time point 1","HH","MM","SS","Ext. Device"],
["42","1","RW","TS2 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["43","1","RW","TS2 Time point 2","HH","MM","SS","Ext. Device"],
["44","1","RW","TS2 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["45","1","RW","TS2 Time point 3","HH","MM","SS","Ext. Device"],
["46","1","RW","TS2 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["47","1","RW","TS2 Time point 4","HH","MM","SS","Ext. Device"],
["48","1","RW","TS2 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["49","1","RW","TS2 Time point 5","HH","MM","SS","Ext. Device"],
["50","1","RW","TS2 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["51","1","RW","TS2 Time point 6","HH","MM","SS","Ext. Device"],
["52","1","RW","TS2 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["53","1","RW","TS2 Time point 7","HH","MM","SS","Ext. Device"],
["54","1","RW","TS2 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["55","1","RW","TS2 Time point 8","HH","MM","SS","Ext. Device"],
["56","1","RW","TS2 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["57","1","RW","TS2 Time point 9","HH","MM","SS","Ext. Device"],
["58","1","RW","TS2 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["59","1","RW","TS2 Time point 10","HH","MM","SS","Ext. Device"],
["60","1","RW","TS2 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["61","1","RW","TS2 Time point 11","HH","MM","SS","Ext. Device"],
["62","1","RW","TS2 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["63","1","RW","TS2 Time point 12","HH","MM","SS","Ext. Device"],
["64","1","RW","TS2 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["65","1","RW","TS2 Time point 13","HH","MM","SS","Ext. Device"],
["66","1","RW","TS2 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["67","1","RW","TS2 Time point 14","HH","MM","SS","Ext. Device"],
["68","1","RW","TS2 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["69","1","RW","TS2 Time point 15","HH","MM","SS","Ext. Device"],
["70","1","RW","TS2 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["71","1","RW","TS2 Time point 16","HH","MM","SS","Ext. Device"],
["72","1","RW","TS2 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["73","1","RW","Power meter status","TBD","","",""],
["74","1","R","Input Voltage","","","",""],
["75","1","R","Input Current","","","",""],
["76","1","R","Input Power","","","",""],
["77","1","R","Cos phi","","","",""],
["78","1","R","Frequency","","","",""],
["79","1","RW","Energy","","","",""],
["80","1","RW","Lifetime","","","",""],
["81","1","RW","Power on cycles (input)","","","",""],
["82","1","RW","Power on cycles (relay)","","","",""],
["83","1","R","Time since last power on","","","",""],
["84","1","R","Accelerometer data","","","",""],
["85","1","RW","GPS latitude","pos/neg","deg","min ","sec"],
["86","1","RW","GPS longitude","pos/neg","deg","min ","sec"],
["87","1","RW","Actual time","HH","MM","SS","RES."],
["88","1","RW","Actual date","Day of week","Day","Month","Year"],
["89","1","R","Production data 1","","","",""],
["90","1","R","Production data 2","","","",""],
["91","1","RW","Network ID","NID3","NID2","NID1","NID0"],
["95","1","RW","Actual Lux level from cabinet","RES.","RES.","HB","LB"],
["96","1","RW","Threshold lux level","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
["97","1","RW","Adjust period","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
["98","1","RW","Offset","RES.","RES.","Dusk","Dawn"],
["99","1","RW","CCT min/max range","max-H","max-L","min-H","min-L"],
["100","1","RW","DALI interface","Cmd ID","Add","Cmd","Resp"],
["101","1","RW","Module FW ver","v1","v2","v3","v4"],
["102","1","RW","Module MAC-H","unused","unused","M1","M2"],
["103","1","RW","Module MAC-L","M3","M4","M5","M6"],
["122","1","R","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
["123","1","R","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
["124","1","R","FW update - data","Byte3","Byte2","Byte1","Byte0"],
["0","0","R","Status","","","",""],
["1","0","RW","Control register","RES.","RES.","RES.","init mode enable"],
["2","0","R","Device types","","","",""],
["3","0","R","Serial number (MAC)","","","",""],
["4","0","R","Production data 1","","","",""],
["5","0","R","Production data 2","","","",""],
["6","0","RW","Network ID","NID3","NID2","NID1","NID0"],
["7","0","RW","RS232 settings","param.","param.","Baudrate H","Baudrate L"],
["8","0","R","Accelerometer data","","","",""],
["9","0","RW","Module FW ver","v1","v2","v3","v4"],
["10","0","RW","Module MAC-H","unused","unused","M1","M2"],
["11","0","RW","Module MAC-L","M3","M4","M5","M6"],
["32","0","RW","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
["33","0","RW","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
["34","0","RW","FW update - data","Byte3","Byte2","Byte1","Byte0"],
["125","0","RW","Debug Register","Byte3","Byte2","Byte1","Byte0"],
["126","0","RW","Network Control Register","Byte3","Byte2","Byte1","Byte0"],
["127","0","R","Network Status Register","Byte3","Byte2","Byte1","Byte0"],
["128","0","RW","Node XX Serial Number Register","SER3","SER2","SER1","SER0"],
["256","0","R","Node XX Network Status Register","","","",""]
];
let register = {};
module.exports = {
registers,
register
}

View file

@ -0,0 +1,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
View file

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

161
RVO35/flow/helper/utils.js Executable file
View 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
View file

@ -0,0 +1,137 @@
exports.id = 'httprequest';
exports.title = 'HTTP Request';
exports.group = 'HTTP';
exports.color = '#5D9CEC';
exports.input = true;
exports.version = '2.0.1';
exports.output = 1;
exports.author = 'Peter Širka';
exports.icon = 'cloud-upload';
exports.html = `<div class="padding">
<div data-jc="textbox" data-jc-path="url" class="m" data-jc-config="required:true;maxlength:500;placeholder:@(E.g. https\\://www.totaljs.com)">@(URL address)</div>
<div class="row">
<div class="col-md-6 m">
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE">@(HTTP method)</div>
</div>
<div class="col-md-6 m">
<div data-jc="dropdown" data-jc-path="stringify" data-jc-config="required:true;items:,URL encoded|encoded,JSON|json,RAW|raw,None|none">@(Serialization)</div>
</div>
</div>
<div data-jc="checkbox" data-jc-path="chunks">@(Download the content <b>in chunks</b>)</div>
<div data-jc="checkbox" data-jc-path="persistentcookies">@(Keep persistent cookies)</div>
<div data-jc="checkbox" data-jc-path="nodns">@(Disable DNS cache)</div>
<div data-jc="checkbox" data-jc-path="keepalive">@(Keep alive connection)</div>
</div>
<hr class="nmt nmb" />
<div class="padding">
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
</div>
<div class="padding bg-smoke">
<section>
<label><i class="fa fa-lock"></i>@(HTTP basic access authentication)</label>
<div class="padding npb">
<div class="row">
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="username">@(User)</div>
</div>
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="userpassword">@(Password)</div>
</div>
</div>
</div>
</section>
</div>`;
exports.readme = `# Request
This component creates a request with received data.
__Response:__
\`\`\`javascript
{
data: String,
headers: Object,
status: Number,
host: String
}
\`\`\`
__Dynamic arguments__:
Are performed via FlowData repository and can be used for URL address or for custom headers/cookies/auth. Use \`repository\` component for creating of dynamic arguments. Dynamic values are replaced in the form \`{key}\`:
- url address e.g. \`https://.../{key}/\`
- headers values e.g. \`{token}\`
- cookies values e.g. \`{token}\``;
exports.install = function(instance) {
var can = false;
var flags = null;
var cookies2 = null;
instance.on('data', function(response) {
can && instance.custom.send(response);
});
instance.custom.send = function(response) {
var options = instance.options;
var headers = null;
var cookies = null;
options.headers && Object.keys(options.headers).forEach(function(key) {
!headers && (headers = {});
headers[key] = response.arg(options.headers[key]);
});
if (options.username && options.userpassword) {
!headers && (headers = {});
headers['Authorization'] = 'Basic ' + U.createBuffer(response.arg(options.username + ':' + options.userpassword)).toString('base64');
}
options.cookies && Object.keys(options.cookies).forEach(function(key) {
!cookies && (cookies = {});
cookies[key] = response.arg(options.cookies[key]);
});
if (options.chunks) {
U.download(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, response) {
response.on('data', (chunks) => instance.send2(chunks));
}, cookies || cookies2, headers);
} else {
U.request(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, data, status, headers, host) {
if (response && !err) {
response.data = { data: data, status: status, headers: headers, host: host };
instance.send2(response);
} else if (err)
instance.error(err, response);
}, cookies || cookies2, headers);
}
};
instance.reconfigure = function() {
var options = instance.options;
can = options.url && options.url && options.method && options.stringify ? true : false;
instance.status(can ? '' : 'Not configured', can ? undefined : 'red');
if (!can)
return;
flags = [];
flags.push(options.method.toLowerCase());
options.stringify === 'json' && flags.push('json');
options.stringify === 'raw' && flags.push('raw');
options.keepalive && flags.push('keepalive');
!options.nodns && flags.push('dnscache');
if (options.persistentcookies) {
flags.push('cookies');
cookies2 = {};
} else
cookies2 = null;
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
};

76
RVO35/flow/httpresponse.js Executable file
View file

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

326
RVO35/flow/httproute.js Executable file
View file

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

81
RVO35/flow/infosender.js Executable file
View 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
View 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
View file

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

96
RVO35/flow/monitordisk.js Executable file
View file

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

87
RVO35/flow/monitormemory.js Executable file
View file

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

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

@ -0,0 +1,79 @@
exports.id = 'trigger';
exports.title = 'Trigger';
exports.group = 'Inputs';
exports.color = '#F6BB42';
exports.click = true;
exports.output = 1;
exports.version = '1.1.0';
exports.author = 'Martin Smola';
exports.icon = 'play';
exports.html = `<div class="padding">
<div data-jc="dropdown__datatype__items:,String|string,Integer|integer,Float|float,Boolean|boolean,Date|date,Object|object,Base64 as Buffer|buffer" class="m">@(Data type (String by default))</div>
<div data-jc="textbox__data__placeholder:@(e.g. Hello world or { hello: 'world'} or ['hello', 'world']))" class="m">@(Data)</div>
<div data-jc="checkbox__restart">Trigger 5s after initialization.</div>
<div class="help">@(Useful when there's a need to run certain flow when the app restarts, etc.)</div>
</div>`;
exports.readme = `# Trigger
- Clicking on the component starts the chain
- Settings allows to set a data-type and a value`;
exports.install = function(instance) {
var value;
instance.on('click', () => instance.send2(value));
instance.reconfigure = function() {
var options = instance.options;
value = null;
switch (options.datatype) {
case 'integer':
value = options.data.parseInt2('error');
value = value === 'error' ? NaN : value;
break;
case 'float':
value = options.data.parseFloat2('error');
value = value === 'error' ? NaN : value;
break;
case 'date':
options.data = options.data.toString();
var num = options.data.parseInt('error');
num === 'error' && (num = options.data.parseDate('error'));
num === 'error' && (num = null);
value = num ? new Date(num).toUTCString() : num;
break;
case 'object':
try {
value = (new Function('return ' + options.data))();
} catch (e) {
instance.error(e);
}
break;
case 'boolean':
value = options.data.parseBoolean();
break;
case 'buffer':
try {
value = U.createBuffer(options.data);
} catch (e) {
instance.error(e);
}
break;
case 'string':
default:
value = '' + (options.data || '');
break;
}
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
if (instance.options.restart)
setTimeout(function(){
instance.send2(value);
}, 5000);
};

0
RVO35/flow/variables.txt Executable file
View file

43
RVO35/flow/virtualwirein.js Executable file
View file

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

41
RVO35/flow/virtualwireout.js Executable file
View file

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

448
RVO35/flow/wsmqttpublish.js Executable file
View 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();
};

View file

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

View file

@ -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

File diff suppressed because it is too large Load diff

90
RVO35/flow_old/code.js Executable file
View file

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

11
RVO35/flow_old/comment.js Executable file
View file

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

60
RVO35/flow_old/count.js Executable file
View 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
View file

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

286
RVO35/flow_old/db_connector.js Executable file
View file

@ -0,0 +1,286 @@
exports.id = 'db_connector';
exports.title = 'DbConnector';
exports.version = '1.0.0';
exports.group = 'Worksys';
exports.color = '#2134B0';
exports.input = 1;
exports.output = ["red", "white"];
exports.click = false;
exports.author = 'Daniel Segeš';
exports.icon = 'bolt';
exports.options = { edge: "undefined" };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">DbConnector</div>
</div>
</div>
</div>`;
exports.readme = `# read/write data to tables`;
const instanceSendTo = {
debug: 0,
http_response: 1,
}
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js');
function extractWhereParams(params)
{
let name = params[0];
let operator = '=';
let value = params[1];
if(params.length == 3)
{
operator = params[1];
value = params[2];
}
return {name: name, operator: operator, value: value};
}
exports.install = function(instance) {
let refFlowdata = null;//holds reference to httprequest flowdata
instance.on("close", () => {
})
instance.on("data", async function(flowdata) {
let params = flowdata.data.body;
console.log("DbConnector", params);
refFlowdata = flowdata;
if(refFlowdata != undefined)
{
//make http response
let responseObj = {};
responseObj["type"] = "SUCESS";
try{
let table = params.table;
let action = params.action;
if(params.data != undefined)
{
let className = params.data.className;
if(className == "SqlQueryBuilder")
{
let type = params.data.type;
console.log("SqlQueryBuilder---->", params.data);
if(type == "SELECT")
{
let table = params.data.queryData.tables[0].table;
const db = TABLE(table);
var builder = db.find();
let result = await promisifyBuilder(builder);
let response = {};
response["result"] = result;
response["success"] = true;
responseObj["data"] = response;
//console.log(responseObj);
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
}
return;
}
}
console.log("db_connector---->", table, action);
//actions: read, replace, insert, delete, update...
if(action == "read")
{
const db = TABLE(params.table);
//where builder.where('age', '<', 15);
//builder.where('id', 3403);
var builder = db.find();
//https://docs.totaljs.com/latest/en.html#api~DatabaseBuilder~builder.where
if(params.hasOwnProperty("where"))
{
//optionalCan contain "=", "<=", "<", ">=", ">".
//Default value: '='
//1.["production_line", 1]
//2. ["or", ["production_line", 1], ["production_line", 2], "end"]
if (Array.isArray(params.where)) {
let multipleConditions = false;
if(params.where[0] == "or") multipleConditions = true;
if (Array.isArray(params.where[0])) multipleConditions = true;
if(multipleConditions)
{
for(var i = 0; i < params.where.length; i++)
{
const item = params.where[i];
if(item === "or") builder.or();
if (Array.isArray(item))
{
const { name, operator, value } = extractWhereParams(item);
builder.where(name, operator, value);
}
if(item === "end") builder.end();
}
}
else
{
const { name, operator, value } = extractWhereParams(params.where);
builder.where(name, operator, value);
}
}
/*
if(params.where.length >=2 )
{
let name = params.where[0];
let operator = '=';
let value = params.where[1];
if(params.where.length == 3)
{
operator = params.where[1];
value = params.where[2];
}
builder.where(name, operator, value);
}
*/
}
if(params.hasOwnProperty("between"))
{
builder.between(params.between[0], params.between[1], params.between[2]);
}
let response = await promisifyBuilder(builder);
responseObj["data"] = response;
//console.log(responseObj);
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
return;
}
if(action == "delete")
{
}
if(action == "update")
{
//! data receiving from terminal (params)
// {
// hostname: 'localhost',
// table: 'settings',
// action: 'update',
// body: {
// rvo_name: 'terrrr',
// lang: 'en',
// temperature_adress: '28.427B45920702',
// latitude: 48.70826502,
// longitude: 17.28455203,
// mqtt_host: '192.168.252.4',
// mqtt_clientid: 'showroom_test_panel_led',
// mqtt_username: 'xmRd6RJxW53WZe4vMFLU',
// mqtt_port: 1883,
// maintanace_mode: false
// }
// }
const tableToModify = TABLE(params.table);
const newValues = params.body;
tableToModify.modify(newValues).make(function(builder) {
builder.callback(function(err, response) {
if(!err)
{
responseObj["data"] = response;
responseObj["tableUpdated"] = true;
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
}
});
});
}
if(action == "replace")
{
//truncate table
const db = TABLE(params.table);
var builder = db.remove();
db.clean();
//insert data
let data = params.data;
for(let i = 0; i < data.length; i++)
{
//console.log(data[i]);
db.insert(data[i]);
}
console.log("insert done");
let responseObj = {};
responseObj["type"] = "SUCESS";
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
}
} catch (error) {
//console.log(error);
responseObj["type"] = "ERROR";
responseObj["message"] = error;
refFlowdata.data = responseObj;
instance.send(instanceSendTo.http_response, refFlowdata);
}
}
})
}

106
RVO35/flow_old/db_init.js Executable file
View 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
View file

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

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

File diff suppressed because it is too large Load diff

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

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

View file

@ -0,0 +1,44 @@
function promisifyBuilder(builder)
{
return new Promise((resolve, reject) => {
try{
builder.callback(function(err, response) {
if(err != null) reject(err);
resolve(response);
});
} catch (error) {
reject(error);
}
})
}
function makeMapFromDbResult(response, ...keys)
{
let s = "-";
let data = {};
for(let i = 0; i < response.length; i++)
{
let record = response[i];
let k = [];
for(let j = 0; j < keys.length; j++)
{
k.push( record[keys[j]] );
}
let key = k.join(s);
data[ key ] = record;
}
return data;
}
module.exports = {
promisifyBuilder,
makeMapFromDbResult
}

30
RVO35/flow_old/helper/logger.js Executable file
View 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
View file

@ -0,0 +1,5 @@
function MD5(d){var r = M(V(Y(X(d),8*d.length)));return r.toLowerCase()};function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
module.exports = {
MD5
}

View file

@ -0,0 +1,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
View file

@ -0,0 +1,144 @@
/*
0 - cislo registra / prikaz
1 - recepient - 0 = master, 1 = slave (slave je automaticky group a broadcast)
2 - r/rw - read/write
3- register name - nazov registra / prikazu (len pre info - zobrazenie v aplikacii)
4,5,6,7, - jednotlive byte-y - nazov byte-u
4-7 - RES. a prazdny string "" sa nezobrazia!!!
*/
//124 zaznamov
const registers = [
["0","1","R","Status","","",""],
["1","1","RW","Dimming","R-Channel ","G-Channel","B-Channel ","W - Channel"],
["2","1","R","Device types","","",".",""],
["3","1","RW","Group addresses 1-4","Groups Add. 4","Groups Add. 3","Groups Add. 2","Groups Add. 1"],
["4","1","RW","Group addresses 5-8","Groups Add. 8","Groups Add. 7","Groups Add. 6","Groups Add. 5"],
["5","1","RW","Serial number (MAC)","","","",""],
["6","1","RW","Time of dusk","HH","MM","SS","EXTRA"],
["7","1","RW","Time of dawn","HH","MM","SS","EXTRA"],
["8","1","RW","Time Schedule settings","TBD","TBD","Movement sensor","Time Schedule"],
["9","1","RW","TS1 Time point 1","HH","MM","SS","Ext. Device"],
["10","1","RW","TS1 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["11","1","RW","TS1 Time point 2","HH","MM","SS","Ext. Device"],
["12","1","RW","TS1 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["13","1","RW","TS1 Time point 3","HH","MM","SS","Ext. Device"],
["14","1","RW","TS1 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["15","1","RW","TS1 Time point 4","HH","MM","SS","Ext. Device"],
["16","1","RW","TS1 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["17","1","RW","TS1 Time point 5","HH","MM","SS","Ext. Device"],
["18","1","RW","TS1 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["19","1","RW","TS1 Time point 6","HH","MM","SS","Ext. Device"],
["20","1","RW","TS1 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["21","1","RW","TS1 Time point 7","HH","MM","SS","Ext. Device"],
["22","1","RW","TS1 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["23","1","RW","TS1 Time point 8","HH","MM","SS","Ext. Device"],
["24","1","RW","TS1 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["25","1","RW","TS1 Time point 9","HH","MM","SS","Ext. Device"],
["26","1","RW","TS1 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["27","1","RW","TS1 Time point 10","HH","MM","SS","Ext. Device"],
["28","1","RW","TS1 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["29","1","RW","TS1 Time point 11","HH","MM","SS","Ext. Device"],
["30","1","RW","TS1 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["31","1","RW","TS1 Time point 12","HH","MM","SS","Ext. Device"],
["32","1","RW","TS1 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["33","1","RW","TS1 Time point 13","HH","MM","SS","Ext. Device"],
["34","1","RW","TS1 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["35","1","RW","TS1 Time point 14","HH","MM","SS","Ext. Device"],
["36","1","RW","TS1 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["37","1","RW","TS1 Time point 15","HH","MM","SS","Ext. Device"],
["38","1","RW","TS1 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["39","1","RW","TS1 Time point 16","HH","MM","SS","Ext. Device"],
["40","1","RW","TS1 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["41","1","RW","TS2 Time point 1","HH","MM","SS","Ext. Device"],
["42","1","RW","TS2 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["43","1","RW","TS2 Time point 2","HH","MM","SS","Ext. Device"],
["44","1","RW","TS2 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["45","1","RW","TS2 Time point 3","HH","MM","SS","Ext. Device"],
["46","1","RW","TS2 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["47","1","RW","TS2 Time point 4","HH","MM","SS","Ext. Device"],
["48","1","RW","TS2 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["49","1","RW","TS2 Time point 5","HH","MM","SS","Ext. Device"],
["50","1","RW","TS2 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["51","1","RW","TS2 Time point 6","HH","MM","SS","Ext. Device"],
["52","1","RW","TS2 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["53","1","RW","TS2 Time point 7","HH","MM","SS","Ext. Device"],
["54","1","RW","TS2 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["55","1","RW","TS2 Time point 8","HH","MM","SS","Ext. Device"],
["56","1","RW","TS2 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["57","1","RW","TS2 Time point 9","HH","MM","SS","Ext. Device"],
["58","1","RW","TS2 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["59","1","RW","TS2 Time point 10","HH","MM","SS","Ext. Device"],
["60","1","RW","TS2 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["61","1","RW","TS2 Time point 11","HH","MM","SS","Ext. Device"],
["62","1","RW","TS2 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["63","1","RW","TS2 Time point 12","HH","MM","SS","Ext. Device"],
["64","1","RW","TS2 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["65","1","RW","TS2 Time point 13","HH","MM","SS","Ext. Device"],
["66","1","RW","TS2 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["67","1","RW","TS2 Time point 14","HH","MM","SS","Ext. Device"],
["68","1","RW","TS2 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["69","1","RW","TS2 Time point 15","HH","MM","SS","Ext. Device"],
["70","1","RW","TS2 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["71","1","RW","TS2 Time point 16","HH","MM","SS","Ext. Device"],
["72","1","RW","TS2 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"],
["73","1","RW","Power meter status","TBD","","",""],
["74","1","R","Input Voltage","","","",""],
["75","1","R","Input Current","","","",""],
["76","1","R","Input Power","","","",""],
["77","1","R","Cos phi","","","",""],
["78","1","R","Frequency","","","",""],
["79","1","RW","Energy","","","",""],
["80","1","RW","Lifetime","","","",""],
["81","1","RW","Power on cycles (input)","","","",""],
["82","1","RW","Power on cycles (relay)","","","",""],
["83","1","R","Time since last power on","","","",""],
["84","1","R","Accelerometer data","","","",""],
["85","1","RW","GPS latitude","pos/neg","deg","min ","sec"],
["86","1","RW","GPS longitude","pos/neg","deg","min ","sec"],
["87","1","RW","Actual time","HH","MM","SS","RES."],
["88","1","RW","Actual date","Day of week","Day","Month","Year"],
["89","1","R","Production data 1","","","",""],
["90","1","R","Production data 2","","","",""],
["91","1","RW","Network ID","NID3","NID2","NID1","NID0"],
["95","1","RW","Actual Lux level from cabinet","RES.","RES.","HB","LB"],
["96","1","RW","Threshold lux level","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
["97","1","RW","Adjust period","Dusk HB","Dusk LB","Dawn HB","Dawn LB"],
["98","1","RW","Offset","RES.","RES.","Dusk","Dawn"],
["99","1","RW","CCT min/max range","max-H","max-L","min-H","min-L"],
["100","1","RW","DALI interface","Cmd ID","Add","Cmd","Resp"],
["101","1","RW","Module FW ver","v1","v2","v3","v4"],
["102","1","RW","Module MAC-H","unused","unused","M1","M2"],
["103","1","RW","Module MAC-L","M3","M4","M5","M6"],
["122","1","R","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
["123","1","R","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
["124","1","R","FW update - data","Byte3","Byte2","Byte1","Byte0"],
["0","0","R","Status","","","",""],
["1","0","RW","Control register","RES.","RES.","RES.","init mode enable"],
["2","0","R","Device types","","","",""],
["3","0","R","Serial number (MAC)","","","",""],
["4","0","R","Production data 1","","","",""],
["5","0","R","Production data 2","","","",""],
["6","0","RW","Network ID","NID3","NID2","NID1","NID0"],
["7","0","RW","RS232 settings","param.","param.","Baudrate H","Baudrate L"],
["8","0","R","Accelerometer data","","","",""],
["9","0","RW","Module FW ver","v1","v2","v3","v4"],
["10","0","RW","Module MAC-H","unused","unused","M1","M2"],
["11","0","RW","Module MAC-L","M3","M4","M5","M6"],
["32","0","RW","FW update status/control register","Byte3","Byte2","Byte1","Byte0"],
["33","0","RW","FW update - data index","Byte3","Byte2","Byte1","Byte0"],
["34","0","RW","FW update - data","Byte3","Byte2","Byte1","Byte0"],
["125","0","RW","Debug Register","Byte3","Byte2","Byte1","Byte0"],
["126","0","RW","Network Control Register","Byte3","Byte2","Byte1","Byte0"],
["127","0","R","Network Status Register","Byte3","Byte2","Byte1","Byte0"],
["128","0","RW","Node XX Serial Number Register","SER3","SER2","SER1","SER0"],
["256","0","R","Node XX Network Status Register","","","",""]
];
let register = {};
module.exports = {
registers,
register
}

View file

@ -0,0 +1,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
View file

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

124
RVO35/flow_old/helper/utils.js Executable file
View file

@ -0,0 +1,124 @@
function bytesToInt(bytes, numberOfBytes)
{
let buffer = [];
if(Array.isArray(bytes))
{
buffer = bytes.slice(0);
if(numberOfBytes != undefined)
{
buffer = bytes.slice(bytes.length - numberOfBytes);
}
}
else buffer.push(bytes);
//var decimal = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
let l = (buffer.length - 1) * 8;
let decimal = 0;
for(let i = 0; i < buffer.length; i++)
{
var s = buffer[i] << l;
if(l < 8) s = buffer[i]
decimal = decimal + s;
l = l - 8;
}
return decimal;
}
function resizeArray(arr, newSize, defaultValue) {
while(newSize > arr.length)
arr.push(defaultValue);
arr.length = newSize;
}
longToByteArray = function(/*long*/long) {
// we want to represent the input as a 8-bytes array
var byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
for ( var index = 0; index < byteArray.length; index ++ ) {
var byte = long & 0xff;
byteArray [ index ] = byte;
long = (long - byte) / 256 ;
}
return byteArray;
};
function addDays(date, days) {
var result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
/*
sleep(2000).then(() => {
// Do something after the sleep!
});
*/
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
function isEmptyObject( obj ) {
for ( var name in obj ) {
return false;
}
return true;
}
function convertUTCDateToLocalDate(date) {
var newDate = new Date(date);
newDate.setMinutes(date.getMinutes() + date.getTimezoneOffset());
return newDate;
}
function addZeroBefore(n) {
return (n < 10 ? '0' : '') + n;
}
var convertBase = function () {
function convertBase(baseFrom, baseTo) {
return function (num) {
return parseInt(num, baseFrom).toString(baseTo);
};
}
// binary to decimal
convertBase.bin2dec = convertBase(2, 10);
// binary to hexadecimal
convertBase.bin2hex = convertBase(2, 16);
// decimal to binary
convertBase.dec2bin = convertBase(10, 2);
// decimal to hexadecimal
convertBase.dec2hex = convertBase(10, 16);
// hexadecimal to binary
convertBase.hex2bin = convertBase(16, 2);
// hexadecimal to decimal
convertBase.hex2dec = convertBase(16, 10);
return convertBase;
}();
module.exports = {
bytesToInt,
longToByteArray,
addDays,
addZeroBefore,
resizeArray,
isEmptyObject,
sleep,
convertUTCDateToLocalDate
}

137
RVO35/flow_old/httprequest.js Executable file
View file

@ -0,0 +1,137 @@
exports.id = 'httprequest';
exports.title = 'HTTP Request';
exports.group = 'HTTP';
exports.color = '#5D9CEC';
exports.input = true;
exports.version = '2.0.1';
exports.output = 1;
exports.author = 'Peter Širka';
exports.icon = 'cloud-upload';
exports.html = `<div class="padding">
<div data-jc="textbox" data-jc-path="url" class="m" data-jc-config="required:true;maxlength:500;placeholder:@(E.g. https\\://www.totaljs.com)">@(URL address)</div>
<div class="row">
<div class="col-md-6 m">
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE">@(HTTP method)</div>
</div>
<div class="col-md-6 m">
<div data-jc="dropdown" data-jc-path="stringify" data-jc-config="required:true;items:,URL encoded|encoded,JSON|json,RAW|raw,None|none">@(Serialization)</div>
</div>
</div>
<div data-jc="checkbox" data-jc-path="chunks">@(Download the content <b>in chunks</b>)</div>
<div data-jc="checkbox" data-jc-path="persistentcookies">@(Keep persistent cookies)</div>
<div data-jc="checkbox" data-jc-path="nodns">@(Disable DNS cache)</div>
<div data-jc="checkbox" data-jc-path="keepalive">@(Keep alive connection)</div>
</div>
<hr class="nmt nmb" />
<div class="padding">
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
</div>
<div class="padding bg-smoke">
<section>
<label><i class="fa fa-lock"></i>@(HTTP basic access authentication)</label>
<div class="padding npb">
<div class="row">
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="username">@(User)</div>
</div>
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="userpassword">@(Password)</div>
</div>
</div>
</div>
</section>
</div>`;
exports.readme = `# Request
This component creates a request with received data.
__Response:__
\`\`\`javascript
{
data: String,
headers: Object,
status: Number,
host: String
}
\`\`\`
__Dynamic arguments__:
Are performed via FlowData repository and can be used for URL address or for custom headers/cookies/auth. Use \`repository\` component for creating of dynamic arguments. Dynamic values are replaced in the form \`{key}\`:
- url address e.g. \`https://.../{key}/\`
- headers values e.g. \`{token}\`
- cookies values e.g. \`{token}\``;
exports.install = function(instance) {
var can = false;
var flags = null;
var cookies2 = null;
instance.on('data', function(response) {
can && instance.custom.send(response);
});
instance.custom.send = function(response) {
var options = instance.options;
var headers = null;
var cookies = null;
options.headers && Object.keys(options.headers).forEach(function(key) {
!headers && (headers = {});
headers[key] = response.arg(options.headers[key]);
});
if (options.username && options.userpassword) {
!headers && (headers = {});
headers['Authorization'] = 'Basic ' + U.createBuffer(response.arg(options.username + ':' + options.userpassword)).toString('base64');
}
options.cookies && Object.keys(options.cookies).forEach(function(key) {
!cookies && (cookies = {});
cookies[key] = response.arg(options.cookies[key]);
});
if (options.chunks) {
U.download(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, response) {
response.on('data', (chunks) => instance.send2(chunks));
}, cookies || cookies2, headers);
} else {
U.request(response.arg(options.url), flags, options.stringify === 'none' ? null : response.data, function(err, data, status, headers, host) {
if (response && !err) {
response.data = { data: data, status: status, headers: headers, host: host };
instance.send2(response);
} else if (err)
instance.error(err, response);
}, cookies || cookies2, headers);
}
};
instance.reconfigure = function() {
var options = instance.options;
can = options.url && options.url && options.method && options.stringify ? true : false;
instance.status(can ? '' : 'Not configured', can ? undefined : 'red');
if (!can)
return;
flags = [];
flags.push(options.method.toLowerCase());
options.stringify === 'json' && flags.push('json');
options.stringify === 'raw' && flags.push('raw');
options.keepalive && flags.push('keepalive');
!options.nodns && flags.push('dnscache');
if (options.persistentcookies) {
flags.push('cookies');
cookies2 = {};
} else
cookies2 = null;
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
};

76
RVO35/flow_old/httpresponse.js Executable file
View file

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

326
RVO35/flow_old/httproute.js Executable file
View file

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

101
RVO35/flow_old/infosender.js Executable file
View 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
View 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();
})
}

View file

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

96
RVO35/flow_old/monitordisk.js Executable file
View file

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

87
RVO35/flow_old/monitormemory.js Executable file
View file

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

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

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

241
RVO35/flow_old/show_dbdata.js Executable file
View 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
View 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
View 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
View file

@ -0,0 +1,79 @@
exports.id = 'trigger';
exports.title = 'Trigger';
exports.group = 'Inputs';
exports.color = '#F6BB42';
exports.click = true;
exports.output = 1;
exports.version = '1.1.0';
exports.author = 'Martin Smola';
exports.icon = 'play';
exports.html = `<div class="padding">
<div data-jc="dropdown__datatype__items:,String|string,Integer|integer,Float|float,Boolean|boolean,Date|date,Object|object,Base64 as Buffer|buffer" class="m">@(Data type (String by default))</div>
<div data-jc="textbox__data__placeholder:@(e.g. Hello world or { hello: 'world'} or ['hello', 'world']))" class="m">@(Data)</div>
<div data-jc="checkbox__restart">Trigger 5s after initialization.</div>
<div class="help">@(Useful when there's a need to run certain flow when the app restarts, etc.)</div>
</div>`;
exports.readme = `# Trigger
- Clicking on the component starts the chain
- Settings allows to set a data-type and a value`;
exports.install = function(instance) {
var value;
instance.on('click', () => instance.send2(value));
instance.reconfigure = function() {
var options = instance.options;
value = null;
switch (options.datatype) {
case 'integer':
value = options.data.parseInt2('error');
value = value === 'error' ? NaN : value;
break;
case 'float':
value = options.data.parseFloat2('error');
value = value === 'error' ? NaN : value;
break;
case 'date':
options.data = options.data.toString();
var num = options.data.parseInt('error');
num === 'error' && (num = options.data.parseDate('error'));
num === 'error' && (num = null);
value = num ? new Date(num).toUTCString() : num;
break;
case 'object':
try {
value = (new Function('return ' + options.data))();
} catch (e) {
instance.error(e);
}
break;
case 'boolean':
value = options.data.parseBoolean();
break;
case 'buffer':
try {
value = U.createBuffer(options.data);
} catch (e) {
instance.error(e);
}
break;
case 'string':
default:
value = '' + (options.data || '');
break;
}
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
if (instance.options.restart)
setTimeout(function(){
instance.send2(value);
}, 5000);
};

0
RVO35/flow_old/variables.txt Executable file
View file

43
RVO35/flow_old/virtualwirein.js Executable file
View file

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

View file

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

477
RVO35/flow_old/wsmqttpublish.js Executable file
View 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

File diff suppressed because it is too large Load diff

2125
RVO35/package-lock.json generated Executable file

File diff suppressed because it is too large Load diff

29
RVO35/package.json Executable file
View 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
View 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
View 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
View 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": []
}
}