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()