Skip to content

Commit

Permalink
refactored into model-view-controller
Browse files Browse the repository at this point in the history
  • Loading branch information
kieranabrennan committed Dec 27, 2023
1 parent 7287ac2 commit f9a7bc2
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 169 deletions.
3 changes: 2 additions & 1 deletion BeatTracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def get_beat_count_from_wind(self, start_time, end_time):
self.plot_graph(wind_values, wind_times)

def get_accuracy_score(self, beat_count_entered):
self.accuracyCalcuated.emit(1 - abs(self.beat_count - beat_count_entered)/(0.5*(self.beat_count + beat_count_entered)))
accuracy = 1 - abs(self.beat_count - beat_count_entered)/(0.5*(self.beat_count + beat_count_entered))
self.accuracyCalcuated.emit(accuracy)

def plot_graph(self, wind_values, wind_times):
plt.figure()
Expand Down
150 changes: 56 additions & 94 deletions Controller.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,62 @@
import asyncio
from enum import Enum
import time
from PySide6.QtCore import Qt, Signal, Slot, QTimer, QTime
from PySide6.QtWidgets import QVBoxLayout, QWidget, QPushButton, QLabel, QSpinBox

from Model import Model
from View import View
import vars
class ControlState(Enum):
INITIALISING = 1
READY_TO_START = 2
RECORDING_BEATS = 3
RECORDING_INPUT = 4
FINISHED = 5

class Controller(QWidget):
beatCountingFinished = Signal(float, float)
beatEntered = Signal(int)
class Controller:

def __init__(self):
super().__init__()
self.model = Model()
self.view = View()

self.countdown_timer = CountdownTimer()
self.state = ControlState.INITIALISING
self.view.setWindowTitle("Beat Tracker")
self.view.resize(1200, 600)
self.view.show()

self.model.sensorConnected.connect(self.sensorConnectedHandler)
self.model.beat_tracker.accuracyCalcuated.connect(self.accuracyCalculatedHandler)

self.initUI()

def initUI(self):

controls_layout = QVBoxLayout()
self.setLayout(controls_layout)

self.message_box = QLabel()
self.message_box.setStyleSheet("background-color: yellow; color: black;")
self.message_box.setText("Connecting to sensor...")
self.message_box.setMaximumWidth(800)
self.message_box.setMinimumWidth(500)
self.message_box.setMinimumHeight(60)
self.message_box.setAlignment(Qt.AlignCenter)
controls_layout.addWidget(self.message_box, alignment=Qt.AlignCenter)

self.start_button = QPushButton("Start")
self.start_button.setStyleSheet("background-color: white; color: grey; border: 1px solid grey;")
self.start_button.setMaximumWidth(500)
self.start_button.setMinimumWidth(100)
self.start_button.setMinimumHeight(30)
controls_layout.addWidget(self.start_button, alignment=Qt.AlignCenter)

self.spin_box = QSpinBox()
self.spin_box.setStyleSheet("background-color: white; color: grey; border: 1px solid grey;")
self.spin_box.setRange(0, 100)
self.spin_box.setSingleStep(1)
self.spin_box.setValue(0)
self.spin_box.setMaximumWidth(200)
self.spin_box.setMinimumWidth(100)
self.spin_box.setMinimumHeight(30)
self.spin_box.setAlignment(Qt.AlignCenter)
controls_layout.addWidget(self.spin_box, alignment=Qt.AlignCenter)
self.spin_box.setEnabled(False)
self.state = ControlState.INITIALISING

self.timer_widget = CountdownTimer()
self.timer_widget.timerFinished.connect(self.countdownFinished)
self.start_button.clicked.connect(self.buttonPressed)
controls_layout.addWidget(self.timer_widget)


self.record_start_time = None
self.timer_sensor_connected = QTimer() # Timer to give the sensor time to connect

self.beat_accuracy = None
self.view.controls_widget.start_button.clicked.connect(self.buttonPressed)

self.configureSeriesTimer()

@Slot()
def sensorConnectedHandler(self):
self.setStateReadyAfterDelay()

@Slot(float)
def accuracyCalculatedHandler(self, accuracy):
print(f"Accuracy: {accuracy:.3f}")

@Slot()
def countdownFinished(self):
self.state = ControlState.RECORDING_INPUT
self.model.beat_tracker.get_beat_count_from_wind(self.record_start_time, time.time_ns()/1.0e9)
self.view.control_recording_input()

def configureSeriesTimer(self):
self.update_ecg_series_timer = QTimer()
self.update_ecg_series_timer.timeout.connect(self.updateViewWithModelData)
self.update_ecg_series_timer.setInterval(vars.UPDATE_ECG_SERIES_PERIOD)
self.update_ecg_series_timer.start()

def setStateReadyAfterDelay(self):
if self.state == ControlState.INITIALISING:
Expand All @@ -73,83 +66,51 @@ def setStateReadyAfterDelay(self):

def setStateReady(self):
self.state = ControlState.READY_TO_START
self.message_box.setText("Press start and begin heart beat counting")
self.message_box.setStyleSheet("background-color: green; color: black;")
self.start_button.setStyleSheet("background-color: white; color: black; border: 1px solid black;")
self.view.control_ready_to_start()

@Slot()
def buttonPressed(self):
if self.state == ControlState.INITIALISING:
pass
elif self.state == ControlState.READY_TO_START:
self.timer_widget.startTimer()
self.state = ControlState.RECORDING_BEATS
self.message_box.setText("Count your heart beats\nWithout checking your pulse")
self.message_box.setStyleSheet("background-color: red; color: white;")
self.spin_box.setEnabled(False)
self.spin_box.setStyleSheet("background-color: white; color: grey; border: 1px solid grey;")
self.timer_widget.startTimer()
self.view.control_recording_beats()
self.record_start_time = time.time_ns()/1.0e9
elif self.state == ControlState.RECORDING_BEATS:
pass
elif self.state == ControlState.RECORDING_INPUT:
self.state = ControlState.FINISHED
self.beatEntered.emit(self.spin_box.value())
self.message_box.setText("Finished")
self.message_box.setStyleSheet("background-color: green; color: white;")
self.start_button.setText("Restart")
self.start_button.setStyleSheet("background-color: white; color: black; border: 1px solid black;")
self.spin_box.setEnabled(False)
self.spin_box.setStyleSheet("background-color: white; color: grey; border: 1px solid grey;")
self.model.beat_tracker.get_accuracy_score(self.view.controls_widget.spin_box.value())
self.view.control_finished()

elif self.state == ControlState.FINISHED:
self.state = ControlState.READY_TO_START
self.message_box.setText("Press start and begin heart beat counting")
self.message_box.setStyleSheet("background-color: green; color: black;")
self.start_button.setText("Start")
self.start_button.setStyleSheet("background-color: white; color: black; border: 1px solid black;")
self.spin_box.setValue(0)
self.view.control_ready_to_start()
self.record_start_time = None

@Slot()
def countdownFinished(self):
self.state = ControlState.RECORDING_INPUT
self.spin_box.setEnabled(True)
self.spin_box.setStyleSheet("background-color: white; color: black; border: 1px solid black;")
self.message_box.setText("Enter how many heart beats you counted")
self.message_box.setStyleSheet("background-color: green; color: white;")
self.start_button.setText("Submit")
self.beatCountingFinished.emit(self.record_start_time, time.time_ns()/1.0e9)
def updateViewWithModelData(self):
ecg_times_rel_s = self.model.beat_tracker.ecg_times - time.time_ns()/1.0e9
self.view.update_ecg_series(ecg_times_rel_s, self.model.beat_tracker.ecg_hist)

async def main(self):
await self.model.connect_polar()
await asyncio.gather(self.model.update_ecg())

class CountdownTimer(QWidget):
timerFinished = Signal()

def __init__(self):
super().__init__()

self.initUI()

def initUI(self):
# Set up the main widget and layout
layout = QVBoxLayout(self)

# Create and configure the label to show the time
self.time_label = QLabel("00:10")
self.time_label.setStyleSheet("color: black;")
self.time_label.setAlignment(Qt.AlignCenter)

layout.addWidget(self.time_label)


self.countdown_time = QTime(0, 0, 10)

# Create a QTimer
self.timer = QTimer(self)
self.timer.timeout.connect(self.updateTimer)

def updateTimer(self):
# Subtract one second
self.countdown_time = self.countdown_time.addSecs(-1)

# Update the label
self.time_label.setText(self.countdown_time.toString("mm:ss"))
print(f"Countdown: {self.countdown_time.toString('mm:ss')}")

# Stop the timer if the countdown has finished
if self.countdown_time == QTime(0, 0, 0):
Expand All @@ -162,4 +123,5 @@ def startTimer(self):

def resetTimer(self):
self.countdown_time = QTime(0, 0, 5)
self.time_label.setText(self.countdown_time.toString("mm:ss"))
print(f"Countdown reset to: {self.countdown_time.toString('mm:ss')}")

9 changes: 3 additions & 6 deletions HeartbeatDetection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@
import asyncio
from PySide6.QtWidgets import QApplication
from qasync import QEventLoop
from View import View
from Controller import Controller

if __name__ == "__main__":

app = QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)

plot = View()
plot.setWindowTitle("Rolling Plot")
plot.resize(1200, 600)
plot.show()
controller = Controller()

loop.run_until_complete(plot.main())
loop.run_until_complete(controller.main())
24 changes: 24 additions & 0 deletions Model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from PolarH10 import PolarH10
from BeatTracker import BeatTracker
from PySide6.QtCore import QObject, Signal
from bleak import BleakScanner

class Model(QObject):
sensorConnected = Signal()
Expand All @@ -24,6 +25,29 @@ async def connect_sensor(self):
async def disconnect_sensor(self):
await self.polar_sensor.disconnect()

async def connect_polar(self):

polar_device_found = False
print("Looking for Polar device...")
while not polar_device_found:

devices = await BleakScanner.discover()
print(f"Found {len(devices)} BLE devices")
for device in devices:
if device.name is not None and "Polar" in device.name:
polar_device_found = True
print(f"Found Polar device")
break
if not polar_device_found:
print("Polar device not found, retrying in 1 second")
await asyncio.sleep(1)

self.set_polar_sensor(device)
await self.connect_sensor()

async def disconnect_polar(self):
await self.disconnect_sensor()

async def update_ecg(self):
await self.polar_sensor.start_ecg_stream()

Expand Down
Loading

0 comments on commit f9a7bc2

Please sign in to comment.