PROCESS
THIS IS HOW I GOT THERE
↓
Neil's GUI.
My GUI.
The code for the GUI is shown below.
import datetime as dt
import tkinter as tk
import tkinter.font as tkFont
import serial,sys
import matplotlib.figure as figure
import matplotlib.animation as animation
import matplotlib.dates as mdates
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
###############################################################################
# Parameters and global variables
# Parameters
update_interval = 5 # Time (ms) between polling/animation updates
max_elements = 500 # Maximum number of elements to store in plot lists
# Declare global variables
root = None
dfont = None
frame = None
canvas = None
ax1 = None
temp_plot_visible = None
# Global variable to remember various states
fullscreen = False
temp_plot_visible = True
light_plot_visible = True
if (len(sys.argv) != 2):
print("command line: my_txrx.py serial_port")
sys.exit()
port = sys.argv[1]
#
# open serial port
#
ser = serial.Serial(port,115200)
ser.setDTR()
###############################################################################
# Functions
# Toggle fullscreen
def toggle_fullscreen(event=None):
global root
global fullscreen
# Toggle between fullscreen and windowed modes
fullscreen = not fullscreen
root.attributes('-fullscreen', fullscreen)
resize(None)
# Return to windowed mode
def end_fullscreen(event=None):
global root
global fullscreen
# Turn off fullscreen mode
fullscreen = False
root.attributes('-fullscreen', False)
resize(None)
# Automatically resize font size based on window size
def resize(event=None):
global dfont
global frame
# Resize font based on frame height (minimum size of 12)
# Use negative number for "pixels" instead of "points"
new_size = -max(12, int((frame.winfo_height() / 15)))
dfont.configure(size=new_size)
# Toggle the temperature plot
def toggle_temp():
global canvas
global ax1
global temp_plot_visible
# Toggle plot and axis ticks/label
temp_plot_visible = not temp_plot_visible
ax1.collections[0].set_visible(temp_plot_visible)
ax1.get_yaxis().set_visible(temp_plot_visible)
canvas.draw()
def toggle_value():
global canvas
global ax1
global temp_plot_visible
# Toggle plot and axis ticks/label
temp_plot_visible = not temp_plot_visible
ax1.collections[0].set_visible(temp_plot_visible)
ax1.get_yaxis().set_visible(temp_plot_visible)
canvas.draw()
# Toggle the light plot
def toggle_light():
global canvas
global ax2
global light_plot_visible
# Toggle plot and axis ticks/label
light_plot_visible = not light_plot_visible
ax2.get_lines()[0].set_visible(light_plot_visible)
ax2.get_yaxis().set_visible(light_plot_visible)
canvas.draw()
# This function is called periodically from FuncAnimation
def animate(i, ax1, xs, values, value_):
# Update data to display temperature and light values
try:
new_value = int(ser.readline().rstrip().decode())
except:
new_value = 0
pass
# Update our labels
value_.set(new_value)
# Append timestamp to x-axis list
timestamp = mdates.date2num(dt.datetime.now())
xs.append(timestamp)
# Append sensor data to lists for plotting
values.append(new_value)
# Limit lists to a set number of elements
xs = xs[-max_elements:]
values = values[-max_elements:]
# Clear, format, and plot light values first (behind)
color = 'tab:red'
ax1.clear()
ax1.set_ylabel('Rx-Tx', color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax1.fill_between(xs, values, 0, linewidth=2, color=color, alpha=0.3)
# Format timestamps to be more readable
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
fig.autofmt_xdate()
# Make sure plots stay visible or invisible as desired
ax1.collections[0].set_visible(temp_plot_visible)
# Dummy function prevents segfault
def _destroy(event):
pass
###############################################################################
# Main script
# Create the main window
root = tk.Tk()
root.title("Sensor Dashboard")
# Create the main container
frame = tk.Frame(root)
frame.configure(bg='white')
# Lay out the main container (expand to fit window)
frame.pack(fill=tk.BOTH, expand=1)
# Create figure for plotting
fig = figure.Figure(figsize=(2, 2))
fig.subplots_adjust(left=0.1, right=0.8)
ax1 = fig.add_subplot(1, 1, 1)
# Empty x and y lists for storing data to plot later
xs = []
values = []
# Variables for holding temperature and light data
value_ = tk.DoubleVar()
# Create dynamic font for text
dfont = tkFont.Font(size=-24)
# Create a Tk Canvas widget out of our figure
canvas = FigureCanvasTkAgg(fig, master=frame)
canvas_plot = canvas.get_tk_widget()
# Create other supporting widgets
label_temp = tk.Label(frame, text='High-Low: ', font=dfont, bg='white')
label_celsius = tk.Label(frame, textvariable=value_, font=dfont, bg='white')
button_temp = tk.Button( frame,
text="Toggle",
font=dfont,
command=toggle_value)
button_quit = tk.Button( frame,
text="Quit",
font=dfont,
command=root.destroy)
# Lay out widgets in a grid in the frame
canvas_plot.grid( row=0,
column=0,
rowspan=5,
columnspan=4,
sticky=tk.W+tk.E+tk.N+tk.S)
label_temp.grid(row=0, column=4, columnspan=2)
label_celsius.grid(row=1, column=4, sticky=tk.E)
button_temp.grid(row=5, column=0, columnspan=2)
button_quit.grid(row=5, column=4, columnspan=2)
# Add a standard 5 pixel padding to all widgets
for w in frame.winfo_children():
w.grid(padx=5, pady=5)
# Make it so that the grid cells expand out to fill window
for i in range(0, 5):
frame.rowconfigure(i, weight=1)
for i in range(0, 5):
frame.columnconfigure(i, weight=1)
# Bind F11 to toggle fullscreen and ESC to end fullscreen
root.bind('', toggle_fullscreen)
root.bind('', end_fullscreen)
# Have the resize() function be called every time the window is resized
root.bind('', resize)
# Call empty _destroy function on exit to prevent segmentation fault
root.bind("", _destroy)
# Call animate() function periodically
fargs = (ax1, xs, values, value_)
ani = animation.FuncAnimation( fig,
animate,
fargs=fargs,
interval=update_interval)
# Start in fullscreen mode and run
# toggle_fullscreen()
root.mainloop()