1 # |
|
2 |
|
3 import os, sys |
|
4 import time |
|
5 import glob |
|
6 |
|
7 from PyQt4 import QtGui, QtCore, uic |
|
8 from PyQt4.QtCore import SIGNAL, Qt |
|
9 |
|
10 if "-m" in sys.argv: |
|
11 from gpib_plotter_mockup import GPIBplotter |
|
12 else: |
|
13 from gpib_plotter import GPIBplotter |
|
14 from hpgl_qt import QHPGLPlotterWidget |
|
15 import hpgl_plotter_rc |
|
16 |
|
17 ldir = os.path.abspath(os.path.dirname(__file__)) |
|
18 sys.path.append(ldir) |
|
19 form_class, base_class = uic.loadUiType(os.path.join(ldir, "qhpgl_plotter.ui")) |
|
20 |
|
21 from qpreferences import BaseItem, IntItem, UnicodeItem, ColorItem |
|
22 from qpreferences import PointItem, SizeItem, ByteArrayItem |
|
23 from qpreferences import AbstractPreferences |
|
24 from qpreferenceseditor import PreferencesEditor |
|
25 |
|
26 from tools import str_num_cmp |
|
27 |
|
28 class Preferences(AbstractPreferences): |
|
29 ORGANISATION="Logilab" |
|
30 APPLICATION="qgpib_plotter" |
|
31 |
|
32 _pos = PointItem() |
|
33 _size = SizeItem() |
|
34 _appState = ByteArrayItem() |
|
35 |
|
36 device = UnicodeItem(default='/dev/ttyUSB0', |
|
37 name=u'device', |
|
38 description=u'GPIB device', |
|
39 group="GPIB settings") |
|
40 address = IntItem(default=5, min=0, max=16, |
|
41 name=u'GPIB address', |
|
42 group="GPIB settings") |
|
43 |
|
44 background = ColorItem(default=QtGui.QColor("white"), |
|
45 name="Background", |
|
46 group="Colors") |
|
47 color0 = ColorItem(default=QtGui.QColor("black"), |
|
48 name="Pen #0", |
|
49 group="Colors") |
|
50 color1 = ColorItem(default=QtGui.QColor("green"), |
|
51 name="Pen #1", |
|
52 group="Colors") |
|
53 color2 = ColorItem(default=QtGui.QColor("red"), |
|
54 name="Pen #2", |
|
55 group="Colors") |
|
56 color3 = ColorItem(default=QtGui.QColor("blue"), |
|
57 name="Pen #3", |
|
58 group="Colors") |
|
59 color4 = ColorItem(default=QtGui.QColor("yellow"), |
|
60 name="Pen #4", |
|
61 group="Colors") |
|
62 color5 = ColorItem(default=QtGui.QColor("cyan"), |
|
63 name="Pen #5", |
|
64 group="Colors") |
|
65 color6 = ColorItem(default=QtGui.QColor("magenta"), |
|
66 name="Pen #6", |
|
67 group="Colors") |
|
68 color7 = ColorItem(default=QtGui.QColor("darkred"), |
|
69 name="Pen #7", |
|
70 group="Colors") |
|
71 |
|
72 class QtHPGLPlotter(QtGui.QMainWindow, form_class): |
|
73 def __init__(self, parent=None): |
|
74 QtGui.QMainWindow.__init__(self, parent) |
|
75 self._plots = {} |
|
76 self._prefs = Preferences() |
|
77 self.setupUi() |
|
78 self.initializeGPIB() |
|
79 if self._prefs._pos: |
|
80 self.move(self._prefs._pos) |
|
81 if self._prefs._size: |
|
82 self.resize(self._prefs._size) |
|
83 if self._prefs._appState: |
|
84 self.restoreState(self._prefs._appState) |
|
85 self.readPreferences() |
|
86 |
|
87 def readPreferences(self): |
|
88 bg = self._prefs.background |
|
89 if bg and bg.isValid(): |
|
90 self.plotterWidget.qview.setBackgroundBrush(QtGui.QBrush(bg)) |
|
91 pen_colors = [self._prefs["color%d"%i] for i in range(8)] |
|
92 self.plotterWidget.pen_colors = pen_colors |
|
93 |
|
94 def replotCurrent(self): |
|
95 self.currentPlotChanged(self.plotsView.currentIndex()) |
|
96 |
|
97 def setupUi(self): |
|
98 form_class.setupUi(self, self) # call qtdesigner generated form creation |
|
99 # actions defined in designer |
|
100 self.connect(self.actionPreferences, SIGNAL('triggered(bool)'), |
|
101 self.preferencesTriggered) |
|
102 self.connect(self.actionQuit, SIGNAL('triggered(bool)'), |
|
103 self.quitTriggered) |
|
104 self.actionQuit.setShortcut(QtGui.QKeySequence(u'Ctrl+Q')) |
|
105 self.connect(self.actionOpen, SIGNAL('triggered(bool)'), |
|
106 self.openTriggered) |
|
107 self.actionOpen.setShortcut(QtGui.QKeySequence(u'Ctrl+O')) |
|
108 self.connect(self.actionSave, SIGNAL('triggered(bool)'), |
|
109 self.saveTriggered) |
|
110 self.actionSave.setShortcut(QtGui.QKeySequence(u'Ctrl+S')) |
|
111 self.connect(self.actionSaveAs, SIGNAL('triggered(bool)'), |
|
112 self.saveAsTriggered) |
|
113 |
|
114 self.plotterWidget = QHPGLPlotterWidget(self) |
|
115 self.setCentralWidget(self.plotterWidget) |
|
116 |
|
117 self.connect(self.captureButton, SIGNAL("toggled(bool)"), |
|
118 self.captureToggled) |
|
119 |
|
120 self._plots_list = QtGui.QStringListModel() |
|
121 self.plotsView.setModel(self._plots_list) |
|
122 self.connect(self.plotsView, SIGNAL('activated(const QModelIndex&)'), |
|
123 self.currentPlotChanged) |
|
124 self.connect(self.plotsView.selectionModel(), |
|
125 SIGNAL('currentChanged(const QModelIndex&, const QModelIndex&)'), |
|
126 self.currentPlotChanged) |
|
127 |
|
128 def currentPlotChanged(self, index, old_index=None): |
|
129 if index.isValid(): |
|
130 value = unicode(self.plotsView.model().data(index, Qt.DisplayRole).toString()) |
|
131 |
|
132 self.plotterWidget.clear() |
|
133 self.plotterWidget.parse(self._plots[value]) |
|
134 |
|
135 def preferencesTriggered(self, checked=False): |
|
136 PreferencesEditor(self._prefs, self).exec_() |
|
137 self.readPreferences() |
|
138 self.replotCurrent() |
|
139 |
|
140 def quitTriggered(self, checked=False): |
|
141 self.close() |
|
142 |
|
143 def closeEvent(self, event): |
|
144 if 1: |
|
145 #if self.promptForSave(): |
|
146 self._prefs._pos = self.pos() |
|
147 self._prefs._size = self.size() |
|
148 self._prefs._appState = self.saveState() |
|
149 event.accept() |
|
150 else: |
|
151 event.ignore() |
|
152 |
|
153 def openTriggered(self, checked=False): |
|
154 filenames = QtGui.QFileDialog.getOpenFileNames(self, "Open a HPGL file to display", '.', 'HPGL files (*.plt)\nAll files (*)') |
|
155 self.openFiles(filenames) |
|
156 self.displayFirst() |
|
157 |
|
158 def displayFirst(self): |
|
159 if not self.plotsView.currentIndex().isValid(): |
|
160 self.plotsView.setCurrentIndex(self.plotsView.model().index(0, 0)) |
|
161 |
|
162 def openFiles(self, filenames): |
|
163 ok = False |
|
164 for filename in filenames: |
|
165 filename = str(filename) |
|
166 if os.path.exists(filename): |
|
167 data = open(filename).read() |
|
168 name = os.path.basename(filename) |
|
169 name = os.path.splitext(name)[0] |
|
170 lst = self.plotsView.model().stringList() |
|
171 lst.append(name) |
|
172 self._plots[name] = data |
|
173 self.plotsView.model().setStringList(lst) |
|
174 ok = True |
|
175 return ok |
|
176 |
|
177 def plotReceived(self, num): |
|
178 self._receiving = False |
|
179 self.setReceivingLed() |
|
180 plot, timestamp = self.captureThread.getPlot(num) |
|
181 name = "plot_%s"%(num) |
|
182 lst = self.plotsView.model().stringList() |
|
183 lst.append(name) |
|
184 self._plots[name] = plot |
|
185 self.plotsView.model().setStringList(lst) |
|
186 |
|
187 def plotStarted(self): |
|
188 self._receiving = True |
|
189 self.setReceivingLed() |
|
190 |
|
191 def saveTriggered(self, checked=False): |
|
192 print "save" |
|
193 |
|
194 def saveAsTriggered(self, checked=False): |
|
195 index = self.plotsView.selectionModel().currentIndex() |
|
196 if index.isValid(): |
|
197 filename = QtGui.QFileDialog.getSaveFileName(self, "Selecte a file name to save HPGL file", '.', 'HPGL files (*.plt)\nAll files (*)') |
|
198 n = index.row() |
|
199 value = unicode(self.plotsView.model().data(index, Qt.DisplayRole).toString()) |
|
200 open(filename, 'w').write(self._plots[value]) |
|
201 |
|
202 |
|
203 def initializeGPIB(self): |
|
204 self._online = False |
|
205 try: |
|
206 self.gpib_plotter = QGPIBplotter(device=self._prefs.device, |
|
207 address=self._prefs.address, |
|
208 ) |
|
209 self.captureThread = GPIBReceiver(self.gpib_plotter) |
|
210 self.connect(self.captureThread, SIGNAL('plotReceived(int)'), |
|
211 self.plotReceived) |
|
212 self.connect(self.captureThread, SIGNAL('plotStarted()'), |
|
213 self.plotStarted) |
|
214 self.captureThread.start() |
|
215 except Exception, e: |
|
216 #print e |
|
217 self.gpib_plotter = None |
|
218 self.setCaptureLed() |
|
219 |
|
220 def captureToggled(self, state): |
|
221 if state: |
|
222 if self.gpib_plotter is None: |
|
223 self.initializeGPIB() |
|
224 if self.gpib_plotter is None: |
|
225 QtGui.QMessageBox.critical(self, self.tr("GPIB error"), |
|
226 self.tr("<b>Unable to initialize GPIB connection</b>.<br>Please check your GPIB dongle and settings.")) |
|
227 self._online = False |
|
228 self.setCaptureLed() |
|
229 return |
|
230 self._online = True |
|
231 self.captureThread.startCapture() |
|
232 else: |
|
233 if self.captureThread: |
|
234 self.captureThread.stopCapture() |
|
235 self._online = False |
|
236 self.setCaptureLed() |
|
237 |
|
238 def setCaptureLed(self): |
|
239 if self._online: |
|
240 icn = QtGui.QIcon(':/icons/led_green.svg') |
|
241 else: |
|
242 icn = QtGui.QIcon(':/icons/led_green_off.svg') |
|
243 self.captureButton.setIcon(icn) |
|
244 self.captureButton.setChecked(self._online) |
|
245 |
|
246 def setReceivingLed(self): |
|
247 if self._receiving: |
|
248 icn = QtGui.QIcon(':/icons/led_red.svg') |
|
249 else: |
|
250 icn = QtGui.QIcon(':/icons/led_red_off.svg') |
|
251 self.receivingButton.setIcon(icn) |
|
252 |
|
253 class QGPIBplotter(GPIBplotter): |
|
254 def __init__(self, device="/dev/ttyUSB0", baudrate=115200, timeout=0.1, |
|
255 address=5): |
|
256 GPIBplotter.__init__(self, device, baudrate, timeout, address) |
|
257 self.emitter = None |
|
258 |
|
259 def plotStarted(self): |
|
260 if self.emitter: |
|
261 self.emitter.emit(SIGNAL('plotStarted()')) |
|
262 #self.emitter.msleep(1) |
|
263 |
|
264 class GPIBReceiver(QtCore.QThread): |
|
265 def __init__(self, cnx): |
|
266 QtCore.QThread.__init__(self) |
|
267 self.gpibplotter = cnx |
|
268 self.gpibplotter.emitter = self |
|
269 |
|
270 self._cancelmutex = QtCore.QMutex() |
|
271 self._cancel = False |
|
272 #self._nreceived = 0 |
|
273 self._plotsmutex = QtCore.QMutex() |
|
274 self._plots = [] |
|
275 self._startstopmutex = QtCore.QMutex() |
|
276 self._startstop = QtCore.QWaitCondition() |
|
277 self._capturing = False |
|
278 |
|
279 def cancel(self): |
|
280 self._cancelmutex.lock() |
|
281 self._cancel = True |
|
282 self._cancelmutex.unlock() |
|
283 |
|
284 def startCapture(self): |
|
285 self._startstop.wakeOne() |
|
286 |
|
287 def stopCapture(self): |
|
288 self._startstopmutex.lock() |
|
289 self._capturing = False |
|
290 self._startstopmutex.unlock() |
|
291 |
|
292 def run(self): |
|
293 while 1: |
|
294 self._cancelmutex.lock() |
|
295 if self._cancel: |
|
296 return |
|
297 self._cancelmutex.unlock() |
|
298 self._startstopmutex.lock() |
|
299 if not self._capturing: |
|
300 self._startstop.wait(self._startstopmutex) |
|
301 self._capturing = True |
|
302 self._startstopmutex.unlock() |
|
303 |
|
304 plot = self.gpibplotter.load_plot(wait_timeout=0.1) |
|
305 timestamp = time.time() |
|
306 if plot: |
|
307 self._plotsmutex.lock() |
|
308 self._plots.append((plot, timestamp)) |
|
309 n = len(self._plots) |
|
310 self._plotsmutex.unlock() |
|
311 self.emit(SIGNAL('plotReceived(int)'), n-1) |
|
312 self.msleep(10) |
|
313 |
|
314 def getPlot(self, num): |
|
315 self._plotsmutex.lock() |
|
316 try: |
|
317 return self._plots[num] |
|
318 finally: |
|
319 self._plotsmutex.unlock() |
|
320 |
|
321 def main(): |
|
322 import optparse |
|
323 opt = optparse.OptionParser('A simple PyQt4 HP7470A GPIB plotter emulator for USB-GPIB bundle (ProLogix)') |
|
324 opt.add_option('-m', '--mockup', default=False, |
|
325 action="store_true", |
|
326 dest='mockup', |
|
327 help='Use a pseudo GPIB connection (for test purpose)', |
|
328 ) |
|
329 opt.add_option('-v', '--verbose', default=False, |
|
330 action="store_true", |
|
331 dest="verbose", |
|
332 help="Verbose mode",) |
|
333 |
|
334 options, argv = opt.parse_args(sys.argv) |
|
335 |
|
336 a = QtGui.QApplication(argv) |
|
337 w = QtHPGLPlotter() |
|
338 files = [f for f in argv[1:] if os.path.isfile(f)] |
|
339 files.sort(cmp=str_num_cmp) |
|
340 if w.openFiles(files): |
|
341 w.displayFirst() |
|
342 |
|
343 w.show() |
|
344 a.exec_() |
|
345 |
|
346 if __name__ == '__main__': |
|
347 main() |
|