]> sigrok.org Git - sigrok-meter.git/blob - samplingthread.py
Add a '--config' command line option.
[sigrok-meter.git] / samplingthread.py
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
22 import qtcompat
23 import re
24 import sigrok.core as sr
25
26 QtCore = qtcompat.QtCore
27 QtGui = qtcompat.QtGui
28
29 class 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
49         def parse_configstring(self, cs):
50             '''Dissects a config string and returns the options as a
51             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             if not re.match('(([^:=]+=[^:=]+)(:[^:=]+=[^:=]+)*)?$', cs):
69                 raise ValueError(
70                     '"{}" is not a valid configuration string.'.format(cs))
71
72             if not cs:
73                 return {}
74
75             opts = cs.split(':')
76             opts = [tuple(kv.split('=')) for kv in opts]
77             opts = [parse_option(k, v) for (k, v) in opts]
78             return dict(opts)
79
80         def parse_driverstring(self, ds):
81             '''Dissects the driver string and returns a tuple consiting of
82             the driver name and the options (as a dictionary).'''
83
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))
90
91         @QtCore.Slot()
92         def start_sampling(self):
93             devices = []
94             for (ds, cs) in self.drivers:
95                 # process driver string
96                 try:
97                     (name, opts) = self.parse_driverstring(ds)
98                     if not name in self.context.drivers:
99                         raise RuntimeError('No driver called "{}".'.format(name))
100
101                     driver = self.context.drivers[name]
102                     devs = driver.scan(**opts)
103                     if not devs:
104                         raise RuntimeError('No devices found.')
105
106                     device = devs[0]
107                 except Exception as e:
108                     self.error.emit(
109                         'Error processing driver string:\n{}'.format(e))
110                     return
111
112                 # process configuration string
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
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
160             # the most recent value
161             value = packet.payload.data[0][-1]
162
163             self.measured.emit(device, channel,
164                     (value, packet.payload.unit, packet.payload.mq_flags))
165
166     # signal used to start the worker across threads
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
178         # expose the signals of the worker
179         self.measured = self.worker.measured
180         self.error = self.worker.error
181
182         self.thread.start()
183
184     def start(self):
185         '''Starts sampling'''
186         self._start_signal.emit()
187
188     def stop(self):
189         '''Stops sampling and the background thread.'''
190         self.worker.stop_sampling()
191         self.thread.quit()
192         self.thread.wait()