[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Commit-gnuradio] [gnuradio] 08/50: controlport: adding abstraction laye
From: |
git |
Subject: |
[Commit-gnuradio] [gnuradio] 08/50: controlport: adding abstraction layer for the controlport backends; support thrift currently. |
Date: |
Wed, 15 Apr 2015 21:07:51 +0000 (UTC) |
This is an automated email from the git hooks/post-receive script.
jcorgan pushed a commit to branch master
in repository gnuradio.
commit f2e8dd82b5436a344dadc0faeb1f3aaec18e0e09
Author: Nate Goergen <address@hidden>
Date: Sun Feb 22 11:56:31 2015 -0500
controlport: adding abstraction layer for the controlport backends; support
thrift currently.
---
.../gnuradio/ctrlport/GNURadioControlPortClient.py | 276 ++++++++
.../python/gnuradio/ctrlport/gr-ctrlport-monitor | 768 +++++++++++++++++++++
2 files changed, 1044 insertions(+)
diff --git
a/gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py
b/gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py
new file mode 100644
index 0000000..3cf5186
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py
@@ -0,0 +1,276 @@
+#
+# Copyright 2015 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio 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.
+#
+# GNU Radio 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+"""
+Python Client classes for interfacing with the GNU Radio ControlPort interface
+and for accessing Performance Counters.
+
+While ControlPort and these client classes are designed to support multiple
+Remote Procedure Call (RPC) transports, the Apache Thrift middle-ware RPC
+is currently the only supported transport.
+
+"""
+
+"""
+RPCMethods is a dictionary listing RPC transports currently supported
+by this client.
+
+Args:
+ function: the function whose parameter list will be examined
+ excluded_args: function arguments that are NOT to be added to the
dictionary (sequence of strings)
+ options: result of command argument parsing (optparse.Values)
+"""
+
+RPCMethods = {'thrift': 'Apache Thrift',
+ #'ice': 'Zeroc ICE'
+ }
+
+import exceptions
+
+"""
+Base class for RPC transport clients
+
+Methods that all RPC clients should implement include:
+
+ def newConnection(host,port): Method for re-establishing a new client
+ connection to a different host / port
+
+ def properties([]): Given a list of ControlPort property names,
+ or an empty list to specify all currently registered properties,
+ this method returns a dictionary of metadata describing the
+ the specified properties. The dictionary key contains the name
+ of each returned properties.
+
+ def getKnobs([]): Given a list of ControlPort property names,
+ or an empty list to specify all currently registered properties,
+ this method returns a dictionary of the current value of
+ the specified properties.
+
+ def getRe([]): Given a list of regular expression strings,
+ this method returns a dictionary of the current value of
+ the all properties with names that match the specified
+ expressions.
+
+ def setKnobs({}): Given a dictionary of ControlPort property
+ key / value pairs, this method requests that ControlPort
+ attempt to set the specified named properties to the
+ value given. Success in setting each property to the
+ value specified requires that the property be registered
+ as a 'setable' ControlPort property, that the client have the
+ requisite privilege level to set the property, and
+ the underlying Block's implementation in handling
+ the set request.
+
+Args:
+ method: name of the RPC transport
+ port: port number of the connection
+ host: hostname of the connection
+"""
+
+class RPCConnection(object):
+ def __init__(self, method, port, host=None):
+ (self.method, self.port) = (method, port)
+ if host is None: self.host = '127.0.0.1'
+ else: self.host = host
+
+ def __str__(self):
+ return "%s connection on %s:%s"%(self.getName(), self.getHost(),
self.getPort())
+
+ def getName(self):
+ return RPCMethods[self.method]
+
+ def getHost(self):
+ return self.host
+
+ def getPort(self):
+ return self.port
+
+ def newConnection(self, host=None, port=None):
+ raise exceptions.NotImplementedError()
+
+ def properties(self, *args):
+ raise exceptions.NotImplementedError()
+
+ def getKnobs(self, *args):
+ raise exceptions.NotImplementedError()
+
+ def getRe(self,*args):
+ raise exceptions.NotImplementedError()
+
+ def setKnobs(self,*args):
+ raise exceptions.NotImplementedError()
+
+ def shutdown(self):
+ raise exceptions.NotImplementedError()
+
+"""
+RPC Client interface for the Apache Thrift middle-ware RPC transport.
+
+Args:
+ port: port number of the connection
+ host: hostname of the connection
+"""
+
+class RPCConnectionThrift(RPCConnection):
+ def __init__(self, host=None, port=None):
+ if port is None: port = 9090
+ super(RPCConnectionThrift, self).__init__(method='thrift', port=port,
host=host)
+ self.newConnection(host, port)
+
+ def newConnection(self, host=None, port=None):
+ from gnuradio.ctrlport.ThriftRadioClient import ThriftRadioClient
+ self.thriftclient = ThriftRadioClient(self.getHost(), self.getPort())
+
+ def properties(self, *args):
+ return self.thriftclient.radio.properties(*args)
+
+ def getKnobs(self, *args):
+ class Knob():
+ def __init__(self, key, value):
+ (self.key, self.value) = (key, value)
+
+ result = {}
+ for key, knob in self.thriftclient.radio.getKnobs(*args).iteritems():
+ if knob.type == 0: result[key] = Knob(key, knob.value.a_bool)
+ elif knob.type == 1: result[key] = Knob(key, knob.value.a_byte)
+ elif knob.type == 2: result[key] = Knob(key, knob.value.a_short)
+ elif knob.type == 3: result[key] = Knob(key, knob.value.a_int)
+ elif knob.type == 4: result[key] = Knob(key, knob.value.a_long)
+ elif knob.type == 5: result[key] = Knob(key, knob.value.a_double)
+ elif knob.type == 6: result[key] = Knob(key, knob.value.a_string)
+ elif knob.type == 7: result[key] = Knob(key, knob.value.a_complex)
+ elif knob.type == 8: result[key] = Knob(key,
knob.value.a_f32vector)
+ elif knob.type == 9: result[key] = Knob(key,
knob.value.a_f64vector)
+ elif knob.type == 10: result[key] = Knob(key,
knob.value.a_s64vector)
+ elif knob.type == 11: result[key] = Knob(key,
knob.value.a_s32vector)
+ elif knob.type == 12: result[key] = Knob(key,
knob.value.a_s16vector)
+ elif knob.type == 13: result[key] = Knob(key,
knob.value.a_s8vector)
+ elif knob.type == 14: result[key] = Knob(key,
knob.value.a_s32vector)
+ elif knob.type == 15: result[key] = Knob(key, knob.value.byte)
+ else:
+ raise exceptions.ValueError
+
+ return result
+
+ def getRe(self,*args):
+ return self.thriftclient.radio.getRe(*args)
+
+ def setKnobs(self,*args):
+ self.thriftclient.radio.setKnobs(*args)
+
+ def shutdown(self):
+ self.thriftclient.radio.shutdown()
+
+"""
+GNURadioControlPortClient is the main class for creating a GNU Radio
+ControlPort client application for all transports.
+
+Two constructors are provided for creating a connection to ControlPort.
+
+"""
+
+class GNURadioControlPortClient():
+ """
+ Constructor for creating a ControlPort connection to a specified host /
port
+
+ Args:
+ host: hostname of the connection. Specifying None (default) will
+ select the loopback interface.
+
+ port: port number to use for the connection. Specifying None (default)
+ will select the specified RPC transport's default port number, if
+ the transport has a default.
+
+ rpcmethod: This string specifies the RPC transport to use for the
+ client connection. The default implementation currently uses
+ the Apache Thrift RPC transport. The value specified here must
+ be one of the transport keys listed in the RPCMethods dictionary
+ above
+
+ callback: This optional parameter is a callback function that will be
passed
+ a reference to the Client implementation for the RPC transport
specified
+ by rpcmethod. The callback will be executed after the client has
been
+ constructed, but before __init__ returns.
+
+ blockingcallback: This optional parameter is a callback function with
+ no parameters that will be executed after callback() is executed,
+ but before __init__ returns. It is useful if your application
+ requires that a blocking function be called to start the
application,
+ such as QtGui.QApplication.exec_
+
+ """
+
+ def __init__(self, host = None, port = None, rpcmethod = 'thrift',
callback = None, blockingcallback = None):
+ __init__([host, port], rpcmethod, callback, blockingcallback)
+
+ """
+ Constructor for creating a ControlPort from a tuple of command line
arguments (i.e. sys.argv)
+
+ Args:
+ argv: List of command line arguments. Future implementations may parse
the argument list
+ for OptionParser style key / value pairs, however the current
implementation
+ simply takes argv[1] and argv[2] as the connection hostname and
port, respectively.
+
+ Example Usage:
+
+ In the following QT client example, the ControlPort host and port are
specified to
+ the Client application as the first two command line arguments. The
MAINWindow class is
+ of the type QtGui.QMainWindow, and is the main window for the QT
application. MyApp
+ is a simple helper class for starting the application.
+
+ class MAINWindow(QtGui.QMainWindow):
+ ... QT Application implementation ...
+
+ class MyApp(object):
+ def __init__(self, args):
+ from GNURadioControlPortClient import GNURadioControlPortClient
+ GNURadioControlPortClient(args, 'thrift', self.run,
QtGui.QApplication(sys.argv).exec_)
+
+ def run(self, client):
+ MAINWindow(client).show()
+
+ MyApp(sys.argv)
+
+
+ """
+
+ def __init__(self, argv = [], rpcmethod = 'thrift', callback = None,
blockingcallback = None):
+ if len(argv) > 1: host = argv[1]
+ else: host = None
+
+ if len(argv) > 2: port = argv[2]
+ else: port = None
+
+ self.client = None
+
+ if RPCMethods.has_key(rpcmethod):
+ if rpcmethod == 'thrift':
+# print("making RPCConnectionThrift")
+ self.client = RPCConnectionThrift(host, port)
+# print("made %s" % self.client)
+
+# print("making callback call")
+ if not callback is None: callback(self.client)
+
+# print("making blockingcallback call")
+ if not blockingcallback is None: blockingcallback()
+ else:
+ print("Unsupported RPC method: ", rpcmethod)
+ raise exceptions.ValueError()
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor
b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor
new file mode 100644
index 0000000..5411b24
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor
@@ -0,0 +1,768 @@
+#!/usr/bin/env python
+#
+# Copyright 2012,2013 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio 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.
+#
+# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+# from gnuradio import gr, ctrlport
+
+from PyQt4 import QtCore,Qt
+import PyQt4.QtGui as QtGui
+import os, sys, time, struct
+
+# from gnuradio.ctrlport.IceRadioClient import *
+from gnuradio.ctrlport.GrDataPlotter import *
+# from gnuradio.ctrlport import GNURadio
+
+class RateDialog(QtGui.QDialog):
+ def __init__(self, delay, parent=None):
+ super(RateDialog, self).__init__(parent)
+ self.gridLayout = QtGui.QGridLayout(self)
+ self.setWindowTitle("Update Delay (ms)");
+ self.delay = QtGui.QLineEdit(self);
+ self.delay.setText(str(delay));
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
+ self.gridLayout.addWidget(self.delay);
+ self.gridLayout.addWidget(self.buttonBox);
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+ def accept(self):
+ self.done(1);
+ def reject(self):
+ self.done(0);
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return Qtgui.QSize(800,600)
+
+ def __init__(self, radioclient):
+
+ super(MAINWindow, self).__init__()
+ self.radioclient = radioclient
+ self.updateRate = 1000;
+ self.conns = []
+ self.plots = []
+ self.knobprops = []
+
+ self.mdiArea = QtGui.QMdiArea()
+ self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.setCentralWidget(self.mdiArea)
+
+ self.mdiArea.subWindowActivated.connect(self.updateMenus)
+ self.windowMapper = QtCore.QSignalMapper(self)
+
self.windowMapper.mapped[QtGui.QWidget].connect(self.setActiveSubWindow)
+
+ self.createActions()
+ self.createMenus()
+ self.createToolBars()
+ self.createStatusBar()
+ self.updateMenus()
+
+ self.setWindowTitle("GNU Radio Control Port Monitor")
+ self.setUnifiedTitleAndToolBarOnMac(True)
+
+ self.newCon(radioclient)
+ icon = QtGui.QIcon(sys.argv[0] + "/icon.png" )
+ self.setWindowIcon(icon)
+
+ # Locally turn off ControlPort export from GR. This prevents
+ # our GR-based plotters from launching their own ControlPort
+ # instance (and possibly causing a port collision if one has
+ # been specified).
+ os.environ['GR_CONF_CONTROLPORT_ON'] = 'False'
+
+ def setUpdateRate(self,nur):
+ self.updateRate = int(nur);
+ for c in self.conns:
+ c.updateRate = self.updateRate;
+ c.timer.setInterval(self.updateRate);
+
+ def newCon(self, csomeBool):
+ child = MForm(self.radioclient, len(self.conns), parent = self,
dialogprompt = not csomeBool)
+ if(child.radioclient is not None):
+ child.setWindowTitle(str(child.radioclient))
+ self.mdiArea.addSubWindow(child)
+ self.mdiArea.currentSubWindow().showMaximized()
+ self.conns.append(child)
+ self.plots.append([])
+
+ def propertiesMenu(self, key, radio, uid):
+ r = str(radio).split(" ")
+ title = "{0}:{1}".format(r[3], r[5])
+
+ props = radio.properties([key])
+
+ pmin,pmax = get_minmax(props[key])
+
+ # Use display option mask of item to set up available plot
+ # types and default options.
+ disp = self.knobprops[uid][key].display
+ cplx = disp & gr.DISPOPTCPLX | disp & gr.DISPXY
+ strip = disp & gr.DISPOPTSTRIP
+ stem = disp & gr.DISPOPTSTEM
+ log = disp & gr.DISPOPTLOG
+ scatter = disp & gr.DISPOPTSCATTER
+
+ def newUpdaterProxy():
+ self.newUpdater(key, radio)
+
+ def newPlotterFProxy():
+ self.newPlotF(key, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ def newPlotterCProxy():
+ self.newPlotC(key, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ def newPlotterConstProxy():
+ self.newPlotConst(key, uid, title, pmin, pmax,
+ scatter, strip)
+
+ def newPlotterPsdFProxy():
+ self.newPlotPsdF(key, uid, title)
+
+ def newPlotterPsdCProxy():
+ self.newPlotPsdC(key, uid, title)
+
+ def newPlotterRasterFProxy():
+ self.newPlotRasterF(key, uid, title, pmin, pmax)
+
+ def newPlotterRasterBProxy():
+ self.newPlotRasterB(key, uid, title, pmin, pmax)
+
+ menu = QtGui.QMenu(self)
+ menu.setTitle("Item Actions")
+ menu.setTearOffEnabled(False)
+
+ # object properties
+ menu.addAction("Properties", newUpdaterProxy)
+
+ # displays available
+ if(cplx == 0):
+ menu.addAction("Plot Time", newPlotterFProxy)
+ menu.addAction("Plot PSD", newPlotterPsdFProxy)
+ menu.addAction("Plot Raster (real)", newPlotterRasterFProxy)
+ #menu.addAction("Plot Raster (bits)", newPlotterRasterBProxy)
+ else:
+ menu.addAction("Plot Time", newPlotterCProxy)
+ menu.addAction("Plot PSD", newPlotterPsdCProxy)
+ menu.addAction("Plot Constellation", newPlotterConstProxy)
+
+ menu.popup(QtGui.QCursor.pos())
+
+ def newUpdater(self, key, radio):
+ updater = UpdaterWindow(key, radio, None)
+ updater.setWindowTitle("Updater: " + key)
+ updater.setModal(False)
+ updater.exec_()
+
+ def newSub(self, e):
+ tag = str(e.text(0))
+ tree = e.treeWidget().parent()
+ uid = tree.uid
+ knobprop = self.knobprops[uid][tag]
+
+ r = str(tree.radio).split(" ")
+ title = "{0}:{1}".format(r[3], r[5])
+ pmin,pmax = get_minmax(knobprop)
+
+ disp = knobprop.display
+ if(disp & gr.DISPTIME):
+ strip = disp & gr.DISPOPTSTRIP
+ stem = disp & gr.DISPOPTSTEM
+ log = disp & gr.DISPOPTLOG
+ if(disp & gr.DISPOPTCPLX == 0):
+ self.newPlotF(tag, uid, title, pmin, pmax,
+ log, strip, stem)
+ else:
+ self.newPlotC(tag, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ elif(disp & gr.DISPXY):
+ scatter = disp & gr.DISPOPTSCATTER
+ self.newPlotConst(tag, uid, title, pmin, pmax, scatter)
+
+ elif(disp & gr.DISPPSD):
+ if(disp & gr.DISPOPTCPLX == 0):
+ self.newPlotPsdF(tag, uid, title)
+ else:
+ self.newPlotPsdC(tag, uid, title)
+
+ def startDrag(self, e):
+ drag = QtGui.QDrag(self)
+ mime_data = QtCore.QMimeData()
+
+ tag = str(e.text(0))
+ tree = e.treeWidget().parent()
+ knobprop = self.knobprops[tree.uid][tag]
+ disp = knobprop.display
+ iscomplex = (disp & gr.DISPOPTCPLX) or (disp & gr.DISPXY)
+
+ if(disp != gr.DISPNULL):
+ data = "PlotData:::{0}:::{1}".format(tag, iscomplex)
+ else:
+ data = "OtherData:::{0}:::{1}".format(tag, iscomplex)
+
+ mime_data.setText(data)
+ drag.setMimeData(mime_data)
+
+ drop = drag.start()
+
+ def createPlot(self, plot, uid, title):
+ plot.start()
+ self.plots[uid].append(plot)
+
+ self.mdiArea.addSubWindow(plot)
+ plot.setWindowTitle("{0}: {1}".format(title, plot.name()))
+ self.connect(plot.qwidget(),
+ QtCore.SIGNAL('destroyed(QObject*)'),
+ self.destroyPlot)
+
+ # when the plot is updated via drag-and-drop, we need to be
+ # notified of the new qwidget that's created so we can
+ # properly destroy it.
+ plot.plotupdated.connect(self.plotUpdated)
+
+ plot.show()
+
+ def plotUpdated(self, q):
+ # the plot has been updated with a new qwidget; make sure this
+ # gets dies to the destroyPlot function.
+ for i, plots in enumerate(self.plots):
+ for p in plots:
+ if(p == q):
+ #plots.remove(p)
+ #plots.append(q)
+ self.connect(q.qwidget(),
+ QtCore.SIGNAL('destroyed(QObject*)'),
+ self.destroyPlot)
+ break
+
+ def destroyPlot(self, obj):
+ for plots in self.plots:
+ for p in plots:
+ if p.qwidget() == obj:
+ plots.remove(p)
+ break
+
+ def newPlotConst(self, tag, uid, title="", pmin=None, pmax=None,
+ scatter=False, stripchart=False):
+ plot = GrDataPlotterConst(tag, 32e6, pmin, pmax, stripchart)
+ plot.scatter(scatter)
+ self.createPlot(plot, uid, title)
+
+ def newPlotF(self, tag, uid, title="", pmin=None, pmax=None,
+ logy=False, stripchart=False, stem=False):
+ plot = GrDataPlotterF(tag, 32e6, pmin, pmax, stripchart)
+ plot.semilogy(logy)
+ plot.stem(stem)
+ self.createPlot(plot, uid, title)
+
+ def newPlotC(self, tag, uid, title="", pmin=None, pmax=None,
+ logy=False, stripchart=False, stem=False):
+ plot = GrDataPlotterC(tag, 32e6, pmin, pmax, stripchart)
+ plot.semilogy(logy)
+ plot.stem(stem)
+ self.createPlot(plot, uid, title)
+
+ def newPlotPsdF(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrDataPlotterPsdF(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotPsdC(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrDataPlotterPsdC(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotRasterF(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrTimeRasterF(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotRasterB(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrTimeRasterB(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def update(self, knobs, uid):
+ #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
+ for plot in self.plots[uid]:
+ print("update plotuid:", uid)
+ data = []
+ for n in plot.knobnames:
+ print("update plotuid:", uid, "name:", n)
+ d = knobs[n].value
+ if(type(d) == GNURadio.complex):
+ d = [d.re, d.im]
+
+ # If it's a byte stream, Python thinks it's a string.
+ # Unpack and convert to floats for plotting.
+ if(type(d) == str and n.find('probe2_b') == 0):
+ d = struct.unpack(len(d)*'b', d)
+ d = [float(di) for di in d]
+
+ data.append(d)
+ plot.update(data)
+ plot.stop()
+ plot.wait()
+ plot.start()
+
+ def setActiveSubWindow(self, window):
+ if window:
+ self.mdiArea.setActiveSubWindow(window)
+
+
+ def createActions(self):
+ self.newConAct = QtGui.QAction("&New Connection",
+ self, shortcut=QtGui.QKeySequence.New,
+ statusTip="Create a new file", triggered=self.newCon)
+
+ self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
+ statusTip="Exit the application",
+ triggered=QtGui.qApp.closeAllWindows)
+
+ self.closeAct = QtGui.QAction("Cl&ose", self, shortcut="Ctrl+F4",
+ statusTip="Close the active window",
+ triggered=self.mdiArea.closeActiveSubWindow)
+
+ self.closeAllAct = QtGui.QAction("Close &All", self,
+ statusTip="Close all the windows",
+ triggered=self.mdiArea.closeAllSubWindows)
+
+ self.urAct = QtGui.QAction("Update Rate", self, shortcut="F5",
+ statusTip="Change Update Rate",
+ triggered=self.updateRateShow)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T);
+ self.tileAct = QtGui.QAction("&Tile", self,
+ statusTip="Tile the windows",
+ triggered=self.mdiArea.tileSubWindows,
+ shortcut=qks)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C);
+ self.cascadeAct = QtGui.QAction("&Cascade", self,
+ statusTip="Cascade the windows", shortcut=qks,
+ triggered=self.mdiArea.cascadeSubWindows)
+
+ self.nextAct = QtGui.QAction("Ne&xt", self,
+ shortcut=QtGui.QKeySequence.NextChild,
+ statusTip="Move the focus to the next window",
+ triggered=self.mdiArea.activateNextSubWindow)
+
+ self.previousAct = QtGui.QAction("Pre&vious", self,
+ shortcut=QtGui.QKeySequence.PreviousChild,
+ statusTip="Move the focus to the previous window",
+ triggered=self.mdiArea.activatePreviousSubWindow)
+
+ self.separatorAct = QtGui.QAction(self)
+ self.separatorAct.setSeparator(True)
+
+ self.aboutAct = QtGui.QAction("&About", self,
+ statusTip="Show the application's About box",
+ triggered=self.about)
+
+ self.aboutQtAct = QtGui.QAction("About &Qt", self,
+ statusTip="Show the Qt library's About box",
+ triggered=QtGui.qApp.aboutQt)
+
+ def createMenus(self):
+ self.fileMenu = self.menuBar().addMenu("&File")
+ self.fileMenu.addAction(self.newConAct)
+ self.fileMenu.addAction(self.urAct)
+ self.fileMenu.addSeparator()
+ self.fileMenu.addAction(self.exitAct)
+
+ self.windowMenu = self.menuBar().addMenu("&Window")
+ self.updateWindowMenu()
+ self.windowMenu.aboutToShow.connect(self.updateWindowMenu)
+
+ self.menuBar().addSeparator()
+
+ self.helpMenu = self.menuBar().addMenu("&Help")
+ self.helpMenu.addAction(self.aboutAct)
+ self.helpMenu.addAction(self.aboutQtAct)
+
+ def updateRateShow(self):
+ askrate = RateDialog(self.updateRate, self);
+ if askrate.exec_():
+ ur = float(str(askrate.delay.text()));
+ self.setUpdateRate(ur);
+ return;
+ else:
+ return;
+
+ def createToolBars(self):
+ self.fileToolBar = self.addToolBar("File")
+ self.fileToolBar.addAction(self.newConAct)
+ self.fileToolBar.addAction(self.urAct)
+
+ self.fileToolBar = self.addToolBar("Window")
+ self.fileToolBar.addAction(self.tileAct)
+ self.fileToolBar.addAction(self.cascadeAct)
+
+ def createStatusBar(self):
+ self.statusBar().showMessage("Ready")
+
+
+ def activeMdiChild(self):
+ activeSubWindow = self.mdiArea.activeSubWindow()
+ if activeSubWindow:
+ return activeSubWindow.widget()
+ return None
+
+ def updateMenus(self):
+ hasMdiChild = (self.activeMdiChild() is not None)
+ self.closeAct.setEnabled(hasMdiChild)
+ self.closeAllAct.setEnabled(hasMdiChild)
+ self.tileAct.setEnabled(hasMdiChild)
+ self.cascadeAct.setEnabled(hasMdiChild)
+ self.nextAct.setEnabled(hasMdiChild)
+ self.previousAct.setEnabled(hasMdiChild)
+ self.separatorAct.setVisible(hasMdiChild)
+
+ def updateWindowMenu(self):
+ self.windowMenu.clear()
+ self.windowMenu.addAction(self.closeAct)
+ self.windowMenu.addAction(self.closeAllAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.tileAct)
+ self.windowMenu.addAction(self.cascadeAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.nextAct)
+ self.windowMenu.addAction(self.previousAct)
+ self.windowMenu.addAction(self.separatorAct)
+
+ def about(self):
+ about_info = \
+'''Copyright 2012 Free Software Foundation, Inc.\n
+This program is part of GNU Radio.\n
+GNU Radio 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.\n
+GNU Radio 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.\n
+You should have received a copy of the GNU General Public License along with
GNU Radio; see the file COPYING. If not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Boston, MA 02110-1301, USA.'''
+
+ QtGui.QMessageBox.about(None, "gr-ctrlport-monitor", about_info)
+
+
+class ConInfoDialog(QtGui.QDialog):
+ def __init__(self, parent=None):
+ super(ConInfoDialog, self).__init__(parent)
+
+ self.gridLayout = QtGui.QGridLayout(self)
+
+
+ self.host = QtGui.QLineEdit(self);
+ self.port = QtGui.QLineEdit(self);
+ self.host.setText("localhost");
+ self.port.setText("43243");
+
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
+
+ self.gridLayout.addWidget(self.host);
+ self.gridLayout.addWidget(self.port);
+ self.gridLayout.addWidget(self.buttonBox);
+
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+
+
+ def accept(self):
+ self.done(1);
+
+ def reject(self):
+ self.done(0);
+
+
+class UpdaterWindow(QtGui.QDialog):
+ def __init__(self, key, radio, parent):
+ QtGui.QDialog.__init__(self, parent)
+
+ self.key = key;
+ self.radio = radio
+
+ self.resize(300,200)
+ self.layout = QtGui.QVBoxLayout()
+
+ self.props = radio.properties([key])[key]
+ info = str(self.props)
+
+ self.infoLabel = QtGui.QLabel(info)
+ self.layout.addWidget(self.infoLabel)
+
+ # Test here to make sure that a 'set' function exists
+ try:
+ a = radio.set(radio.get([key]))
+ has_set = True
+ except Ice.UnknownException:
+ has_set = False
+
+ if(has_set is False):
+ self.cancelButton = QtGui.QPushButton("Ok")
+ self.cancelButton.connect(self.cancelButton,
QtCore.SIGNAL('clicked()'), self.reject)
+
+ self.buttonlayout = QtGui.QHBoxLayout()
+ self.buttonlayout.addWidget(self.cancelButton)
+ self.layout.addLayout(self.buttonlayout)
+
+ else: # we have a set function
+ self.textInput = QtGui.QLineEdit()
+ self.layout.addWidget(self.textInput)
+
+ self.applyButton = QtGui.QPushButton("Apply")
+ self.setButton = QtGui.QPushButton("OK")
+ self.cancelButton = QtGui.QPushButton("Cancel")
+
+ rv = radio.get([key])
+ val = rv[key].value
+ if(type(val) == GNURadio.complex):
+ val = val.re + val.im*1j
+
+ self.textInput.setText(str(val))
+ self.sv = rv[key]
+
+ self.applyButton.connect(self.applyButton,
QtCore.SIGNAL('clicked()'), self._apply)
+ self.setButton.connect(self.setButton, QtCore.SIGNAL('clicked()'),
self._set)
+ self.cancelButton.connect(self.cancelButton,
QtCore.SIGNAL('clicked()'), self.reject)
+
+ self.is_num = ((type(self.sv.value)==float) or
(type(self.sv.value)==int))
+ if(self.is_num):
+ self.sliderlayout = QtGui.QHBoxLayout()
+
+ self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
+
+
self.sliderlayout.addWidget(QtGui.QLabel(str(self.props.min.value)))
+ self.sliderlayout.addWidget(self.slider)
+
self.sliderlayout.addWidget(QtGui.QLabel(str(self.props.max.value)))
+
+ self.steps = 10000
+ self.valspan = self.props.max.value - self.props.min.value
+
+ self.slider.setRange(0, 10000)
+ self._set_slider_value(self.sv.value)
+
+ self.connect(self.slider, QtCore.SIGNAL("sliderReleased()"),
self._slide)
+
+ self.layout.addLayout(self.sliderlayout)
+ else:
+ self._set_slider_value = None
+
+ self.buttonlayout = QtGui.QHBoxLayout()
+ self.buttonlayout.addWidget(self.applyButton)
+ self.buttonlayout.addWidget(self.setButton)
+ self.buttonlayout.addWidget(self.cancelButton)
+ self.layout.addLayout(self.buttonlayout)
+
+ # set layout and go...
+ self.setLayout(self.layout)
+
+ def _set_slider_value(self, val):
+
self.slider.setValue(self.steps*(val-self.props.min.value)/self.valspan)
+
+ def _slide(self):
+ val = (self.slider.value()*self.valspan +
self.props.min.value)/float(self.steps)
+ self.textInput.setText(str(val))
+
+ def _apply(self):
+ if(type(self.sv.value) == str):
+ val = str(self.textInput.text())
+ elif(type(self.sv.value) == int):
+ val = int(round(float(self.textInput.text())))
+ elif(type(self.sv.value) == float):
+ val = float(self.textInput.text())
+ elif(type(self.sv.value) == GNURadio.complex):
+ t = str(self.textInput.text())
+ t = complex(t.strip("(").strip(")").replace(" ", ""))
+ val = GNURadio.complex()
+ val.re = t.real
+ val.im = t.imag
+ else:
+ sys.stderr.write("set type not supported!
({0})\n".format(type(self.sv.value)))
+ return
+
+ self.sv.value = val
+ km = {}
+ km[self.key] = self.sv
+ self.radioclient.setKnobs(km)
+ if self._set_slider_value:
+ self._set_slider_value(self.sv.value)
+
+ def _set(self):
+ self._apply()
+ self.done(0)
+
+
+class MForm(QtGui.QWidget):
+ def update(self):
+ # TODO: revisit this try-except block, figure out what it's doing, and
if we need to keep it. at very lease makes debugging dificult
+ if True: #try:
+ st = time.time();
+ knobs = self.radioclient.getKnobs([])
+ ft = time.time();
+ latency = ft-st;
+ self.parent.statusBar().showMessage("Current GNU Radio Control
Port Query Latency: %f ms"%(latency*1000))
+
+# except Exception, e:
+# sys.stderr.write("ctrlport-monitor: radio.get threw exception
({0}).\n".format(e))
+# if(type(self.parent) is MAINWindow):
+# # Find window of connection
+# remove = []
+# for p in self.parent.mdiArea.subWindowList():
+# if self.parent.conns[self.uid] == p.widget():
+# remove.append(p)
+#
+# # Find any subplot windows of connection
+# for p in self.parent.mdiArea.subWindowList():
+# for plot in self.parent.plots[self.uid]:
+# if plot.qwidget() == p.widget():
+# remove.append(p)
+#
+# # Clean up local references to these
+# self.parent.conns.remove(self.parent.conns[self.uid])
+# self.parent.plots.remove(self.parent.plots[self.uid])
+#
+# # Remove subwindows for connection and plots
+# for r in remove:
+# self.parent.mdiArea.removeSubWindow(r)
+#
+# # Clean up self
+# self.close()
+# else:
+# sys.exit(1)
+# return
+
+ tableitems = knobs.keys()
+
+ #UPDATE TABLE:
+ #try:
+ self.table.updateItems(knobs, self.knobprops)
+ #except:
+ # self.knobprops = self.radioclient.properties([])
+ # print("knobsprops1:", len(self.knobprops))
+
+ #UPDATE PLOTS
+ self.parent.update(knobs, self.uid)
+
+
+ def __init__(self, radioclient, uid=0, updateRate=2000, parent=None,
dialogprompt = False):
+
+ super(MForm, self).__init__()
+ self.radioclient = radioclient
+# print("before radioclient.getHost()", radioclient.getHost(),
radioclient.getPort(), "prompt", prompt)
+ if(dialogprompt or radioclient.getHost() is None or
radioclient.getPort() is None):
+# print("before ConInfoDialog")
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ host = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+# print("before radioclient.newConnection host: %s port:
%s"%(host,port))
+ newradio = self.radioclient.newConnection(host, port)
+ if newradio is None:
+ print("Error making a %s connection to %s:%s from %s" %
(radioclient.getName(), host, port, radioclient))
+ else:
+ self.radioclient = newradio
+
+ else:
+ self.radioclient = Nonclient = None
+ return
+
+
+ self.uid = uid
+ self.parent = parent
+ self.horizontalLayout = QtGui.QVBoxLayout(self)
+ self.gridLayout = QtGui.QGridLayout()
+
+ self.knobprops = self.radioclient.properties([])
+ #print("props5:", self.knobprops)
+ self.parent.knobprops.append(self.knobprops)
+ self.resize(775,500)
+ self.timer = QtCore.QTimer()
+ self.constupdatediv = 0
+ self.tableupdatediv = 0
+ plotsize=250
+
+ # make table
+ self.table = GrDataPlotterValueTable(uid, self, 0, 0, 400, 200)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,
QtGui.QSizePolicy.Preferred)
+ self.table.treeWidget.setSizePolicy(sizePolicy)
+
self.table.treeWidget.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed)
+ self.table.treeWidget.setSortingEnabled(True)
+ self.table.treeWidget.setDragEnabled(True)
+
+ # add things to layouts
+ self.horizontalLayout.addWidget(self.table.treeWidget)
+
+ # set up timer
+ self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
+ self.updateRate = updateRate;
+ self.timer.start(self.updateRate)
+
+ # set up context menu ..
+ self.table.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.table.treeWidget.customContextMenuRequested.connect(self.openMenu)
+
+ # Set up double-click to launch default plotter
+ self.connect(self.table.treeWidget,
+ QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'),
+ self.parent.newSub);
+
+ # Allow drag/drop event from table item to plotter
+ self.connect(self.table.treeWidget,
+ QtCore.SIGNAL('itemPressed(QTreeWidgetItem*, int)'),
+ self.parent.startDrag)
+
+ def openMenu(self, pos):
+ index = self.table.treeWidget.selectedIndexes()
+ item = self.table.treeWidget.itemFromIndex(index[0])
+ itemname = str(item.text(0))
+ self.parent.propertiesMenu(itemname, self.radio, self.uid)
+
+
+def get_minmax(p):
+ pmin = p.min.value
+ pmax = p.max.value
+
+ # Find min/max or real or imag for GNURadio::complex
+ if(type(pmin) == GNURadio.complex):
+ pmin = min(pmin.re, pmin.im)
+ if(type(pmax) == GNURadio.complex):
+ pmax = max(pmax.re, pmax.im)
+
+ # If it's a byte stream, Python thinks it's a string.
+ if(type(pmin) == str):
+ pmin = struct.unpack('b', pmin)[0]
+ if(type(pmax) == str):
+ pmax = struct.unpack('b', pmax)[0]
+
+ if pmin == []:
+ pmin = None
+ else:
+ pmin = 1.1*float(pmin)
+ if pmax == []:
+ pmax = None
+ else:
+ pmax = 1.1*float(pmax)
+
+ return pmin, pmax
+
+class MyApp(object):
+ def __init__(self, args):
+ from gnuradio.ctrlport.GNURadioControlPortClient import
GNURadioControlPortClient
+ GNURadioControlPortClient(args, 'thrift', self.run,
QtGui.QApplication(sys.argv).exec_)
+
+ def run(self, client):
+ MAINWindow(client).show()
+
+MyApp(sys.argv)
- [Commit-gnuradio] [gnuradio] 24/50: controlport: adding performance and controlport monitor GRC blocks., (continued)
- [Commit-gnuradio] [gnuradio] 24/50: controlport: adding performance and controlport monitor GRC blocks., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 29/50: controlport: changing the default port number on the Thrift interface to zero., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 21/50: controlport: Adds ability to configure Thrift through a config file, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 30/50: controlport: ephemeral / unused port number selection by OS working., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 13/50: controlport: using threaded server for multiple connections., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 39/50: controlport: documentation cleanup, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 35/50: controlport: cleanup, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 33/50: controlport: more cleanup and conveniences, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 11/50: controlport: more work on the translation layer; properties and setting parameters in gr-ctrlport-monitor now working., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 45/50: controlport: ensure proper ctrlport shutdown., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 08/50: controlport: adding abstraction layer for the controlport backends; support thrift currently.,
git <=
- [Commit-gnuradio] [gnuradio] 25/50: controlport: fixing up some issues; generate thrift sources into thrift subdir., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 16/50: controlport: switching rpcpmtconverter::to_pmt() to To_PMT singleton, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 40/50: controlport: documentation cleanup, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 10/50: controlport: more cleanup of python code to help generalize the interface, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 38/50: controlport: cleanup, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 41/50: controlport: cmake fixes to FindThrift for when thrift is not installed., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 43/50: controlport: more documentation and linking info., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 37/50: controlport: Some documentation, git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 32/50: controlport: moving the generation of the Thrift endpoint string to the application_started() function., git, 2015/04/16
- [Commit-gnuradio] [gnuradio] 42/50: controlport: QA needs to get host and port out of the endpoint., git, 2015/04/16