diff options
author | Johnathan Corgan <johnathan@corganlabs.com> | 2014-05-06 13:40:15 -0700 |
---|---|---|
committer | Johnathan Corgan <johnathan@corganlabs.com> | 2014-05-06 13:40:15 -0700 |
commit | ddb4c1d744b417e79cf2d263d6cd8db37c4dbcb2 (patch) | |
tree | 3855d9b0d071ab07246c6779c0c5554ba2d943b9 /gr-zeromq/examples/python | |
parent | 73a6083b84dd3506bf4fdfbcb15bfd9a7033c98c (diff) |
zeromq: cleanup docs and examples
Diffstat (limited to 'gr-zeromq/examples/python')
-rw-r--r-- | gr-zeromq/examples/python/README | 71 | ||||
-rwxr-xr-x | gr-zeromq/examples/python/client.py | 116 | ||||
-rwxr-xr-x | gr-zeromq/examples/python/fixui4py.sh | 2 | ||||
-rwxr-xr-x | gr-zeromq/examples/python/gui.py | 164 | ||||
-rw-r--r-- | gr-zeromq/examples/python/main_window.ui | 199 | ||||
-rwxr-xr-x | gr-zeromq/examples/python/run_app.sh | 4 | ||||
-rwxr-xr-x | gr-zeromq/examples/python/server.py | 125 |
7 files changed, 681 insertions, 0 deletions
diff --git a/gr-zeromq/examples/python/README b/gr-zeromq/examples/python/README new file mode 100644 index 0000000000..2bc2dc8a52 --- /dev/null +++ b/gr-zeromq/examples/python/README @@ -0,0 +1,71 @@ +THIS IS EXPERIMENTAL SOFTWARE AND API IS SUBJECT TO CHANGE + +How to run the example +---------------------- + +Assuming that the module has been compiled but not installed, in the +gr-zeromq folder do + + cd examples + ./run_app.sh server + +on another terminal or machine + + ./run_app.sh client -s hostname + +You can also run a (remote) GUI on any of the two or a third machine for monitoring and control. + + ./run_app.sh gui.py -s servername -c hostname + +in doing so the order of starting the scripts is arbitrary. When installing the +module, the run\_app.sh script is of course not needed. + + +How to use the API +------------------ + +### PROBE API +Connect a zmq pubsub sink to the block you want to monitor + + self.zmq_probe = zeromq.sink_pubsub(gr.sizeof_float, "tcp://*:5556") + +add a probe manager to your Python GUI + + # ZeroMQ + probe_manager = zeromq.probe_manager() + probe_manager.add_socket("tcp://localhost:5556", + 'float32', self.plot_data) + + def plot_data(self,samples): + [...] + +basically creates a watcher thread that calls the call back functions and +unpacks sample data. Now you can use a timer to update the plot, e.g. in PyQt + + update_timer = Qt.QTimer() + self.connect(update_timer, + QtCore.SIGNAL("timeout()"), + probe_manager.watcher) + update_timer.start(30) + +### RPC API +Add an rpc manager to your Python app to receive RPCs + + rpc_manager = zeromq.rpc_manager() + rpc_manager.set_reply_socket("tcp://*:6666") + rpc_manager.add_interface("start_fg",self.start) + rpc_manager.start_watcher() + +to be able to send requests also add one on the other side + + rpc_manager = zeromq.rpc_manager() + rpc_manager.set_request_socket("tcp://localhost:6666") + +send a request + + rpc_mganager.request("start_fg") + rpc_mgr_server.request("set_k",gain) + +RPCs use GNU Radio pmt's to serialize arguments, the watcher thread will +regularly poll for incoming RPC requests, deserializes arguments and call the +interface callback function accordingly. diff --git a/gr-zeromq/examples/python/client.py b/gr-zeromq/examples/python/client.py new file mode 100755 index 0000000000..3d55cc9a98 --- /dev/null +++ b/gr-zeromq/examples/python/client.py @@ -0,0 +1,116 @@ +# +# Copyright 2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio. +# +# This 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 3, or (at your option) +# any later version. +# +# This software 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 software; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +############################################################################### +# Imports +############################################################################### +from gnuradio import zeromq +#import zeromq +from gnuradio import gr +from gnuradio import blocks +from gnuradio import analog +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser +import numpy +import sys +from threading import Thread +import time + +############################################################################### +# GNU Radio top_block +############################################################################### +class top_block(gr.top_block): + def __init__(self, options): + gr.top_block.__init__(self) + + self.options = options + + # socket addresses + rpc_adr = "tcp://*:6667" + probe_adr = "tcp://*:5557" + source_adr = "tcp://"+self.options.servername+":5555" + + # blocks + self.zmq_source = zeromq.req_source(gr.sizeof_float, 1, source_adr) + #self.zmq_source = zeromq.pull_source(gr.sizeof_float, 1, source_adr) + #self.zmq_probe = zeromq.push_sink(gr.sizeof_float,probe_adr) + self.zmq_probe = zeromq.pub_sink(gr.sizeof_float,probe_adr) + + # connects + self.connect(self.zmq_source, self.zmq_probe) + + # ZeroMQ + self.rpc_manager = zeromq.rpc_manager() + self.rpc_manager.set_reply_socket(rpc_adr) + self.rpc_manager.add_interface("start_fg",self.start_fg) + self.rpc_manager.add_interface("stop_fg",self.stop_fg) + self.rpc_manager.start_watcher() + + def start_fg(self): + print "Start Flowgraph" + try: + self.start() + except RuntimeError: + print "Can't start, flowgraph already running!" + + def stop_fg(self): + print "Stop Flowgraph" + self.stop() + self.wait() + +############################################################################### +# Options Parser +############################################################################### +def parse_options(): + """ Options parser. """ + parser = OptionParser(option_class=eng_option, usage="%prog: [options]") + parser.add_option("-s", "--servername", type="string", default="localhost", + help="Server hostname") + (options, args) = parser.parse_args() + return options + +############################################################################### +# Waiter Thread +############################################################################### +class waiter(Thread): + """ To keep the program alive when flowgraph is stopped. """ + def run(self): + while keep_running: + time.sleep(1) + +############################################################################### +# Main +############################################################################### +if __name__ == "__main__": + options = parse_options() + tb = top_block(options) + try: + # keep the program running when flowgraph is stopped + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + print "Shutting down flowgraph." + tb.rpc_manager.stop_watcher() + tb.stop() + tb.wait() + tb = None diff --git a/gr-zeromq/examples/python/fixui4py.sh b/gr-zeromq/examples/python/fixui4py.sh new file mode 100755 index 0000000000..d2978000bf --- /dev/null +++ b/gr-zeromq/examples/python/fixui4py.sh @@ -0,0 +1,2 @@ +#!/bin/bash +sed -i 's/qwt_plot.h/PyQt4.Qwt5.Qwt/' $1 diff --git a/gr-zeromq/examples/python/gui.py b/gr-zeromq/examples/python/gui.py new file mode 100755 index 0000000000..91223dd734 --- /dev/null +++ b/gr-zeromq/examples/python/gui.py @@ -0,0 +1,164 @@ +# +# Copyright 2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio. +# +# This 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 3, or (at your option) +# any later version. +# +# This software 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 software; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +############################################################################### +# Imports +############################################################################### +from optparse import OptionParser +from gnuradio.eng_option import eng_option +import gui +import sys +import os +from PyQt4 import Qt, QtGui, QtCore, uic +import PyQt4.Qwt5 as Qwt +from gnuradio import zeromq +import signal + +class gui(QtGui.QMainWindow): + def __init__(self, window_name, options, parent=None): + QtGui.QMainWindow.__init__(self, parent) + + # give Ctrl+C back to system + signal.signal(signal.SIGINT, signal.SIG_DFL) + + self.gui = uic.loadUi(os.path.join(os.path.dirname(__file__),'main_window.ui'), self) + + self.update_timer = Qt.QTimer() + + # socket addresses + rpc_adr_server = "tcp://"+options.servername+":6666" + rpc_adr_client = "tcp://"+options.clientname+":6667" + probe_adr_server = "tcp://"+options.servername+":5556" + probe_adr_client = "tcp://"+options.clientname+":5557" + + # ZeroMQ + self.probe_manager = zeromq.probe_manager() + self.probe_manager.add_socket(probe_adr_server, 'float32', self.plot_data_server) + self.probe_manager.add_socket(probe_adr_client, 'float32', self.plot_data_client) + + self.rpc_mgr_server = zeromq.rpc_manager() + self.rpc_mgr_server.set_request_socket(rpc_adr_server) + self.rpc_mgr_client = zeromq.rpc_manager() + self.rpc_mgr_client.set_request_socket(rpc_adr_client) + + self.gui.setWindowTitle(window_name) + self.gui.qwtPlotServer.setTitle("Signal Scope") + self.gui.qwtPlotServer.setAxisTitle(Qwt.QwtPlot.xBottom, "Samples") + self.gui.qwtPlotServer.setAxisTitle(Qwt.QwtPlot.yLeft, "Amplitude") + self.gui.qwtPlotServer.setAxisScale(Qwt.QwtPlot.xBottom, 0, 100) + self.gui.qwtPlotServer.setAxisScale(Qwt.QwtPlot.yLeft, -2, 2) + self.gui.qwtPlotClient.setTitle("Signal Scope") + self.gui.qwtPlotClient.setAxisTitle(Qwt.QwtPlot.xBottom, "Samples") + self.gui.qwtPlotClient.setAxisTitle(Qwt.QwtPlot.yLeft, "Amplitude") + self.gui.qwtPlotClient.setAxisScale(Qwt.QwtPlot.xBottom, 0, 100) + self.gui.qwtPlotClient.setAxisScale(Qwt.QwtPlot.yLeft, -2, 2) + + # Grid + pen = Qt.QPen(Qt.Qt.DotLine) + pen.setColor(Qt.Qt.black) + pen.setWidth(0) + grid_server = Qwt.QwtPlotGrid() + grid_client = Qwt.QwtPlotGrid() + grid_server.setPen(pen) + grid_client.setPen(pen) + grid_server.attach(self.gui.qwtPlotServer) + grid_client.attach(self.gui.qwtPlotClient) + + #Signals + self.connect(self.update_timer, QtCore.SIGNAL("timeout()"), self.probe_manager.watcher) + self.connect(self.gui.pushButtonRunServer, QtCore.SIGNAL("clicked()"), self.start_fg_server) + self.connect(self.gui.pushButtonStopServer, QtCore.SIGNAL("clicked()"), self.stop_fg_server) + self.connect(self.gui.pushButtonRunClient, QtCore.SIGNAL("clicked()"), self.start_fg_client) + self.connect(self.gui.pushButtonStopClient, QtCore.SIGNAL("clicked()"), self.stop_fg_client) + self.connect(self.gui.comboBox, QtCore.SIGNAL("currentIndexChanged(QString)"), self.set_waveform) + self.connect(self.gui.spinBox, QtCore.SIGNAL("valueChanged(int)"), self.set_gain) + self.shortcut_start = QtGui.QShortcut(Qt.QKeySequence("Ctrl+S"), self.gui) + self.shortcut_stop = QtGui.QShortcut(Qt.QKeySequence("Ctrl+C"), self.gui) + self.shortcut_exit = QtGui.QShortcut(Qt.QKeySequence("Ctrl+D"), self.gui) + self.connect(self.shortcut_exit, QtCore.SIGNAL("activated()"), self.gui.close) + + # start update timer + self.update_timer.start(30) + + def start_fg_server(self): + self.rpc_mgr_server.request("start_fg") + + def stop_fg_server(self): + self.rpc_mgr_server.request("stop_fg") + + def start_fg_client(self): + self.rpc_mgr_client.request("start_fg") + + def stop_fg_client(self): + self.rpc_mgr_client.request("stop_fg") + + # plot the data from the queues + def plot_data(self, plot, samples): + self.x = range(0,len(samples),1) + self.y = samples + # clear the previous points from the plot + plot.clear() + # draw curve with new points and plot + curve = Qwt.QwtPlotCurve() + curve.setPen(Qt.QPen(Qt.Qt.blue, 2)) + curve.attach(plot) + curve.setData(self.x, self.y) + plot.replot() + + def plot_data_server(self, samples): + self.plot_data(self.gui.qwtPlotServer, samples) + + def plot_data_client(self, samples): + self.plot_data(self.gui.qwtPlotClient, samples) + + def set_waveform(self, waveform_str): + self.rpc_mgr_server.request("set_waveform",str(waveform_str)) + + def set_gain(self, gain): + self.rpc_set_gain(gain) + + def rpc_set_gain(self, gain): + self.rpc_mgr_server.request("set_k",gain) + +############################################################################### +# Options Parser +############################################################################### +def parse_options(): + """ Options parser. """ + parser = OptionParser(option_class=eng_option, usage="%prog: [options]") + parser.add_option("-s", "--servername", type="string", default="localhost", + help="Server hostname") + parser.add_option("-c", "--clientname", type="string", default="localhost", + help="Server hostname") + (options, args) = parser.parse_args() + return options + + +############################################################################### +# Main +############################################################################### +if __name__ == "__main__": + options = parse_options() + qapp = Qt.QApplication(sys.argv) + qapp.main_window = gui("Remote GNU Radio GUI",options) + qapp.main_window.show() + qapp.exec_() + diff --git a/gr-zeromq/examples/python/main_window.ui b/gr-zeromq/examples/python/main_window.ui new file mode 100644 index 0000000000..14f810e67d --- /dev/null +++ b/gr-zeromq/examples/python/main_window.ui @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ControlPort_Example</class> + <widget class="QMainWindow" name="ControlPort_Example"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1000</width> + <height>600</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="windowTitle"> + <string>ControlPort Example</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="2"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Client</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="1" column="0"> + <widget class="QPushButton" name="pushButtonRunClient"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QwtPlot" name="qwtPlotClient"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="pushButtonStopClient"> + <property name="text"> + <string>Stop</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1" rowspan="4"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item row="2" column="0"> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Waveform:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="comboBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="currentIndex"> + <number>1</number> + </property> + <item> + <property name="text"> + <string>Constant</string> + </property> + </item> + <item> + <property name="text"> + <string>Sine</string> + </property> + </item> + <item> + <property name="text"> + <string>Cosine</string> + </property> + </item> + <item> + <property name="text"> + <string>Square</string> + </property> + </item> + <item> + <property name="text"> + <string>Triangle</string> + </property> + </item> + <item> + <property name="text"> + <string>Saw Tooth</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Gain:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="spinBox"> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Server</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <widget class="QPushButton" name="pushButtonRunServer"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="pushButtonStopServer"> + <property name="text"> + <string>Stop</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QwtPlot" name="qwtPlotServer"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1000</width> + <height>25</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + </widget> + <customwidgets> + <customwidget> + <class>QwtPlot</class> + <extends>QFrame</extends> + <header>PyQt4.Qwt5.Qwt</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/gr-zeromq/examples/python/run_app.sh b/gr-zeromq/examples/python/run_app.sh new file mode 100755 index 0000000000..4af936ca82 --- /dev/null +++ b/gr-zeromq/examples/python/run_app.sh @@ -0,0 +1,4 @@ +#!/bin/sh +export LD_LIBRARY_PATH="$PWD/../../build/gr-zeromq/lib" +export PYTHONPATH="$PWD/../../build/gr-zeromq/swig:$PWD/../../python" +/usr/bin/python $1 $2 $3 $4 $5 $6 $7 $8 $9 diff --git a/gr-zeromq/examples/python/server.py b/gr-zeromq/examples/python/server.py new file mode 100755 index 0000000000..28b732ed3e --- /dev/null +++ b/gr-zeromq/examples/python/server.py @@ -0,0 +1,125 @@ +# +# Copyright 2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio. +# +# This 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 3, or (at your option) +# any later version. +# +# This software 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 software; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +############################################################################### +# Imports +############################################################################### +from gnuradio import zeromq +from gnuradio import gr +from gnuradio import blocks +from gnuradio import analog +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser +import numpy +import sys +from threading import Thread +import time + + +############################################################################### +# GNU Radio top_block +############################################################################### +class top_block(gr.top_block): + def __init__(self, options): + gr.top_block.__init__(self) + + self.options = options + + # socket addresses + rpc_adr = "tcp://*:6666" + probe_adr = "tcp://*:5556" + sink_adr = "tcp://*:5555" + + # the strange sampling rate gives a nice movement in the plot :P + self.samp_rate = samp_rate = 48200 + + # blocks + self.gr_sig_source = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE , 1000, 1, 0) + self.throttle = blocks.throttle(gr.sizeof_float, samp_rate) + self.mult = blocks.multiply_const_ff(1) + self.zmq_sink = zeromq.rep_sink(gr.sizeof_float, 1, sink_adr) + #self.zmq_sink = zeromq.push_sink(gr.sizeof_float, 1, sink_adr) + #self.zmq_probe = zeromq.push_sink(gr.sizeof_float, probe_adr) + self.zmq_probe = zeromq.pub_sink(gr.sizeof_float, probe_adr) + #self.null_sink = blocks.null_sink(gr.sizeof_float) + + # connects + self.connect(self.gr_sig_source, self.mult, self.throttle, self.zmq_sink) + self.connect(self.throttle, self.zmq_probe) + + # ZeroMQ + self.rpc_manager = zeromq.rpc_manager() + self.rpc_manager.set_reply_socket(rpc_adr) + self.rpc_manager.add_interface("start_fg",self.start_fg) + self.rpc_manager.add_interface("stop_fg",self.stop_fg) + self.rpc_manager.add_interface("set_waveform",self.set_waveform) + self.rpc_manager.add_interface("set_k",self.mult.set_k) + self.rpc_manager.add_interface("get_sample_rate",self.throttle.sample_rate) + self.rpc_manager.start_watcher() + + def start_fg(self): + print "Start Flowgraph" + try: + self.start() + except RuntimeError: + print "Can't start, flowgraph already running!" + + def stop_fg(self): + print "Stop Flowgraph" + self.stop() + self.wait() + + def set_waveform(self, waveform_str): + waveform = {'Constant' : analog.GR_CONST_WAVE, + 'Sine' : analog.GR_SIN_WAVE, + 'Cosine' : analog.GR_COS_WAVE, + 'Square' : analog.GR_SQR_WAVE, + 'Triangle' : analog.GR_TRI_WAVE, + 'Saw Tooth' : analog.GR_SAW_WAVE}[waveform_str] + self.gr_sig_source.set_waveform(waveform) + +############################################################################### +# Options Parser +############################################################################### +def parse_options(): + """ Options parser. """ + parser = OptionParser(option_class=eng_option, usage="%prog: [options]") + (options, args) = parser.parse_args() + return options + +############################################################################### +# Main +############################################################################### +if __name__ == "__main__": + options = parse_options() + tb = top_block(options) + try: + # keep the program running when flowgraph is stopped + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + print "Shutting down flowgraph." + tb.rpc_manager.stop_watcher() + tb.stop() + tb.wait() + tb = None |