Skip to content

Instantly share code, notes, and snippets.

@rosterloh
Created August 16, 2018 08:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rosterloh/e18fb7ea853fa45da9fd83c5b7433242 to your computer and use it in GitHub Desktop.
Save rosterloh/e18fb7ea853fa45da9fd83c5b7433242 to your computer and use it in GitHub Desktop.
import datetime as dt
import tkinter as tk
import tkinter.font as tkFont
import matplotlib.figure as figure
import matplotlib.animation as animation
import matplotlib.dates as mdates
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tmp102
import apds9301
###############################################################################
# Parameters and global variables
# Parameters
update_interval = 60000 # Time (ms) between polling/animation updates
max_elements = 1440 # 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
###############################################################################
# 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()
# 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, ax2, xs, temps, lights, temp_c, lux):
# Update data to display temperature and light values
try:
new_temp = round(tmp102.read_temp(), 2)
new_lux = round(apds9301.read_lux(), 1)
except:
pass
# Update our labels
temp_c.set(new_temp)
lux.set(new_lux)
# Append timestamp to x-axis list
timestamp = mdates.date2num(dt.datetime.now())
xs.append(timestamp)
# Append sensor data to lists for plotting
temps.append(new_temp)
lights.append(new_lux)
# Limit lists to a set number of elements
xs = xs[-max_elements:]
temps = temps[-max_elements:]
lights = lights[-max_elements:]
# Clear, format, and plot light values first (behind)
color = 'tab:red'
ax1.clear()
ax1.set_ylabel('Temperature (C)', color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax1.fill_between(xs, temps, 0, linewidth=2, color=color, alpha=0.3)
# Clear, format, and plot temperature values (in front)
color = 'tab:blue'
ax2.clear()
ax2.set_ylabel('Light (lux)', color=color)
ax2.tick_params(axis='y', labelcolor=color)
ax2.plot(xs, lights, linewidth=2, color=color)
# Format timestamps to be more readable
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
fig.autofmt_xdate()
# Make sure plots stay visible or invisible as desired
ax1.collections[0].set_visible(temp_plot_visible)
ax2.get_lines()[0].set_visible(light_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)
# Instantiate a new set of axes that shares the same x-axis
ax2 = ax1.twinx()
# Empty x and y lists for storing data to plot later
xs = []
temps = []
lights = []
# Variables for holding temperature and light data
temp_c = tk.DoubleVar()
lux = 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='Temperature:', font=dfont, bg='white')
label_celsius = tk.Label(frame, textvariable=temp_c, font=dfont, bg='white')
label_unitc = tk.Label(frame, text="C", font=dfont, bg='white')
label_light = tk.Label(frame, text="Light:", font=dfont, bg='white')
label_lux = tk.Label(frame, textvariable=lux, font=dfont, bg='white')
label_unitlux = tk.Label(frame, text="lux", font=dfont, bg='white')
button_temp = tk.Button( frame,
text="Toggle Temperature",
font=dfont,
command=toggle_temp)
button_light = tk.Button( frame,
text="Toggle Light",
font=dfont,
command=toggle_light)
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)
label_unitc.grid(row=1, column=5, sticky=tk.W)
label_light.grid(row=2, column=4, columnspan=2)
label_lux.grid(row=3, column=4, sticky=tk.E)
label_unitlux.grid(row=3, column=5, sticky=tk.W)
button_temp.grid(row=5, column=0, columnspan=2)
button_light.grid(row=5, column=2, 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('<F11>', toggle_fullscreen)
root.bind('<Escape>', end_fullscreen)
# Have the resize() function be called every time the window is resized
root.bind('<Configure>', resize)
# Call empty _destroy function on exit to prevent segmentation fault
root.bind("<Destroy>", _destroy)
# Initialize our sensors
tmp102.init()
apds9301.init()
# Call animate() function periodically
fargs = (ax1, ax2, xs, temps, lights, temp_c, lux)
ani = animation.FuncAnimation( fig,
animate,
fargs=fargs,
interval=update_interval)
# Start in fullscreen mode and run
toggle_fullscreen()
root.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment