diff --git a/webserver/monitoring.py b/webserver/monitoring.py new file mode 100644 index 0000000..bcf2811 --- /dev/null +++ b/webserver/monitoring.py @@ -0,0 +1,101 @@ +import time, threading +from pymodbus.client.sync import ModbusTcpClient + +class debug_var(): + name = '' + location = '' + type = '' + forced = 'No' + value = 0 + +debug_vars = [] +monitor_active = False +mb_client = None + +def parse_st(st_file): + global debug_vars + filepath = './st_files/' + st_file + + st_program = open(filepath, 'r') + + for line in st_program.readlines(): + if line.find(' AT ') > 0: + debug_data = debug_var() + tmp = line.strip().split(' ') + debug_data.name = tmp[0] + debug_data.location = tmp[2] + debug_data.type = tmp[4].split(';')[0] + debug_vars.append(debug_data) + + for debugs in debug_vars: + print('Name: ' + debugs.name) + print('Location: ' + debugs.location) + print('Type: ' + debugs.type) + print('') + + +def cleanup(): + del debug_vars[:] + +def modbus_monitor(): + global mb_client + for debug_data in debug_vars: + if (debug_data.location.find('IX')) > 0: + #Reading Input Status + mb_address = debug_data.location.split('%IX')[1].split('.') + result = mb_client.read_discrete_inputs(int(mb_address[0])*8 + int(mb_address[1]), 1) + debug_data.value = result.bits[0] + + elif (debug_data.location.find('QX')) > 0: + #Reading Coils + mb_address = debug_data.location.split('%QX')[1].split('.') + result = mb_client.read_coils(int(mb_address[0])*8 + int(mb_address[1]), 1) + debug_data.value = result.bits[0] + + elif (debug_data.location.find('IW')) > 0: + #Reading Input Registers + mb_address = debug_data.location.split('%IW')[1] + result = mb_client.read_input_registers(int(mb_address), 1) + debug_data.value = result.registers[0] + + elif (debug_data.location.find('QW')) > 0: + #Reading Holding Registers + mb_address = debug_data.location.split('%QW')[1] + result = mb_client.read_holding_registers(int(mb_address), 1) + debug_data.value = result.registers[0] + + elif (debug_data.location.find('MW')) > 0: + #Reading Word Memory + mb_address = debug_data.location.split('%MW')[1] + result = mb_client.read_holding_registers(int(mb_address) + 1024, 1) + debug_data.value = result.registers[0] + + elif (debug_data.location.find('MD')) > 0: + #Reading Double Memory + print('hi') + + elif (debug_data.location.find('ML')) > 0: + #Reading Long Memory + print('hi') + + + if (monitor_active == True): + threading.Timer(0.5, modbus_monitor).start() + +def start_monitor(): + global monitor_active + global mb_client + + if (monitor_active != True): + monitor_active = True + mb_client = ModbusTcpClient('127.0.0.1') + + modbus_monitor() + +def stop_monitor(): + global monitor_active + global mb_client + + if (monitor_active != False): + monitor_active = False + mb_client.close() \ No newline at end of file diff --git a/webserver/pages.py b/webserver/pages.py index 131ada9..8d4c078 100644 --- a/webserver/pages.py +++ b/webserver/pages.py @@ -680,6 +680,154 @@ loading logs... """ +monitoring_head = """ +/* OpenPLC Style */ + .top { + position:absolute; + left:0; right:0; top:0; + height: 50px; + background-color: #1F1F1F; + position: fixed; + overflow: hidden; + z-index: 10 + } + + .main { + position: absolute; + left:0px; top:50px; right:0; bottom:0; + } + + .user { + position:absolute; + left:75%; right:0; top:0; + height: 50px; + position: fixed; + overflow: hidden; + z-index: 11; + text-align:right; + } + + .button { + background-color: #E02222; + border: 1px solid #1F1F1F; + border-radius: 4px; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + font-family: "Roboto", sans-serif; + } + + .button:hover { + background-color: #B51A1A; + } + + table, h1, h2, h3, p { + font-family: "Roboto", sans-serif; + border-collapse: collapse; + width: 100%; + } + + td, th { + border: 1px solid #cccccc; + text-align: left; + padding: 8px; + } + + tr:nth-child(even) { + background-color: #eeeeee; + } + + tr:hover { + cursor: hand;background-color: slategray; + } + + label { + font-family: arial, sans-serif; + } + + input[type=text], input[type=password], select, textarea { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; + font-family: "Roboto", sans-serif; + } + + """ + + +monitoring_tail = """ + + + + + + + + +""" + add_user_tail = """
diff --git a/webserver/webserver.py b/webserver/webserver.py index 8cb8aeb..599ec1d 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -9,6 +9,7 @@ import datetime import time import pages import openplc +import monitoring as monitor import sys import flask @@ -452,9 +453,12 @@ def start_plc(): if (flask_login.current_user.is_authenticated == False): return flask.redirect(flask.url_for('login')) else: + monitor.stop_monitor() openplc_runtime.start_runtime() time.sleep(1) configure_runtime() + monitor.cleanup() + monitor.parse_st(openplc_runtime.project_file) return flask.redirect(flask.url_for('dashboard')) @@ -466,6 +470,7 @@ def stop_plc(): else: openplc_runtime.stop_runtime() time.sleep(1) + monitor.stop_monitor() return flask.redirect(flask.url_for('dashboard')) @@ -484,6 +489,7 @@ def dashboard(): if (flask_login.current_user.is_authenticated == False): return flask.redirect(flask.url_for('login')) else: + monitor.stop_monitor() if (openplc_runtime.status() == "Compiling"): return draw_compiling_page() return_str = pages.w3_style + pages.dashboard_head + draw_top_div() return_str += """ @@ -529,6 +535,7 @@ def programs(): if (flask_login.current_user.is_authenticated == False): return flask.redirect(flask.url_for('login')) else: + monitor.stop_monitor() if (openplc_runtime.status() == "Compiling"): return draw_compiling_page() list_all = False if (flask.request.args.get('list_all') == '1'): @@ -847,6 +854,7 @@ def modbus(): if (flask_login.current_user.is_authenticated == False): return flask.redirect(flask.url_for('login')) else: + monitor.stop_monitor() if (openplc_runtime.status() == "Compiling"): return draw_compiling_page() return_str = pages.w3_style + pages.style + draw_top_div() return_str += """ @@ -1265,15 +1273,101 @@ def monitoring(): return flask.redirect(flask.url_for('login')) else: if (openplc_runtime.status() == "Compiling"): return draw_compiling_page() - return_str = draw_blank_page() + return_str = pages.w3_style + pages.monitoring_head + draw_top_div() return_str += """ +
+
+
+
+ Dashboard

Dashboard

+ Programs

Programs

+ Modbus

Slave Devices

+ Monitoring

Monitoring

+ Hardware

Hardware

+ Users

Users

+ Settings

Settings

+ Logout

Logout

+
+
""" + return_str += draw_status() + return_str += """ +
+
+
+

Monitoring

-

This feature is not available yet! Check back later...

+

The table below displays a list of the OpenPLC points used by the currently running program. By clicking in one of the listed points it is possible to see more information about it and also to force it to be a different value.

+
+ + + + + """ + + if (openplc_runtime.status() == "Running"): + monitor.start_monitor() + data_index = 0 + for debug_data in monitor.debug_vars: + return_str += '' + return_str += '' + else: + return_str += 'bool_trueTRUE' + else: + percentage = (debug_data.value*100)/65535 + return_str += '

' + str(debug_data.value) + '

' + return_str += '' + data_index += 1 + return_str += pages.monitoring_tail + + else: + return_str += """ +
Point NameTypeLocationForcedValue
' + debug_data.name + '' + debug_data.type + '' + debug_data.location + '' + debug_data.forced + '' + if (debug_data.type == 'BOOL'): + if (debug_data.value == 0): + return_str += 'bool_falseFALSE
+
""" + + return return_str + +@app.route('/monitor-update', methods=['GET', 'POST']) +def monitor_update(): + if (flask_login.current_user.is_authenticated == False): + return flask.redirect(flask.url_for('login')) + else: + #if (openplc_runtime.status() == "Compiling"): return 'OpenPLC is compiling new code. Please wait' + return_str = """ + + + + + """ + + #if (openplc_runtime.status() == "Running"): + if (True): + monitor.start_monitor() + data_index = 0 + for debug_data in monitor.debug_vars: + return_str += '' + return_str += '' + else: + return_str += 'bool_trueTRUE' + else: + percentage = (debug_data.value*100)/65535 + return_str += '

' + str(debug_data.value) + '

' + return_str += '' + data_index += 1 + + return_str += """ +
Point NameTypeLocationForcedValue
' + debug_data.name + '' + debug_data.type + '' + debug_data.location + '' + debug_data.forced + '' + if (debug_data.type == 'BOOL'): + if (debug_data.value == 0): + return_str += 'bool_falseFALSE
""" + return return_str @@ -1282,6 +1376,7 @@ def hardware(): if (flask_login.current_user.is_authenticated == False): return flask.redirect(flask.url_for('login')) else: + monitor.stop_monitor() if (openplc_runtime.status() == "Compiling"): return draw_compiling_page() if (flask.request.method == 'GET'): with open('./scripts/openplc_driver') as f: current_driver = f.read().rstrip() @@ -1365,6 +1460,7 @@ def users(): if (flask_login.current_user.is_authenticated == False): return flask.redirect(flask.url_for('login')) else: + monitor.stop_monitor() if (openplc_runtime.status() == "Compiling"): return draw_compiling_page() return_str = pages.w3_style + pages.style + draw_top_div() return_str += """ @@ -1656,6 +1752,7 @@ def settings(): if (flask_login.current_user.is_authenticated == False): return flask.redirect(flask.url_for('login')) else: + monitor.stop_monitor() if (openplc_runtime.status() == "Compiling"): return draw_compiling_page() if (flask.request.method == 'GET'): return_str = pages.w3_style + pages.settings_style + draw_top_div() + pages.settings_head @@ -1866,8 +1963,12 @@ def settings(): @app.route('/logout') def logout(): - flask_login.logout_user() - return flask.redirect(flask.url_for('login')) + if (flask_login.current_user.is_authenticated == False): + return flask.redirect(flask.url_for('login')) + else: + monitor.stop_monitor() + flask_login.logout_user() + return flask.redirect(flask.url_for('login')) @login_manager.unauthorized_handler