]>
Commit | Line | Data |
---|---|---|
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 | ||
22 | import qtcompat | |
dab07392 | 23 | import re |
48723bbb JS |
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 | ||
dab07392 JS |
49 | def parse_driverstring(self, ds): |
50 | '''Dissects the driver string and returns a tuple consiting of | |
51 | the driver name and the options (as a 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 | m = re.match('(?P<name>[^:]+)(?P<opts>(:[^:=]+=[^:=]+)*)$', ds) | |
69 | if not m: | |
70 | raise ValueError('"{}" is not a valid driver string.'.format(ds)) | |
71 | ||
72 | opts = m.group('opts').split(':')[1:] | |
73 | opts = [tuple(kv.split('=')) for kv in opts] | |
74 | opts = [parse_option(k, v) for (k, v) in opts] | |
75 | opts = dict(opts) | |
76 | ||
77 | return (m.group('name'), opts) | |
78 | ||
48723bbb JS |
79 | @QtCore.Slot() |
80 | def start_sampling(self): | |
81 | devices = [] | |
dab07392 | 82 | for ds in self.drivers: |
48723bbb | 83 | try: |
dab07392 JS |
84 | (name, opts) = self.parse_driverstring(ds) |
85 | if not name in self.context.drivers: | |
86 | raise RuntimeError('No driver called "{}".'.format(name)) | |
87 | ||
88 | driver = self.context.drivers[name] | |
89 | devs = driver.scan(**opts) | |
90 | if not devs: | |
91 | raise RuntimeError('No devices found.') | |
92 | devices.append(devs[0]) | |
93 | except Exception as e: | |
48723bbb | 94 | self.error.emit( |
dab07392 | 95 | 'Error processing driver string:\n{}'.format(e)) |
48723bbb JS |
96 | return |
97 | ||
98 | self.session = self.context.create_session() | |
99 | for dev in devices: | |
100 | self.session.add_device(dev) | |
101 | dev.open() | |
102 | self.session.add_datafeed_callback(self.callback) | |
103 | self.session.start() | |
104 | self.sampling = True | |
105 | self.session.run() | |
106 | ||
107 | # If sampling is 'True' here, it means that 'stop_sampling()' was | |
108 | # not called, therefore 'session.run()' ended too early, indicating | |
109 | # an error. | |
110 | if self.sampling: | |
111 | self.error.emit('An error occured during the acquisition.') | |
112 | ||
113 | def stop_sampling(self): | |
114 | if self.sampling: | |
115 | self.sampling = False | |
116 | self.session.stop() | |
117 | ||
118 | def callback(self, device, packet): | |
119 | if not sr: | |
120 | # In rare cases it can happen that the callback fires while | |
121 | # the interpreter is shutting down. Then the sigrok module | |
122 | # is already set to 'None'. | |
123 | return | |
124 | ||
125 | if packet.type != sr.PacketType.ANALOG: | |
126 | return | |
127 | ||
128 | if not len(packet.payload.channels): | |
129 | return | |
130 | ||
131 | # TODO: find a device with multiple channels in one packet | |
132 | channel = packet.payload.channels[0] | |
133 | ||
134 | # the most recent value | |
135 | value = packet.payload.data[0][-1] | |
136 | ||
137 | self.measured.emit(device, channel, | |
138 | (value, packet.payload.unit, packet.payload.mq_flags)) | |
139 | ||
140 | # signal used to start the worker across threads | |
141 | _start_signal = QtCore.Signal() | |
142 | ||
143 | def __init__(self, context, drivers): | |
144 | super(self.__class__, self).__init__() | |
145 | ||
146 | self.worker = self.Worker(context, drivers) | |
147 | self.thread = QtCore.QThread() | |
148 | self.worker.moveToThread(self.thread) | |
149 | ||
150 | self._start_signal.connect(self.worker.start_sampling) | |
151 | ||
152 | # expose the signals of the worker | |
153 | self.measured = self.worker.measured | |
154 | self.error = self.worker.error | |
155 | ||
156 | self.thread.start() | |
157 | ||
158 | def start(self): | |
159 | '''Starts sampling''' | |
160 | self._start_signal.emit() | |
161 | ||
162 | def stop(self): | |
163 | '''Stops sampling and the background thread.''' | |
164 | self.worker.stop_sampling() | |
165 | self.thread.quit() | |
166 | self.thread.wait() |