Newer
Older
#Reference for Threading and GUI architecture: https://maldus512.medium.com/how-to-setup-correctly-an-application-with-python-and-tkinter-107c6bc5a45
#Reference for function animate https://towardsdatascience.com/plotting-live-data-with-matplotlib-d871fac7500b
import matplotlib.pyplot as plt
import numpy as np
from tkinter import TOP, BOTH, X, LEFT, RIGHT
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.animation import FuncAnimation
from types import SimpleNamespace
from enum import Enum
from threading import Thread
TIME_OUT = 0.5 #Time out used for refreshing the User Interface
class Messages(Enum):
HRSETALARM = 0
#Method in charge of updating the UI. It is called from another thread and uses queue for communication
def refreshUI(guiRef, model, guiQueue):
msg = None
while True:
try:
msg = guiQueue.get(timeout = TIME_OUT)
if msg == Messages.HRSETALARM:
model.count += 1
guiRef.entry["state"] = 'normal'
guiRef.entry.delete(0,"end")
guiRef.entry.insert(0,"12")
guiRef.entry["state"] = 'disabled'
guiRef.entry_two.delete(0,"end")
guiRef.entry_two.insert(0,"Prueba")
except queue.Empty:
pass
#Other tasks...
#print("Han pasado 500 milisegundos")
#Class for the User Interface
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
#self.master.geometry('1000x1000')
self.master.title('Smart Watch')
self.pack(fill=BOTH, expand=True)
#Animate needs to be created here and declared self. If not, will be gargabe collected
self.figure, self.ax = plt.subplots(1, 1, figsize=(6,5))
self.animate = FuncAnimation(self.figure, self.refreshChart, interval=500)
#FRAME 1
self.frame_one = tk.Frame(self.master)
self.frame_one.pack(fill=X)
self.label_title = tk.Label(self.frame_one, text = "Smart Watch")
self.label_title.pack(side=LEFT)
self.frame_two = tk.Frame(self.master)
self.frame_two.pack(fill=X)
chart_type = FigureCanvasTkAgg(self.figure, self.frame_two)
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#FRAME 3
self.frame_three = tk.Frame(self.master)
self.frame_three.pack(fill=X)
self.label_subtitle_two = tk.Label(self.frame_three, text = "Valores Instantáneos")
self.label_subtitle_two.pack(side=LEFT)
#FRAME 4
self.frame_four = tk.Frame(self.master)
self.frame_four.pack(fill=X)
self.label_heartrate = tk.Label(self.frame_four, text = "Heart Rate")
self.label_heartrate.pack(side=LEFT)
#TODO Entry must be enabled before updating it
self.entry_heartrate = tk.Entry(self.frame_four, state='disabled')
self.entry_heartrate.pack(side=LEFT)
self.label_heartrate_units_one = tk.Label(self.frame_four, text = "bpm")
self.label_heartrate_units_one.pack(side=LEFT)
self.label_heartrate_alarm = tk.Label(self.frame_four, text = "ALARMA")
self.label_heartrate_alarm.pack(side=LEFT)
self.label_heartrate_minimum = tk.Label(self.frame_four, text = "Mínimo")
self.label_heartrate_minimum.pack(side=LEFT)
self.entry_heartrate_minimum = tk.Entry(self.frame_four)
self.entry_heartrate_minimum.pack(side=LEFT)
self.label_heartrate_units_two = tk.Label(self.frame_four, text = "bpm")
self.label_heartrate_units_two.pack(side=LEFT)
self.label_heartrate_maximum = tk.Label(self.frame_four, text = "Máximo")
self.label_heartrate_maximum.pack(side=LEFT)
self.entry_heartrate_maximum = tk.Entry(self.frame_four)
self.entry_heartrate_maximum.pack(side=LEFT)
self.label_units_heartrate_three = tk.Label(self.frame_four, text = "bpm")
self.label_units_heartrate_three.pack(side=LEFT)
self.button_heartrate = tk.Button(self.frame_four, text = "Configurar alarma")
#self.button_heartrate = tk.Button(self.frame_four, text = "Configurar alarma", command = self.configure_heartrate_alarm)
self.button_heartrate.pack(side=LEFT)
#FRAME 5
self.frame_five = tk.Frame(self.master)
self.frame_five.pack(fill=X)
self.quit = tk.Button(self.frame_five, text="QUIT", fg="red", command=self.master.destroy)
self.quit.pack(side=RIGHT)
def configure_heartrate_alarm(self):
self.entry_heartrate["state"] = 'normal'
self.entry_heartrate.delete(0,"end")
self.entry_heartrate.insert(0,"88")
self.entry_heartrate["state"] = 'disabled'
print("Alarma HR!")
#Method that provides reference to the user interface. It uses a queue to communicate the UI with the exterior. It returns references to
#inner objetcs so the exterior can communicate with it. More references can be given in "SimpleNameSpace"
def gui_reference(self, root, queue):
entry = self.entry_heartrate
entry["state"] = 'normal'
entry.delete(0,"end")
entry.insert(0,"10")
entry["state"] = 'disabled'
self.button_heartrate["command"] = lambda : queue.put(Messages.HRSETALARM)
#Just for testing purposes to emit reference to another widget
entry_two = self.entry_heartrate_minimum
return SimpleNamespace(entry=entry, entry_two = entry_two)
#Function used to update the chart with external data. It is called every 500 mseconds. Data needs to be passed by reference using "gui_reference"
def refreshChart(self, x):
#get data
x = np.arange(10)
y = 2.5 * np.sin(x / 20 * np.pi)
self.ax.plot(x, y, color='tab:blue')
print("Chart refreshed")
#set limits and title
self.ax.set_xlim([0, 10])
self.ax.set_ylim([-1, 1])
self.ax.set_title('PPG - RED')
if __name__ == '__main__':
root = tk.Tk()
app = Application(master=root)
#Queue used to communicate the UI with the exterior
guiQueue = queue.Queue()
guiRef = app.gui_reference(root, guiQueue)
model = SimpleNamespace(count=0)
#Thread used to separate the use interface with the front end. It is used to not slow down the GUI when processing information
t = Thread(target=refreshUI, args=(guiRef, model, guiQueue,))
t.daemon = True
t.start()
app.mainloop()