diff options
author | Nate Goergen <nate.goergen2@mile10.com> | 2015-02-22 11:56:31 -0500 |
---|---|---|
committer | Tom Rondeau <tom@trondeau.com> | 2015-04-02 15:38:56 -0700 |
commit | f2e8dd82b5436a344dadc0faeb1f3aaec18e0e09 (patch) | |
tree | a296dd469f13abb4172c8e3a11a327ec2518fb64 | |
parent | 1875c3381bc4e2d2468f9dfe44557cd2a94d5e1c (diff) |
controlport: adding abstraction layer for the controlport backends; support thrift currently.
-rw-r--r-- | gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py | 276 | ||||
-rw-r--r-- | gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor | 768 |
2 files changed, 1044 insertions, 0 deletions
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py b/gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py new file mode 100644 index 0000000000..3cf5186594 --- /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 0000000000..5411b244b0 --- /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) |