Backup senica-RVO16 on 16.10.2025

This commit is contained in:
Jakub Klena 2025-10-16 02:24:24 +02:00
parent a1befbc183
commit 7c6669ddba
62 changed files with 32647 additions and 0 deletions

36
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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 };

128
RVO16/databases/nodes.table Executable file
View file

@ -0,0 +1,128 @@
node:number|pole_number:string|node_type:string|tbname:string|line:number|profile:string|processed:boolean|status:boolean|time_of_last_communication:number
+|3831|17|NEMA|JzwxZXOvDj1bVrN4nkWwJKk8qdyBl3MRKLpGPgaQ|1||1|1|1760574248582|.............
+|3739|6|NEMA|5dBNwRp9graYJxZn409wGV7lVov1b2QLPDqGm6XK|1||1|1|1760573832281|.............
+|3801|44|NEMA|roKgWqY95V3mXMRzyAjmrz7bLjexpJPvaGDBw826|1||1|1|1760574246856|.............
+|3659|60|NEMA|ZmRwd93QL4gaezxEbAxW2O71prn2XjlPvGyqJ6BO|1||1|1|1760573713804|.............
+|3659|78|NEMA|ZmRwd93QL4gaezxEbAxW2O71prn2XjlPvGyqJ6BO|1||1|1|1760573713804|.............
+|3861|47|NEMA|gYbDLqlyZVoRerQpB72rGp7WJnwM5z24POKa8Exj|1||1|1|1760573552771|.............
+|3870|76|NEMA|B5EoxeMVp4zwr8nqW0GjBxARjvD1PNamOGbLg63Z|1||1|0|1760244554423|.............
+|4107|98|NEMA|rDbQ84xzwgdqEoPm3kbJErk9anOZY1RXyBv2LVM6|1||1|1|1760573931758|.............
+|4766|51|NEMA|zrR51V2ajQ9ZLygPKkEmPpkYDq38xOJolENBXGnv|1||1|1|1760573654852|.............
+|3593|54|NEMA|apKVJBwOyrP35m2lv7KzEKkYXbeWNd64En9GxRqg|1||1|1|1760574049100|.............
+|3755|82|NEMA|wvKJdZML6mXP4DzWBAXWx17jxNloa5g23Ve9Y1ry|1||1|1|1760573915778|.............
+|3697|96|NEMA|PLBJzmK1r3Gynd6OW0gGKo0e5wV4vx9bDEqNgYR8|1||1|1|1760573736032|.............
+|3712|5|NEMA|WlVJBygjDZMeKX3vnAMzGQ08NqdmG2x1Y69LQ4P5|1||1|1|1760573762576|.............
+|3724|4|NEMA|zdQO8GwxDqjRgP4137Y1xq7NyKlpem2nL65rvVJY|1||1|1|1760573913125|.............
+|4370|29|NEMA|gRoJEyXVx4qD9er287LPwm7wBzGldaPjLWQKm3Mv|1||1|1|1760573974777|.............
+|4372|70|NEMA|PjLblDgRBO6WQqnxmkJ5150Jv3ewZN4p5a89yKdY|1||1|1|1760573638552|.............
+|4373|33|NEMA|aw4eELG2DlPMdn1JW0B1zbAqQXOZRN3xB5yp8VKr|1||1|1|1760573638696|.............
+|3594|53|NEMA|DbQY6zyveZRwK5drV0Z9lLAjoE4XJM83N9xl2nWq|1||1|1|1760574093302|.............
+|3603|50|NEMA|5dBNwRp9graYJxZn409wRV7lVov1b2QLPDqGm6XK|1||1|1|1760574097105|.............
+|3605|49|NEMA|WlVJBygjDZMeKX3vnAMzWQ08NqdmG2x1Y69LQ4P5|1||1|1|1760573706900|.............
+|3963|25|NEMA|1JMYvnx2RzKEo4aWQ7Dm9GAL8yZV3m9NBePXbrdj|1||1|1|1760573588007|.............
+|3608|16A|NEMA|m6EYyZoJ4gWexdjVPARNzEARDOq9wv2N5XzKGplr|1||1|1|1760574106821|.............
+|3609|24|NEMA|Z5KyJe9nEg1QNbWlX0wWmm0oDjBLdqzR83VGv624|1||1|1|1760573708275|.............
+|3610|18|NEMA|g9OxBZ5KRwNznlY6pAp64dkWXvjdEL4eGQobMDy2|1||1|1|1760574107380|.............
+|3614|31|NEMA|d9x2V5LGYBzXp4mMRAOBPq7PloaqJwnQj6DgrNe3|1||1|1|1760574129401|.............
+|4768|23|NEMA|3JjOWdylwgNLzxVab7NEPBkZ2vG64rq8PEB5QmDo|1||1|1|1760573661963|.............
+|3618|20|NEMA|JX1ObgmqGZ54DMyYL7a95d7EVdve38WKRzwjNrQ9|1||1|1|1760574136560|.............
+|2736|21|NEMA|RvmwNz8QPblKp41GD7lDmV7JrLVYoBO92dMegn6W|1||1|1|1760574193434|.............
+|3620|34|NEMA|ZmRwd93QL4gaezxEbAxW1O71prn2XjlPvGyqJ6BO|1||1|1|1760574136720|.............
+|3623|32|NEMA|B5EoxeMVp4zwr8nqW0GjexARjvD1PNamOGbLg63Z|1||1|1|1760574113964|.............
+|3624|26|NEMA|PjLblDgRBO6WQqnxmkJ5w50Jv3ewZN4p5a89yKdY|1||1|1|1760574116313|.............
+|3626|19|NEMA|OzNMgZ9n43qPbjXmy7zWGdA2DKdYvW5e6pxGRrVa|1||1|1|1760574116489|.............
+|3627|27|NEMA|dz4ojlpP85JMgDLZWkQO1zAaKYqQexEr62GXRV1y|1||1|1|1760574119286|.............
+|3732|22|NEMA|RO8rjaBDy21qPQJzW7oDK9ApK3xmNleVZg9Ed4Gw|1||1|1|1760573632480|.............
+|3631|118|NEMA|g9OxBZ5KRwNznlY6pAp66EkWXvjdEL4eGQobMDy2|1||1|1|1760574120900|.............
+|3637|122|NEMA|RO8rjaBDy21qPQJzW7oDDOApK3xmNleVZg9Ed4Gw|1||1|1|1760574121251|.............
+|3638|121|NEMA|RvmwNz8QPblKp41GD7lDDZ7JrLVYoBO92dMegn6W|1||1|1|1760573706692|.............
+|4117|120|NEMA|JX1ObgmqGZ54DMyYL7a99m7EVdve38WKRzwjNrQ9|1||1|1|1760573759587|.............
+|3685|35|NEMA|eod9aRWLVl34Gx1Dn7VoYMA2rz6qjgmpEXwQJN5Z|1||1|1|1760574134003|.............
+|3686|37|NEMA|EjgWGnXaLy9opPOz20n6gV086BlYM3w1deVQvbKr|1||1|1|1760573851554|.............
+|3688|38|NEMA|wvKJdZML6mXP4DzWBAXWO17jxNloa5g23Ve9Y1ry|1||1|1|1760574134323|.............
+|3692|30|NEMA|K94XLav1glVRnyQ6r01BVakme3YJwBxM5oOzdP2j|1||1|1|1760574134483|.............
+|3694|79|NEMA|eod9aRWLVl34Gx1Dn7VoNMA2rz6qjgmpEXwQJN5Z|1||1|1|1760573856587|.............
+|3696|85|NEMA|52dD6ZlV1QaOpRBmbAqKGQkKnGzWMLj4eJq38Pgo|1||1|1|1760573856763|.............
+|3698|59|NEMA|6lQGaY9RDywdVzObj0P1ZykPg4NBn3exEK51LWZq|1||1|1|1760573863475|.............
+|3699|58|NEMA|pE5X8NQPaow6vlOZxk6gYy7q42ezGBMyWgDVjR3L|1||1|1|1760574138941|.............
+|3700|63|NEMA|OzNMgZ9n43qPbjXmy7zWOdA2DKdYvW5e6pxGRrVa|1||1|1|1760573866735|.............
+|3701|64|NEMA|JX1ObgmqGZ54DMyYL7a9Jd7EVdve38WKRzwjNrQ9|1||1|1|1760573868940|.............
+|2647|86|NEMA|rDbQ84xzwgdqEoPm3kbJEdk9anOZY1RXyBv2LVM6|1||1|1|1760574072000|.............
+|3703|62|NEMA|g9OxBZ5KRwNznlY6pAp6bdkWXvjdEL4eGQobMDy2|1||1|1|1760573872455|.............
+|2613|65|NEMA|RvmwNz8QPblKp41GD7lD4V7JrLVYoBO92dMegn6W|1||1|1|1760574079990|.............
+|3706|87|NEMA|E6Kg9oDnLWyzPRMva7vr5x7Jxp4VG58qO2w1lZYe|1||1|1|1760573880525|.............
+|3707|66|NEMA|RO8rjaBDy21qPQJzW7oDm9ApK3xmNleVZg9Ed4Gw|1||1|1|1760573880717|.............
+|3708|61|NEMA|JzwxZXOvDj1bVrN4nkWweKk8qdyBl3MRKLpGPgaQ|1||1|1|1760573759987|.............
+|3709|60|NEMA|m6EYyZoJ4gWexdjVPARNaEARDOq9wv2N5XzKGplr|1||1|1|1760574157351|.............
+|3713|12|NEMA|gP1eOZVj3Q9lv5aDEk4E1Q7rdpqW8yLm2BbKzJxM|1||1|1|1760573902514|.............
+|3716|84|NEMA|PLBJzmK1r3Gynd6OW0gGKP0e5wV4vx9bDEqNgYR8|1||1|1|1760573907500|.............
+|3719|95|NEMA|Nzp2OoJlqn6r1ZgvdA3GBjAabBwP5G4eE3RQmyxD|1||1|1|1760573908347|.............
+|3723|92|NEMA|3a5oqJN1bgnx4Ol9dk869wAByE6jQ8mKDWMpGrLV|1||1|1|1760573912550|.............
+|3725|83|NEMA|Nzp2OoJlqn6r1ZgvdA3GBKAabBwP5G4eE3RQmyxD|1||1|1|1760574201249|.............
+|3727|9|NEMA|DbQY6zyveZRwK5drV0Z9ZLAjoE4XJM83N9xl2nWq|1||1|1|1760574222870|.............
+|3729|10|NEMA|apKVJBwOyrP35m2lv7KzWKkYXbeWNd64En9GxRqg|1||1|1|1760572741778|.............
+|3731|8|NEMA|BaY3Xpy1EbKGjLq2O7mGKo0rx8owgQz9P4dDJRmN|1||1|1|1760574222118|.............
+|3880|15|NEMA|6lQGaY9RDywdVzObj0P1oykPg4NBn3exEK51LWZq|1||1|1|1760573584220|.............
+|3219|14|NEMA|pE5X8NQPaow6vlOZxk6gMy7q42ezGBMyWgDVjR3L|1||1|1|1760574097712|.............
+|3735|94|NEMA|wvKJdZML6mXP4DzWBAXWxB7jxNloa5g23Ve9Y1ry|1||1|1|1760573638137|.............
+|3736|11|NEMA|o9vbeQlLMVg8j5dq4kegbRANxZpEmnXzwYKO1ar2|1||1|1|1760574229166|.............
+|3737|90|NEMA|ZmRwd93QL4gaezxEbAxW2X71prn2XjlPvGyqJ6BO|1||1|1|1760573882603|.............
+|3743|3|NEMA|gYbDLqlyZVoRerQpB72r8p7WJnwM5z24POKa8Exj|1||1|1|1760573901987|.............
+|3746|57|NEMA|2O14VBzl8aDmWdNw3A513bAGyZ5qLJoEMpj6R9ng|1||1|1|1760573907692|.............
+|3763|56|NEMA|gP1eOZVj3Q9lv5aDEk4EMQ7rdpqW8yLm2BbKzJxM|1||1|1|1760574243772|.............
+|3490|67|NEMA|3JjOWdylwgNLzxVab7NEaBkZ2vG64rq8PEB5QmDo|1||1|1|1760574044114|.............
+|3824|42|NEMA|rDbQ84xzwgdqEoPm3kbJPdk9anOZY1RXyBv2LVM6|1||1|1|1760574248038|.............
+|3825|40|NEMA|PLBJzmK1r3Gynd6OW0gG2P0e5wV4vx9bDEqNgYR8|1||1|1|1760574248230|.............
+|3830|75|NEMA|d9x2V5LGYBzXp4mMRAOBmq7PloaqJwnQj6DgrNe3|1||1|1|1760573952276|.............
+|3842|93|NEMA|EjgWGnXaLy9opPOz20n64m086BlYM3w1deVQvbKr|1||1|1|1760573953044|.............
+|3848|89|NEMA|nJL5lPMwBx23YpqRe0rlpv7damXvWVbOrD4gNzy8|1||1|1|1760573953251|.............
+|3850|88|NEMA|roKgWqY95V3mXMRzyAjmKz7bLjexpJPvaGDBw826|1||1|1|1760573953683|.............
+|3964|77|NEMA|aw4eELG2DlPMdn1JW0B1MbAqQXOZRN3xB5yp8VKr|1||1|1|1760573998763|.............
+|3862|45|NEMA|nJL5lPMwBx23YpqRe0rlqv7damXvWVbOrD4gNzy8|1||1|1|1760573553074|.............
+|3867|69|NEMA|1JMYvnx2RzKEo4aWQ7DmGGAL8yZV3m9NBePXbrdj|1||1|1|1760573559978|.............
+|3869|73|NEMA|gRoJEyXVx4qD9er287LP4m7wBzGldaPjLWQKm3Mv|1||1|1|1760573561560|.............
+|3871|43|NEMA|E6Kg9oDnLWyzPRMva7vrWx7Jxp4VG58qO2w1lZYe|1||1|1|1760573578435|.............
+|3876|46|NEMA|XMBbew5z4ELrZa2mRAd5ZW08vPN6gy3DdVYlpKjq|1||1|1|1760573578962|.............
+|3722|105|NEMA|WlVJBygjDZMeKX3vnAMzRj08NqdmG2x1Y69LQ4P5|1||1|1|1760573912374|.............
+|4090|110|NEMA|apKVJBwOyrP35m2lv7Kzz5kYXbeWNd64En9GxRqg|1||1|1|1760573588902|.............
+|4091|102|NEMA|XMBbew5z4ELrZa2mRAd53V08vPN6gy3DdVYlpKjq|1||1|1|1760574011691|.............
+|4092|117|NEMA|JzwxZXOvDj1bVrN4nkWwwNk8qdyBl3MRKLpGPgaQ|1||1|1|1760573592641|.............
+|4095|106|NEMA|5dBNwRp9graYJxZn409wNQ7lVov1b2QLPDqGm6XK|1||1|1|1760573597787|.............
+|4097|112|NEMA|gP1eOZVj3Q9lv5aDEk4EEM7rdpqW8yLm2BbKzJxM|1||1|1|1760574017667|.............
+|4099|113|NEMA|2O14VBzl8aDmWdNw3A5119AGyZ5qLJoEMpj6R9ng|1||1|1|1760573929265|.............
+|4102|107|NEMA|zrR51V2ajQ9ZLygPKkEmMxkYDq38xOJolENBXGnv|1||1|1|1760573609037|.............
+|4104|103|NEMA|gYbDLqlyZVoRerQpB72rML7WJnwM5z24POKa8Exj|1||1|1|1760573609261|.............
+|3115|108|NEMA|BaY3Xpy1EbKGjLq2O7mGa60rx8owgQz9P4dDJRmN|1||1|1|1760574074381|.............
+|4106|100|NEMA|roKgWqY95V3mXMRzyAjmKj7bLjexpJPvaGDBw826|1||1|1|1760573930336|.............
+|4110|111|NEMA|o9vbeQlLMVg8j5dq4keggbANxZpEmnXzwYKO1ar2|1||1|1|1760573755129|.............
+|4767|101|NEMA|nJL5lPMwBx23YpqRe0rlpw7damXvWVbOrD4gNzy8|1||1|1|1760573661772|.............
+|3702|99|NEMA|E6Kg9oDnLWyzPRMva7vr5j7Jxp4VG58qO2w1lZYe|1||1|1|1760573738525|.............
+|4139|125|NEMA|1JMYvnx2RzKEo4aWQ7DmmZAL8yZV3m9NBePXbrdj|1||1|1|1760573661372|.............
+|4142|123|NEMA|3JjOWdylwgNLzxVab7NEEKkZ2vG64rq8PEB5QmDo|1||1|1|1760573958972|.............
+|4147|124|NEMA|Z5KyJe9nEg1QNbWlX0wWWD0oDjBLdqzR83VGv624|1||1|1|1760573959532|.............
+|4151|16B|NEMA|PjLblDgRBO6WQqnxmkJ5560Jv3ewZN4p5a89yKdY|1||1|1|1760574072383|.............
+|4202|74|NEMA|K94XLav1glVRnyQ6r01BPakme3YJwBxM5oOzdP2j|1||1|1|1760574151726|.............
+|4204|72|NEMA|d5xjWYMwEJon6rLlK7yBEKAqgV4DaOeNB9ZX3Gzb|1||1|1|1760574157590|.............
+|4208|68|NEMA|Z5KyJe9nEg1QNbWlX0wW4m0oDjBLdqzR83VGv624|1||1|1|1760574157798|.............
+|4215|48|NEMA|zdQO8GwxDqjRgP4137Y15q7NyKlpem2nL65rvVJY|1||1|1|1760573631122|.............
+|3710|1|NEMA|nJL5lPMwBx23YpqRe0rlKv7damXvWVbOrD4gNzy8|1||1|1|1760573884153|.............
+|3711|97|NEMA|52dD6ZlV1QaOpRBmbAqKGMkKnGzWMLj4eJq38Pgo|1||1|1|1760573761425|.............
+|3726|2|NEMA|XMBbew5z4ELrZa2mRAd5dW08vPN6gy3DdVYlpKjq|1||1|1|1760573806537|.............
+|4094|104|NEMA|zdQO8GwxDqjRgP4137Y1V67NyKlpem2nL65rvVJY|1||1|1|1760573592913|.............
+|4765|81|NEMA|EjgWGnXaLy9opPOz20n64V086BlYM3w1deVQvbKr|1||1|1|1760573653766|.............
+|3607|28|NEMA|d5xjWYMwEJon6rLlK7yBlKAqgV4DaOeNB9ZX3Gzb|1||1|1|1760574104424|.............
+|4758|109|NEMA|DbQY6zyveZRwK5drV0Z98bAjoE4XJM83N9xl2nWq|1||1|1|1760573642819|.............
+|2715|52|NEMA|BaY3Xpy1EbKGjLq2O7mG9o0rx8owgQz9P4dDJRmN|1||1|1|1760574035453|.............
+|3881|71|NEMA|dz4ojlpP85JMgDLZWkQOJzAaKYqQexEr62GXRV1y|1||1|1|1760573584379|.............
+|3715|13|NEMA|2O14VBzl8aDmWdNw3A516bAGyZ5qLJoEMpj6R9ng|1||1|1|1760573902786|.............
+|3616|36|NEMA|3a5oqJN1bgnx4Ol9dk86BZAByE6jQ8mKDWMpGrLV|1||1|1|1760574129561|.............
+|3742|7|NEMA|zrR51V2ajQ9ZLygPKkEm1pkYDq38xOJolENBXGnv|1||1|1|1760573834071|.............
+|3717|80|NEMA|3a5oqJN1bgnx4Ol9dk869ZAByE6jQ8mKDWMpGrLV|1||1|1|1760573907867|.............
+|3705|91|NEMA|eod9aRWLVl34Gx1Dn7VoNbA2rz6qjgmpEXwQJN5Z|1||1|1|1760573742313|.............
+|3630|55|NEMA|o9vbeQlLMVg8j5dq4kegdRANxZpEmnXzwYKO1ar2|1||1|1|1760574119477|.............
+|3865|41|NEMA|52dD6ZlV1QaOpRBmbAqKvQkKnGzWMLj4eJq38Pgo|1||1|1|1760573559802|.............
+|3632|119|NEMA|OzNMgZ9n43qPbjXmy7zWWaA2DKdYvW5e6pxGRrVa|1||1|1|1760574121075|.............
+|3684|39|NEMA|Nzp2OoJlqn6r1ZgvdA3GRKAabBwP5G4eE3RQmyxD|1||1|1|1760574128250|.............
+|4096|114|NEMA|pE5X8NQPaow6vlOZxk6ggg7q42ezGBMyWgDVjR3L|1|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"05:30","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|1760573922713|.............
+|4764|116|NEMA|m6EYyZoJ4gWexdjVPARNNwARDOq9wv2N5XzKGplr|1|{"intervals":[{"cct":3000,"value":0,"end_time":"20:00","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"05:30","start_time":"20:00"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"05:30"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|1760573653574|.............
+|4101|115|NEMA|6lQGaY9RDywdVzObj0P11OkPg4NBn3exEK51LWZq|1|{"intervals":[{"cct":3000,"value":0,"end_time":"16:20","start_time":"13:00"},{"cct":3000,"value":10,"end_time":"06:50","start_time":"16:20"},{"cct":3000,"value":0,"end_time":"13:00","start_time":"06:50"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}|1|1|1760573929473|.............

File diff suppressed because one or more lines are too long

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

14
RVO16/databases/pins.table Executable file
View file

@ -0,0 +1,14 @@
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|...........
*|28F46E9D0E00008B|temperature|0|...........
*|twilight_sensor|twilight_sensor|0|...........

5
RVO16/databases/relays.table Executable file
View file

@ -0,0 +1,5 @@
line:number|tbname:string|contactor:number|profile:string
+|0|PLBJzmK1r3Gynd6OW0gKRo0e5wV4vx9bDEqNgYR8|1||...........
+|1|5dBNwRp9graYJxZn409wv37lVov1b2QLPDqGm6XK|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|zrR51V2ajQ9ZLygPKkEmGJkYDq38xOJolENBXGnv|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|BaY3Xpy1EbKGjLq2O7mG3p0rx8owgQz9P4dDJRmN|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
RVO16/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_16_10.0.0.131|en|28.569E9C0E0000|48.70826502|17.28455203|192.168.252.1|rvo_senica_16_10.0.0.131|6EANzPOaNVolqCTE4iXN|1883|0|67|unipi|ttyUSB0|1|20|5|6|3|u131|0|1|1|...................................................

0
RVO16/databases/tbdata.nosql Executable file
View file

View file

38
RVO16/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
RVO16/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);

33
RVO16/err.txt Executable file
View file

@ -0,0 +1,33 @@
[2025-09-23T13:44:13.406] [ERROR] errLogs - uncaughtException: Cannot read property 'getHours' of undefined
[2025-09-23T13:44:13.407] [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-23T13:44:13.408] [ERROR] errLogs - uncaughtException: Cannot read property 'getHours' of undefined
[2025-09-23T13:44:13.408] [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-23T13:44:13.418] [ERROR] errLogs - uncaughtException: Cannot read property 'getHours' of undefined
[2025-09-23T13:44:13.418] [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-23T13:44:19.196] [ERROR] errLogs - uncaughtException: date.getHours is not a function
[2025-09-23T13:44:19.197] [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-23T13:44:19.197] [ERROR] errLogs - uncaughtException: date.getHours is not a function
[2025-09-23T13:44:19.198] [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-23T13:44:19.201] [ERROR] errLogs - uncaughtException: date.getHours is not a function
[2025-09-23T13:44:19.202] [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
RVO16/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
RVO16/flow/cmd_manager.js Executable file

File diff suppressed because it is too large Load diff

2800
RVO16/flow/cmd_manager_orig.txt Executable file

File diff suppressed because it is too large Load diff

90
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/flow/designer.json Executable file

File diff suppressed because it is too large Load diff

1486
RVO16/flow/dido_controller.js Executable file

File diff suppressed because it is too large Load diff

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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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
RVO16/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();
})
};

99
RVO16/flow/thermometer.js Executable file
View file

@ -0,0 +1,99 @@
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
RVO16/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
RVO16/flow/variables.txt Executable file
View file

43
RVO16/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
RVO16/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
RVO16/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();
};

7953
RVO16/monitor.txt Executable file

File diff suppressed because it is too large Load diff

2139
RVO16/package-lock.json generated Executable file

File diff suppressed because it is too large Load diff

30
RVO16/package.json Executable file
View file

@ -0,0 +1,30 @@
{
"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",
"moment": "^2.30.1",
"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
RVO16/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
RVO16/release.js.json Executable file
View file

@ -0,0 +1,34 @@
{
"pid": 17794,
"stats": [
{
"id": null,
"version": {
"node": "v14.18.1",
"total": "3.4.13",
"app": "1.0.0"
},
"pid": 17794,
"thread": "",
"mode": "release",
"overload": 0,
"date": "2025-10-16T00:23:43.622Z",
"memory": 25.99,
"rm": 0,
"fm": 0,
"wm": 0,
"mm": 0,
"om": 6,
"em": 0,
"dbrm": 0,
"dbwm": 0,
"usage": 0,
"requests": 5,
"pending": 0,
"errors": 0,
"timeouts": 0,
"uptime": 1588,
"online": 0
}
]
}

2191
RVO16/report_data.log Executable file

File diff suppressed because it is too large Load diff