450 lines
16 KiB
Python
450 lines
16 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import os
|
|
import pprint
|
|
import random
|
|
import sys
|
|
import wx
|
|
|
|
REFRESH_INTERVAL_MS = 1000
|
|
|
|
import matplotlib
|
|
matplotlib.use('WXAgg')
|
|
import matplotlib.lines
|
|
from matplotlib.figure import Figure
|
|
from matplotlib.pyplot import legend
|
|
from matplotlib.backends.backend_wxagg import \
|
|
FigureCanvasWxAgg as FigCanvas, \
|
|
NavigationToolbar2WxAgg as NavigationToolbar
|
|
from matplotlib.path import Path
|
|
import matplotlib.patches as patches
|
|
import wx.lib.buttons as buttons
|
|
|
|
import numpy as np
|
|
import pylab
|
|
|
|
from Arduino_Monitor import SerialData as DataGen
|
|
import Arduino_Monitor
|
|
|
|
|
|
class GraphFrame(wx.Frame):
|
|
""" The main frame of the application
|
|
"""
|
|
title = 'reflowctl gui'
|
|
|
|
def __init__(self):
|
|
wx.Frame.__init__(self, None, -1, self.title)
|
|
|
|
self.datagen = DataGen()
|
|
self.data = [self.datagen.next()]
|
|
self.started = False
|
|
|
|
self.profile = []
|
|
self.state = []
|
|
self.count = 0
|
|
|
|
self.create_menu()
|
|
self.create_status_bar()
|
|
self.create_main_panel()
|
|
|
|
self.redraw_timer = wx.Timer(self)
|
|
self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)
|
|
self.redraw_timer.Start(REFRESH_INTERVAL_MS)
|
|
|
|
|
|
def create_menu(self):
|
|
self.menubar = wx.MenuBar()
|
|
|
|
menu_file = wx.Menu()
|
|
m_expt = menu_file.Append(-1, "&Save plot\tCtrl-S", "Save plot to file")
|
|
self.Bind(wx.EVT_MENU, self.on_save_plot, m_expt)
|
|
menu_file.AppendSeparator()
|
|
m_exit = menu_file.Append(-1, "E&xit\tCtrl-X", "Exit")
|
|
self.Bind(wx.EVT_MENU, self.on_exit, m_exit)
|
|
|
|
self.menubar.Append(menu_file, "&File")
|
|
self.SetMenuBar(self.menubar)
|
|
|
|
def create_main_panel(self):
|
|
self.panel = wx.Panel(self)
|
|
|
|
self.hbox1 = wx.BoxSizer(wx.HORIZONTAL)
|
|
self.init_profile()
|
|
self.init_log()
|
|
self.init_oven_status()
|
|
self.init_plot()
|
|
self.canvas = FigCanvas(self.panel, -1, self.fig)
|
|
|
|
self.recv_config_button = wx.Button(self.panel, -1, "Receive Config")
|
|
self.Bind(wx.EVT_BUTTON, self.on_recv_config_button, self.recv_config_button)
|
|
|
|
self.send_button = wx.Button(self.panel, -1, "Send Config")
|
|
self.Bind(wx.EVT_BUTTON, self.on_send_button, self.send_button)
|
|
|
|
self.start_button = buttons.GenToggleButton(self.panel, -1, "Start")
|
|
self.Bind(wx.EVT_BUTTON, self.on_start_button, self.start_button)
|
|
|
|
#self.on_bitmap = wx.Image('burn.png', wx.BITMAP_TYPE_PNG).Scale(32, 32, wx.IMAGE_QUALITY_HIGH).ConvertToBitmap()
|
|
#self.off_bitmap = wx.Image('unburn.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap()
|
|
|
|
|
|
self.ctrls = wx.BoxSizer(wx.VERTICAL)
|
|
self.ctrls.Add(self.recv_config_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
|
|
self.ctrls.Add(self.send_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
|
|
self.ctrls.Add(self.start_button, border=5, flag=wx.ALL | wx.ALIGN_LEFT)
|
|
self.hbox1.Add(self.ctrls, border=5, flag=wx.ALL | wx.ALIGN_TOP)
|
|
|
|
self.vbox = wx.BoxSizer(wx.VERTICAL)
|
|
self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.ALL)
|
|
self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW)
|
|
#self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)
|
|
|
|
|
|
self.panel.SetSizer(self.vbox)
|
|
self.vbox.Fit(self)
|
|
|
|
def profile_spin_changed(self, event):
|
|
print dir(event)
|
|
|
|
|
|
def add_profile_item(self, title, sizer, min_=1, max_=250):
|
|
|
|
mc = 8
|
|
|
|
item = wx.SpinCtrl(self.panel, -1, "", (30, 50))
|
|
item.SetRange(min_, max_)
|
|
item.SetValue(Arduino_Monitor.profile[self.count])
|
|
|
|
self.Bind(wx.EVT_SPIN, self.profile_spin_changed, item)
|
|
|
|
sizer.Add(wx.StaticText(self.panel, -1, title), (self.count, 0))
|
|
sizer.Add(item, (self.count, 1))
|
|
|
|
self.count += 1
|
|
self.profile.append(item)
|
|
|
|
def init_profile(self):
|
|
self.preheat_sizer = wx.GridBagSizer(5, 5)
|
|
self.rampup_sizer = wx.GridBagSizer(5, 5)
|
|
self.peak_sizer = wx.GridBagSizer(5, 5)
|
|
self.rampdown_sizer = wx.GridBagSizer(5, 5)
|
|
|
|
self.add_profile_item("Ts_min (°C)", self.preheat_sizer, 0, 300)
|
|
self.add_profile_item("Ts_max (°C)", self.preheat_sizer, 0, 300)
|
|
self.add_profile_item("Ts duration min (s)", self.preheat_sizer, 0, 300)
|
|
self.add_profile_item("Ts duration max (s)", self.preheat_sizer, 0, 300)
|
|
|
|
self.add_profile_item("ts ramp up min (°C/s)", self.preheat_sizer, 1, 100)
|
|
self.add_profile_item("ts ramp up max (°C/s)", self.preheat_sizer, 1, 100)
|
|
self.add_profile_item("tp ramp up min (°C/s)", self.peak_sizer, 1, 100)
|
|
self.add_profile_item("tp ramp up max (°C/s)", self.peak_sizer1, 100)
|
|
|
|
self.add_profile_item("Tl duration min (s)", self.rampup_sizer, 0, 300)
|
|
self.add_profile_item("Tl duration max (s)", self.rampup_sizer, 0, 300)
|
|
|
|
self.add_profile_item("Tp (°C)", self.peak_sizer, 0, 300)
|
|
self.add_profile_item("Tp duration min (s)", self.peak_sizer, 0, 300)
|
|
self.add_profile_item("Tp duration max (s)", self.peak_sizer, 0, 300)
|
|
|
|
self.add_profile_item("ramp down min (°C/s)", self.rampdown_sizer, -100, 0)
|
|
self.add_profile_item("ramp down max (°C/s)", self.rampdown_sizer, -100, 0)
|
|
|
|
self.add_profile_item("time max (s)", 0, 800)
|
|
|
|
self.box = wx.StaticBox(self.panel, -1, "Profile Settings")
|
|
self.bsizer = wx.StaticBoxSizer(self.box, wx.VERTICAL)
|
|
self.bsizer.Add(self.profile_sizer, 0, flag=wx.ALL, border=5)
|
|
self.hbox1.Add(self.bsizer, border=5, flag=wx.ALL | wx.ALIGN_TOP)
|
|
|
|
def init_oven_status(self):
|
|
self.oven_status_sizer = wx.GridBagSizer(5, 5)
|
|
|
|
#set_min = 0;
|
|
#set_max = 0;
|
|
#set_dt_min = 0;
|
|
#set_dt_max = 0;
|
|
|
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Connected"), (0, 0))
|
|
self.oven_connected = wx.StaticText(self.panel, -1, str(self.datagen.connected()))
|
|
self.oven_status_sizer.Add(self.oven_connected, (0, 1))
|
|
|
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Temperature"), (1, 0))
|
|
self.temperature = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[1]))
|
|
self.oven_status_sizer.Add(self.temperature, (1, 1))
|
|
|
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Time"), (2, 0))
|
|
self.time = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[0]))
|
|
self.oven_status_sizer.Add(self.time, (2, 1))
|
|
|
|
|
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "State"), (3, 0))
|
|
self.pstate = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[3]))
|
|
self.oven_status_sizer.Add(self.pstate, (3, 1))
|
|
|
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Error"), (4, 0))
|
|
self.perror = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[4]))
|
|
self.oven_status_sizer.Add(self.perror, (4, 1))
|
|
|
|
self.oven_status_sizer.Add(wx.StaticText(self.panel, -1, "Heating"), (5, 0))
|
|
self.is_oven_heating = wx.TextCtrl(self.panel, -1, str(Arduino_Monitor.status[5]))
|
|
self.oven_status_sizer.Add(self.is_oven_heating, (5, 1))
|
|
|
|
self.obox = wx.StaticBox(self.panel, -1, "Oven status")
|
|
self.osizer = wx.StaticBoxSizer(self.obox, wx.VERTICAL)
|
|
self.osizer.Add(self.oven_status_sizer, 0, flag=wx.ALL, border=5)
|
|
self.hbox1.Add(self.osizer, border=5, flag=wx.ALL | wx.ALIGN_TOP)
|
|
|
|
|
|
def init_log(self):
|
|
self.log_sizer = wx.GridBagSizer(5, 5)
|
|
|
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Ts_time_start"), (0, 0))
|
|
self.ts_time_start = wx.TextCtrl(self.panel, -1)
|
|
self.log_sizer.Add(self.ts_time_start, (0, 1))
|
|
|
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Ts_time_end"), (1, 0))
|
|
self.ts_time_end = wx.TextCtrl(self.panel, -1)
|
|
self.log_sizer.Add(self.ts_time_end, (1, 1))
|
|
|
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Tl_time_start"), (2, 0))
|
|
self.tl_time_start = wx.TextCtrl(self.panel, -1)
|
|
self.log_sizer.Add(self.tl_time_start, (2, 1))
|
|
|
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Tl_time_end"), (3, 0))
|
|
self.tl_time_end = wx.TextCtrl(self.panel, -1)
|
|
self.log_sizer.Add(self.tl_time_end, (3, 1))
|
|
|
|
|
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Tp_time_start"), (4, 0))
|
|
self.tp_time_start = wx.TextCtrl(self.panel, -1)
|
|
self.log_sizer.Add(self.tp_time_start, (4, 1))
|
|
|
|
self.log_sizer.Add(wx.StaticText(self.panel, -1, "Tp_time_end"), (5, 0))
|
|
self.tp_time_end = wx.TextCtrl(self.panel, -1)
|
|
self.log_sizer.Add(self.tp_time_end, (5, 1))
|
|
|
|
self.lbox = wx.StaticBox(self.panel, -1, "Profile Log")
|
|
self.lsizer = wx.StaticBoxSizer(self.lbox, wx.VERTICAL)
|
|
self.lsizer.Add(self.log_sizer, 0, flag=wx.ALL, border=5)
|
|
self.hbox1.Add(self.lsizer, border=5, flag=wx.ALL | wx.ALIGN_TOP)
|
|
|
|
def create_status_bar(self):
|
|
self.statusbar = self.CreateStatusBar()
|
|
|
|
def init_plot(self):
|
|
self.dpi = 100
|
|
self.fig = Figure((4.0, 4.0), dpi=self.dpi)
|
|
|
|
self.axes = self.fig.add_subplot(111)
|
|
self.axes.set_axis_bgcolor('black')
|
|
self.axes.set_title(u'Reflow Temperature', size=12)
|
|
self.axes.set_xlabel(u'Time / seconds', size=12)
|
|
self.axes.set_ylabel(u'Temperature (°C)', size=12)
|
|
|
|
pylab.setp(self.axes.get_xticklabels(), fontsize=8)
|
|
pylab.setp(self.axes.get_yticklabels(), fontsize=8)
|
|
|
|
# no 1
|
|
ts_min_x_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN] / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
|
ts_min_y_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN]
|
|
|
|
# no 2
|
|
ts_max_x_min = ts_min_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TS_DURATION_MIN]
|
|
ts_max_y_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MAX]
|
|
|
|
# no t1
|
|
ts_max_x_max = ts_min_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TS_DURATION_MAX]
|
|
ts_max_y_max = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MAX]
|
|
|
|
# no t2
|
|
ts_min_x_max = ts_max_x_max - (ts_max_y_max - ts_min_y_min) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
|
ts_min_y_max = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN]
|
|
|
|
# no 10
|
|
t0_x_max = ts_min_x_max - (ts_max_x_max / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX])
|
|
t0_y_max = 0
|
|
|
|
# no 4
|
|
tp_x_min = ts_max_x_min + (Arduino_Monitor.profile[Arduino_Monitor.PI_TP] - ts_max_y_min) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
|
tp_y_min = Arduino_Monitor.profile[Arduino_Monitor.PI_TP]
|
|
|
|
# no 5
|
|
tp_x_max = tp_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TP_DURATION_MAX]
|
|
tp_y_max = tp_y_min
|
|
|
|
# no 8
|
|
tp5_x_max = tp_x_min + Arduino_Monitor.profile[Arduino_Monitor.PI_TP_DURATION_MIN]
|
|
tp5_y_max = tp_y_max - 5
|
|
|
|
# no 9
|
|
tp5_x_min = ts_max_x_max + (tp5_y_max - ts_max_y_max) / Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_UP_MAX]
|
|
tp5_y_min = tp5_y_max
|
|
|
|
# no 6
|
|
end_x_max = tp_x_max + tp_y_max / abs(Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_DOWN_MIN])
|
|
end_y_max = 0
|
|
|
|
self.xmax = end_x_max + 20
|
|
self.ymax = Arduino_Monitor.profile[Arduino_Monitor.PI_TP] + 20
|
|
|
|
# no 7
|
|
end_x_min = tp5_x_max + tp5_y_max / abs(Arduino_Monitor.profile[Arduino_Monitor.PI_RAMP_DOWN_MAX])
|
|
end_y_min = 0
|
|
|
|
tsmin = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MIN]
|
|
tsmax = Arduino_Monitor.profile[Arduino_Monitor.PI_TS_MAX]
|
|
tl = Arduino_Monitor.profile[Arduino_Monitor.PI_TL]
|
|
tp = Arduino_Monitor.profile[Arduino_Monitor.PI_TP]
|
|
self.ts_line_min = matplotlib.lines.Line2D([0, self.xmax], [tsmin, tsmin],
|
|
transform=self.axes.transData, figure=self.fig, color='green')
|
|
self.ts_line_max = matplotlib.lines.Line2D([0, self.xmax], [tsmax, tsmax],
|
|
transform=self.axes.transData, figure=self.fig, label="Ts_max", color='lightgreen')
|
|
self.tl_line = matplotlib.lines.Line2D([0, self.xmax], [tl, tl],
|
|
transform=self.axes.transData, figure=self.fig, label="Tl", color='yellow')
|
|
self.tp_line = matplotlib.lines.Line2D([0, self.xmax], [tp, tp],
|
|
transform=self.axes.transData, figure=self.fig, label="Tp", color='blue')
|
|
|
|
self.ts_line_min.set_label("Ts_min")
|
|
self.ts_line_min.set_label("Ts_max")
|
|
self.tl_line.set_label("Tl")
|
|
self.tp_line.set_label("Tp")
|
|
self.fig.lines.extend([self.ts_line_min, self.ts_line_max, self.tl_line, self.tp_line])
|
|
|
|
verts = [
|
|
[0.0, 0.0],
|
|
[ts_min_x_min, ts_min_y_min],
|
|
[ts_max_x_min, ts_max_y_min],
|
|
[ts_max_x_max, ts_max_y_max],
|
|
[ts_min_x_max, ts_min_y_max],
|
|
#[tp_x_min, tp_y_min],
|
|
#[tp_x_max, tp_y_max],
|
|
#[end_x_max, end_y_max],
|
|
#[end_x_min, end_y_min],
|
|
#[tp5_x_max, tp5_y_max],
|
|
#[tp5_x_min, tp5_y_min],
|
|
[t0_x_max, t0_y_max],
|
|
[0.0, 0.0]]
|
|
|
|
codes = [
|
|
Path.MOVETO,
|
|
Path.LINETO,
|
|
Path.LINETO,
|
|
Path.LINETO,
|
|
Path.LINETO,
|
|
Path.LINETO,
|
|
#Path.LINETO,
|
|
#Path.LINETO,
|
|
#Path.LINETO,
|
|
#Path.LINETO,
|
|
Path.CLOSEPOLY]
|
|
|
|
self.plot_data = self.axes.plot(
|
|
self.data,
|
|
linewidth=1,
|
|
color=(1, 1, 0),
|
|
)[0]
|
|
|
|
print "verts", verts
|
|
|
|
|
|
path = Path(verts, codes)
|
|
self.patch = patches.PathPatch(path, edgecolor="red", facecolor='orange', lw=2)
|
|
self.axes.add_patch(self.patch)
|
|
self.axes.legend( (self.ts_line_min, self.ts_line_max, self.tl_line, self.tp_line), ('Ts_min', 'Ts_max', 'Tl', 'Tp'), loc=2)
|
|
|
|
def draw_plot(self):
|
|
""" Redraws the plot
|
|
"""
|
|
|
|
self.axes.set_xbound(lower=0, upper=self.xmax)
|
|
self.axes.set_ybound(lower=0, upper=self.ymax)
|
|
|
|
self.axes.grid(True, color='gray')
|
|
|
|
pylab.setp(self.axes.get_xticklabels(), visible=True)
|
|
|
|
self.plot_data.set_xdata(np.arange(len(self.data)))
|
|
self.plot_data.set_ydata(np.array(self.data))
|
|
|
|
self.canvas.draw()
|
|
|
|
def update_config(self):
|
|
for ix, i in enumerate(self.profile):
|
|
i.SetValue(str(Arduino_Monitor.profile[i]))
|
|
|
|
def update_state(self):
|
|
if Arduino_Monitor.status[3] > 0:
|
|
self.started = True
|
|
|
|
self.time.SetValue(str(Arduino_Monitor.status[0]))
|
|
self.temperature.SetValue(str(Arduino_Monitor.status[1]))
|
|
self.pstate.SetValue(str(Arduino_Monitor.status[3]))
|
|
self.perror.SetValue(str(Arduino_Monitor.status[4]))
|
|
self.is_oven_heating.SetValue(str(Arduino_Monitor.status[5]))
|
|
|
|
|
|
def on_start_button(self, event):
|
|
self.started = self.datagen.send_start()
|
|
self.recv_config_button.Disable()
|
|
self.send_button.Disable()
|
|
self.profile = []
|
|
for i in range(30):
|
|
self.profile_sizer.Remove(i)
|
|
self.profile_sizer.Layout()
|
|
|
|
def on_recv_config_button(self, event):
|
|
if not self.started:
|
|
self.datagen.recv_config()
|
|
|
|
|
|
def on_send_button(self, event):
|
|
if not self.started:
|
|
self.datagen.send_config()
|
|
|
|
def on_save_plot(self, event):
|
|
file_choices = "PNG (*.png)|*.png"
|
|
|
|
dlg = wx.FileDialog(
|
|
self,
|
|
message="Save plot as...",
|
|
defaultDir=os.getcwd(),
|
|
defaultFile="plot.png",
|
|
wildcard=file_choices,
|
|
style=wx.SAVE)
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
path = dlg.GetPath()
|
|
self.canvas.print_figure(path, dpi=self.dpi)
|
|
self.flash_status_message("Saved to %s" % path)
|
|
|
|
def on_redraw_timer(self, event):
|
|
|
|
if self.started:
|
|
self.data.append(self.datagen.next())
|
|
|
|
self.update_state()
|
|
self.draw_plot()
|
|
|
|
def on_exit(self, event):
|
|
self.Destroy()
|
|
|
|
def flash_status_message(self, msg, flash_len_ms=1500):
|
|
self.statusbar.SetStatusText(msg)
|
|
self.timeroff = wx.Timer(self)
|
|
self.Bind(
|
|
wx.EVT_TIMER,
|
|
self.on_flash_status_off,
|
|
self.timeroff)
|
|
self.timeroff.Start(flash_len_ms, oneShot=True)
|
|
|
|
def on_flash_status_off(self, event):
|
|
self.statusbar.SetStatusText('')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app = wx.PySimpleApp()
|
|
app.frame = GraphFrame()
|
|
app.frame.Show()
|
|
app.MainLoop()
|
|
|