# -*- 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()