2 ## This file is part of the sigrok-meter project.
4 ## Copyright (C) 2013 Uwe Hermann <uwe@hermann-uwe.de>
5 ## Copyright (C) 2014 Jens Steinhauser <jens.steinhauser@gmail.com>
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.
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.
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
24 import sigrok.core as sr
27 QtCore = qtcompat.QtCore
28 QtGui = qtcompat.QtGui
30 class SamplingThread(QtCore.QObject):
31 '''Class that handles the reception of sigrok packets in the background.'''
33 class Worker(QtCore.QObject):
34 '''Helper class that does the actual work in another thread.'''
36 '''Signal emitted when new data arrived.'''
37 measured = QtCore.Signal(float, sr.classes.Device, sr.classes.Channel, tuple)
39 '''Signal emmited in case of an error.'''
40 error = QtCore.Signal(str)
42 def __init__(self, context, drivers):
43 super(self.__class__, self).__init__()
45 self.context = context
46 self.drivers = drivers
50 def parse_configstring(self, cs):
51 '''Dissect a config string and return the options as a
54 def parse_option(k, v):
55 '''Parse the value for a single option.'''
57 ck = sr.ConfigKey.get_by_identifier(k)
59 raise ValueError('No option named "{}".'.format(k))
62 val = ck.parse_string(v)
65 'Invalid value "{}" for option "{}".'.format(v, k))
69 if not re.match('(([^:=]+=[^:=]+)(:[^:=]+=[^:=]+)*)?$', cs):
71 '"{}" is not a valid configuration string.'.format(cs))
77 opts = [tuple(kv.split('=')) for kv in opts]
78 opts = [parse_option(k, v) for (k, v) in opts]
81 def parse_driverstring(self, ds):
82 '''Dissect the driver string and return a tuple consisting of
83 the driver name and the options (as a dictionary).'''
85 m = re.match('(?P<name>[^:]+)(?P<opts>(:[^:=]+=[^:=]+)*)$', ds)
87 raise ValueError('"{}" is not a valid driver string.'.format(ds))
89 opts = m.group('opts')[1:]
90 return (m.group('name'), self.parse_configstring(opts))
93 def start_sampling(self):
95 for (ds, cs) in self.drivers:
96 # Process driver string.
98 (name, opts) = self.parse_driverstring(ds)
99 if not name in self.context.drivers:
100 raise RuntimeError('No driver named "{}".'.format(name))
102 driver = self.context.drivers[name]
103 devs = driver.scan(**opts)
105 raise RuntimeError('No devices found.')
108 except Exception as e:
110 'Error processing driver string:\n{}'.format(e))
113 # Process configuration string.
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:
120 'Error processing configuration string:\n{}'.format(e))
123 devices.append(device)
125 self.session = self.context.create_session()
127 self.session.add_device(dev)
129 self.session.add_datafeed_callback(self.callback)
134 # If sampling is 'True' here, it means that 'stop_sampling()' was
135 # not called, therefore 'session.run()' ended too early, indicating
138 self.error.emit('An error occured during the acquisition.')
140 def stop_sampling(self):
142 self.sampling = False
145 def callback(self, device, packet):
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'.
154 if packet.type != sr.PacketType.ANALOG:
157 if not len(packet.payload.channels):
160 # TODO: find a device with multiple channels in one packet
161 channel = packet.payload.channels[0]
163 # The most recent value.
164 value = packet.payload.data[0][-1]
166 self.measured.emit(now, device, channel,
167 (value, packet.payload.unit, packet.payload.mq_flags))
169 # Signal used to start the worker across threads.
170 _start_signal = QtCore.Signal()
172 def __init__(self, context, drivers):
173 super(self.__class__, self).__init__()
175 self.worker = self.Worker(context, drivers)
176 self.thread = QtCore.QThread()
177 self.worker.moveToThread(self.thread)
179 self._start_signal.connect(self.worker.start_sampling)
181 # Expose the signals of the worker.
182 self.measured = self.worker.measured
183 self.error = self.worker.error
188 '''Start sampling.'''
189 self._start_signal.emit()
192 '''Stop sampling and stop the background thread.'''
193 self.worker.stop_sampling()