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