]> sigrok.org Git - sigrok-meter.git/blame - samplingthread.py
Fix a bug with color picking.
[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 24import sigrok.core as sr
6c05c913 25import time
48723bbb
JS
26
27QtCore = qtcompat.QtCore
28QtGui = qtcompat.QtGui
29
30class SamplingThread(QtCore.QObject):
480cdb7b 31 '''Class that handles the reception of sigrok packets in the background.'''
48723bbb
JS
32
33 class Worker(QtCore.QObject):
34 '''Helper class that does the actual work in another thread.'''
35
36 '''Signal emitted when new data arrived.'''
6c05c913 37 measured = QtCore.Signal(float, sr.classes.Device, sr.classes.Channel, tuple)
48723bbb
JS
38
39 '''Signal emmited in case of an error.'''
40 error = QtCore.Signal(str)
41
42 def __init__(self, context, drivers):
43 super(self.__class__, self).__init__()
44
45 self.context = context
46 self.drivers = drivers
47
48 self.sampling = False
49
739a1d54 50 def parse_configstring(self, cs):
480cdb7b 51 '''Dissect a config string and return the options as a
739a1d54 52 dictionary.'''
dab07392
JS
53
54 def parse_option(k, v):
55 '''Parse the value for a single option.'''
56 try:
57 ck = sr.ConfigKey.get_by_identifier(k)
58 except:
59 raise ValueError('No option named "{}".'.format(k))
60
61 try:
62 val = ck.parse_string(v)
63 except:
64 raise ValueError(
739a1d54 65 'Invalid value "{}" for option "{}".'.format(v, k))
dab07392
JS
66
67 return (k, val)
68
739a1d54
JS
69 if not re.match('(([^:=]+=[^:=]+)(:[^:=]+=[^:=]+)*)?$', cs):
70 raise ValueError(
71 '"{}" is not a valid configuration string.'.format(cs))
dab07392 72
739a1d54
JS
73 if not cs:
74 return {}
75
76 opts = cs.split(':')
dab07392
JS
77 opts = [tuple(kv.split('=')) for kv in opts]
78 opts = [parse_option(k, v) for (k, v) in opts]
739a1d54
JS
79 return dict(opts)
80
81 def parse_driverstring(self, ds):
480cdb7b 82 '''Dissect the driver string and return a tuple consisting of
739a1d54 83 the driver name and the options (as a dictionary).'''
dab07392 84
739a1d54
JS
85 m = re.match('(?P<name>[^:]+)(?P<opts>(:[^:=]+=[^:=]+)*)$', ds)
86 if not m:
87 raise ValueError('"{}" is not a valid driver string.'.format(ds))
88
89 opts = m.group('opts')[1:]
90 return (m.group('name'), self.parse_configstring(opts))
dab07392 91
48723bbb
JS
92 @QtCore.Slot()
93 def start_sampling(self):
94 devices = []
739a1d54 95 for (ds, cs) in self.drivers:
480cdb7b 96 # Process driver string.
48723bbb 97 try:
dab07392
JS
98 (name, opts) = self.parse_driverstring(ds)
99 if not name in self.context.drivers:
480cdb7b 100 raise RuntimeError('No driver named "{}".'.format(name))
dab07392
JS
101
102 driver = self.context.drivers[name]
103 devs = driver.scan(**opts)
104 if not devs:
105 raise RuntimeError('No devices found.')
739a1d54
JS
106
107 device = devs[0]
dab07392 108 except Exception as e:
48723bbb 109 self.error.emit(
dab07392 110 'Error processing driver string:\n{}'.format(e))
48723bbb
JS
111 return
112
480cdb7b 113 # Process configuration string.
739a1d54
JS
114 try:
115 cfgs = self.parse_configstring(cs)
116 for k, v in cfgs.items():
117 device.config_set(sr.ConfigKey.get_by_identifier(k), v)
118 except Exception as e:
119 self.error.emit(
120 'Error processing configuration string:\n{}'.format(e))
121 return
122
123 devices.append(device)
124
48723bbb
JS
125 self.session = self.context.create_session()
126 for dev in devices:
127 self.session.add_device(dev)
128 dev.open()
129 self.session.add_datafeed_callback(self.callback)
130 self.session.start()
131 self.sampling = True
132 self.session.run()
133
134 # If sampling is 'True' here, it means that 'stop_sampling()' was
135 # not called, therefore 'session.run()' ended too early, indicating
136 # an error.
137 if self.sampling:
138 self.error.emit('An error occured during the acquisition.')
139
140 def stop_sampling(self):
141 if self.sampling:
142 self.sampling = False
143 self.session.stop()
144
145 def callback(self, device, packet):
6c05c913
JS
146 now = time.time()
147
48723bbb
JS
148 if not sr:
149 # In rare cases it can happen that the callback fires while
150 # the interpreter is shutting down. Then the sigrok module
151 # is already set to 'None'.
152 return
153
154 if packet.type != sr.PacketType.ANALOG:
155 return
156
157 if not len(packet.payload.channels):
158 return
159
160 # TODO: find a device with multiple channels in one packet
161 channel = packet.payload.channels[0]
162
480cdb7b 163 # The most recent value.
48723bbb
JS
164 value = packet.payload.data[0][-1]
165
6c05c913 166 self.measured.emit(now, device, channel,
48723bbb
JS
167 (value, packet.payload.unit, packet.payload.mq_flags))
168
480cdb7b 169 # Signal used to start the worker across threads.
48723bbb
JS
170 _start_signal = QtCore.Signal()
171
172 def __init__(self, context, drivers):
173 super(self.__class__, self).__init__()
174
175 self.worker = self.Worker(context, drivers)
176 self.thread = QtCore.QThread()
177 self.worker.moveToThread(self.thread)
178
179 self._start_signal.connect(self.worker.start_sampling)
180
480cdb7b 181 # Expose the signals of the worker.
48723bbb
JS
182 self.measured = self.worker.measured
183 self.error = self.worker.error
184
185 self.thread.start()
186
187 def start(self):
480cdb7b 188 '''Start sampling.'''
48723bbb
JS
189 self._start_signal.emit()
190
191 def stop(self):
480cdb7b 192 '''Stop sampling and stop the background thread.'''
48723bbb
JS
193 self.worker.stop_sampling()
194 self.thread.quit()
195 self.thread.wait()