The plan has been to design a Tkinter GUI on the Raspberry Pi to readout temperature data, display a camera view inside the MPA and dynamically alter the PID temperature control input to the furnace based on a method design, i.e. Ramp to start temp?, Start Temp=x, End Temp=y, waiting time=z, degrees C /min=r.
So far, i have designed a GUI in tkinter that functions as intended and has most all of the functionality inbuilt, currently the method variables are stored in a .csv file on the raspberry pi where each method is separated by a line space, i.e each method is printed on a new line. I have also made a USB add-on module that has an arduino nano connected to a max6675 chip to read temperature and a Keyes sr1y 5v relay module.
I have been able to design a PID temperature controller that functions on the arduino nano as a sketch file, however it is required to be sent to the nano via the arduino IDE and has a static input variable set in the arduino code itself, this variable would have to be dynamic rather than static as the furnace temperature would be required to change over the course of the method. I have also managed to read temperature from the arduino into the GUI on the raspberry pi via a sketch code (arduino IDE) that reads temp and outputs it as serial data, python then reads the usb serial data and saves it to file before plotting on a graph with matplotlib.
Needless to say, all of these solutions result in uploading a code to the arduino, then executing code to save the serial data to file before executing the GUI on the raspberry pi, ideally i would like a more seamless integration of these elements. I have looked at nanpy and pyFirmata to use the arduino as a slave device, allowing for direct coding in python. but i'm not really sure what the applications/benefits/cons of each are and so i'm putting it to the RaspPi forums! i hope that someone out there has had some similar experience with temperature controllers on the pi and can help me out.
below is the code for the GUI i'm using on my raspberry pi:
Code: Select all
from tkinter import *
from datetime import datetime
import time
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2TkAgg)
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import csv
import tkinter
import RPi.GPIO as GPIO
#import picamera
from matplotlib import rcParams
rcParams.update({'figure.autolayout': True})
import serial
from gpiozero import CPUTemperature
from time import sleep, strftime, time
#camera = picamera.PiCamera()
class DataOutput(Frame):
def __init__(self, root):
Frame.__init__(self, root)
title = Label(self, text="Data Output", font="BOLD 18").grid(row=0, column=0, sticky=W)
temperature = Label(self, text="CPU Temperature :", font="15").grid(row=1, column=0, sticky=W)
tempout = Label(self, text="20°C", font="BOLD 10").grid(row=2, column=0, sticky=W)
temperature2 = Label(self, text="Ambient Temperature :", font="15").grid(row=3, column=0, sticky=W)
self.tempout2 = Label(self, text="20°C", font="BOLD 10").grid(row=4, column=0, sticky=W)
time = Label(self, text="Time [MM:SS] :", font="15").grid(row=5, column=0, sticky=W)
clock = SmlClk(self).grid(row=6, column=0, sticky=W)
class Clock(Frame):
def __init__(self, root):
Frame.__init__(self, root)
clock_frame = Label(self)
clock_frame['text'] = '19:12:09'
clock_frame.grid(row=0, column=0)
def tic():
clock_frame['text'] = datetime.now().strftime("%d %B %Y %I:%M:%S %p")
def tac():
tic()
clock_frame.after(1, tac)
tac()
class SmlClk(Frame):
def __init__(self, root):
Frame.__init__(self, root)
clock_frame = Label(self)
clock_frame['text'] = '19:12:09'
clock_frame['font'] = 'BOLD 10'
clock_frame.grid(row=0, column=0)
def tic():
clock_frame['text'] = datetime.now().strftime("%M:%S")
def tac():
tic()
clock_frame.after(1, tac)
tac()
class SavedMethodButtons(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.sm1 = Button(self, height=3, width=10, text='Btn').grid(row=0, column=0, sticky='')
self.sm2 = Button(self, height=3, width=10, text='Btn').grid(row=0, column=1, sticky='')
self.sm3 = Button(self, height=3, width=10, text='Btn').grid(row=1, column=0, sticky='')
self.sm4 = Button(self, height=3, width=10, text='Btn').grid(row=1, column=1, sticky='')
self.sm5 = Button(self, height=3, width=10, text='Btn').grid(row=2, column=0, sticky='')
self.sm6 = Button(self, height=3, width=10, text='Btn').grid(row=2, column=1, sticky='')
def CameraON():
camera.preview_fullscreen=False
camera.preview_window=(148,-50, 400, 345)
camera.resolution=(390,200)
camera.start_preview()
def CameraOFF():
camera.stop_preview()
def EXIT():
#camera.stop_preview()
#camera.close()
app.destroy()
def UpdateBrightness(value):
camera.brightness = int(value)
def UpdateContrast(value):
camera.contrast = int(value)
def UpdateSharpness(value):
camera.sharpness = int(value)
def UpdateSaturation(value):
camera.saturation = int(value)
def SetAWB(var):
camera.awb_mode = var
def SetEFFECTS(var):
camera.image_effect = var
def Zoom(var):
x = float("0."+var)
camera.zoom = (0.5,0.5,x,x)
class CameraButtonFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
Scale(self, from_=30, to=100, orient=HORIZONTAL, label = "Brightness", width=15, command=UpdateBrightness).grid(row=0, padx=10, column=0)
Scale(self, from_=-100, to=100, orient=HORIZONTAL, label = "Contrast", width=15, command=UpdateContrast).grid(row=0, padx=10, column=1)
Scale(self, from_=-100, to=100, orient=HORIZONTAL, label = "Sharpness", width=15, command=UpdateSharpness).grid(row=1, padx=10, column=0)
Scale(self, from_=-100, to=100, orient=HORIZONTAL, label = "Saturation", width=15, command=UpdateSaturation).grid(row=1, padx=10, column=1)
Scale(self, from_=10, to=99, orient=HORIZONTAL, label = "Zoom", width=15, command=Zoom).grid(row=2, column=0, padx=10, columnspan=2)
Button(self, text='Cam ON', command=CameraON).grid(row=3, column=0)
Button(self, text='Cam OFF', command=CameraOFF).grid(row=3, column=1)
def animate(i):
csv_reader = csv.reader(open('/home/pi/ambient_temp.csv'))
csv_reader2 = csv.reader(open('/home/pi/cpu_temp.csv'))
xList = []
yList = []
xList2 = []
yList2 = []
for line in csv_reader2:
#xList2.append(datetime.strptime(line[0], '%H:%M:%S'))
yList2.append(float(line[1]))
for line in csv_reader:
#xList.append(datetime.strptime(line[0], '%H:%M:%S'))
yList.append(float(line[1]))
a.clear()
a.plot(yList, 'g-', linewidth=2, label='Ambient Temp')
a.plot(yList2, 'r-', linewidth=2, label='CPU Temp')
#a.plot(xList, yList, 'g-', linewidth=2, label='Ambient Temp')
#a.plot(xList, yList2, 'r-', linewidth=2, label='CPU Temp')
a.legend()
a.set_title('Current CPU Temperature / Time')
a.set_xlabel("Time[Seconds]")
a.set_ylabel("Temperature[°C]")
f = Figure(figsize=(5.9, 3.3), dpi=75)
a = f.add_subplot(111)
class SampleApp(Tk):
def __init__(self):
Tk.__init__(self)
self._frame = None
self.fullscreen = True
self.switch_frame(MeltPoint)
ani = animation.FuncAnimation(f, animate, interval=100)
def switch_frame(self, frame_class):
"""Destroys current frame and replaces it with a new one."""
#camera.stop_preview()
new_frame = frame_class(self)
if self._frame is not None:
self._frame.destroy()
self.wm_attributes("-fullscreen", True)
self.fullscreen = True
self._frame = new_frame
self._frame.grid(row=1, column=0)
self.fst = Button(self, height='2', text="Toggle Fullscreen",font='BOLD 10', command=self.fullscreen_toggle)
self.fst.grid(row=0, column=0, rowspan=2, sticky='NW')
def fullscreen_toggle(self):
if self.fullscreen == False:
self.wm_attributes("-fullscreen", True)
self.fullscreen = True
else:
self.wm_attributes("-fullscreen", False)
self.fullscreen = False
class Chart(Frame):
def __init__(self, master):
Frame.__init__(self, master)
canvas = FigureCanvasTkAgg(f, self)
canvas.draw_idle()
canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)
toolbar = NavigationToolbar2TkAgg(canvas, self)
toolbar.update()
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=True)
b1 = Button(self, height=4, width=12, text='Back', command=lambda: master.switch_frame(ManMeth)).pack(side=LEFT, anchor="w", fill=BOTH, expand=True)
b2 = Button(self, height=4, width=12, text='Home', command=lambda: master.switch_frame(MeltPoint)).pack(side=LEFT, anchor="w", fill=BOTH, expand=True)
b4 = Button(self, height=4, width=12, text='Add Method', command=lambda: master.switch_frame(AddMethod)).pack(side=LEFT, anchor="w", fill=BOTH, expand=True)
class MeltPoint(Frame):
def __init__(self, master):
Frame.__init__(self, master)
#camera = picamera.PiCamera
#CameraON()
title = Label(self, text="Melting Point Determination").grid(row=0, column=1)
clock = Clock(self).grid(row=0, column=2, columnspan=2)
b1 = Button(self, height=4, width=12, text='Add Method', command=lambda: master.switch_frame(AddMethod)).grid(row=2, column=0, sticky='')
b2 = Button(self, height=4, width=12, text='Manual Method', command=lambda: master.switch_frame(ManMeth)).grid(row=3,column=0, sticky='')
b3 = Button(self, height=4, width=12, text='Results', command=lambda: master.switch_frame(Chart)).grid(row=4,column=0, sticky='')
b4 = Button(self, height=4, width=12, text='Setup').grid(row=5,column=0, sticky='')
b5 = Button(self, height=4, width=12, text='Exit', command=EXIT).grid(row=6,column=0, sticky='')
tl = Frame(self, height=210, width=410, bd=1, relief=SUNKEN).grid(row=1, rowspan=3, column=1, sticky='')
savdmeth = SavedMethodButtons(self).grid(row=1, column=2, rowspan=3)
dato = DataOutput(self).grid(row=4, rowspan=3, column=2, columnspan=2)
canvas = FigureCanvasTkAgg(f, self)
canvas.draw_idle()
canvas.get_tk_widget().grid(row=4, column=1, rowspan=3)
#toolbar = NavigationToolbar2TkAgg(canvas, self)
#toolbar.update()
#canvas._tkcanvas.grid(row=8,column=1)
class AddMethod(Frame):
def __init__(self, master):
Frame.__init__(self, master)
title = Label(self, text="Method Settings").grid(row=0, column=1)
clock = Clock(self).grid(row=0, column=2, columnspan=2)
a1 = Button(self, text='Back', height=5, width=20, command=lambda: master.switch_frame(ManMeth)).grid(row=1, rowspan=2, column=0, sticky='')
a2 = Button(self, text='Save and Return to Analysis', height=5, width=20, command=lambda:[self.writeToFile, master.switch_frame(ManMeth)]).grid(row=3, rowspan=2, column=0, sticky='')
a3 = Button(self, text='Save Method', height=5, width=20, command=self.writeToFile).grid(row=5, rowspan=2, column=0, sticky='')
mthtit = Label(self, text="Method Title", font="12").grid(row=2, column=1, sticky=W)
sttemp = Label(self, text="Start Temperature", font="12").grid(row=3, column=1, sticky=W)
wttm = Label(self, text="Waiting Time", font="12").grid(row=4, column=1, sticky=W)
sktm = Label(self, text="Soaking Time", font="12").grid(row=5, column=1, sticky=W)
endtemp = Label(self, text="End Temperature", font="12").grid(row=6, column=1, sticky=W)
htrt = Label(self, text="Heating Rate", font="12").grid(row=7, column=1, sticky=W)
self.checkbutton = Checkbutton(self)
self.checkbutton.grid(row=1, column=2)
self.checkbutton.configure(height=3)
self.chklabel = Label(self)
self.chklabel.grid(row=1, column=1, sticky=W)
self.chklabel.configure(text="Ramp to Starting Temperature", font="12")
self.mthtite = Entry(self, relief=SUNKEN, bd=5)
self.mthtite.grid(row=2, column=2)
self.sttempe = Entry(self, relief=SUNKEN, bd=5)
self.sttempe.grid(row=3, column=2)
self.wttme = Entry(self, relief=SUNKEN, bd=5)
self.wttme.grid(row=4, column=2)
self.sktme = Entry(self, relief=SUNKEN, bd=5)
self.sktme.grid(row=5, column=2)
self.endtempe = Entry(self, relief=SUNKEN, bd=5)
self.endtempe.grid(row=6, column=2)
self.htrte = Entry(self, relief=SUNKEN, bd=5)
self.htrte.grid(row=7, column=2)
sttemp1 = Label(self, text="°C", font="BOLD 12").grid(row=3, column=3, sticky=W)
wttm1 = Label(self, text="Sec", font="BOLD 12").grid(row=4, column=3, sticky=W)
sktm1 = Label(self, text="MM:SS", font="BOLD 12").grid(row=5, column=3, sticky=W)
endtemp1 = Label(self, text="°C", font="BOLD 12").grid(row=6, column=3, sticky=W)
htrt1 = Label(self, text="°C/Min", font="BOLD 12").grid(row=7, column=3, sticky=W)
buttons = [
'~','`','!','@','#','$','%','^','&','*','(',')','-','_','Clear',
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P','0','7','8','9','Back',
'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L','[',']','4','5','6','Tab',
'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.','?','/','1','2','3','Space',
]
def select(value):
if value =="Clear":
Entry.focus_get(self).delete(0, END)
elif value == "Back":
widget = Entry.focus_get(self)
if widget:
widget.delete(widget.index("end")-1)
elif value == "Space":
Entry.focus_get(self).insert(END, ' ')
elif value == "Tab":
Entry.focus_get(self).insert(END, ' ')
else :
Entry.focus_get(self).insert(END, value)
class Keyboard(Frame):
def __init__(self, root):
Frame.__init__(self, root)
def HosoPop():
varRow = 2
varColumn = 0
for button in buttons:
command = lambda x=button: select(x)
if button == "Clear" or button == "Space" or button == "Tab" or button == "Back":
Button(self,text= button,width=4, height=2, bg="white", fg="black", relief=RAISED,
activebackground = "#ffffff", activeforeground="#3c4987",
command=command).grid(row=varRow,column=varColumn)
else:
Button(self,text= button,width=3, height=2, bg="white", fg="black", relief=RAISED,
activebackground = "#ffffff", activeforeground="#3c4987",
command=command).grid(row=varRow,column=varColumn)
varColumn +=1
if varColumn > 14 and varRow == 2:
varColumn = 0
varRow+=1
if varColumn > 14 and varRow == 3:
varColumn = 0
varRow+=1
if varColumn > 14 and varRow == 4:
varColumn = 0
varRow+=1
HosoPop()
keys = Keyboard(self).grid(row=8, column=0, columnspan=4)
def writeToFile(self):
with open('Working_MethodFile.csv', 'a') as f:
w=csv.writer(f, quoting=csv.QUOTE_ALL)
w.writerow([self.mthtite.get(), self.sttempe.get(), self.wttme.get(), self.sktme.get(), self.endtempe.get(), self.htrte.get()])
class ManMeth(Frame):
def __init__(self, master):
Frame.__init__(self, master)
#CameraON()
title = Label(self, text="Manual Method").grid(row=0, column=1)
clock = Clock(self).grid(row=0, column=2, columnspan=4)
b1 = Button(self, height=4, width=12, text='Home', command=lambda: master.switch_frame(MeltPoint)).grid(row=2, column=0)
b2 = Button(self, height=4, width=12, text='Method Settings', command=lambda: master.switch_frame(AddMethod)).grid(row=3, column=0)
b3 = Button(self, height=4, width=12, text='Start Analysis').grid(row=4, rowspan=2, column=0)
b4 = Button(self, height=4, width=12, text='Data Handling', command=lambda: master.switch_frame(Chart)).grid(row=6,column=0)
b5 = Button(self, height=4, width=12, text='Save Method').grid(row=7,column=0)
vidfrm = Frame(self, height=210, width=410, bd=1, relief=SUNKEN).grid(row=1, column=1, columnspan=2, rowspan=4)
cambtnfrm = CameraButtonFrame(self).grid(row=1, column=3, rowspan=4)
dato = DataOutput(self).grid(row=5, rowspan=3, column=2, columnspan=3)
canvas = FigureCanvasTkAgg(f, self)
canvas.draw_idle()
canvas.get_tk_widget().grid(row=5, column=1, rowspan=3, sticky='')
if __name__ == "__main__":
app = SampleApp()
ani = animation.FuncAnimation(f, animate, interval=100)
app.mainloop()
Thanks in advance i know its a long post and in depth project but i'm at a loss