Add PySide compatibility.
[sigrok-meter/gsi.git] / samplingthread.py
blobfa95f424c43d5bcaef3f5cbefa7e332a7fc9bcb9
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.
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
22 import qtcompat
23 import re
24 import sigrok.core as sr
25 import time
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
48 self.sampling = False
50 def parse_configstring(self, cs):
51 '''Dissect a config string and return the options as a
52 dictionary.'''
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))
61 try:
62 val = ck.parse_string(v)
63 except:
64 raise ValueError(
65 'Invalid value "{}" for option "{}".'.format(v, k))
67 return (k, val)
69 if not re.match('(([^:=]+=[^:=]+)(:[^:=]+=[^:=]+)*)?$', cs):
70 raise ValueError(
71 '"{}" is not a valid configuration string.'.format(cs))
73 if not cs:
74 return {}
76 opts = cs.split(':')
77 opts = [tuple(kv.split('=')) for kv in opts]
78 opts = [parse_option(k, v) for (k, v) in opts]
79 return dict(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)
86 if not m:
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))
92 @QtCore.Slot()
93 def start_sampling(self):
94 devices = []
95 for (ds, cs) in self.drivers:
96 # Process driver string.
97 try:
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)
104 if not devs:
105 raise RuntimeError('No devices found.')
107 device = devs[0]
108 except Exception as e:
109 self.error.emit(
110 'Error processing driver string:\n{}'.format(e))
111 return
113 # Process configuration string.
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
123 devices.append(device)
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()
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.')
140 def stop_sampling(self):
141 if self.sampling:
142 self.sampling = False
143 self.session.stop()
145 def callback(self, device, packet):
146 now = time.time()
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
154 if packet.type != sr.PacketType.ANALOG:
155 return
157 if not len(packet.payload.channels):
158 return
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
185 self.thread.start()
187 def start(self):
188 '''Start sampling.'''
189 self._start_signal.emit()
191 def stop(self):
192 '''Stop sampling and stop the background thread.'''
193 self.worker.stop_sampling()
194 self.thread.quit()
195 self.thread.wait()