Dear Marcus,
Thank you very much for that advice. After thinking and reading a bit
more
based on what you wrote, I think I managed to solve my problem! I attach
a
brief example I wrote to try it out, including only the essential things
+
simple plotting.
Regarding the examples you mentioned: In my system (Linux Mint 17), to
be
able to check the examples you mentioned, I had to install the docs by
“sudo aptget python-qt4-doc” and they were then accessible at
“/usr/share/doc/python-qt4-doc/examples/network/threadedfortuneserver.py”.
I learned some good things here, but regarding threading I found this
page
even more useful (which I found once I realised what I was looking for,
thanks to you):
I also went ahead and implemented a simple graphics window as well, with
help of this page:
I am relatively new to Qt and threading, so I am sure I make mistakes
and
have bad coding style. But, since this simple test program works, I
thought
I might as well share it anyway. Since I myself find much information as
text online, I include in this email also below the code I wrote as text
(not as attached files), with hopes that it might be useful for someone
searching for related things online.
Best regards,
Eskil
SUMMARY of the test program:
Shows a window on screen where the user can click a button to start a
GNUradio flowgraph. The flowgraph will run in a separate thread to not
disturb the UI. When data has been generated by GNUradio, the user can
plot
the data on screen. Tested on Linux Mint 17 computer using gnuradio
3.7.2
(as available via apt-get) and relevant Python2/PyQt4 modules.
List of files included:
Main.py – The main program to be run by the user.
FlowGraph.grc – Flowgraph made with GNUradio-companion. Used to
generate
(in GRC) the file top_block.py.
top_block.py – File generated automatically by GRC. This contains all
GNURadio code for the flowgraph. Used in Main.py. When the flowgraph is
run, it will generate a file called “threadingdata.dat” with samples
from a
signal generator.
GUI.ui – Graphical user interface created with QtDesigner. Used to
generate GUI.py.
GUI.py – Generated from GUI.ui by running “pyuic4 GUI.ui > GUI.py” in
terminal. Used in Main.py.
PASTED CODE FOR EACH FILE BELOW:
-------Main.py-------
#!/usr/bin/env python
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as
FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg
as
NavigationToolbar
import matplotlib.pyplot as plt
from GUI import Ui_Dialog
from PyQt4 import QtGui, QtCore
import sys
from top_block import *
import numpy as np
Define object used to run GNUradio in a separate QThread, according
to:
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
@QtCore.pyqtSlot()
def runflowgraph(self):
self.tb.start()
self.tb.wait()
self.finished.emit()
Implement custom Thread class, according to:
class Thread(QtCore.QThread):
def init(self, parent=None):
QtCore.QThread.init(self, parent)
def start(self):
QtCore.QThread.start(self)
def run(self):
QtCore.QThread.run(self)
Define GUI, using the code generated by QtDesigner.
Made by running in terminal “pyuic4 GUI.ui > GUI.py” to get GUI.py
containing class Ui_Dialog.
class main_Dialog(Ui_Dialog, QtGui.QDialog):
def init(self):
super(main_Dialog, self).init()
self.setupUi(self)
self.init_Ui()
# Set states of interface and connect button signals
# to relevant slots.
def init_Ui(self):
self.testGUIbutton.clicked.connect(self.showGUIisalive)
self.getdatabutton.clicked.connect(self.runGNURadio)
self.plotresbutton.clicked.connect(self.plot)
# Do not allow plotting before data has been taken
self.plotresbutton.setEnabled(False)
# ADD MATPLOTLIB CANVAS, based on:
#
# a figure instance to plot on
self.figure = plt.figure()
# this is the Canvas Widget that displays the figure
# it takes the figure
instance as a parameter to init
self.canvas = FigureCanvas(self.figure)
self.canvas.setParent(self)
# set the layout
# Position as left, top, width, height
self.canvas.setGeometry(QtCore.QRect(400, 160, 271, 300))
# Show that GUI is responsive when this slot is activated by GUI.
def showGUIisalive(self):
self.logmsg('GUI alive!')
# Create worker object and QThread to handle GNURadio execution in
the
background
# without blocking the UI. This will run a simple flowgraph with a
# signal generator, generating a file with floating
# point numbers that can later be plotted.
# Based on
def runGNURadio(self):
self.plotresbutton.setEnabled(False)
self.getdatabutton.setEnabled(False)
self.obj = Worker()
self.obj.tb = top_block()
self.thread = Thread() # Create thread to run GNURadio in
background
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.thread.finished.connect(self.GNUradiofinished)
self.thread.started.connect(self.obj.runflowgraph)
self.logmsg(‘GNURadio started.’)
self.thread.start() # Will run the GNU radio flow graph
# Things to do when GNURadio is finished, such as enable plotting.
def GNUradiofinished(self):
self.logmsg('GNURadio finished.')
self.getdatabutton.setEnabled(True)
self.plotresbutton.setEnabled(True)
# Put message in textwindow
def logmsg(self, msg):
self.plainTextEdit.appendPlainText(QtCore.QDateTime.currentDateTime().toString()
-
': ’ + msg )
Plot data generated by GNUradio
def plot(self):
self.logmsg(‘Plotting 100 samples of threadingdata.dat’)
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# Read data from file saved by GNUradio (floats). Need to use
correct
# datatype to read numbers properly from GNUradio output.
data = np.memmap(‘threadingdata.dat’, mode = ‘r’,
dtype=np.float32)
# plot data, only first part to make it clear
ax.plot(data[0:100], ‘*-’)
# refresh canvas
self.canvas.draw()
Run main event loop and show GUI on screen.
def main():
app = QtGui.QApplication(sys.argv)
dialog = main_Dialog()
dialog.show()
sys.exit(app.exec_())
If this file is run (e.g. from terminal by “python Main.py”, then run
code, else act as library.
if name == ‘main’:
main()
-------FlowGraph.grc-------
<?xml version='1.0' encoding='ASCII'?>
<flow_graph>
Thu Jul 3 09:48:33 2014
options
id
top_block
_enabled
True
title
author
description
window_size
1280, 1024
generate_options
no_gui
category
Custom
run_options
run
run
True
max_nouts
0
realtime_scheduling
_coordinate
(10, 10)
_rotation
0
variable
id
time_seconds
_enabled
True
value
5
_coordinate
(299, 19)
_rotation
0
variable
id
samp_rate
_enabled
True
value
32000
_coordinate
(200, 16)
_rotation
0
analog_sig_source_x
id
analog_sig_source_x_0
_enabled
True
type
float
samp_rate
samp_rate
waveform
analog.GR_COS_WAVE
freq
1000
amp
1
offset
0
affinity
minoutbuf
0
maxoutbuf
0
_coordinate
(16, 112)
_rotation
0
blocks_throttle
id
blocks_throttle_0
_enabled
True
type
float
samples_per_second
samp_rate
vlen
1
affinity
minoutbuf
0
maxoutbuf
0
_coordinate
(211, 104)
_rotation
0
blocks_head
id
blocks_head_0
_enabled
True
type
float
num_items
time_seconds*samp_rate
vlen
1
affinity
minoutbuf
0
maxoutbuf
0
_coordinate
(240, 190)
_rotation
0
blocks_file_sink
id
blocks_file_sink_0
_enabled
True
file
threadingdata.dat
type
float
vlen
1
unbuffered
False
append
False
affinity
_coordinate
(202, 261)
_rotation
0
<source_block_id>blocks_head_0</source_block_id>
<sink_block_id>blocks_file_sink_0</sink_block_id>
<source_key>0</source_key>
<sink_key>0</sink_key>
<source_block_id>analog_sig_source_x_0</source_block_id>
<sink_block_id>blocks_throttle_0</sink_block_id>
<source_key>0</source_key>
<sink_key>0</sink_key>
<source_block_id>blocks_throttle_0</source_block_id>
<sink_block_id>blocks_head_0</sink_block_id>
<source_key>0</source_key>
<sink_key>0</sink_key>
</flow_graph>
-------top_block.py-------
#!/usr/bin/env python
##################################################
Gnuradio Python Flow Graph
Title: Top Block
Generated: Thu Jul 3 09:48:35 2014
##################################################
from gnuradio import analog
from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from optparse import OptionParser
class top_block(gr.top_block):
def __init__(self):
gr.top_block.__init__(self, "Top Block")
##################################################
# Variables
##################################################
self.time_seconds = time_seconds = 5
self.samp_rate = samp_rate = 32000
##################################################
# Blocks
##################################################
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1,
samp_rate)
self.blocks_head_0 = blocks.head(gr.sizeof_float1,
time_secondssamp_rate)
self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_float*1,
“threadingdata.dat”, False)
self.blocks_file_sink_0.set_unbuffered(False)
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate,
analog.GR_COS_WAVE, 1000, 1, 0)
##################################################
# Connections
##################################################
self.connect((self.blocks_head_0, 0), (self.blocks_file_sink_0,
0))
self.connect((self.analog_sig_source_x_0, 0),
(self.blocks_throttle_0, 0))
self.connect((self.blocks_throttle_0, 0), (self.blocks_head_0,
0))
QT sink close method reimplementation
def get_time_seconds(self):
return self.time_seconds
def set_time_seconds(self, time_seconds):
self.time_seconds = time_seconds
def get_samp_rate(self):
return self.samp_rate
def set_samp_rate(self, samp_rate):
self.samp_rate = samp_rate
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)
self.blocks_throttle_0.set_sample_rate(self.samp_rate)
if name == ‘main’:
parser = OptionParser(option_class=eng_option, usage=“%prog:
[options#]”)
(options, args) = parser.parse_args()
tb = top_block()
tb.start()
tb.wait()
-------GUI.ui-------
<?xml version="1.0" encoding="UTF-8"?>
Dialog
0
0
724
503
Dialog
50
50
201
41
Run GNU Radio in background
40
140
131
31
Test GUI (print)
380
50
131
31
Plotdata
40
230
71
16
Textoutput
390
110
58
15
Graph
40
260
311
171
-------GUI.py-------
# -*- coding: utf-8 -*-
Form implementation generated from reading ui file ‘GUI.ui’
Created: Thu Jul 3 11:17:09 2014
by: PyQt4 UI code generator 4.10.4
WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig,
_encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName(_fromUtf8(“Dialog”))
Dialog.resize(724, 503)
self.getdatabutton = QtGui.QPushButton(Dialog)
self.getdatabutton.setGeometry(QtCore.QRect(50, 50, 201, 41))
self.getdatabutton.setObjectName(_fromUtf8(“getdatabutton”))
self.testGUIbutton = QtGui.QPushButton(Dialog)
self.testGUIbutton.setGeometry(QtCore.QRect(40, 140, 131, 31))
self.testGUIbutton.setObjectName(_fromUtf8(“testGUIbutton”))
self.plotresbutton = QtGui.QPushButton(Dialog)
self.plotresbutton.setGeometry(QtCore.QRect(380, 50, 131, 31))
self.plotresbutton.setObjectName(_fromUtf8(“plotresbutton”))
self.textlabel = QtGui.QLabel(Dialog)
self.textlabel.setGeometry(QtCore.QRect(40, 230, 71, 16))
self.textlabel.setObjectName(_fromUtf8(“textlabel”))
self.graphlabel = QtGui.QLabel(Dialog)
self.graphlabel.setGeometry(QtCore.QRect(390, 110, 58, 15))
self.graphlabel.setObjectName(_fromUtf8(“graphlabel”))
self.plainTextEdit = QtGui.QPlainTextEdit(Dialog)
self.plainTextEdit.setGeometry(QtCore.QRect(40, 260, 311, 171))
self.plainTextEdit.setObjectName(_fromUtf8(“plainTextEdit”))
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(_translate("Dialog", "Dialog", None))
self.getdatabutton.setText(_translate("Dialog", "Run GNU Radio
in
background", None))
self.testGUIbutton.setText(_translate(“Dialog”, “Test GUI
(print)”,
None))
self.plotresbutton.setText(_translate(“Dialog”, “Plotdata”,
None))
self.textlabel.setText(_translate(“Dialog”, “Textoutput”, None))
self.graphlabel.setText(_translate(“Dialog”, “Graph”, None))
2014-07-02 21:07 GMT+02:00 Marcus Müller [email protected]: