]> sigrok.org Git - sigrok-meter.git/blame_incremental - sigrok-meter
Format the measured value in the GUI class.
[sigrok-meter.git] / sigrok-meter
... / ...
CommitLineData
1#!/usr/bin/env python
2
3##
4## This file is part of the sigrok-meter project.
5##
6## Copyright (C) 2013 Uwe Hermann <uwe@hermann-uwe.de>
7## Copyright (C) 2014 Jens Steinhauser <jens.steinhauser@gmail.com>
8##
9## This program is free software; you can redistribute it and/or modify
10## it under the terms of the GNU General Public License as published by
11## the Free Software Foundation; either version 2 of the License, or
12## (at your option) any later version.
13##
14## This program is distributed in the hope that it will be useful,
15## but WITHOUT ANY WARRANTY; without even the implied warranty of
16## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17## GNU General Public License for more details.
18##
19## You should have received a copy of the GNU General Public License
20## along with this program; if not, write to the Free Software
21## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22##
23
24import argparse
25import datetime
26import os.path
27import PyQt4.QtCore as QtCore
28import PyQt4.QtGui as QtGui
29import re
30import sigrok.core as sr
31import sys
32import textwrap
33
34default_drivers = [('demo', {'analog_channels': 1})]
35default_loglevel = sr.LogLevel.WARN
36
37class SamplingThread(QtCore.QObject):
38 '''A class that handles the reception of sigrok packets in the background.'''
39
40 class Worker(QtCore.QObject):
41 '''Helper class that does the actual work in another thread.'''
42
43 '''Signal emitted when new data arrived.'''
44 measured = QtCore.pyqtSignal(object)
45
46 def __init__(self, drivers, loglevel):
47 super(self.__class__, self).__init__()
48
49 self.context = sr.Context_create()
50 self.context.log_level = loglevel
51
52 self.sr_pkg_version = self.context.package_version
53 self.sr_lib_version = self.context.lib_version
54
55 self.devices = []
56 for name, options in drivers:
57 try:
58 dr = self.context.drivers[name]
59 self.devices.append(dr.scan(**options)[0])
60 except:
61 print('error getting device for driver "{}", skipping'.format(name))
62
63 if not self.devices:
64 print('no devices found')
65
66 def start_sampling(self):
67 self.session = self.context.create_session()
68 for dev in self.devices:
69 self.session.add_device(dev)
70 dev.open()
71 self.session.add_datafeed_callback(self.callback)
72 self.session.start()
73 self.session.run()
74
75 def stop_sampling(self):
76 self.session.stop()
77
78 def callback(self, device, packet):
79 if packet.type == sr.PacketType.ANALOG:
80 dev = '{} {}'.format(device.vendor, device.model)
81
82 # only send the most recent value
83 mag = packet.payload.data[0][-1]
84
85 self.measured.emit((dev, mag, packet.payload.unit,
86 packet.payload.mq_flags))
87
88 # wait a short time so that in any case we don't flood the GUI
89 # with new data (for example if the demo device is used)
90 self.thread().msleep(100)
91
92 # signal used to start the worker across threads
93 _start_signal = QtCore.pyqtSignal()
94
95 def __init__(self, drivers, loglevel):
96 super(self.__class__, self).__init__()
97
98 self.worker = self.Worker(drivers, loglevel)
99 self.thread = QtCore.QThread()
100 self.worker.moveToThread(self.thread)
101
102 self._start_signal.connect(self.worker.start_sampling)
103
104 self.measured = self.worker.measured
105
106 self.thread.start()
107
108 def start(self):
109 '''Starts sampling'''
110 self._start_signal.emit()
111
112 def stop(self):
113 '''Stops sampling and the background thread.'''
114 self.worker.stop_sampling()
115 self.thread.quit()
116 # the timeout is needed when the demo device is used, because it
117 # produces so much outstanding data that quitting takes a long time
118 self.thread.wait(500)
119
120 def sr_pkg_version(self):
121 '''Returns the version number of the libsigrok package.'''
122 return self.worker.sr_pkg_version
123
124 def sr_lib_version(self):
125 '''Returns the version number fo the libsigrok library.'''
126 return self.worker.sr_lib_version
127
128class SigrokMeter(QtGui.QMainWindow):
129 '''The main window of the application.'''
130
131 def __init__(self, thread):
132 super(SigrokMeter, self).__init__()
133 self.setup_ui()
134
135 self.inf = float('inf')
136
137 self.thread = thread
138 self.thread.measured.connect(self.update, QtCore.Qt.QueuedConnection)
139 self.thread.start()
140
141 def setup_ui(self):
142 self.setWindowTitle('sigrok-meter')
143 self.setMinimumHeight(130)
144 self.setMinimumWidth(260)
145
146 p = os.path.abspath(os.path.dirname(__file__))
147 p = os.path.join(p, 'sigrok-logo-notext.png')
148 self.setWindowIcon(QtGui.QIcon(p))
149
150 actionQuit = QtGui.QAction(self)
151 actionQuit.setText('&Quit')
152 actionQuit.setIcon(QtGui.QIcon.fromTheme('application-exit'))
153 actionQuit.setShortcut('Ctrl+Q')
154 actionQuit.triggered.connect(self.close)
155
156 actionAbout = QtGui.QAction(self)
157 actionAbout.setText('&About')
158 actionAbout.setIcon(QtGui.QIcon.fromTheme('help-about'))
159 actionAbout.triggered.connect(self.show_about)
160
161 menubar = self.menuBar()
162 menuFile = menubar.addMenu('&File')
163 menuFile.addAction(actionQuit)
164 menuHelp = menubar.addMenu('&Help')
165 menuHelp.addAction(actionAbout)
166
167 self.lblValue = QtGui.QLabel('waiting for data...')
168 self.lblValue.setAlignment(QtCore.Qt.AlignCenter)
169 font = self.lblValue.font()
170 font.setPointSize(font.pointSize() * 1.7)
171 font.setBold(True)
172 self.lblValue.setFont(font)
173 self.setCentralWidget(self.lblValue)
174 self.centralWidget().setContentsMargins(0, 0, 0, 0)
175
176 self.lblDevName = QtGui.QLabel()
177 self.lblDevName.setToolTip('Name of used measurement device.')
178 self.statusBar().insertWidget(0, self.lblDevName, 10)
179 self.lblTime = QtGui.QLabel()
180 self.lblTime.setToolTip('Time of the last measurement.')
181 self.statusBar().insertWidget(1, self.lblTime)
182
183 self.statusBar().setSizeGripEnabled(False)
184
185 def show_about(self):
186 text = textwrap.dedent('''\
187 <div align="center">
188 <b>sigrok-meter</b><br/>
189 0.1.0<br/>
190 Using libsigrok {} (lib version {}).<br/>
191 <a href='http://www.sigrok.org'>
192 http://www.sigrok.org</a><br/>
193 <br/>
194 This program comes with ABSOLUTELY NO WARRANTY;<br/>
195 for details visit
196 <a href='http://www.gnu.org/licenses/gpl.html'>
197 http://www.gnu.org/licenses/gpl.html</a>
198 </div>
199 '''.format(self.thread.sr_pkg_version(), self.thread.sr_lib_version()))
200
201 QtGui.QMessageBox.about(self, 'About sigrok-meter', text)
202
203 def format_unit(self, u):
204 units = {
205 sr.Unit.VOLT: 'V',
206 sr.Unit.AMPERE: 'A',
207 sr.Unit.OHM: u'\u03A9',
208 sr.Unit.FARAD: 'F',
209 sr.Unit.KELVIN: 'K',
210 sr.Unit.CELSIUS: u'\u00B0C',
211 sr.Unit.FAHRENHEIT: u'\u00B0F',
212 sr.Unit.HERTZ: 'Hz',
213 sr.Unit.PERCENTAGE: '%',
214 # sr.Unit.BOOLEAN
215 sr.Unit.SECOND: 's',
216 sr.Unit.SIEMENS: 'S',
217 sr.Unit.DECIBEL_MW: 'dBu',
218 sr.Unit.DECIBEL_VOLT: 'dBV',
219 # sr.Unit.UNITLESS
220 sr.Unit.DECIBEL_SPL: 'dB',
221 # sr.Unit.CONCENTRATION
222 sr.Unit.REVOLUTIONS_PER_MINUTE: 'rpm',
223 sr.Unit.VOLT_AMPERE: 'VA',
224 sr.Unit.WATT: 'W',
225 sr.Unit.WATT_HOUR: 'Wh',
226 sr.Unit.METER_SECOND: 'm/s',
227 sr.Unit.HECTOPASCAL: 'hPa',
228 sr.Unit.HUMIDITY_293K: '%rF',
229 sr.Unit.DEGREE: u'\u00B0',
230 sr.Unit.HENRY: 'H'
231 }
232
233 return units.get(u, '')
234
235 def format_mqflags(self, mqflags):
236 if sr.QuantityFlag.AC in mqflags:
237 s = 'AC'
238 elif sr.QuantityFlag.DC in mqflags:
239 s = 'DC'
240 else:
241 s = ''
242
243 return s
244
245 def format_mag(self, mag):
246 if mag == self.inf:
247 return u'\u221E'
248 return '{:f}'.format(mag)
249
250 def update(self, data):
251 '''Updates the labels with new measurement values.'''
252
253 device, mag, unit, mqflags = data
254
255 unit_str = self.format_unit(unit)
256 mqflags_str = self.format_mqflags(mqflags)
257 mag_str = self.format_mag(mag)
258 value = ' '.join([mag_str, unit_str, mqflags_str])
259
260 n = datetime.datetime.now().time()
261 now = '{:02}:{:02}:{:02}.{:03}'.format(
262 n.hour, n.minute, n.second, n.microsecond / 1000)
263
264 self.lblValue.setText(value)
265 self.lblDevName.setText(device)
266 self.lblTime.setText(now)
267
268def parse_cli():
269 parser = argparse.ArgumentParser(
270 description='Simple sigrok GUI for multimeters and dataloggers.',
271 epilog=textwrap.dedent('''\
272 The DRIVER string is the same as for sigrok-cli(1).
273
274 examples:
275
276 %(prog)s --driver tecpel-dmm-8061-ser:conn=/dev/ttyUSB0
277
278 %(prog)s --driver uni-t-ut61e:conn=1a86.e008
279 '''),
280 formatter_class=argparse.RawDescriptionHelpFormatter)
281
282 parser.add_argument('-d', '--driver',
283 action='append',
284 help='The driver to use')
285 parser.add_argument('-l', '--loglevel',
286 type=int,
287 help='Set loglevel (5 is most verbose)')
288 args = parser.parse_args()
289
290 result = {
291 'drivers': default_drivers,
292 'loglevel': default_loglevel
293 }
294
295 if args.driver:
296 result['drivers'] = []
297 for d in args.driver:
298 m = re.match('(?P<name>[^:]+)(?P<opts>(:[^:=]+=[^:=]+)*)', d)
299 if not m:
300 sys.exit('error parsing option "{}"'.format(d))
301
302 opts = m.group('opts').split(':')[1:]
303 opts = [tuple(kv.split('=')) for kv in opts]
304 opts = dict(opts)
305
306 result['drivers'].append((m.group('name'), opts))
307
308 if args.loglevel != None:
309 try:
310 result['loglevel'] = sr.LogLevel.get(args.loglevel)
311 except:
312 sys.exit('error: invalid log level')
313
314 return result
315
316if __name__ == '__main__':
317 args = parse_cli()
318
319 thread = SamplingThread(args['drivers'], args['loglevel'])
320
321 app = QtGui.QApplication([])
322 s = SigrokMeter(thread)
323 s.show()
324
325 r = app.exec_()
326 thread.stop()
327 sys.exit(r)