From 81c5a1724159e2263580e70b649f7b29539a7261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Wed, 21 Nov 2012 12:06:39 +0100 Subject: [PATCH] more robust implementation, prepared communication with oven, started oven controls view --- reflowctl/reflowctl_gui.py | 422 +++++++++++++++++++++++++++++-------- 1 file changed, 329 insertions(+), 93 deletions(-) diff --git a/reflowctl/reflowctl_gui.py b/reflowctl/reflowctl_gui.py index f175290..307749e 100755 --- a/reflowctl/reflowctl_gui.py +++ b/reflowctl/reflowctl_gui.py @@ -1,8 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -import sys, os, random, copy +import sys, os, random, copy, struct +from collections import deque from operator import attrgetter import xml.etree.ElementTree as etree @@ -16,29 +17,80 @@ from matplotlib.figure import Figure from matplotlib.lines import Line2D from matplotlib.path import Path import matplotlib.patches as patches -#from mpltools import annotation + +#from mpltools import annotation- progname = os.path.basename(sys.argv[0]) progversion = "0.1" def calc_colors(count): + if count == 1: + return [QtGui.QColor(0, 255, 0),] r = 0 g = 255 step = int(512. / (count-1)) colors = list() - print "step", step for i in range(count): colors.append(QtGui.QColor(r, g, 0)) if r < 255: r += step if r > 255: - g -= r - 255 + g -= (r - 256) r = 255 + g = max(0, g) else: g -= step g = max(0, g) return colors +def set_colors(temp_levels): + colors = calc_colors(len(temp_levels) - 1) + ix = 0 + for temp_level in temp_levels: + if not temp_level.is_env: + temp_level.color = colors[ix] + ix += 1 + else: + temp_level.color = QtGui.QColor("black") + + + +def create_profile_header(x_list, y_list): + tpl = "#ifndef _H_SOLDER_PROFILE\n" \ + "#define _H_SOLDER_PROFILE\n" \ + "\n" \ + "%s\n" \ + "\n" \ + "};\n" \ + "\n" \ + "#endif\n" + + data_tpl = "unsigned int profile_%d[%d][2] = \n%s\n" \ + + s = list() + + ix = 0 + for x, y in zip(x_list, y_list): + tmp = list() + for a, b in zip(x, y): + tmp.append("{%d, %d}" % (a, b)) + s.append(data_tpl % (ix, len(x), "\n".join(tmp))) + ix += 1 + + open("test.h", "w").write(tpl % ("\n".join(s))) + +def create_profile_packet(x, y): + # msg = [length, x_0, y_0,x_1, y_1, ..., x_length-1, y_length-1] + + tmp = [len(x) * 2 * 2,] + + for a, b in zip(x, y): + tmp.append(a) + tmp.append(b) + + tpl = "H" + (len(x) * 2) * "H" + res = struct.pack(tpl, *tmp) + return res def getTemperature(): return 20. @@ -60,6 +112,7 @@ class Solder(object): self.temp_levels = list() self.durations = list() self.rates = list() + self.changed = False def __unicode__(self): return unicode(self.name) @@ -100,12 +153,12 @@ class Solder(object): for ix, temp_level in enumerate(self.temp_levels): if temp_level != self.temp_levels[0] and temp_level not in used_temp_levels: ix = self.temp_levels.index(temp_level) - raise ValueError("TempLevel %r not connected to %r" % (temp_level.name[ix-1], self.temp_levels[ix].name)) + raise ValueError("TempLevel %r not connected to %r" % (self.temp_levels[ix-1].name, self.temp_levels[ix].name)) temp_levels = None duration = None for sts, dur in self.durations: - if sts[0] == temp_level: + if sts and sts[0] == temp_level: duration = dur temp_levels = sts break @@ -137,7 +190,7 @@ class Solder(object): duration_points[ix] = (x[-2:], y[-2:]) else: for ex, (sts, rate) in enumerate(self.rates): - if sts[0] == temp_level: + if sts and sts[0] == temp_level: used_temp_levels.add(sts[1]) duration = (sts[1].temp - temp_level.temp) / rate self.time += duration @@ -167,14 +220,7 @@ class Solder(object): env_count += 1 s.add_temp_level(temp_level.attrib["name"], temp, is_env) - colors = calc_colors(len(s.temp_levels) - 1) - ix = 0 - for temp_level in s.temp_levels: - if not temp_level.is_env: - temp_level.color = colors[ix] - ix += 1 - else: - temp_level.color = QtGui.QColor("black") + set_colors(s.temp_levels) for duration in solder_node.findall("duration"): temp_levels = list() @@ -225,7 +271,9 @@ class SolderListModel(QtCore.QAbstractListModel): def create_solder(self): solder = Solder("new", "") - solder.add_temp_level("environment temp", getTemperature(), True) + + tl = solder.add_temp_level("environment temp", getTemperature(), True) + tl.color = QtGui.QColor(0, 0, 0) self.listdata.append(solder) self.reset() @@ -291,9 +339,8 @@ class TempLevelModel(QtCore.QAbstractTableModel): return tmp def add_temp_level(self, index, temp_level): - self.beginInsertRows(QtGui.QModelIndex(), index.row(), 1) - self.temp_levels.temp_levels.insert(index.row(), temp_level) - self.endInsertRows() + self.temp_levels.insert(index.row() + 1, temp_level) + set_colors(self.temp_levels) self.reset() self._changed = True @@ -346,23 +393,23 @@ class Plotter(FigureCanvas): updated = False self.x, self.y, self.xmax, self.ymax, self.duration_points, self.rate_points = self.solder.calc_profile() - #for states, value in self.durations.iteritems(): - #annotation.slope_marker((states[0]) + #for states, value in self.durations.iteritems(): + #annotation.slope_marker((states[0]) - self.axes.set_xbound(lower=0, upper=self.xmax + 20) - self.axes.set_ybound(lower=0, upper=self.ymax + 20) + self.axes.set_xbound(lower=0, upper=self.xmax + 20) + self.axes.set_ybound(lower=0, upper=self.ymax + 20) - self.axes.set_yticks([state.temp for state in self.solder.temp_levels]) - self.axes.set_xticks(self.x) + self.axes.set_yticks([state.temp for state in self.solder.temp_levels]) + self.axes.set_xticks(self.x) - self.plot_data.set_xdata(self.x) - self.plot_data.set_ydata(self.y) - self.plot_data.set_zorder(20) + self.plot_data.set_xdata(self.x) + self.plot_data.set_ydata(self.y) + self.plot_data.set_zorder(20) - duration_widget = self.myapp.duration_widget + duration_widget = self.myapp.duration_widget - #self.selection_data.set_xdata(array(da)) - #self.selection_data.set_ydata(array(db)) + #self.selection_data.set_xdata(array(da)) + #self.selection_data.set_ydata(array(db)) self.fig.lines = lines = list() for temp_level in self.solder.temp_levels: @@ -374,6 +421,10 @@ class Plotter(FigureCanvas): self.axes.legend(("Estimated profile",)) self.draw() + def setData(self, solder): + self.solder = solder + self.updated = True + class AddRemoveWidget(QtGui.QWidget): def __init__(self, parent, with_upown=True): @@ -395,6 +446,8 @@ class AddRemoveWidget(QtGui.QWidget): self.down_button = QtGui.QPushButton("Down") self._layout.addWidget(self.up_button) self._layout.addWidget(self.down_button) + self.up_button.setStyleSheet(sh) + self.down_button.setStyleSheet(sh) self._layout.addStretch(4) @@ -437,10 +490,10 @@ class ConstraintListModel(QtCore.QAbstractListModel): class ConstraintWidget(QtGui.QWidget): - def __init__(self, name, solder): + def __init__(self, name): super(ConstraintWidget, self).__init__() self.name = name - #self.solder = solder + self.spinbox_block = False self.constraint_model = ConstraintListModel(self) # constraint selection @@ -501,7 +554,7 @@ class ConstraintWidget(QtGui.QWidget): self.connect( self.constraint_controls.add_button, QtCore.SIGNAL("clicked()"), - self.constraint_model.append_constraint) + self.add_constraint) self.connect( self.constraint_controls.remove_button, @@ -514,18 +567,36 @@ class ConstraintWidget(QtGui.QWidget): self.constraint_value_changed) def setData(self, solder): - print self.setData, 1 self.solder = solder - print self.setData, 2 - self.all_temp_levels.setTempLevels(self.solder.temp_levels) - print self.setData, 3 - self._set_data() - print self.setData, 4 + self.all_temp_levels.setTempLevels(solder.temp_levels) + self._set_data(solder) + self.set_controls() + + def set_controls(self): + if not self.constraint_model.constraint_list: + self.controls.value.setEnabled(False) + self.constraint_controls.remove_button.setEnabled(False) + self.controls.add_button.setEnabled(False) + self.controls.remove_button.setEnabled(False) + self.controls.up_button.setEnabled(False) + self.controls.down_button.setEnabled(False) + else: + self.controls.value.setEnabled(True) + self.constraint_controls.remove_button.setEnabled(True) + self.controls.add_button.setEnabled(True) + self.controls.remove_button.setEnabled(True) + self.controls.up_button.setEnabled(True) + self.controls.down_button.setEnabled(True) + + + def add_constraint(self): + self.constraint_model.append_constraint() + self.set_controls() def _constraint_selected(self, index): raise NotImplementedError() - def _set_data(self): + def _set_data(self, solder): raise NotImplementedError() def add_temp_level_to_constraint(self): @@ -548,21 +619,25 @@ class ConstraintWidget(QtGui.QWidget): del self.constraint_model.constraint_list[src_row] self.constraint_model.reset() self.selected_temp_levels.clear() + self.set_controls() def constraint_value_changed(self, value): - print self.constraint_value_changed if self.spinbox_block: self.spinbox_block = False return - print - print - print "IIIIEK" - print src_index = self.constraint_view.currentIndex().row() self.constraint_model.constraint_list[src_index][1] = value def slot_temp_level_removed(self, temp_level): - print "temp_level" + + deletes = deque() + ix = 0 + for temp_levels, v in self.constraint_model.constraint_list: + if temp_level in temp_levels: + deletes.appendleft(ix) + ix += 1 + for i in deletes: + del self.constraint_model.constraint_list[i] self.reset() def temp_level_up(self): @@ -578,50 +653,116 @@ class ConstraintWidget(QtGui.QWidget): class DurationConstraintWidget(ConstraintWidget): - def _set_data(self): + def _set_data(self, solder): self.spinbox_block = True - print self._set_data, 1 - self.constraint_model.constraint_list.extend(self.solder.durations) - print self._set_data, 2 + self.constraint_model.constraint_list = solder.durations ix = self.constraint_model.index(0, 0) - print self._set_data, 3 - self.constraint_view.setCurrentIndex(ix) - print self._set_data, 4 self._constraint_selected(ix) - print self._set_data, 5 + self.constraint_view.setCurrentIndex(ix) def _constraint_selected(self, index): - print self._constraint_selected - self.spinbox_block = True - temp_levels, value = self.constraint_model.constraint_list[index.row()] - self.selected_temp_levels.setTempLevels(temp_levels) - self.controls.value.setValue(value) + if index.isValid(): + self.spinbox_block = True + temp_levels, value = self.constraint_model.constraint_list[index.row()] + self.selected_temp_levels.setTempLevels(temp_levels) + self.controls.value.setValue(value) + else: + self.spinbox_block = True + self.selected_temp_levels.setTempLevels([]) + self.controls.value.setValue(0) + + +class OvenControlsWidget(QtGui.QWidget): + def __init__(self, parent=None): + super(OvenControlsWidget, self).__init__(parent) + + self.left = QtGui.QWidget(self) + + self.temp_lcd = QtGui.QLCDNumber(self) + self.time_lcd = QtGui.QLCDNumber(self) + + self.time_lcd.setDigitCount(3) + self.temp_lcd.setDigitCount(3) + + palette = QtGui.QPalette() + palette.setColor(QtGui.QPalette.WindowText, QtCore.Qt.black) + self.temp_lcd.setPalette(palette) + self.time_lcd.setPalette(palette) + self.time_lcd.setSmallDecimalPoint(False) + self.temp_lcd.setSmallDecimalPoint(False) + + self.temp_lcd.setSegmentStyle(2) + self.time_lcd.setSegmentStyle(2) + self.time_lcd.display("000") + self.temp_lcd.display("142") + + self.temp_label = QtGui.QLabel("Time", self) + self.time_label = QtGui.QLabel("Temp", self) + self.temp_label.setBuddy(self.temp_lcd) + self.time_label.setBuddy(self.time_lcd) + + self.sicon = QtGui.QIcon.fromTheme("media-playback-start") + self.picon = QtGui.QIcon.fromTheme("media-playback-pause") + self.start_button = QtGui.QPushButton(self.sicon, "start") + self.start_button.setCheckable(True) + + layout = QtGui.QHBoxLayout(self.left) + layout.addWidget(self.time_label) + layout.addWidget(self.time_lcd) + layout.addWidget(self.temp_label) + layout.addWidget(self.temp_lcd) + layout.addWidget(self.start_button) + + layout2 = QtGui.QHBoxLayout() + self.temp_level_widget = TempLevelWidget(self, True) + layout2.addWidget(self.temp_level_widget, 2) + layout2.addWidget(self.left, 4) + self.setLayout(layout2) + + + + + + self.connect( + self.start_button, + QtCore.SIGNAL("clicked()"), + self.toggle_button) + + def toggle_button(self): + if self.start_button.isChecked(): + self.start_button.setIcon(self.picon) + else: + self.start_button.setIcon(self.sicon) class RateConstraintWidget(ConstraintWidget): - def _set_data(self): - print self._set_data + def _set_data(self, solder): self.spinbox_block = True - self.constraint_model.constraint_list.extend(self.solder.rates) + self.constraint_model.constraint_list = solder.rates ix = self.constraint_model.index(0, 0) - self.constraint_view.setCurrentIndex(ix) self._constraint_selected(ix) + self.constraint_view.setCurrentIndex(ix) + def _constraint_selected(self, index): - print self._constraint_selected - self.spinbox_block = True - temp_levels, value = self.constraint_model.constraint_list[index.row()] - self.selected_temp_levels.setTempLevels(temp_levels) - self.controls.value.setValue(value) + if index.isValid(): + self.spinbox_block = True + temp_levels, value = self.constraint_model.constraint_list[index.row()] + self.selected_temp_levels.setTempLevels(temp_levels) + self.controls.value.setValue(value) + else: + self.spinbox_block = True + self.selected_temp_levels.setTempLevels([]) + self.controls.value.setValue(0) class TempLevelWidget(QtGui.QWidget): temp_level_removed = QtCore.pyqtSignal(TempLevel) - def __init__(self, parent, solder): + def __init__(self, parent, readonly=False): super(TempLevelWidget, self).__init__(parent) self.temp_level_model = TempLevelModel(self) @@ -632,11 +773,8 @@ class TempLevelWidget(QtGui.QWidget): self.temp_level_view.horizontalHeader().setStretchLastSection(True) self.temp_level_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.controls = AddRemoveWidget(self) - h = QtGui.QHBoxLayout() h.addWidget(self.temp_level_view) - h.addWidget(self.controls) self.setLayout(h) self.connect( @@ -644,31 +782,69 @@ class TempLevelWidget(QtGui.QWidget): QtCore.SIGNAL("clicked(QModelIndex)"), self.temp_level_selected) - self.connect( - self.controls.remove_button, - QtCore.SIGNAL("clicked()"), - self.remove_temp_level) + if not readonly: + self.controls = AddRemoveWidget(self) + h.addWidget(self.controls) + + self.connect( + self.controls.add_button, + QtCore.SIGNAL("clicked()"), + self.add_temp_level) + + self.connect( + self.controls.remove_button, + QtCore.SIGNAL("clicked()"), + self.remove_temp_level) + + self.connect( + self.controls.up_button, + QtCore.SIGNAL("clicked()"), + self.temp_level_up) + + self.connect( + self.controls.down_button, + QtCore.SIGNAL("clicked()"), + self.temp_level_down) + + + def setData(self, solder): + self.temp_level_model.setTempLevels(solder.temp_levels) + self.temp_level_view.resizeColumnsToContents() def add_temp_level(self): index = self.temp_level_view.currentIndex() - self.temp_level_model.add_temp_level(index,TempLevel("new", 0)) + self.temp_level_model.add_temp_level(index, TempLevel("new " + str(self.temp_level_model.rowCount(None)), 0)) + self.temp_level_view.setCurrentIndex(self.temp_level_model.index(index.row() + 1, 0)) + self.plotter.solder.changed = True def remove_temp_level(self): self.temp_level_removed.emit( self.temp_level_model.remove_temp_level( self.temp_level_view.currentIndex().row())) + self.plotter.solder.changed = True + + def temp_level_up(self): + dst_row = self.temp_level_view.currentIndex().row() + self.temp_level_model.temp_levels[dst_row - 1], self.temp_level_model.temp_levels[dst_row] = self.temp_level_model.temp_levels[dst_row], self.temp_level_model.temp_levels[dst_row - 1] + self.temp_level_model.reset() + self.plotter.solder.changed = True + + def temp_level_down(self): + dst_row = self.selected_temp_levels_view.currentIndex().row() + self.temp_level_model.temp_levels[dst_row], self.temp_level_model.temp_levels[dst_row + 1] = self.temp_level_model.temp_levels[dst_row + 1], self.temp_level_model.temp_levels[dst_row] + self.temp_level_model.reset() + self.plotter.solder.changed = True def temp_level_selected(self, index): if index.isValid(): row = index.row() is_env = self.temp_level_model.temp_levels[row].is_env - is_end = row == len(self.temp_level_model.temp_levels) - 1 - self.controls.add_button.setEnabled(not is_end) + #is_end = row == len(self.temp_level_model.temp_levels) - 1 + #self.controls.add_button.setEnabled(not is_end) self.controls.remove_button.setEnabled(not is_env) - print "remove_button state", self.controls.remove_button.isEnabled() class ApplicationWindow(QtGui.QMainWindow): def __init__(self): @@ -679,12 +855,41 @@ class ApplicationWindow(QtGui.QMainWindow): self.setWindowTitle("application main window") self.file_menu = QtGui.QMenu('&File', self) - self.file_menu.addAction('&Quit', self.fileQuit, - QtCore.Qt.CTRL + QtCore.Qt.Key_Q) + self.file_menu.addAction('&Create profile header', self.create_header, + QtCore.Qt.CTRL + QtCore.Qt.Key_C) self.file_menu.addAction('&Save plot', self.save_plot, QtCore.Qt.CTRL + QtCore.Qt.Key_S) + self.file_menu.addAction('&Quit', self.fileQuit, + QtCore.Qt.CTRL + QtCore.Qt.Key_Q) + self.menuBar().addMenu(self.file_menu) + self.view_menu = QtGui.QMenu('&View', self) + self.profile_view_action = self.view_menu.addAction("&Profile View") + self.controls_view_action = self.view_menu.addAction("&Oven Controls View") + + + self.view_group = QtGui.QActionGroup(self) + self.view_group.setExclusive(True) + + self.view_group.addAction(self.controls_view_action) + self.view_group.addAction(self.profile_view_action) + + self.profile_view_action.setCheckable(True) + self.controls_view_action.setCheckable(True) + + self.connect(self.profile_view_action, + QtCore.SIGNAL("triggered()"), + self.open_profile_view) + + self.connect(self.controls_view_action, + QtCore.SIGNAL("triggered()"), + self.open_controls_view) + + self.profile_view_action.setChecked(True) + + self.menuBar().addMenu(self.view_menu) + self.help_menu = QtGui.QMenu('&Help', self) self.menuBar().addSeparator() self.menuBar().addMenu(self.help_menu) @@ -698,13 +903,17 @@ class ApplicationWindow(QtGui.QMainWindow): self.solder_controls = AddRemoveWidget(self, False) self.tab_widget = QtGui.QTabWidget(self) - self.temp_level_widget = TempLevelWidget(self, self.solder_model.listdata[0]) - self.duration_widget = DurationConstraintWidget(u"Duration (s)", self.solder_model.listdata[0]) - self.rate_widget = RateConstraintWidget(u"Rate (°C/s)", self.solder_model.listdata[0]) + self.temp_level_widget = TempLevelWidget(self) + self.duration_widget = DurationConstraintWidget(u"Duration (s)") + self.rate_widget = RateConstraintWidget(u"Rate (°C/s)") self.tab_widget.addTab(self.temp_level_widget, u"Temperature Levels") self.tab_widget.addTab(self.duration_widget, u"Duration (s)") self.tab_widget.addTab(self.rate_widget, u"Rate (°C/s)") + self.plotter = Plotter(self, self, width=5, height=4, dpi=self.dpi) + self.controls_widget = OvenControlsWidget(self) + self.controls_widget.setVisible(False) + self.connect( self.solder_view, QtCore.SIGNAL("clicked(QModelIndex)"), @@ -721,16 +930,20 @@ class ApplicationWindow(QtGui.QMainWindow): pl.addWidget(self.solder_controls, 1) pl.addWidget(self.tab_widget, 6) + self.splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self) - self.plotter = Plotter(self, self, width=5, height=4, dpi=self.dpi) self.solder_view.setCurrentIndex(self.solder_model.index(0,0)) self.solder_selected(self.solder_model.index(0,0)) self.splitter.addWidget(self.settings_widget) + self.splitter.addWidget(self.controls_widget) self.splitter.addWidget(self.plotter) self.splitter.setStretchFactor(0, 2) - self.splitter.setStretchFactor(1, 8) + self.splitter.setStretchFactor(1, 2) + self.splitter.setStretchFactor(2, 8) + + self.solder_controls.hide() self.setCentralWidget(self.splitter) @@ -739,20 +952,43 @@ class ApplicationWindow(QtGui.QMainWindow): self.temp_level_widget.temp_level_removed.connect(self.duration_widget.slot_temp_level_removed) self.temp_level_widget.temp_level_removed.connect(self.rate_widget.slot_temp_level_removed) + def getPlotter(self): + return self.plotter + + def create_header(self): + x_list = list() + y_list = list() + for solder in self.solder_model.listdata: + x, y, xmax, ymax, duration_points, rate_points = solder.calc_profile() + x_list.append(x) + y_list.append(y) + print "packet", repr(create_profile_packet(x, y)) + + create_profile_header(x_list, y_list) + def solder_selected(self, index): if index.isValid(): - solder = self.plotter.solder = self.solder_model.listdata[index.row()] - self.temp_level_widget.temp_level_model.setTempLevels(solder.temp_levels) - self.temp_level_widget.temp_level_view.resizeColumnsToContents() + solder = self.solder_model.listdata[index.row()] + self.temp_level_widget.setData(solder) self.duration_widget.setData(solder) self.rate_widget.setData(solder) - self.plotter.updated = True + self.plotter.setData(solder) + self.controls_widget.temp_level_widget.setData(solder) + + def open_controls_view(self): + print self.open_controls_view + self.controls_widget.show() + self.settings_widget.hide() + + def open_profile_view(self): + print self.open_profile_view + self.controls_widget.hide() + self.settings_widget.show() def save_plot(self): file_choices = "PNG (*.png)|*.png" filename = QtGui.QFileDialog.getSaveFileName(self, 'Save File', 'qtplot.png') - print type(filename), dir(filename) self.plotter.print_figure(str(filename), dpi=self.dpi) def fileQuit(self):