]> sigrok.org Git - sigrok-meter.git/blame - sigrok-meter
Format the measured value in the GUI class.
[sigrok-meter.git] / sigrok-meter
CommitLineData
5add80f6
JS
1#!/usr/bin/env python
2
c09ca11b
UH
3##
4## This file is part of the sigrok-meter project.
5##
6## Copyright (C) 2013 Uwe Hermann <uwe@hermann-uwe.de>
73f2129a 7## Copyright (C) 2014 Jens Steinhauser <jens.steinhauser@gmail.com>
c09ca11b
UH
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
782f5926 24import argparse
73f2129a
JS
25import datetime
26import os.path
27import PyQt4.QtCore as QtCore
28import PyQt4.QtGui as QtGui
f94bb73f 29import re
efdef4fa 30import sigrok.core as sr
782f5926 31import sys
f94bb73f 32import textwrap
13e332b7 33
f94bb73f 34default_drivers = [('demo', {'analog_channels': 1})]
782f5926
JS
35default_loglevel = sr.LogLevel.WARN
36
73f2129a
JS
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.'''
50523e84 44 measured = QtCore.pyqtSignal(object)
73f2129a
JS
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:
50523e84 80 dev = '{} {}'.format(device.vendor, device.model)
73f2129a 81
50523e84
JS
82 # only send the most recent value
83 mag = packet.payload.data[0][-1]
73f2129a 84
50523e84
JS
85 self.measured.emit((dev, mag, packet.payload.unit,
86 packet.payload.mq_flags))
73f2129a
JS
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
50523e84
JS
135 self.inf = float('inf')
136
73f2129a
JS
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
50523e84
JS
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):
73f2129a
JS
251 '''Updates the labels with new measurement values.'''
252
50523e84
JS
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
73f2129a
JS
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)
2f5ef701 267
f94bb73f 268def parse_cli():
782f5926 269 parser = argparse.ArgumentParser(
f94bb73f
JS
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')
782f5926
JS
285 parser.add_argument('-l', '--loglevel',
286 type=int,
287 help='Set loglevel (5 is most verbose)')
288 args = parser.parse_args()
289
f94bb73f
JS
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
782f5926
JS
308 if args.loglevel != None:
309 try:
f94bb73f 310 result['loglevel'] = sr.LogLevel.get(args.loglevel)
782f5926
JS
311 except:
312 sys.exit('error: invalid log level')
313
f94bb73f
JS
314 return result
315
316if __name__ == '__main__':
317 args = parse_cli()
c09ca11b 318
73f2129a
JS
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)