reflow/reflowctl/plot.py

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