]> sigrok.org Git - sigrok-meter.git/blame - samplingthread.py
Better driver string handling and better error messages.
[sigrok-meter.git] / samplingthread.py
CommitLineData
48723bbb
JS
1##
2## This file is part of the sigrok-meter project.
3##
4## Copyright (C) 2013 Uwe Hermann <uwe@hermann-uwe.de>
5## Copyright (C) 2014 Jens Steinhauser <jens.steinhauser@gmail.com>
6##
7## This program is free software; you can redistribute it and/or modify
8## it under the terms of the GNU General Public License as published by
9## the Free Software Foundation; either version 2 of the License, or
10## (at your option) any later version.
11##
12## This program is distributed in the hope that it will be useful,
13## but WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15## GNU General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with this program; if not, write to the Free Software
19## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20##
21
22import qtcompat
dab07392 23import re
48723bbb
JS
24import sigrok.core as sr
25
26QtCore = qtcompat.QtCore
27QtGui = qtcompat.QtGui
28
29class SamplingThread(QtCore.QObject):
30 '''A class that handles the reception of sigrok packets in the background.'''
31
32 class Worker(QtCore.QObject):
33 '''Helper class that does the actual work in another thread.'''
34
35 '''Signal emitted when new data arrived.'''
36 measured = QtCore.Signal(object, object, object)
37
38 '''Signal emmited in case of an error.'''
39 error = QtCore.Signal(str)
40
41 def __init__(self, context, drivers):
42 super(self.__class__, self).__init__()
43
44 self.context = context
45 self.drivers = drivers
46
47 self.sampling = False
48
dab07392
JS
49 def parse_driverstring(self, ds):
50 '''Dissects the driver string and returns a tuple consiting of
51 the driver name and the options (as a dictionary).'''
52
53 def parse_option(k, v):
54 '''Parse the value for a single option.'''
55 try:
56 ck = sr.ConfigKey.get_by_identifier(k)
57 except:
58 raise ValueError('No option named "{}".'.format(k))
59
60 try:
61 val = ck.parse_string(v)
62 except:
63 raise ValueError(
64 'Invalid value "{}" for option "{}"'.format(v, k))
65
66 return (k, val)
67
68 m = re.match('(?P<name>[^:]+)(?P<opts>(:[^:=]+=[^:=]+)*)$', ds)
69 if not m:
70 raise ValueError('"{}" is not a valid driver string.'.format(ds))
71
72 opts = m.group('opts').split(':')[1:]
73 opts = [tuple(kv.split('=')) for kv in opts]
74 opts = [parse_option(k, v) for (k, v) in opts]
75 opts = dict(opts)
76
77 return (m.group('name'), opts)
78
48723bbb
JS
79 @QtCore.Slot()
80 def start_sampling(self):
81 devices = []
dab07392 82 for ds in self.drivers:
48723bbb 83 try:
dab07392
JS
84 (name, opts) = self.parse_driverstring(ds)
85 if not name in self.context.drivers:
86 raise RuntimeError('No driver called "{}".'.format(name))
87
88 driver = self.context.drivers[name]
89 devs = driver.scan(**opts)
90 if not devs:
91 raise RuntimeError('No devices found.')
92 devices.append(devs[0])
93 except Exception as e:
48723bbb 94 self.error.emit(
dab07392 95 'Error processing driver string:\n{}'.format(e))
48723bbb
JS
96 return
97
98 self.session = self.context.create_session()
99 for dev in devices:
100 self.session.add_device(dev)
101 dev.open()
102 self.session.add_datafeed_callback(self.callback)
103 self.session.start()
104 self.sampling = True
105 self.session.run()
106
107 # If sampling is 'True' here, it means that 'stop_sampling()' was
108 # not called, therefore 'session.run()' ended too early, indicating
109 # an error.
110 if self.sampling:
111 self.error.emit('An error occured during the acquisition.')
112
113 def stop_sampling(self):
114 if self.sampling:
115 self.sampling = False
116 self.session.stop()
117
118 def callback(self, device, packet):
119 if not sr:
120 # In rare cases it can happen that the callback fires while
121 # the interpreter is shutting down. Then the sigrok module
122 # is already set to 'None'.
123 return
124
125 if packet.type != sr.PacketType.ANALOG:
126 return
127
128 if not len(packet.payload.channels):
129 return
130
131 # TODO: find a device with multiple channels in one packet
132 channel = packet.payload.channels[0]
133
134 # the most recent value
135 value = packet.payload.data[0][-1]
136
137 self.measured.emit(device, channel,
138 (value, packet.payload.unit, packet.payload.mq_flags))
139
140 # signal used to start the worker across threads
141 _start_signal = QtCore.Signal()
142
143 def __init__(self, context, drivers):
144 super(self.__class__, self).__init__()
145
146 self.worker = self.Worker(context, drivers)
147 self.thread = QtCore.QThread()
148 self.worker.moveToThread(self.thread)
149
150 self._start_signal.connect(self.worker.start_sampling)
151
152 # expose the signals of the worker
153 self.measured = self.worker.measured
154 self.error = self.worker.error
155
156 self.thread.start()
157
158 def start(self):
159 '''Starts sampling'''
160 self._start_signal.emit()
161
162 def stop(self):
163 '''Stops sampling and the background thread.'''
164 self.worker.stop_sampling()
165 self.thread.quit()
166 self.thread.wait()