]> sigrok.org Git - sigrok-meter.git/commitdiff
Remove the thread used for sampling.
authorJens Steinhauser <redacted>
Sat, 10 Oct 2015 14:45:25 +0000 (16:45 +0200)
committerJens Steinhauser <redacted>
Tue, 13 Oct 2015 18:53:00 +0000 (20:53 +0200)
Changes in libsigrok allow for doing everything in one (the main) thread
now.

acquisition.py [new file with mode: 0644]
mainwindow.py
samplingthread.py [deleted file]
test.py

diff --git a/acquisition.py b/acquisition.py
new file mode 100644 (file)
index 0000000..fdb1658
--- /dev/null
@@ -0,0 +1,144 @@
+##
+## This file is part of the sigrok-meter project.
+##
+## Copyright (C) 2013 Uwe Hermann <uwe@hermann-uwe.de>
+## Copyright (C) 2014 Jens Steinhauser <jens.steinhauser@gmail.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+##
+
+import qtcompat
+import re
+import sigrok.core as sr
+import time
+
+QtCore = qtcompat.QtCore
+
+class Acquisition(QtCore.QObject):
+    '''Class that handles the sigrok session and the reception of data.'''
+
+    '''Signal emitted when new data arrived.'''
+    measured = QtCore.Signal(float, sr.classes.Device, sr.classes.Channel, tuple)
+
+    '''Signal emitted when the session has stopped.'''
+    stopped = QtCore.Signal()
+
+    def __init__(self, context):
+        super(self.__class__, self).__init__()
+
+        self.context = context
+        self.session = self.context.create_session()
+        self.session.add_datafeed_callback(self._datafeed_callback)
+        self.session.set_stopped_callback(self._stopped_callback)
+
+    def _parse_configstring(self, cs):
+        '''Dissect a config string and return the options as a dictionary.'''
+
+        def parse_option(k, v):
+            '''Parse the value for a single option.'''
+            try:
+                ck = sr.ConfigKey.get_by_identifier(k)
+            except:
+                raise ValueError('No option named "{}".'.format(k))
+
+            try:
+                val = ck.parse_string(v)
+            except:
+                raise ValueError(
+                    'Invalid value "{}" for option "{}".'.format(v, k))
+
+            return (k, val)
+
+        if not re.match('(([^:=]+=[^:=]+)(:[^:=]+=[^:=]+)*)?$', cs):
+            raise ValueError(
+                '"{}" is not a valid configuration string.'.format(cs))
+
+        if not cs:
+            return {}
+
+        opts = cs.split(':')
+        opts = [tuple(kv.split('=')) for kv in opts]
+        opts = [parse_option(k, v) for (k, v) in opts]
+        return dict(opts)
+
+    def _parse_driverstring(self, ds):
+        '''Dissect the driver string and return a tuple consisting of
+        the driver name and the options (as a dictionary).'''
+
+        m = re.match('(?P<name>[^:]+)(?P<opts>(:[^:=]+=[^:=]+)*)$', ds)
+        if not m:
+            raise ValueError('"{}" is not a valid driver string.'.format(ds))
+
+        opts = m.group('opts')[1:]
+        return (m.group('name'), self._parse_configstring(opts))
+
+    def add_device(self, driverstring, configstring):
+        '''Add a device to the session.'''
+
+        # Process driver string.
+        (name, opts) = self._parse_driverstring(driverstring)
+        if not name in self.context.drivers:
+            raise RuntimeError('No driver named "{}".'.format(name))
+
+        driver = self.context.drivers[name]
+        devs = driver.scan(**opts)
+        if not devs:
+            raise RuntimeError('No devices found.')
+
+        device = devs[0]
+
+        # Process configuration string.
+        cfgs = self._parse_configstring(configstring)
+        for k, v in cfgs.items():
+            device.config_set(sr.ConfigKey.get_by_identifier(k), v)
+
+        self.session.add_device(device)
+        device.open()
+
+    def is_running(self):
+        '''Return whether the session is running.'''
+        return self.session.is_running()
+
+    @QtCore.Slot()
+    def start(self):
+        '''Start the session.'''
+        self.session.start()
+
+    @QtCore.Slot()
+    def stop(self):
+        '''Stop the session.'''
+        if self.is_running():
+            self.session.stop()
+
+    def _datafeed_callback(self, device, packet):
+        now = time.time()
+
+        if packet.type != sr.PacketType.ANALOG:
+            return
+
+        if not len(packet.payload.channels):
+            return
+
+        # TODO: find a device with multiple channels in one packet
+        channel = packet.payload.channels[0]
+
+        # The most recent value.
+        value = packet.payload.data[0][-1]
+
+        self.measured.emit(now, device, channel,
+                (value, packet.payload.unit, packet.payload.mq_flags))
+
+    def _stopped_callback(self, **kwargs):
+        self.stopped.emit()
index 7398a5a84c5da87bb255efcc186e524785c41f02..315d805ceed1d124931b2397b8b632f967df0db5 100644 (file)
 ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 ##
 
+import acquisition
 import datamodel
 import multiplotwidget
 import os.path
 import qtcompat
-import samplingthread
 import textwrap
 import time
 import util
@@ -61,7 +61,12 @@ class MainWindow(QtGui.QMainWindow):
     def __init__(self, context, drivers):
         super(self.__class__, self).__init__()
 
+        # Used to coordinate the stopping of the acquisition and
+        # the closing of the window.
+        self._closing = False
+
         self.context = context
+        self.drivers = drivers
 
         self.delegate = datamodel.MultimeterDelegate(self, self.font())
         self.model = datamodel.MeasurementDataModel(self)
@@ -69,10 +74,22 @@ class MainWindow(QtGui.QMainWindow):
 
         self.setup_ui()
 
-        self.thread = samplingthread.SamplingThread(self.context, drivers)
-        self.thread.measured.connect(self.model.update)
-        self.thread.error.connect(self.error)
-        self.thread.start()
+        QtCore.QTimer.singleShot(0, self._start_acquisition)
+
+    def _start_acquisition(self):
+        self.acquisition = acquisition.Acquisition(self.context)
+        self.acquisition.measured.connect(self.model.update)
+        self.acquisition.stopped.connect(self._stopped)
+
+        try:
+            for (ds, cs) in self.drivers:
+                self.acquisition.add_device(ds, cs)
+        except Exception as e:
+            QtGui.QMessageBox.critical(self, 'Error', str(e))
+            self.close()
+            return
+
+        self.acquisition.start()
 
     def setup_ui(self):
         self.setWindowTitle('sigrok-meter')
@@ -133,6 +150,10 @@ class MainWindow(QtGui.QMainWindow):
 
         self.startTimer(MainWindow.UPDATEINTERVAL)
 
+    def stop(self):
+        self.acquisition.stop()
+        print(self.acquisition.is_running())
+
     def _getPlot(self, unit):
         '''Looks up or creates a new plot for 'unit'.'''
 
@@ -228,9 +249,18 @@ class MainWindow(QtGui.QMainWindow):
                 if traceunit == plotunit:
                     trace.new = False
 
+    @QtCore.Slot()
+    def _stopped(self):
+        if self._closing:
+            self.close()
+
     def closeEvent(self, event):
-        self.thread.stop()
-        event.accept()
+        if self.acquisition.is_running():
+            self._closing = True
+            self.acquisition.stop()
+            event.ignore()
+        else:
+            event.accept()
 
     @QtCore.Slot()
     def show_about(self):
@@ -252,12 +282,6 @@ class MainWindow(QtGui.QMainWindow):
 
         QtGui.QMessageBox.about(self, 'About sigrok-meter', text)
 
-    @QtCore.Slot(str)
-    def error(self, msg):
-        '''Error handler for the sampling thread.'''
-        QtGui.QMessageBox.critical(self, 'Error', msg)
-        self.close()
-
     @QtCore.Slot(object, int, int)
     def modelRowsInserted(self, parent, start, end):
         '''Resize the list view to the size of the content.'''
diff --git a/samplingthread.py b/samplingthread.py
deleted file mode 100644 (file)
index fa95f42..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-##
-## This file is part of the sigrok-meter project.
-##
-## Copyright (C) 2013 Uwe Hermann <uwe@hermann-uwe.de>
-## Copyright (C) 2014 Jens Steinhauser <jens.steinhauser@gmail.com>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with this program; if not, write to the Free Software
-## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
-##
-
-import qtcompat
-import re
-import sigrok.core as sr
-import time
-
-QtCore = qtcompat.QtCore
-QtGui = qtcompat.QtGui
-
-class SamplingThread(QtCore.QObject):
-    '''Class that handles the reception of sigrok packets in the background.'''
-
-    class Worker(QtCore.QObject):
-        '''Helper class that does the actual work in another thread.'''
-
-        '''Signal emitted when new data arrived.'''
-        measured = QtCore.Signal(float, sr.classes.Device, sr.classes.Channel, tuple)
-
-        '''Signal emmited in case of an error.'''
-        error = QtCore.Signal(str)
-
-        def __init__(self, context, drivers):
-            super(self.__class__, self).__init__()
-
-            self.context = context
-            self.drivers = drivers
-
-            self.sampling = False
-
-        def parse_configstring(self, cs):
-            '''Dissect a config string and return the options as a
-            dictionary.'''
-
-            def parse_option(k, v):
-                '''Parse the value for a single option.'''
-                try:
-                    ck = sr.ConfigKey.get_by_identifier(k)
-                except:
-                    raise ValueError('No option named "{}".'.format(k))
-
-                try:
-                    val = ck.parse_string(v)
-                except:
-                    raise ValueError(
-                        'Invalid value "{}" for option "{}".'.format(v, k))
-
-                return (k, val)
-
-            if not re.match('(([^:=]+=[^:=]+)(:[^:=]+=[^:=]+)*)?$', cs):
-                raise ValueError(
-                    '"{}" is not a valid configuration string.'.format(cs))
-
-            if not cs:
-                return {}
-
-            opts = cs.split(':')
-            opts = [tuple(kv.split('=')) for kv in opts]
-            opts = [parse_option(k, v) for (k, v) in opts]
-            return dict(opts)
-
-        def parse_driverstring(self, ds):
-            '''Dissect the driver string and return a tuple consisting of
-            the driver name and the options (as a dictionary).'''
-
-            m = re.match('(?P<name>[^:]+)(?P<opts>(:[^:=]+=[^:=]+)*)$', ds)
-            if not m:
-                raise ValueError('"{}" is not a valid driver string.'.format(ds))
-
-            opts = m.group('opts')[1:]
-            return (m.group('name'), self.parse_configstring(opts))
-
-        @QtCore.Slot()
-        def start_sampling(self):
-            devices = []
-            for (ds, cs) in self.drivers:
-                # Process driver string.
-                try:
-                    (name, opts) = self.parse_driverstring(ds)
-                    if not name in self.context.drivers:
-                        raise RuntimeError('No driver named "{}".'.format(name))
-
-                    driver = self.context.drivers[name]
-                    devs = driver.scan(**opts)
-                    if not devs:
-                        raise RuntimeError('No devices found.')
-
-                    device = devs[0]
-                except Exception as e:
-                    self.error.emit(
-                        'Error processing driver string:\n{}'.format(e))
-                    return
-
-                # Process configuration string.
-                try:
-                    cfgs = self.parse_configstring(cs)
-                    for k, v in cfgs.items():
-                        device.config_set(sr.ConfigKey.get_by_identifier(k), v)
-                except Exception as e:
-                    self.error.emit(
-                        'Error processing configuration string:\n{}'.format(e))
-                    return
-
-                devices.append(device)
-
-            self.session = self.context.create_session()
-            for dev in devices:
-                self.session.add_device(dev)
-                dev.open()
-            self.session.add_datafeed_callback(self.callback)
-            self.session.start()
-            self.sampling = True
-            self.session.run()
-
-            # If sampling is 'True' here, it means that 'stop_sampling()' was
-            # not called, therefore 'session.run()' ended too early, indicating
-            # an error.
-            if self.sampling:
-                self.error.emit('An error occured during the acquisition.')
-
-        def stop_sampling(self):
-            if self.sampling:
-                self.sampling = False
-                self.session.stop()
-
-        def callback(self, device, packet):
-            now = time.time()
-
-            if not sr:
-                # In rare cases it can happen that the callback fires while
-                # the interpreter is shutting down. Then the sigrok module
-                # is already set to 'None'.
-                return
-
-            if packet.type != sr.PacketType.ANALOG:
-                return
-
-            if not len(packet.payload.channels):
-                return
-
-            # TODO: find a device with multiple channels in one packet
-            channel = packet.payload.channels[0]
-
-            # The most recent value.
-            value = packet.payload.data[0][-1]
-
-            self.measured.emit(now, device, channel,
-                    (value, packet.payload.unit, packet.payload.mq_flags))
-
-    # Signal used to start the worker across threads.
-    _start_signal = QtCore.Signal()
-
-    def __init__(self, context, drivers):
-        super(self.__class__, self).__init__()
-
-        self.worker = self.Worker(context, drivers)
-        self.thread = QtCore.QThread()
-        self.worker.moveToThread(self.thread)
-
-        self._start_signal.connect(self.worker.start_sampling)
-
-        # Expose the signals of the worker.
-        self.measured = self.worker.measured
-        self.error = self.worker.error
-
-        self.thread.start()
-
-    def start(self):
-        '''Start sampling.'''
-        self._start_signal.emit()
-
-    def stop(self):
-        '''Stop sampling and stop the background thread.'''
-        self.worker.stop_sampling()
-        self.thread.quit()
-        self.thread.wait()
diff --git a/test.py b/test.py
index 2adb108ab8bdb5e14627cba27e19d787e7a6b899..70b9f7458a0ddc4abd0604b62ae970627c1812a6 100755 (executable)
--- a/test.py
+++ b/test.py
 ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 ##
 
+import sigrok.core as sr
 import unittest
 
 if __name__ == '__main__':
     import qtcompat
     qtcompat.load_modules(False)
-    import samplingthread
+    import acquisition
 
 class TestDriverstringParsing(unittest.TestCase):
     def setUp(self):
-        self.w = samplingthread.SamplingThread.Worker(None, None)
+        self.context = sr.Context_create()
+        self.a = acquisition.Acquisition(self.context)
 
     def test_valid_driverstring(self):
         self.assertEqual(
-            self.w.parse_driverstring('demo'),
+            self.a._parse_driverstring('demo'),
             ('demo', {}))
         self.assertEqual(
-            self.w.parse_driverstring('demo:samplerate=1'),
+            self.a._parse_driverstring('demo:samplerate=1'),
             ('demo', {'samplerate': 1}))
         self.assertEqual(
-            self.w.parse_driverstring('demo:samplerate=1:analog_channels=1'),
+            self.a._parse_driverstring('demo:samplerate=1:analog_channels=1'),
             ('demo', {'samplerate': 1, 'analog_channels': 1}))
 
     def test_invalid_driverstring(self):
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, '')
+            self.a._parse_driverstring, '')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, ':')
+            self.a._parse_driverstring, ':')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, ':a')
+            self.a._parse_driverstring, ':a')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, 'd:a')
+            self.a._parse_driverstring, 'd:a')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, 'd:a=')
+            self.a._parse_driverstring, 'd:a=')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, 'd:a=:')
+            self.a._parse_driverstring, 'd:a=:')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, 'd:a=b:')
+            self.a._parse_driverstring, 'd:a=b:')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, 'd:=b:')
+            self.a._parse_driverstring, 'd:=b:')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, 'd:=:')
+            self.a._parse_driverstring, 'd:=:')
         self.assertRaisesRegexp(ValueError, 'is not a valid driver string',
-            self.w.parse_driverstring, 'd:=')
+            self.a._parse_driverstring, 'd:=')
 
 if __name__ == '__main__':
     unittest.main()