diff --git a/raspberry-backend/arduino_recorder.py b/raspberry-backend/arduino_recorder.py index 5561c9c9cae0a0aeb19a50feff84de846be4ae33..581c3230714d631cb9dd964dbb3641cb44e89a9b 100755 --- a/raspberry-backend/arduino_recorder.py +++ b/raspberry-backend/arduino_recorder.py @@ -9,20 +9,19 @@ import sys import time import argparse import sqlite3 -from random import random from datetime import datetime import threading from hevclient import HEVClient - +from commsConstants import dataFormat SQLITE_FILE = 'database/HEC_monitoringDB.sqlite' # name of the sqlite database file TABLE_NAME = 'hec_monitor' # name of the table to be created -def get_temperature(): - """ - Returns a random number to simulate data obtained from a sensor - """ - return random() * 20 +def getList(dict): + return [*dict] + +# List of data variables in the data packet from the Arduino +data_format = getList(dataFormat().getDict()) def database_setup(): @@ -36,28 +35,19 @@ def database_setup(): try: # Connecting to the database file conn = sqlite3.connect(SQLITE_FILE) - conn.execute('''CREATE TABLE IF NOT EXISTS ''' + TABLE_NAME + ''' ( - created_at INTEGER NOT NULL, - alarms STRING NOT NULL, - version FLOAT NOT NULL, - fsm_state FLOAT NOT NULL, - pressure_air_supply FLOAT NOT NULL, - pressure_air_regulated FLOAT NOT NULL, - pressure_o2_supply FLOAT NOT NULL, - pressure_o2_regulated FLOAT NOT NULL, - pressure_buffer FLOAT NOT NULL, - pressure_inhale FLOAT NOT NULL, - pressure_patient FLOAT NOT NULL, - temperature_buffer FLOAT NOT NULL, - pressure_diff_patient FLOAT NOT NULL, - readback_valve_air_in FLOAT NOT NULL, - readback_valve_o2_in FLOAT NOT NULL, - readback_valve_inhale FLOAT NOT NULL, - readback_valve_exhale FLOAT NOT NULL, - readback_valve_purge FLOAT NOT NULL, - readback_mode FLOAT NOT NULL - );''' - ) + + exec_string = "created_at INTEGER NOT NULL, " + for var in data_format: + exec_string += var + " FLOAT NOT NULL, " + exec_string += "alarms STRING NOT NULL " + + # Setting the maximum size of the DB to 100 MB + conn.execute("PRAGMA max_page_count = 204800") + conn.execute("PRAGMA page_size = 512") + + conn.execute('''CREATE TABLE IF NOT EXISTS {tn} ({ex_str});''' + .format(tn=TABLE_NAME, ex_str=exec_string)) + conn.commit() conn.close() except sqlite3.Error as err: @@ -72,7 +62,6 @@ def monitoring(source_address): # Instantiating the client hevclient = HEVClient() - print(hevclient.send_cmd("CMD_START")) epoch = datetime(1970, 1, 1) @@ -94,46 +83,26 @@ def monitoring(source_address): data_alarms = ','.join(data_alarms) else: data_alarms = "none" - - - data_packet = { - 'time' : timestamp, - 'alarms' : data_alarms, - 'version': data_receiver["version"], - 'fsm_state': data_receiver["fsm_state"], - 'pressure_air_supply': data_receiver["pressure_air_supply"], - 'pressure_air_regulated': data_receiver["pressure_air_regulated"], - 'pressure_o2_supply': data_receiver["pressure_o2_supply"], - 'pressure_o2_regulated': data_receiver["pressure_o2_regulated"], - 'pressure_buffer': data_receiver["pressure_buffer"], - 'pressure_inhale': data_receiver["pressure_inhale"], - 'pressure_patient': data_receiver["pressure_patient"], - 'temperature_buffer': data_receiver["temperature_buffer"], - 'pressure_diff_patient': data_receiver["pressure_diff_patient"], - 'readback_valve_air_in': data_receiver["readback_valve_air_in"], - 'readback_valve_o2_in': data_receiver["readback_valve_o2_in"], - 'readback_valve_inhale': data_receiver["readback_valve_inhale"], - 'readback_valve_exhale': data_receiver["readback_valve_exhale"], - 'readback_valve_purge': data_receiver["readback_valve_purge"], - 'readback_mode': data_receiver["readback_mode"] - } + + data_packet = { el : data_receiver[el] for el in data_format} + data_packet.update({"time" : timestamp}) + data_packet.update({"alarms" : data_alarms}) print("Writing to database ...") try: + exec_string = "( :time, " + for el in data_format: + exec_string += ":" + el + ", " + exec_string += ":alarms) " + cursor.execute( - 'INSERT INTO {tn} VALUES ' - '(:time, :alarms, :version, ' - ':fsm_state, :pressure_air_supply, ' - ':pressure_air_regulated, :pressure_o2_supply,' - ':pressure_o2_regulated, :pressure_buffer,' - ':pressure_inhale, :pressure_patient,' - ':temperature_buffer, :pressure_diff_patient,' - ':readback_valve_air_in, :readback_valve_o2_in,' - ':readback_valve_inhale, :readback_valve_exhale,' - ':readback_valve_purge, :readback_mode)' - .format(tn=TABLE_NAME), data_packet + 'INSERT INTO {tn} VALUES {ex_str} ' + .format(tn=TABLE_NAME, ex_str=exec_string), data_packet ) + conn.commit() + + except sqlite3.Error as err: raise Exception("sqlite3 error. Insert into database failed: {}".format(str(err))) finally: diff --git a/raspberry-backend/arduino_recorder_V2.py b/raspberry-backend/arduino_recorder_V2.py deleted file mode 100755 index 5a0785d99ba4486007262e51fa856d5acbb89394..0000000000000000000000000000000000000000 --- a/raspberry-backend/arduino_recorder_V2.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env python3 - -# HEV monitoring application -# USAGE: python3 arduino_recorder.py -# -# Last update: April 4, 2020 - -import sys -import time -import argparse -import sqlite3 -from random import random -from datetime import datetime -import threading -from hevclient import HEVClient -from commsConstants import dataFormat - -SQLITE_FILE = 'database/HEC_monitoringDB.sqlite' # name of the sqlite database file -TABLE_NAME = 'hec_monitor' # name of the table to be created - -def getList(dict): - return [*dict] - -data_format = getList(dataFormat().getDict()) -print(data_format) -print(len(data_format)) - -def get_temperature(): - """ - Returns a random number to simulate data obtained from a sensor - """ - return random() * 20 - - -def database_setup(): - ''' - This function creates the sqlite3 table with the timestamp column - and the columns for the arduino packet data - ''' - print('Creating ' + TABLE_NAME + ' table..' ) - - # Create the table if it does not exist - try: - # Connecting to the database file - conn = sqlite3.connect(SQLITE_FILE) - conn.execute('''CREATE TABLE IF NOT EXISTS ''' + TABLE_NAME + ''' ( - created_at INTEGER NOT NULL, - alarms STRING NOT NULL, - {} FLOAT NOT NULL - );'''.format(*( _ for _ in data_format)) - ) - conn.commit() - conn.close() - except sqlite3.Error as err: - raise Exception("sqlite3 Error. Create failed: {}".format(str(err))) - finally: - print('Table ' + TABLE_NAME + ' created successfully!') - -def monitoring(source_address): - ''' - Store arduino data in the sqlite3 table. - ''' - - # Instantiating the client - hevclient = HEVClient() - - epoch = datetime(1970, 1, 1) - - with sqlite3.connect(SQLITE_FILE) as conn: - cursor = conn.cursor() - while True: - current_time = datetime.now() - - # Computing the time in seconds since the epoch because easier to manipulate. - timestamp = (current_time -epoch).total_seconds() * 1000 - - data_receiver = hevclient.get_values() - data_alarms = hevclient.get_alarms() - - if data_receiver != None: - - # data alarms can have length of 6, joining all the strings - if data_alarms != None: - data_alarms = ','.join(data_alarms) - else: - data_alarms = "none" - - - data_packet = { - 'time' : timestamp, - 'alarms' : data_alarms, - 'version': data_receiver["version"], - 'fsm_state': data_receiver["fsm_state"], - 'pressure_air_supply': data_receiver["pressure_air_supply"], - 'pressure_air_regulated': data_receiver["pressure_air_regulated"], - 'pressure_o2_supply': data_receiver["pressure_o2_supply"], - 'pressure_o2_regulated': data_receiver["pressure_o2_regulated"], - 'pressure_buffer': data_receiver["pressure_buffer"], - 'pressure_inhale': data_receiver["pressure_inhale"], - 'pressure_patient': data_receiver["pressure_patient"], - 'temperature_buffer': data_receiver["temperature_buffer"], - 'pressure_diff_patient': data_receiver["pressure_diff_patient"], - 'readback_valve_air_in': data_receiver["readback_valve_air_in"], - 'readback_valve_o2_in': data_receiver["readback_valve_o2_in"], - 'readback_valve_inhale': data_receiver["readback_valve_inhale"], - 'readback_valve_exhale': data_receiver["readback_valve_exhale"], - 'readback_valve_purge': data_receiver["readback_valve_purge"], - 'readback_mode': data_receiver["readback_mode"] - } - - print("Writing to database ...") - try: - cursor.execute( - 'INSERT INTO hec_monitor VALUES ' - '( :time, :alarms, :{var} ' - .format(var=( _ for _ in data_format)), data_packet - ) - conn.commit() - except sqlite3.Error as err: - raise Exception("sqlite3 error. Insert into database failed: {}".format(str(err))) - finally: - sys.stdout.flush() - time.sleep(1) - -def progress(status, remaining, total): - print(f'Copied {total-remaining} of {total} pages...') - - -def db_backup(backup_time): - threading.Timer(backup_time, db_backup, [backup_time]).start() - print("Executing DB backup") - try: - # Existing DB - sqliteCon = sqlite3.connect(SQLITE_FILE) - # Backup DB - backupCon = sqlite3.connect("database/HEC_monitoringDB_backup.sqlite") - with backupCon: - sqliteCon.backup(backupCon, pages=5, progress=progress) - print("Backup successful") - except sqlite3.Error as err: - raise Exception("sqlite3 error. Error during backup: {}".format(str(err))) - finally: - if(backupCon): - backupCon.close() - sqliteCon.close() - - -def parse_args(): - parser = argparse.ArgumentParser(description='Python script monitorign Arduino data') - parser.add_argument('--source', default='ttys0 or similar') - parser.add_argument('--backup_time', type=int, default=600) - return parser.parse_args() - -if __name__ == "__main__": - ARGS = parse_args() - database_setup() - db_backup(ARGS.backup_time) - monitoring(ARGS.source) diff --git a/raspberry-backend/database/HEC_monitoringDB.sqlite b/raspberry-backend/database/HEC_monitoringDB.sqlite index ab59e60190dbc5286c1a6eba31873099f3d398d8..471743d7857451fdc86b9211851205d5c9177aa2 100644 Binary files a/raspberry-backend/database/HEC_monitoringDB.sqlite and b/raspberry-backend/database/HEC_monitoringDB.sqlite differ diff --git a/raspberry-backend/database/HEC_monitoringDB_backup.sqlite b/raspberry-backend/database/HEC_monitoringDB_backup.sqlite index 881b4b95be98e12b766f771bae3350abdc8d1f64..76867c5ad0eab29c4905a870c3ac969fd457d369 100644 Binary files a/raspberry-backend/database/HEC_monitoringDB_backup.sqlite and b/raspberry-backend/database/HEC_monitoringDB_backup.sqlite differ diff --git a/raspberry-backend/static/js/Chart-display.js b/raspberry-backend/static/js/Chart-display.js new file mode 100644 index 0000000000000000000000000000000000000000..d2886195c001ad61dafd947fb8c6dc94797efbe7 --- /dev/null +++ b/raspberry-backend/static/js/Chart-display.js @@ -0,0 +1,188 @@ +var chart_pressure; +var chart_flow; +var chart_volume; +var time_value = 0 ; + +/** + * Request data from the server, add it to the graph and set a timeout + * to request again + */ +function requestChartVar() { + $.ajax({ + url: '/live-data', + success: function(point) { + + if(chart_pressure.data.datasets[0].data.length > 30){ + chart_pressure.data.labels.shift(); + chart_pressure.data.datasets[0].data.shift(); + } + if(chart_flow.data.datasets[0].data.length > 30){ + chart_flow.data.labels.shift(); + chart_flow.data.datasets[0].data.shift(); + } + if(chart_volume.data.datasets[0].data.length > 30){ + chart_volume.data.labels.shift(); + chart_volume.data.datasets[0].data.shift(); + } + + + // Convert epoch timestamp into seconds + var date = new Date(point.created_at); + var seconds = date.getSeconds(); + + // Show the time that has passed + chart_pressure.data.labels.push(-(60-seconds)); + chart_pressure.data.datasets[0].data.push(point["pressure_buffer"]); + + // add the point + chart_flow.data.labels.push(point.created_at); + chart_flow.data.datasets[0].data.push(point["pressure_inhale"]); + + // add the point + chart_volume.data.labels.push(point.created_at); + chart_volume.data.datasets[0].data.push(point["temperature_buffer"]); + + chart_pressure.update(); + chart_flow.update(); + chart_volume.update(); + }, + cache: false + }); + // call it again after one second + setTimeout(requestChartVar, 1000); +} + + + +$(document).ready(function() { + var ctx_pressure = document.getElementById('pressure_chart'); + chart_pressure = new Chart(ctx_pressure, { + type: 'line', + data: { + labels: [], + datasets: [{ + data: [], + label: "Var1", + borderColor: "#3e95cd", + fill: false + } + ] + }, + options: { + responsive: true, + title: { + display: false, + text: 'Pressure [mbar]' + }, + scales: { + xAxes: [{ + ticks: { + beginAtZero: true + }, + //type: 'time', + time: { + unit: 'second', + displayFormat: 'second' + } + }], + yAxes: [{ + ticks: { + beginAtZero: true, + suggestedMax: 25 + }, + scaleLabel: { + display: true, + labelString: 'Pressure [mbar]' + } + }] + }, + legend : { + display: false} + }, + plugins: { + streaming: { + duration: 20000, + refresh: 1000, + delay: 2000, + onRefresh: requestChartVar() + } + } + }); +}); + + + + +$(document).ready(function() { + var ctx_flow = document.getElementById('flow_chart'); + chart_flow = new Chart(ctx_flow, { + type: 'line', + data: { + labels: [], + datasets: [{ + data: [], + label: "Var1", + borderColor: "#3e95cd", + fill: false + } + ] + }, + options: { + title: { + display: false, + text: 'Variable 1' + }, + scales: { + xAxes: [{ + type: 'time', + time: { + unit: 'second', + displayFormat: 'second' + } + }] + }, + legend : { + display: false} + } + }); + //requestChartVar("pressure_inhale"); +}); + + + +$(document).ready(function() { + var ctx_volume = document.getElementById('volume_chart'); + chart_volume = new Chart(ctx_volume, { + type: 'line', + data: { + labels: [], + datasets: [{ + data: [], + label: "Var1", + borderColor: "#3e95cd", + fill: false + } + ] + }, + options: { + title: { + display: false, + text: 'Variable 1' + }, + scales: { + xAxes: [{ + type: 'time', + time: { + unit: 'second', + displayFormat: 'second' + } + }] + }, + legend : { + display: false} + } + }); + //requestChartVar("temperature_buffer"); +}); + + diff --git a/raspberry-backend/static/js/Chart-display_BACKUP.js b/raspberry-backend/static/js/Chart-display_BACKUP.js new file mode 100644 index 0000000000000000000000000000000000000000..d9c7d0ee5d289a59e0a323be1ca746ff3ad08586 --- /dev/null +++ b/raspberry-backend/static/js/Chart-display_BACKUP.js @@ -0,0 +1,200 @@ +var chart_pressure; +var chart_flow; +var chart_volume; +var time_value = 0 ; + +/** + * Request data from the server, add it to the graph and set a timeout + * to request again + */ +function requestChartVar1() { + $.ajax({ + url: '/live-data', + success: function(point) { + + if(chart_pressure.data.datasets[0].data.length > 10){ + chart_pressure.data.labels.shift(); + chart_pressure.data.datasets[0].data.shift(); + } + + + // add the point + chart_pressure.data.labels.push(point.created_at); + time_value = point.created_at; + console.log(time_value) + chart_pressure.data.datasets[0].data.push(point[var1]); + + // add the point + chart_flow.data.labels.push(point.created_at); + chart_flow.data.datasets[0].data.push(point[var1]); + + + chart_pressure.update(); + }, + cache: false + }); + // call it again after one second + setTimeout(requestChartVar1, 1000); +} + + + +$(document).ready(function() { + var ctx_pressure = document.getElementById('pressure_chart'); + chart_pressure = new Chart(ctx_pressure, { + type: 'line', + data: { + labels: [], + datasets: [{ + data: [], + label: "Var1", + borderColor: "#3e95cd", + fill: false + } + ] + }, + options: { + title: { + display: false, + text: 'Variable 1' + }, + scales: { + xAxes: [{ + time: { + unit: 'second', + displayFormat: 'second' + } + }] + }, + legend : { + display: false} + } + }); + requestChartVar1("pressure_buffer"); +}); + + +function requestChartVar2(var1) { + $.ajax({ + url: '/live-data', + success: function(point) { + + if(chart_flow.data.datasets[0].data.length > 10){ + chart_flow.data.labels.shift(); + chart_flow.data.datasets[0].data.shift(); + } + + + // add the point + chart_flow.data.labels.push(point.created_at); + chart_flow.data.datasets[0].data.push(point[var1]); + + + chart_flow.update(); + }, + cache: false + }); + // call it again after one second + setTimeout(requestChartVar2, 1000, var1); +} + + + +$(document).ready(function() { + var ctx_flow = document.getElementById('flow_chart'); + chart_flow = new Chart(ctx_flow, { + type: 'line', + data: { + labels: [], + datasets: [{ + data: [], + label: "Var1", + borderColor: "#3e95cd", + fill: false + } + ] + }, + options: { + title: { + display: false, + text: 'Variable 1' + }, + scales: { + xAxes: [{ + type: 'time', + time: { + unit: 'second', + displayFormat: 'second' + } + }] + }, + legend : { + display: false} + } + }); + requestChartVar2("pressure_inhale"); +}); + + +function requestChartVar3(var1) { + $.ajax({ + url: '/live-data', + success: function(point) { + + if(chart_volume.data.datasets[0].data.length > 10){ + chart_volume.data.labels.shift(); + chart_volume.data.datasets[0].data.shift(); + } + + + // add the point + chart_volume.data.labels.push(point.created_at); + chart_volume.data.datasets[0].data.push(point[var1]); + + + chart_volume.update(); + }, + cache: false + }); + // call it again after one second + setTimeout(requestChartVar3, 1000, var1); +} + + + +$(document).ready(function() { + var ctx_volume = document.getElementById('volume_chart'); + chart_volume = new Chart(ctx_volume, { + type: 'line', + data: { + labels: [], + datasets: [{ + data: [], + label: "Var1", + borderColor: "#3e95cd", + fill: false + } + ] + }, + options: { + title: { + display: false, + text: 'Variable 1' + }, + scales: { + xAxes: [{ + type: 'time', + time: { + unit: 'second', + displayFormat: 'second' + } + }] + }, + legend : { + display: false} + } + }); + requestChartVar3("temperature_buffer"); +}); + + diff --git a/raspberry-backend/templates/base.html b/raspberry-backend/templates/base.html index ae49e7b0b42dd2d976c0178de4f30b772871d944..1c5c5fe38818349ff7ec8c0497c8aa722a9f592b 100644 --- a/raspberry-backend/templates/base.html +++ b/raspberry-backend/templates/base.html @@ -73,6 +73,7 @@ <script src="{{ url_for('static', filename='js/moment.js') }}"></script> <script src="{{ url_for('static', filename='js/Chart.js') }}"></script> <script src="{{ url_for('static', filename='js/Chart-plot.js') }}"></script> + <script src="{{ url_for('static', filename='js/Chart-display.js') }}"></script> <script src="{{ url_for('static', filename='js/jquery.dataTables.min.js') }}"></script> <script src="{{ url_for('static', filename='js/dataTables.bootstrap4.min.js') }}"></script> diff --git a/raspberry-backend/templates/charts.html b/raspberry-backend/templates/charts.html index ef7a80ece90765e156e2adc32a96fe229baaed37..c284ddcb6a1067cc70e6674affbaf3141f4120ec 100644 --- a/raspberry-backend/templates/charts.html +++ b/raspberry-backend/templates/charts.html @@ -11,94 +11,34 @@ <div class = "row"> - <div class = "col-md-7 py-0"> + <div class = "col-md-10 py-0"> <div class="row"> <div class="col-md-12"> <div class="card mb-6"> - <div class="card-chart-header small ">Variable number 1</div> - <div class="card-body px-0 py-0"><canvas id="pressure_air_supply" width="200%" height="40"></canvas></div> + <div class="card-chart-header small ">Pressure [mbar]</div> + <div class="card-body px-0 py-0"><canvas id="pressure_chart" width="200%" height="30"></canvas></div> </div> </div> </div> <div class = "row"> <div class="col-md-12"> <div class="card mb-6 px-0 py-0"> - <div class="card-chart-header small">Variable number 2</div> - <div class="card-body px-0 py-0"><canvas id="variable2" width="200%" height="40"></canvas></div> + <div class="card-chart-header small">Flow [mL/min]</div> + <div class="card-body px-0 py-0"><canvas id="flow_chart" width="200%" height="30"></canvas></div> </div> </div> </div> <div class = "row"> <div class="col-md-12"> <div class="card mb-6 px-0 py-0"> - <div class="card-chart-header small ">Variable number 3</div> - <div class="card-body px-0 py-0"><canvas id="chart_variable3" width="200%" height="40"></canvas></div> + <div class="card-chart-header small ">Volume [mL]</div> + <div class="card-body px-0 py-0"><canvas id="volume_chart" width="200%" height="30"></canvas></div> </div> </div> </div> </div> - <div class = "col-md-5 py-0 px-0 mr-0"> - - <div class="row"> - - <div class="col-xl-5b px-1 py-0 mr-0"> - <div class="card bg-primary text-dark mb-2"> - <div class="card-header d-flex align-items-center justify-content-between"> - <a class="small text-dark stretched-link" href="#">Temperature</a> - </div> - <div class="card-body"><span class = "reading" id="temperature">000.00</span> ℃</div> - </div> - </div> - - <div class="col-xl-5b px-1 py-0 mr-0"> - <div class="card bg-primary text-dark mb-2"> - <div class="card-header d-flex align-items-center justify-content-between"> - <a class="small text-dark stretched-link" href="#">Pressure</a> - </div> - <div class="card-body"><span class = "reading" id = "pressure">000.00</span> Pa</div> - </div> - </div> - </div> - - <div class="row"> - <div class="col-xl-5b col-md-8 px-1 py-0"> - <div class="card bg-primary text-dark mb-2"> - <div class="card-header d-flex align-items-center justify-content-between"> - <a class="small text-dark stretched-link" href="#">variable3</a> - </div> - <div class="card-body"><span class = "reading" id = "variable3">000.00</span> ℃</div> - </div> - </div> - <div class="col-xl-5b px-1 py-0 col-md-8"> - <div class="card bg-primary text-dark mb-2"> - <div class="card-header d-flex align-items-center justify-content-between"> - <a class="small text-dark stretched-link" href="#">variable4</a> - </div> - <div class="card-body"><span class = "reading" id = "variable4">000.00</span> Pa</div> - </div> - </div> - </div> - - <div class="row"> - <div class="col-xl-5b col-md-8 px-1 py-0"> - <div class="card bg-primary text-dark mb-2"> - <div class="card-header d-flex align-items-center justify-content-between"> - <a class="small text-dark stretched-link" href="#">variable5</a> - </div> - <div class="card-body"><span class = "reading" id = "variable5">000.00</span> ℃</div> - </div> - </div> - <div class="col-xl-5b px-1 py-0 col-md-8"> - <div class="card bg-primary text-dark mb-2"> - <div class="card-header d-flex align-items-center justify-content-between"> - <a class="small text-dark stretched-link" href="#">variable6</a> - </div> - <div class="card-body"><span class = "reading" id = "variable6">000.00</span> Pa</div> - </div> - </div> - </div> - + </div> </div> </div>