def user_connect(): """user_connect The route to the user connect page of the flask application Renders the user connect page of the flask app. This page allows the user to view the status of the pacemaker in different pacing modes. Pacing modes can be changed via a selection box at the top of the page. NOTE: This page will correctly change between different pacing mode states, though these states and their corresponding rendered templates are black as no serial communication with the pacemaker has been implemented. As a result changing modes will have little visible effect on the rendered content of the application. :return: The render template used by the user connect page, in this case the 'user_connect.html' template :rtype: :class:`flask.render_tmeplate` """ #sets the default pacemaker mode to 'None', forcing the user to select a mode manually #This is an attempt to avoid the user programming the pacemaker in a mode they did not intend mode = user.get_pacemaker_mode() global this_port if request.method == 'POST': if 'Program' in request.form: print('PROGRAM REQUEST ------ PROGRAM REQUEST') this_port = request.form['Comm Port'] # programmable_parameters = [] # for i in list(user.get_pacemaker_parameters().values()): # if i == None or i == 'None': # programmable_parameters.append(0) # else: # programmable_parameters.append(float(i)) result = sendSerial(user.get_pacemaker_mode(), user.get_pacemaker_parameters(), request.form['Comm Port']) if result == False: flash('"None" values are not allowed!') elif 'Pacing Mode' in request.form: #print(request.form['Pacing Mode']) #Flashes the new pacing mode to the user to let them know the applications state has changed flash('Pacing Mode Changed To {0}'.format(request.form['Pacing Mode'])) #changes the pacing mode (i.e. the application state) mode = request.form['Pacing Mode'] user.update_pacemaker_mode(mode) else: values = dict(request.form) mode = values.pop('Mode', None) #create a new parameters dictionary by starting with the original dictionary, and updating its values with the modified value from the post request required_values = Config.getInstance().get('Parameters', 'parameters.mode.' + str(mode)).split(',') for value in required_values: if value not in values: values[value] = -1 updates = {j: {k.replace(' ', ''): v for k, v in values.items() if v}.get(j, user.get_pacemaker_parameters()[j]) for j in user.get_pacemaker_parameters()} #updates all the pacemaker parameters with the newly generated list Logger.getInstance().log('DEBUG', 'Pacemaker Parameters Updated: {0}'.format(updates)) user.update_all_pacemaker_parameters(updates.values()) if mode == None: parameters = {} else: parameters = { re.sub(r"(\w)([A-Z])", r"\1 \2", k): user.get_pacemaker_parameters()[k] for k in [ value.replace(' ', '') for value in Config.getInstance().get('Parameters', 'parameters.mode.' + str(mode)).split(',') ] } modes = Config.getInstance().get('Parameters', 'parameters.modes').split(',') ports = serial.tools.list_ports.comports() port_list = [] for port, desc, hwid in sorted(ports): port_list.append(port) return render_template('user_connect.html', port=this_port, ports=port_list, mode=mode, modes=modes, parameters=parameters, limits=user.get_limits())
def open_browser(): """open_browser Opens the flask app automatically in the web brower. So the user doesn't have to memorize the url and port the application is being hosted on. """ webbrowser.open_new('http://' + Config.getInstance().get('HostSelection', 'host.address') + ':' + Config.getInstance().get('HostSelection', 'host.port') + "/")
def __init__(self): """Constructor Method """ self.__parameters = { i.replace(' ', '') : None for i in Config.getInstance().get('Parameters', 'parameters.values').split(',') } self.__num_parameters = len(self.__parameters) self.__mode = None self.__id = '' self.__username = '' self.__password = '' self.__conn, self.__cursor = init_db(Config.getInstance().get('Database', 'db.local-uri')) self.__set_limits()
def update_pacemaker_parameters(conn, cursor, id, values): """update_pacemaker_parameters Given a list of pacemaker parameters, updates the database values When given handler to the database and the unique ID of the user being affected, will update the users pacemaker parameters to match the input list. NOTE: No complete check is done to ensure the validity of the input, it is up to the method user to ensure the lists correctness. :param conn: The connection handler for the database whos contents to change :type conn: :class:`sqlite3.Connection` :param cursor: The cursor handler for the database whos contents to change :type cursor: :class:`sqlite3.Cursor` :param id: The unique ID of the user whos parameters should be changed :type id: int :param values: A list of pacemaker parameters, whos order matches the databases contents :type values: list """ cursor.execute(""" --begin-sql SELECT COUNT(*) FROM {0}; """.format('user_' + str(id))) num_entries = cursor.fetchall()[0][0] parameter_list = [ i.replace(' ', '') for i in Config.getInstance().get( 'Parameters', 'parameters.values').split(',') ] insert_query = 'SELECT ?, mode' create_query = 'INSERT INTO user_{0} (timestamp, mode'.format(id) update_query = "UPDATE user_{0} SET mode = '{1}'".format(id, values[0]) for index, parameter in enumerate(parameter_list, start=1): insert_query += ', {0}'.format(parameter) create_query += ', {0}'.format(parameter) update_query += ", {0} = '{1}'".format(parameter, values[index]) insert_query = create_query + ') ' + insert_query + ' FROM user_{0} WHERE timestamp = ?;'.format( id) create_query += ') VALUES(' + '?' + (len(values)) * ',?' + ');' update_query += ' WHERE timestamp = ?;' if num_entries == 0: cursor.execute(create_query, ['current'] + values) conn.commit() cursor.execute(update_query, ['current']) conn.commit() timestamp = datetime.datetime.now().strftime(Config.getInstance().get( 'Database', 'db.timestamp')) cursor.execute(insert_query, [timestamp, 'current']) conn.commit()
def user_parameters(): """user_parameters The route to the user parameters page of the flask application Renders the user parameters page of the flask app. This page allows the user to view and modify their pacemaker parameters through a submittable form. Users can modify some or all parameters and submit the changes through a post request. Changes will be made immediately in the flask app and database, and the user parameters page updated with these changed values. :return: The render template used by the user parameters page, in this case the 'user_parameters.html' template :rtype: :class:`flask.render_tmeplate` """ #if a post request is made, a user is trying to change their parameters if request.method == 'POST': values = dict(request.form) required_values = Config.getInstance().get('Parameters', 'parameters.values').split(',') for value in required_values: if value not in values: values[value] = -1 #create a new parameters dictionary by starting with the original dictionary, and updating its values with the modified value from the post request updates = {j: {k.replace(' ', ''): v for k, v in values.items() if v}.get(j, user.get_pacemaker_parameters()[j]) for j in user.get_pacemaker_parameters()} #updates all the pacemaker parameters with the newly generated list Logger.getInstance().log('DEBUG', 'Pacemaker Parameters Updated: {0}'.format(updates)) user.update_all_pacemaker_parameters(updates.values()) parameters = { re.sub(r"(\w)([A-Z])", r"\1 \2", k): user.get_pacemaker_parameters()[k] for k in user.get_pacemaker_parameters() } return render_template('user_parameters.html', username=user.get_username(), parameters=parameters, limits=user.get_limits())
def __set_limits(self): """__set_limits Sets the limits of user parameters A private function called by the contructor upon user creation. Will search the application configuration and implement any parameter limits defined there. Limits are enforced in the entry form on the DCM, not the database to ensure a more robust DCM. """ raw_limits = dict(Config.getInstance().get_all('Limits')) limits = {} for key in self.__parameters.keys(): query = 'limits.' + re.sub(r"(\w)([A-Z])", r"\1 \2", key).lower() disable_query = re.sub(r"(\w)([.])", r"\1.can-disable\2", query) limit_entry = {} should_add_entry = False if query in raw_limits.keys(): limit_entry['min'] = float(raw_limits[query].split(',')[0]) limit_entry['max'] = float(raw_limits[query].split(',')[1]) limit_entry['inc'] = float(raw_limits[query].split(',')[2]) should_add_entry = True if disable_query in raw_limits.keys(): limit_entry['can_disable'] = bool(raw_limits[disable_query]) should_add_entry = True if should_add_entry: limits[re.sub(r"(\w)([A-Z])", r"\1 \2", key)] = limit_entry self.__limits = limits
def user_egram(): """user_egram The route to the user history page of the flask application Renders the user egram page of the flask app. This page allows the user to view a live graph (or egram) of the pacemakers atrium and ventrical chambers. NOTE: The graph displayed is an adaptable graph and will take up to one minute to adjust both the graphs domain and range values. :return: The render template used by the user connect page, in this case the 'user_egram.html' template :rtype: :class:`flask.render_tmeplate` """ if request.method == 'POST': publish_data(user.get_username()) set_start_time() domain = int(Config.getInstance().get('Graphing', 'graph.domain')) period = int(Config.getInstance().get('Graphing', 'graph.update-period')) return render_template('user_egram.html', domain=domain, period=period)
def create_history_file(self): """create_history_file Creates a csv file containing the users parameter history """ history = self.get_history() data_file = open(os.path.join(parentfolder, 'downloads', str(self.__username) + '-User-History-' + str(date.today()) + '.csv' ), 'w') csv_writer = csv.writer(data_file) test_headers = ['Date', 'Mode'] + Config.getInstance().get('Parameters', 'parameters.values').split(',') csv_writer.writerow(test_headers) for entry in history: csv_writer.writerow(entry[1:]) data_file.close()
def publish_data(username): """publish_data Publishes the current (or most recent) live graphs data to a csv file Will generate a csv file containing all the data from when the current (or most recent) graph was started. """ x1 = [] x2 = [] y1 = [] y2 = [] for point_set in __data: x1.append(point_set[0][0]) y1.append(point_set[0][1]) x2.append(point_set[1][0]) y2.append(point_set[1][1]) figure = plt.figure() plt.plot(x1, y1, label='Atrium') plt.plot(x2, y2, label='Ventrical') plt.xlabel('Time (ms)') plt.ylabel('Voltage (V)') plt.title("'{0}' Live Egram Data".format(username)) plt.legend() timestamp = datetime.datetime.now().strftime(Config.getInstance().get( 'Database', 'db.timestamp')).replace(' ', '_').replace('/', '-').replace(':', '-') graph_doc_name = "{0}_Live_Egram_Data_From_{1}.pdf".format( username, timestamp) pp = PdfPages(os.path.join(parentfolder, 'downloads', graph_doc_name)) pp.savefig(figure) pp.close() csv_output = list(zip(x1, y1, x2, y2)) csv_doc_name = "{0}_Live_Egram_Data_From_{1}.csv".format( username, timestamp) with open(os.path.join(parentfolder, 'downloads', csv_doc_name), 'w') as file: writer = csv.writer(file) writer.writerow([ 'Atrium Timestamp', 'Atrium Value', 'Ventrical Timestamp', 'Ventrical Value' ]) for line in csv_output: writer.writerow(line)
def update_pacemaker_mode(self, mode): """update_pacemaker_mode Changes the pacemaker mode Given a mode that is contained in the application configurations list of allowed modes, will change the current pacemaker mode to the new one. If no valid mode is given will do nothing and return False. :param mode: The pacemaker mode to change to :type mode: str :return: True if the pacemaker mode was successfully changed, Fale otherwise :rtype: bool """ allowed_modes = Config.getInstance().get('Parameters', 'parameters.modes').split(',') if not mode in allowed_modes: return False self.__mode = mode update_pacemaker_parameters(self.__conn, self.__cursor, self.__id, [self.__mode] + list(self.__parameters.values())) return True
def insert_user(conn, cursor, username, password): """insert_user Given a username and password of a new user, will insert the user into the database This function will create a new entry in the database of a user with the given username and password. Only the users username and password are initialized upon user creation, the pacemaker parameters will default to None, forcing the user to manually enter their parameters. NOTE: This function does no check for conflicting users in the database before inserting a new user. It is up to the user of this function to check for conflicts (if they wish to do so) before calling this function. :param conn: The connection handler for the database to insert a new user into :type conn: :class:`sqlite3.Connection` :param cursor: The cursor handler for the database to insert a new user into :type cursor: :class:`sqlite3.Cursor` :param username: The username for the new user :type username: str :param password: The password for the new user :type password: str """ cursor.execute('INSERT INTO users (username, password) VALUES(?,?)', [username, password]) conn.commit() user_id = find_user(cursor, username=username, password=password)[0][0] query_string = 'CREATE TABLE IF NOT EXISTS user_{0}( _entryid INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, mode TEXT'.format( str(user_id)) parameter_list = [ i.replace(' ', '') for i in Config.getInstance().get( 'Parameters', 'parameters.values').split(',') ] for parameter in parameter_list: query_string += ', {0} INTEGER'.format(parameter) query_string += ');' cursor.execute(query_string) conn.commit()
def get_user_parameters(cursor, id): """get_user_parameters Returns a complete list of the users most recent parameters Return type is a list of parameters. Since the search is done by unique ID, this list is garunteed to be of constant length, defined by the parameter list in the application configuration. :param cursor: The cursor handler for the database the user can be found in :type cursor: :class:`sqlite3.Cursor` :param id: The unique ID of the user to search for :type id: int :return: A list of the users current pacemaker parameters :rtype: list """ cursor.execute(""" --begin-sql SELECT * FROM {0}; """.format('user_' + str(id))) result = cursor.fetchall() if len(result) > 0: return result[0] return [-1, 'current', None] + [ None for i in Config.getInstance().get('Parameters', 'parameters.values').split(',') ]
from threading import Timer from time import time from random import random import webbrowser, sqlite3, re, json import serial.tools.list_ports from config.config_manager import Config, Logger from config.decorators import login_required, logout_required from data.user import User from data.database import init_db from graphs.graphing import update_data, publish_data, set_start_time from serialcom.sendSerial import sendSerial #initialize the config and logger before createing the app Config() if not Config.getInstance().is_config_read(): Config.getInstance().read_config() Logger() if not Logger.getInstance().is_logger_started(): Logger.getInstance().start_logger(Config.getInstance()) #create the user user = User() #create the flask app, passing this file as the main, and a random string as the secret key app = Flask(__name__) app.secret_key = Config.getInstance().get('Applictation', 'app.secret-key') global this_port this_port = None