summaryrefslogtreecommitdiff
path: root/gnuradio-core/src/python
diff options
context:
space:
mode:
authorTom Rondeau <trondeau@vt.edu>2012-12-03 19:42:59 -0500
committerTom Rondeau <trondeau@vt.edu>2012-12-03 19:42:59 -0500
commit1fea3358fbb5796ca50475e55dd7f0d443f1cd73 (patch)
treee15b9b27a607c96df5f42f58e2ab578e6e27a34b /gnuradio-core/src/python
parent76906d9a6bab589abaa96e0dc115c45927f4570f (diff)
Merging ctrlport2 into next branch.
Diffstat (limited to 'gnuradio-core/src/python')
-rw-r--r--gnuradio-core/src/python/gnuradio/ctrlport/CMakeLists.txt129
-rw-r--r--gnuradio-core/src/python/gnuradio/ctrlport/DataPlotter.py382
-rw-r--r--gnuradio-core/src/python/gnuradio/ctrlport/GrDataPlotter.py423
-rw-r--r--gnuradio-core/src/python/gnuradio/ctrlport/IceRadioClient.py102
-rw-r--r--gnuradio-core/src/python/gnuradio/ctrlport/__init__.py30
-rwxr-xr-xgnuradio-core/src/python/gnuradio/ctrlport/ctrlport-monitor477
-rwxr-xr-xgnuradio-core/src/python/gnuradio/ctrlport/gr-ctrlport-monitor581
-rw-r--r--gnuradio-core/src/python/gnuradio/ctrlport/icon.pngbin0 -> 1532 bytes
-rw-r--r--gnuradio-core/src/python/gnuradio/ctrlport/monitor.py60
-rwxr-xr-xgnuradio-core/src/python/gnuradio/ctrlport/qa_cpp_py_binding.py169
-rwxr-xr-xgnuradio-core/src/python/gnuradio/ctrlport/qa_cpp_py_binding_set.py148
11 files changed, 2501 insertions, 0 deletions
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/CMakeLists.txt b/gnuradio-core/src/python/gnuradio/ctrlport/CMakeLists.txt
new file mode 100644
index 0000000000..1268030ebb
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/CMakeLists.txt
@@ -0,0 +1,129 @@
+# Copyright 2012 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.
+
+########################################################################
+include(GrPython)
+
+ADD_CUSTOM_COMMAND(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../../gnuradio_ice.py
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/__init__.py
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/Booter/__init__.py
+ COMMAND ${ICE_SLICE2PY} -I${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/runtime
+ --output-dir=${CMAKE_CURRENT_BINARY_DIR}/../../
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/runtime/gnuradio.ice
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/runtime/gnuradio.ice
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ COMMENT "Slicing gnuradio.slice"
+)
+
+ADD_CUSTOM_COMMAND(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/frontend_ice.py
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/Frontend/__init__.py
+ COMMAND ${ICE_SLICE2PY} -I${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/runtime
+ --output-dir=${CMAKE_CURRENT_BINARY_DIR}/../../
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/runtime/frontend.ice
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/runtime/frontend.ice
+ ${CMAKE_CURRENT_BINARY_DIR}/../../gnuradio_ice.py
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ COMMENT "Slicing frontend.slice"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/IceRadioClient.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/
+ COMPONENT "core_python"
+)
+
+# We don't want to install these in the root Python directory, but we
+# aren't given a choice based on the way slice2py generates the
+# information.
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/../../gnuradio_ice.py
+ ${CMAKE_CURRENT_BINARY_DIR}/../../frontend_ice.py
+ DESTINATION ${GR_PYTHON_DIR}
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/__init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/GNURadio
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/Booter/__init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/GNURadio/Booter
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/Frontend/__init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/GNURadio/Frontend
+ COMPONENT "core_python"
+)
+
+install(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/icon.png
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/DataPlotter.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/GrDataPlotter.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/monitor.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/
+ COMPONENT "core_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/ctrlport-monitor
+ ${CMAKE_CURRENT_SOURCE_DIR}/gr-ctrlport-monitor
+ DESTINATION ${GR_RUNTIME_DIR}
+ PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
+ COMPONENT "core_python"
+)
+
+########################################################################
+# Handle the unit tests
+########################################################################
+if(ENABLE_GR_CTRLPORT)
+ if(ENABLE_TESTING)
+ include(GrTest)
+ file(GLOB py_qa_test_files "qa_*.py")
+ foreach(py_qa_test_file ${py_qa_test_files})
+ get_filename_component(py_qa_test_name ${py_qa_test_file} NAME_WE)
+ set(GR_TEST_PYTHON_DIRS
+ ${CMAKE_BINARY_DIR}/gnuradio-core/src/python
+ ${CMAKE_BINARY_DIR}/gnuradio-core/src/lib/swig
+ )
+ set(GR_TEST_TARGET_DEPS gruel gnuradio-core)
+ GR_ADD_TEST(${py_qa_test_name} ${PYTHON_EXECUTABLE} ${py_qa_test_file})
+ endforeach(py_qa_test_file)
+ endif(ENABLE_TESTING)
+endif(ENABLE_GR_CTRLPORT)
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/DataPlotter.py b/gnuradio-core/src/python/gnuradio/ctrlport/DataPlotter.py
new file mode 100644
index 0000000000..c689bfa055
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/DataPlotter.py
@@ -0,0 +1,382 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 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.
+#
+
+import sys
+
+try:
+ from PyQt4.QtCore import Qt;
+ from PyQt4 import QtGui, QtCore
+except ImportError:
+ print "Error: Program requires PyQt4."
+ sys.exit(1)
+
+try:
+ import PyQt4.Qwt5 as Qwt
+ from PyQt4.Qwt5.anynumpy import *
+except ImportError:
+ print "Error: Program requires PyQwt5."
+ sys.exit(1)
+
+
+class Zoomer(Qwt.QwtPlotZoomer):
+ def __init__(self, a,b,c,d,e):
+ Qwt.QwtPlotZoomer.__init__(self,a,b,c,d,e);
+ self.zoomers = [];
+
+ def zoom(self, r):
+ Qwt.QwtPlotZoomer.zoom(self,r);
+ if(r == 0):
+ #self.plot().setAxisAutoScale(True);
+ self.plot().setAxisAutoScale(Qwt.QwtPlot.xBottom)
+ self.plot().setAxisAutoScale(Qwt.QwtPlot.yLeft)
+ self.plot().replot();
+
+
+class DataPlotterBase(Qwt.QwtPlot):
+ DefaultColors = ( Qt.green, Qt.red, Qt.blue,
+ Qt.cyan, Qt.magenta, Qt.black, Qt.darkRed,
+ Qt.darkGray, Qt.darkGreen, Qt.darkBlue, Qt.yellow)
+
+ dropSignal = QtCore.pyqtSignal(QtCore.QEvent)
+
+ def contextMenuEvent(self,e):
+ menu = QtGui.QMenu(self);
+ menu.addAction(self.gridAct);
+ menu.addAction(self.axesAct);
+ menu.addAction(self.curvAct);
+ menu.exec_(e.globalPos());
+
+ def dragEnterEvent(self,e):
+ e.accept();
+
+ def dropEvent(self, e):
+ Qwt.QwtPlot.dropEvent(self,e)
+ self.dropSignal.emit( e );
+
+
+
+ def __init__(self, parent, title, xlabel, ylabel, size, x, y):
+ Qwt.QwtPlot.__init__(self, parent)
+ self.callback = None;
+
+ self.gridAct = QtGui.QAction("Toggle &Grid", self, triggered=self.toggleGrid);
+ self.axesAct = QtGui.QAction("Toggle &Axes", self, triggered=self.toggleAxes);
+ self.curvAct = QtGui.QAction("Toggle &Lines", self, triggered=self.toggleCurve);
+
+ # Set up the zoomer
+ self.zoomer = Zoomer(Qwt.QwtPlot.xBottom,
+ Qwt.QwtPlot.yLeft,
+ Qwt.QwtPicker.DragSelection,
+ Qwt.QwtPicker.AlwaysOff,
+ self.canvas())
+
+ # Crosshairs + Data labels
+ self.picker = Qwt.QwtPlotPicker(
+ Qwt.QwtPlot.xBottom,
+ Qwt.QwtPlot.yLeft,
+ Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.DragSelection,
+ Qwt.QwtPlotPicker.CrossRubberBand,
+ Qwt.QwtPicker.AlwaysOn,
+ self.canvas())
+ self.picker.setRubberBandPen(QtGui.QPen(QtCore.Qt.white, 1, QtCore.Qt.DashLine))
+ self.picker.setTrackerPen(QtGui.QPen(QtCore.Qt.white))
+
+ self.axisEnable = False;
+ # Turn off bloated data labels
+ self.enableAxis(Qwt.QwtPlot.yLeft, False);
+ self.enableAxis(Qwt.QwtPlot.xBottom, False);
+
+ # Allow panning with middle mouse
+ panner = Qwt.QwtPlotPanner(self.canvas())
+ panner.setAxisEnabled(Qwt.QwtPlot.yRight, False)
+ panner.setMouseButton(Qt.MidButton)
+
+ # Accept dropping of stats
+ self.setAcceptDrops(True);
+ self.grid = None
+ self.curve_en = False
+ self.setCanvasBackground(Qt.black)
+
+ self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.TopLegend);
+
+ self.axisEnabled(True);
+
+ self.resize(size, size)
+ self.setAutoReplot(False)
+ self.show()
+ self.updateTimerInt = 500
+ self.startTimer(self.updateTimerInt)
+
+ # Set Axis on and Grid off by default
+ #self.toggleGrid();
+ self.toggleAxes();
+
+
+ def toggleAxes(self):
+ self.axisEnable = not self.axisEnable;
+ self.enableAxis(Qwt.QwtPlot.yLeft, self.axisEnable);
+ self.enableAxis(Qwt.QwtPlot.xBottom, self.axisEnable);
+
+
+ def toggleGrid(self):
+ # grid
+ if self.grid == None:
+ self.grid = Qwt.QwtPlotGrid()
+ self.grid.enableXMin(True)
+ self.grid.setMajPen(QtGui.QPen(Qt.gray, 0, Qt.DotLine))
+ self.grid.setMinPen(QtGui.QPen(Qt.gray, 0 , Qt.DotLine))
+ self.grid.attach(self)
+ else:
+ self.grid.detach()
+ self.grid = None
+
+ return self
+
+ def toggleCurve(self):
+ self.curve_en = not self.curve_en;
+
+
+class DataPlotterVector(DataPlotterBase):
+ def __init__(self, parent, legend, title, xlabel, ylabel, size, x, y):
+ DataPlotterBase.__init__(self, parent, title, xlabel, ylabel, size, x, y)
+ self.curve = Qwt.QwtPlotCurve(legend)
+ self.curve.attach(self)
+ self.tag = None;
+ self.x = self.y = [0.0];
+
+ def offerData(self, data, tag):
+ if(tag == self.tag):
+ self.x = data[::2]; self.y = data[1::2]
+
+ def timerEvent(self, e):
+ if(self.curve_en):
+ self.curve.setStyle(Qwt.QwtPlotCurve.Lines)
+ self.curve.setPen(QtGui.QPen(Qt.green))
+ else:
+ self.curve.setStyle(Qwt.QwtPlotCurve.NoCurve)
+
+ self.curve.setData(self.x, self.y)
+ self.replot()
+
+ def enableLegend(self):
+ self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend);
+ return self
+
+ def setSeries(self,tag,name):
+ self.tag = tag;
+ self.curve.setTitle(name)
+
+
+class DataPlotterVectorOne(DataPlotterVector):
+ def __init__(self, parent, legend, title, xlabel, ylabel, size, x, y):
+ DataPlotterVector.__init__(self, parent, legend, title, xlabel, ylabel, size, x, y)
+ self.curve.setSymbol(Qwt.QwtSymbol(Qwt.QwtSymbol.XCross,
+ QtGui.QBrush(), QtGui.QPen(Qt.green), QtCore.QSize(2, 2)))
+ self.setAxisAutoScale(True)
+ self.axisSet = False;
+
+ # Lines on by default
+ self.toggleCurve();
+
+ def offerData(self, data, tag):
+ if(tag == self.tag):
+ if not self.axisSet:
+ self.setAxisScale(1, 0, len(data));
+ self.axisSet = True;
+ self.x = range(0,len(data));
+ self.y = data;
+
+
+class DataPlotterConst(DataPlotterVector):
+ def __init__(self, parent, legend, title, xlabel, ylabel, size, x, y):
+ DataPlotterVector.__init__(self, parent, legend, title, xlabel, ylabel, size, x, y)
+ self.x = arange(-2, 100.1, 2)
+ self.y = zeros(len(self.x), Float)
+ self.curve.setSymbol(Qwt.QwtSymbol(Qwt.QwtSymbol.XCross,
+ QtGui.QBrush(), QtGui.QPen(Qt.green), QtCore.QSize(2, 2)))
+ self.curve.setStyle(Qwt.QwtPlotCurve.NoCurve)
+ self.setAxisAutoScale(False)
+
+class DataPlotterEqTaps(DataPlotterVector):
+ def __init__(self, parent, legend, title, xlabel, ylabel, size, x, y, qtcolor):
+ DataPlotterVector.__init__(self, parent, legend, title, xlabel, ylabel, size, x, y)
+ self.x = arange(-.5, .5, 1)
+ self.y = zeros(len(self.x), Float)
+ self.curve.setPen(QtGui.QPen(qtcolor))
+
+class DataPlotterTicker(DataPlotterBase):
+ def __init__(self, parent, title, xlabel, ylabel, size, x, y, seconds = 60):
+ DataPlotterBase.__init__(self, parent, title, xlabel, ylabel, size, x, y)
+ self.series = {}
+ self.setTimeScale(seconds)
+
+# AAAAAAAAAA - enable for legend at bottom
+# self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend);
+ self.skipEvents=1
+
+ def setTimeScale(self, seconds):
+ intv = float(self.updateTimerInt) / 1000
+ self.x = arange(0, seconds, intv)
+ #self.x = arange(0, seconds, 1)
+ return self
+
+ def addSeries(self, tag, label, qtcolor = None, alpha = 1):
+ class Series:
+ def __init__(self, tag, label, qtcolor, x, plot):
+ self.vec = zeros(len(x), Float)
+ self.value = None
+ self.alpha = alpha
+ self.curve = Qwt.QwtPlotCurve(label)
+ self.curve.setPen(QtGui.QPen(qtcolor))
+ self.plot = plot
+
+ if qtcolor == None: qtcolor = self.DefaultColors[len(self.series)]
+ self.series[tag] = s = Series(tag, label, qtcolor, self.x, self)
+ self.enableSeries(tag)
+ return self
+
+ def enableSeries(self, tag):
+ if self.hasSeries(tag):
+ s = self.series[tag]
+ s.enabled = True
+ s.curve.attach(s.plot)
+ return self
+
+ def disableSeries(self, tag):
+ if self.hasSeries(tag):
+ s = self.series[tag]
+ s.enabled = False
+ s.curve.detach()
+ return self
+
+ def toggleSeries(self,tag):
+ if self.seriesIsEnabled(tag):
+ self.disableSeries(tag)
+ else:
+ self.enableSeries(tag)
+ return self
+
+ def timerEvent(self, e):
+ for k, v in self.series.iteritems():
+ if v.value == None: continue
+ elif v.vec[0] == 0: v.vec = ones(len(v.vec), Float)*v.value
+
+ prev = v.vec[0]
+ v.vec = concatenate((v.vec[:1], v.vec[:-1]), 1)
+ v.vec[0] = v.value*v.alpha + prev*(1-v.alpha)
+ self.series[k] = v
+ v.curve.setData(self.x, v.vec)
+ self.replot()
+
+ def offerData(self, value, tag):
+ if(self.series.has_key(tag)):
+ if not value == NaN:
+ self.series[tag].value = value
+ #print "Data Offer %s items:"%(tag)
+ return self
+
+ def hasSeries(self, tag):
+ return self.series.has_key(tag);
+
+ def seriesIsEnabled(self, tag):
+ if self.hasSeries(tag): return self.series[tag].enabled
+ else: return False
+
+class DataPlotterTickerWithSeriesButtons(DataPlotterTicker):
+ def __init__(self, parent, title, xlabel, ylabel, size, x, y, seconds = 60):
+ DataPlotterTicker.__init__(self, parent, title, xlabel, ylabel, size, x, y, seconds)
+ DataPlotterTicker.setAcceptDrops(self,True);
+ self.buttonx = 50; self.buttony = 20; self.buttonSize = 16
+ self.btns = []
+
+
+
+
+ def addSeriesWithButton(self, tag, legend, qtcolor=None, alpha = 1):
+ self.addSeries(tag, legend, qtcolor, alpha)
+ lenbtns = len(self.btns)
+
+ btn = QtGui.QToolButton(self)
+ btn.rank = lenbtns
+ btn.setText(str(btn.rank))
+ btn.tag = tag
+ #btn.setIcon(Qt.QIcon(Qt.QPixmap(print_xpm)))
+ #btn.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
+ #btn.setForegroundColor(Qt.red)
+ self.btns.append(btn)
+ btn.setGeometry(self.buttonx, self.buttony, self.buttonSize, self.buttonSize)
+ self.buttonx += self.buttonSize
+
+ if lenbtns == 0: callback = self.print_0
+ if lenbtns == 1: callback = self.print_1
+ if lenbtns == 2: callback = self.print_2
+ if lenbtns == 3: callback = self.print_3
+ if lenbtns == 4: callback = self.print_4
+ if lenbtns == 5: callback = self.print_5
+ if lenbtns == 6: callback = self.print_6
+ if lenbtns == 7: callback = self.print_7
+ self.connect(btn, QtCore.SIGNAL('clicked()'), callback)
+ return self
+
+ def toggleSeriesWithButton(self,btn):
+ DataPlotterTicker.toggleSeries(self, btn.tag)
+
+ if self.seriesIsEnabled(btn.tag):
+ # btn.setForegroundRole(Qt.QPalette.NoRole)
+ btn.setText(str(btn.rank))
+ else:
+ btn.setText('x')
+ # btn.setForegroundRole(Qt.QPalette.Light)
+
+ def print_0(self): self.toggleSeriesWithButton(self.btns[0])
+ def print_1(self): self.toggleSeriesWithButton(self.btns[1])
+ def print_2(self): self.toggleSeriesWithButton(self.btns[2])
+ def print_3(self): self.toggleSeriesWithButton(self.btns[3])
+ def print_4(self): self.toggleSeriesWithButton(self.btns[4])
+ def print_5(self): self.toggleSeriesWithButton(self.btns[5])
+ def print_6(self): self.toggleSeriesWithButton(self.btns[6])
+ def print_7(self): self.toggleSeriesWithButton(self.btns[7])
+
+class DataPlotterValueTable:
+ def __init__(self, parent, x, y, xsize, ysize, headers=['Statistic Key ( Source Block :: Stat Name ) ', 'Curent Value', 'Units', 'Description']):
+ # must encapsulate, cuz Qt's bases are not classes
+ self.treeWidget = QtGui.QTreeWidget(parent)
+ self.treeWidget.setColumnCount(len(headers))
+ self.treeWidget.setGeometry(x,y,xsize,ysize)
+ self.treeWidget.setHeaderLabels(headers)
+ self.treeWidget.resizeColumnToContents(0)
+
+ def updateItems(self, knobs, knobprops):
+ # save previous selection if exists
+ sel = self.treeWidget.currentItem();
+ row = self.treeWidget.indexOfTopLevelItem(sel);
+ items = [];
+ self.treeWidget.clear()
+ for k, v in knobs.iteritems():
+ items.append(QtGui.QTreeWidgetItem([str(k), str(v.value), knobprops[k].units, knobprops[k].description]))
+ self.treeWidget.insertTopLevelItems(0, items)
+ # re-set previous selection if exists
+ if(row != -1):
+ try:
+ self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(row));
+ except:
+ pass
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/GrDataPlotter.py b/gnuradio-core/src/python/gnuradio/ctrlport/GrDataPlotter.py
new file mode 100644
index 0000000000..f33160aca2
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/GrDataPlotter.py
@@ -0,0 +1,423 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 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
+import sys, time
+
+try:
+ from gnuradio import qtgui
+ from PyQt4 import QtGui, QtCore
+ import sip
+except ImportError:
+ print "Error: Program requires PyQt4 and gr-qtgui."
+ sys.exit(1)
+
+class GrDataPlotterC(gr.top_block):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ gr.top_block.__init__(self)
+
+ self._name = name
+ self._npts = 500
+ samp_rate = 1.0
+
+ self._last_data = self._npts*[0,]
+ self._data_len = 0
+
+ self.src = gr.vector_source_c([])
+ self.thr = gr.throttle(gr.sizeof_gr_complex, rate)
+ self.snk = qtgui.time_sink_c(self._npts, samp_rate,
+ self._name, 1)
+
+ if(pmin is not None or not pmax is None):
+ self.snk.set_y_axis(pmin, pmax)
+
+ self.connect(self.src, self.thr, (self.snk, 0))
+
+ self.snk.set_line_label(0, "Real")
+ self.snk.set_line_label(1, "Imag")
+
+ self.py_window = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget)
+
+ def __del__(self):
+ pass
+
+ def qwidget(self):
+ return self.py_window
+
+ def name(self):
+ return self._name
+
+ def update(self, data):
+ # Ask GUI if there has been a change in nsamps
+ npts = self.snk.nsamps()
+ if(self._npts != npts):
+
+ # Adjust buffers to accomodate new settings
+ if(npts < self._npts):
+ if(self._data_len < npts):
+ self._last_data = self._last_data[0:npts]
+ else:
+ self._last_data = self._last_data[self._data_len-npts:self._data_len]
+ self._data_len = npts
+ else:
+ self._last_data += (npts - self._npts)*[0,]
+ self._npts = npts
+ self.snk.reset()
+
+ # Update the plot data depending on type
+ if(type(data) == list):
+ data_r = data[0::2]
+ data_i = data[1::2]
+ data = [complex(r,i) for r,i in zip(data_r, data_i)]
+ if(len(data) > self._npts):
+ self.src.set_data(data)
+ self._last_data = data[-self._npts:]
+ else:
+ newdata = self._last_data[-(self._npts-len(data)):]
+ newdata += data
+ self.src.set_data(newdata)
+ self._last_data = newdata
+
+ else: # single value update
+ if(self._data_len < self._npts):
+ self._last_data[self._data_len] = data
+ self._data_len += 1
+ else:
+ self._last_data = self._last_data[1:]
+ self._last_data.append(data)
+ self.src.set_data(self._last_data)
+
+class GrDataPlotterF(gr.top_block):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ gr.top_block.__init__(self)
+
+ self._name = name
+ self._npts = 500
+ samp_rate = 1.0
+
+ self._last_data = self._npts*[0,]
+ self._data_len = 0
+
+ self.src = gr.vector_source_f([])
+ self.thr = gr.throttle(gr.sizeof_float, rate)
+ self.snk = qtgui.time_sink_f(self._npts, samp_rate,
+ self._name, 1)
+
+ if(pmin is not None or not pmax is None):
+ self.snk.set_y_axis(pmin, pmax)
+
+ self.connect(self.src, self.thr, (self.snk, 0))
+
+ self.py_window = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget)
+
+ def __del__(self):
+ pass
+
+ def qwidget(self):
+ return self.py_window
+
+ def name(self):
+ return self._name
+
+ def update(self, data):
+ # Ask GUI if there has been a change in nsamps
+ npts = self.snk.nsamps()
+ if(self._npts != npts):
+
+ # Adjust buffers to accomodate new settings
+ if(npts < self._npts):
+ if(self._data_len < npts):
+ self._last_data = self._last_data[0:npts]
+ else:
+ self._last_data = self._last_data[self._data_len-npts:self._data_len]
+ self._data_len = npts
+ else:
+ self._last_data += (npts - self._npts)*[0,]
+ self._npts = npts
+ self.snk.reset()
+
+ # Update the plot data depending on type
+ if(type(data) == list):
+ if(len(data) > self._npts):
+ self.src.set_data(data)
+ self._last_data = data[-self._npts:]
+ else:
+ newdata = self._last_data[-(self._npts-len(data)):]
+ newdata += data
+ self.src.set_data(newdata)
+ self._last_data = newdata
+
+ else: # single value update
+ if(self._data_len < self._npts):
+ self._last_data[self._data_len] = data
+ self._data_len += 1
+ else:
+ self._last_data = self._last_data[1:]
+ self._last_data.append(data)
+ self.src.set_data(self._last_data)
+
+
+class GrDataPlotterConst(gr.top_block):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ gr.top_block.__init__(self)
+
+ self._name = name
+ self._npts = 500
+ samp_rate = 1.0
+
+ self._last_data = self._npts*[0,]
+ self._data_len = 0
+
+ self.src = gr.vector_source_c([])
+ self.thr = gr.throttle(gr.sizeof_gr_complex, rate)
+ self.snk = qtgui.const_sink_c(self._npts,
+ self._name, 1)
+
+ if(pmin is not None or not pmax is None):
+ self.snk.set_x_axis(pmin, pmax)
+ self.snk.set_y_axis(pmin, pmax)
+
+ self.connect(self.src, self.thr, (self.snk, 0))
+
+ self.py_window = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget)
+
+ def __del__(self):
+ pass
+
+ def qwidget(self):
+ return self.py_window
+
+ def name(self):
+ return self._name
+
+ def update(self, data):
+ # Ask GUI if there has been a change in nsamps
+ npts = self.snk.nsamps()
+ if(self._npts != npts):
+
+ # Adjust buffers to accomodate new settings
+ if(npts < self._npts):
+ if(self._data_len < npts):
+ self._last_data = self._last_data[0:npts]
+ else:
+ self._last_data = self._last_data[self._data_len-npts:self._data_len]
+ self._data_len = npts
+ else:
+ self._last_data += (npts - self._npts)*[0,]
+ self._npts = npts
+ self.snk.reset()
+
+ # Update the plot data depending on type
+ if(type(data) == list):
+ data_r = data[0::2]
+ data_i = data[1::2]
+ data = [complex(r,i) for r,i in zip(data_r, data_i)]
+ if(len(data) > self._npts):
+ self.src.set_data(data)
+ self._last_data = data[-self._npts:]
+ else:
+ newdata = self._last_data[-(self._npts-len(data)):]
+ newdata += data
+ self.src.set_data(newdata)
+ self._last_data = newdata
+
+ else: # single value update
+ if(self._data_len < self._npts):
+ self._last_data[self._data_len] = data
+ self._data_len += 1
+ else:
+ self._last_data = self._last_data[1:]
+ self._last_data.append(data)
+ self.src.set_data(self._last_data)
+
+
+class GrDataPlotterPsdC(gr.top_block):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ gr.top_block.__init__(self)
+
+ self._name = name
+ self._samp_rate = 1.0
+ self._fftsize = 2048
+ self._wintype = gr.firdes.WIN_BLACKMAN_hARRIS
+ self._fc = 0
+
+ self._last_data = self._fftsize*[0,]
+ self._data_len = 0
+
+ self.src = gr.vector_source_c([])
+ self.thr = gr.throttle(gr.sizeof_gr_complex, rate)
+ self.snk = qtgui.freq_sink_c(self._fftsize, self._wintype,
+ self._fc, self._samp_rate,
+ self._name, 1)
+
+ if(pmin is not None or not pmax is None):
+ self.snk.set_y_axis(pmin, pmax)
+
+ self.connect(self.src, self.thr, (self.snk, 0))
+
+ self.py_window = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget)
+
+ def __del__(self):
+ pass
+
+ def qwidget(self):
+ return self.py_window
+
+ def name(self):
+ return self._name
+
+ def update(self, data):
+ # Ask GUI if there has been a change in nsamps
+ fftsize = self.snk.fft_size()
+ if(self._fftsize != fftsize):
+
+ # Adjust buffers to accomodate new settings
+ if(fftsize < self._fftsize):
+ if(self._data_len < fftsize):
+ self._last_data = self._last_data[0:fftsize]
+ else:
+ self._last_data = self._last_data[self._data_len-fftsize:self._data_len]
+ self._data_len = fftsize
+ else:
+ self._last_data += (fftsize - self._fftsize)*[0,]
+ self._fftsize = fftsize
+ self.snk.reset()
+
+ # Update the plot data depending on type
+ if(type(data) == list):
+ data_r = data[0::2]
+ data_i = data[1::2]
+ data = [complex(r,i) for r,i in zip(data_r, data_i)]
+ if(len(data) > self._fftsize):
+ self.src.set_data(data)
+ self._last_data = data[-self._fftsize:]
+ else:
+ newdata = self._last_data[-(self._fftsize-len(data)):]
+ newdata += data
+ self.src.set_data(newdata)
+ self._last_data = newdata
+
+ else: # single value update
+ if(self._data_len < self._fftsize):
+ self._last_data[self._data_len] = data
+ self._data_len += 1
+ else:
+ self._last_data = self._last_data[1:]
+ self._last_data.append(data)
+ self.src.set_data(self._last_data)
+
+class GrDataPlotterPsdF(gr.top_block):
+ def __init__(self, name, rate, pmin=None, pmax=None):
+ gr.top_block.__init__(self)
+
+ self._name = name
+ self._samp_rate = 1.0
+ self._fftsize = 2048
+ self._wintype = gr.firdes.WIN_BLACKMAN_hARRIS
+ self._fc = 0
+
+ self._last_data = self._fftsize*[0,]
+ self._data_len = 0
+
+ self.src = gr.vector_source_f([])
+ self.thr = gr.throttle(gr.sizeof_float, rate)
+ self.snk = qtgui.freq_sink_f(self._fftsize, self._wintype,
+ self._fc, self._samp_rate,
+ self._name, 1)
+
+ if(pmin is not None or not pmax is None):
+ self.snk.set_y_axis(pmin, pmax)
+
+ self.connect(self.src, self.thr, (self.snk, 0))
+
+ self.py_window = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget)
+
+ def __del__(self):
+ pass
+
+ def qwidget(self):
+ return self.py_window
+
+ def name(self):
+ return self._name
+
+ def update(self, data):
+ # Ask GUI if there has been a change in nsamps
+ fftsize = self.snk.fft_size()
+ if(self._fftsize != fftsize):
+
+ # Adjust buffers to accomodate new settings
+ if(fftsize < self._fftsize):
+ if(self._data_len < fftsize):
+ self._last_data = self._last_data[0:fftsize]
+ else:
+ self._last_data = self._last_data[self._data_len-fftsize:self._data_len]
+ self._data_len = fftsize
+ else:
+ self._last_data += (fftsize - self._fftsize)*[0,]
+ self._fftsize = fftsize
+ self.snk.reset()
+
+ # Update the plot data depending on type
+ if(type(data) == list):
+ data_r = data[0::2]
+ data_i = data[1::2]
+ data = [complex(r,i) for r,i in zip(data_r, data_i)]
+ if(len(data) > self._fftsize):
+ self.src.set_data(data)
+ self._last_data = data[-self._fftsize:]
+ else:
+ newdata = self._last_data[-(self._fftsize-len(data)):]
+ newdata += data
+ self.src.set_data(newdata)
+ self._last_data = newdata
+
+ else: # single value update
+ if(self._data_len < self._fftsize):
+ self._last_data[self._data_len] = data
+ self._data_len += 1
+ else:
+ self._last_data = self._last_data[1:]
+ self._last_data.append(data)
+ self.src.set_data(self._last_data)
+
+
+class GrDataPlotterValueTable:
+ def __init__(self, uid, parent, x, y, xsize, ysize,
+ headers=['Statistic Key ( Source Block :: Stat Name ) ',
+ 'Curent Value', 'Units', 'Description']):
+ # must encapsulate, cuz Qt's bases are not classes
+ self.uid = uid
+ self.treeWidget = QtGui.QTreeWidget(parent)
+ self.treeWidget.setColumnCount(len(headers))
+ self.treeWidget.setGeometry(x,y,xsize,ysize)
+ self.treeWidget.setHeaderLabels(headers)
+ self.treeWidget.resizeColumnToContents(0)
+
+ def updateItems(self, knobs, knobprops):
+ items = [];
+ self.treeWidget.clear()
+ for k, v in knobs.iteritems():
+ items.append(QtGui.QTreeWidgetItem([str(k), str(v.value),
+ knobprops[k].units,
+ knobprops[k].description]))
+ self.treeWidget.insertTopLevelItems(0, items)
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/IceRadioClient.py b/gnuradio-core/src/python/gnuradio/ctrlport/IceRadioClient.py
new file mode 100644
index 0000000000..0964b5a4ba
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/IceRadioClient.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 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.
+#
+
+import Ice, Glacier2
+from PyQt4 import QtGui, QtCore
+import sys, time, Ice
+from gnuradio import gr
+from gnuradio.ctrlport import GNURadio
+
+class IceRadioClient(Ice.Application):
+ def __init__(self, parentClass):
+ self.parentClass = parentClass
+
+ def getRadio(self, host, port):
+ radiostr = "gnuradio -t:tcp -h " + host + " -p " + port + " -t 3000"
+ base = self.communicator().stringToProxy(radiostr).ice_twoway()
+ radio = GNURadio.ControlPortPrx.checkedCast(base)
+
+ if not radio:
+ sys.stderr.write("{0} : invalid proxy.\n".format(args[0]))
+ return None
+
+ return radio
+
+ def run(self,args):
+ if len(args) < 2:
+ print "useage: [glacierinstance glacierhost glacierport] host port"
+ return
+ if len(args) == 8:
+ self.useglacier = True
+ guser = args[1]
+ gpass = args[2]
+ ginst = args[3]
+ ghost = args[4]
+ gport = args[5]
+ host = args[6]
+ port = args[7]
+ else:
+ self.useglacier = False
+ host = args[1]
+ port = args[2]
+
+ if self.useglacier:
+ gstring = ginst + "/router -t:tcp -h " + ghost + " -p " + gport
+ print "GLACIER: {0}".format(gstring)
+
+ setrouter = Glacier2.RouterPrx.checkedCast(self.communicator().stringToProxy(gstring))
+ self.communicator().setDefaultRouter(setrouter)
+ defaultRouter = self.communicator().getDefaultRouter()
+ #defaultRouter = self.communicator().stringToProxy(gstring)
+ if not defaultRouter:
+ print self.appName() + ": no default router set"
+ return 1
+ else:
+ print str(defaultRouter)
+ router = Glacier2.RouterPrx.checkedCast(defaultRouter)
+ if not router:
+ print self.appName() + ": configured router is not a Glacier2 router"
+ return 1
+
+ while True:
+ print "This demo accepts any user-id / password combination."
+ if not guser == '' and not gpass == '':
+ id = guser
+ pw = gpass
+ else:
+ id = raw_input("user id: ")
+ pw = raw_input("password: ")
+
+ try:
+ router.createSession(id, pw)
+ break
+ except Glacier2.PermissionDeniedException, ex:
+ print "permission denied:\n" + ex.reason
+
+ radio = self.getRadio(host, port)
+ if(radio is None):
+ return 1
+
+ app = QtGui.QApplication(sys.argv)
+ ex = self.parentClass(radio, port, self)
+ ex.show();
+ app.exec_()
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/__init__.py b/gnuradio-core/src/python/gnuradio/ctrlport/__init__.py
new file mode 100644
index 0000000000..031c3b424e
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/__init__.py
@@ -0,0 +1,30 @@
+#
+# Copyright 2012 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.
+#
+
+# The presence of this file turns this directory into a Python package
+
+import Ice, IcePy
+
+# import swig generated symbols into the ctrlport namespace
+#from ctrlport_swig import *
+from monitor import *
+
+# import any pure python here
+#import GNURadio
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/ctrlport-monitor b/gnuradio-core/src/python/gnuradio/ctrlport/ctrlport-monitor
new file mode 100755
index 0000000000..0230c4cf7a
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/ctrlport-monitor
@@ -0,0 +1,477 @@
+#!/usr/bin/env python
+
+from gnuradio import ctrlport
+
+from PyQt4.QtCore import Qt;
+from PyQt4 import QtCore
+import PyQt4.QtGui as QtGui
+import sys, time, Ice, subprocess;
+from gnuradio.ctrlport.IceRadioClient import *;
+from gnuradio.ctrlport.DataPlotter import *;
+
+_gr_prefs = gr.prefs()
+ice_directory = _gr_prefs.get_string('ctrlport', 'ice_directory', '')
+print ice_directory
+Ice.loadSlice(ice_directory + '/gnuradio.ice')
+
+import GNURadio
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return Qtgui.QSize(800,600);
+ def __init__(self, radio, port):
+
+ super(MAINWindow, self).__init__()
+ self.plots = [];
+
+ 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.readSettings()
+
+ self.setWindowTitle("GNU Radio Control Port Monitor")
+ self.setUnifiedTitleAndToolBarOnMac(True)
+
+# self.resize(QtCore.QSize(1024,768));
+
+ self.newCon(radio,port);
+# self.mdiArea.addSubWindow(child);
+# child.resize(QtCore.QSize(800,600));
+ icon = QtGui.QIcon( ctrlport.__path__[0] + "/icon.png" );
+ self.setWindowIcon(icon);
+
+# add empty plots ?
+# self.plots = [];
+# for i in range(3):
+# self.newPlot();
+
+ def newCon(self,host=None,port=None):
+ child = MForm(host,port,self);
+ #child.setWindowTitle("Modem Connected :: %s:%s"%(host,port));
+ child.setWindowTitle(str(host));
+ self.mdiArea.addSubWindow(child);
+ child.resize(QtCore.QSize(800,600));
+ child.showMaximized();
+ return child;
+
+
+ def newUpd(self,k,r):
+ updater = UpdaterWindow(k,r,None);
+ updater.setWindowTitle("Updater: " + k);
+ self.mdiArea.addSubWindow(updater);
+ updater.show();
+
+
+ def newSub(self,e):
+ tag = str(e.text(0));
+ knobprop = self.knobprops[tag]
+
+ if(type(knobprop.min) in [GNURadio.KnobVecB, GNURadio.KnobVecC,
+ GNURadio.KnobVecI,GNURadio.KnobVecF,
+ GNURadio.KnobVecD,GNURadio.KnobVecL]):
+ if(knobprop.display == ctrlport.DISPTIMESERIES):
+ #plot = self.newConstPlot();
+ plot = self.newVecSeriesPlot();
+ plot.setSeries(tag,tag);
+ plot.setWindowTitle(str(tag));
+ else: # Plot others as XY for now
+ plot = self.newConstPlot();
+ plot.setSeries(tag,tag);
+ plot.setWindowTitle(str(tag));
+ elif(type(knobprop.min) in [GNURadio.KnobB,GNURadio.KnobC,GNURadio.KnobI,
+ GNURadio.KnobF,GNURadio.KnobD,GNURadio.KnobL]):
+ plot = self.newPlot();
+ plot.addSeriesWithButton(tag, knobprop.description + ' (' + knobprop.units + ')', None, 1.0);
+ plot.setWindowTitle(str(tag));
+ else:
+ print "WARNING: plotting of this knob-type not yet supported, ignoring attempt..."
+
+
+ def newVecSeriesPlot(self):
+ #plot = DataPlotterEqTaps(None, 'legend', 'title', 'xlabel', 'ylabel', 250, 0, 0, Qt.green)
+ plot = DataPlotterVectorOne(None, 'legend', 'title', 'xlabel', 'ylabel', 250, 0, 0);
+ self.mdiArea.addSubWindow(plot);
+ plot.dropSignal.connect(self.plotDropEvent );
+ plot.show();
+ self.plots.append(plot);
+ return plot;
+
+ def newConstPlot(self):
+ plot = DataPlotterConst(None, 'legend', 'title', 'xlabel', 'ylabel', 250, 0, 0)
+ self.mdiArea.addSubWindow(plot);
+ plot.dropSignal.connect(self.plotDropEvent );
+ plot.show();
+ self.plots.append(plot);
+ return plot;
+
+ def newPlot(self):
+ plot = DataPlotterTickerWithSeriesButtons(None,"",'units', '', 250,0,0,120);
+ self.mdiArea.addSubWindow(plot);
+ plot.dropSignal.connect(self.plotDropEvent );
+ plot.show();
+ self.plots.append(plot);
+ return plot;
+
+
+ def update(self, knobs):
+ for item in knobs.keys():
+ for plot in self.plots:
+ plot.offerData( knobs[item].value, item );
+
+ def plotDropEvent(self, e):
+ model = QtGui.QStandardItemModel()
+ model.dropMimeData(e.mimeData(), QtCore.Qt.CopyAction,0,0,QtCore.QModelIndex())
+ tag = str(QtGui.QTreeWidgetItem([model.item(0,0).text()]).text(0));
+ knobprop = self.knobprops[tag]
+ try:
+ self.sender().addSeriesWithButton(tag, knobprop.description + ' (' + knobprop.units + ')', None, 1.0);
+ except:
+ print "This plot does not accept additional data items! ignoring..."
+
+
+ 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.newAct = QtGui.QAction(QtGui.QIcon(':/images/new.png'), "&New Plot",
+ self.newPlotAct = QtGui.QAction("&New Plot",
+ self, shortcut=QtGui.QKeySequence.New,
+ statusTip="Create a new file", triggered=self.newPlot)
+
+ 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)
+
+
+ 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.newPlotAct)
+ 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 createToolBars(self):
+ self.fileToolBar = self.addToolBar("File")
+ self.fileToolBar.addAction(self.newConAct)
+ self.fileToolBar.addAction(self.newPlotAct)
+
+ 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.QWidget):
+ def __init__(self, key, radio, parent):
+ QtGui.QWidget.__init__(self,parent)
+
+ self.key = key;
+ self.radio = radio;
+
+ self.resize(300,200)
+ self.layout = QtGui.QVBoxLayout();
+
+ self.textInput = QtGui.QLineEdit();
+ self.setButton = QtGui.QPushButton("Set Value")
+
+ rv = radio.get([key]);
+ self.textInput.setText(str(rv[key].value));
+ self.sv = rv[key];
+
+
+ self.props = radio.properties([key])[key];
+ info = str(self.props);
+
+ self.infoLabel = QtGui.QLabel(info);
+ self.layout.addWidget(self.infoLabel);
+
+ self.layout.addWidget(self.textInput);
+
+ self.setButton.connect(self.setButton, QtCore.SIGNAL('clicked()'), self._set)
+
+ 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(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( self.props.min.value, self.props.max.value );
+ self.slider.setRange( 0,10000 );
+
+ self.slider.setValue( self.sv.value );
+ self.slider.setValue( self.steps*(self.sv.value-self.props.min.value)/self.valspan);
+
+ self.connect(self.slider, QtCore.SIGNAL("sliderReleased()"), self._slide);
+
+ self.layout.addLayout(self.sliderlayout);
+
+ self.layout.addWidget(self.setButton);
+
+ # set layout and go...
+ self.setLayout(self.layout);
+
+ def _slide(self):
+ val = (self.slider.value()*self.valspan + self.props.min.value)/float(self.steps);
+ self.textInput.setText(str(val));
+
+ def _set(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());
+ else:
+ print "set type not supported! (%s)"%(type(self.sv.value));
+ sys.exit(-1);
+ #self.sv.value = int(val);
+ self.sv.value = val;
+ km = {};
+ km[self.key] = self.sv;
+ self.radio.set(km);
+
+class MForm(QtGui.QWidget):
+ def update(self):
+ try:
+ st = time.time();
+ knobs = self.radio.get([]);
+ 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):
+ # closing all of these seems to help control shutdown order.
+ self.parent.closeAct.trigger()
+ else:
+ sys.exit(1)
+ return
+
+ tableitems = knobs.keys();
+
+ #UPDATE TABLE:
+ self.table.updateItems(knobs, self.knobprops)
+
+ #UPDATE PLOTS
+ self.parent.update( knobs );
+
+
+ def __init__(self, radio=None, port=None, parent=None):
+
+ super(MForm, self).__init__()
+
+ if(radio == None or port == None):
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ print "connecting..."
+ radio = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+ print "this is broken"
+ return;
+ else:
+ return;
+
+ self.parent = parent;
+ self.horizontalLayout = QtGui.QVBoxLayout(self);
+ self.gridLayout = QtGui.QGridLayout()
+
+
+ self.radio = radio
+ self.knobprops = self.radio.properties([])
+ self.parent.knobprops = self.knobprops;
+ self.resize(775,500)
+ self.timer = QtCore.QTimer()
+ self.constupdatediv = 0
+ self.tableupdatediv = 0
+ plotsize=250;
+
+
+ # make table
+ self.table = DataPlotterValueTable(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.connect(self.table.treeWidget, QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.parent.newSub);
+
+ # set up context menu ..
+ self.table.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu);
+ self.table.treeWidget.customContextMenuRequested.connect(self.openMenu);
+
+ self.timer.start(2000)
+
+ def plotDropEvent(self, e):
+ model = QtGui.QStandardItemModel()
+ model.dropMimeData(e.mimeData(), QtCore.Qt.CopyAction,0,0,QtCore.QModelIndex())
+ tag = str(QtGui.QTreeWidgetItem([model.item(0,0).text()]).text(0));
+ knobprop = self.knobprops[tag]
+ self.sender().addSeriesWithButton(tag, knobprop.description + ' (' + knobprop.units + ')', None, 1.0);
+
+ def openMenu(self, pos):
+ index = self.table.treeWidget.selectedIndexes();
+ item = self.table.treeWidget.itemFromIndex(index[0]);
+ itemname = str(item.text(0));
+ self.parent.newUpd(itemname, self.radio);
+# updater = UpdaterWindow(itemname, self.radio, self.parent);
+# updater.setWindowTitle("Updater: " + itemname);
+# self.parent.mdiArea.addSubWindow(updater);
+# print "done"
+
+
+class MyClient(IceRadioClient):
+ def __init__(self): IceRadioClient.__init__(self, MAINWindow)
+
+sys.exit(MyClient().main(sys.argv))
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/gr-ctrlport-monitor b/gnuradio-core/src/python/gnuradio/ctrlport/gr-ctrlport-monitor
new file mode 100755
index 0000000000..241b8a2043
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/gr-ctrlport-monitor
@@ -0,0 +1,581 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 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 sys, time
+
+import Ice
+from gnuradio.ctrlport.IceRadioClient import *
+from gnuradio.ctrlport.GrDataPlotter import *
+from gnuradio.ctrlport import GNURadio
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return Qtgui.QSize(800,600)
+
+ def __init__(self, radio, port, interface):
+
+ super(MAINWindow, self).__init__()
+ self.conns = []
+ self.plots = []
+ self.knobprops = []
+ self.interface = interface
+
+ 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(radio, port)
+ icon = QtGui.QIcon(ctrlport.__path__[0] + "/icon.png" )
+ self.setWindowIcon(icon)
+
+ def newCon(self, radio=None, port=None):
+ child = MForm(radio, port, len(self.conns), self)
+ if(child.radio is not None):
+ child.setWindowTitle(str(child.radio))
+ self.mdiArea.addSubWindow(child)
+ child.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 = props[key].min.value
+ pmax = props[key].max.value
+ if pmin == []:
+ pmin = None
+ else:
+ pmin = 1.1*pmin
+ if pmax == []:
+ pmax = None
+ else:
+ pmax = 1.1*pmax
+
+ def newUpdaterProxy():
+ self.newUpdater(key, radio)
+
+ def newPlotterFProxy():
+ self.newPlotF(key, uid, title, pmin, pmax)
+
+ def newPlotterCProxy():
+ self.newPlotC(key, uid, title, pmin, pmax)
+
+ def newPlotterConstProxy():
+ self.newPlotConst(key, uid, title, pmin, pmax)
+
+ def newPlotterPsdFProxy():
+ self.newPlotPsdF(key, uid, title)
+
+ def newPlotterPsdCProxy():
+ self.newPlotPsdC(key, uid, title)
+
+ menu = QtGui.QMenu(self)
+ menu.setTitle("Item Actions")
+ menu.setTearOffEnabled(False)
+
+ # object properties
+ menu.addAction("Properties", newUpdaterProxy)
+
+ # displays available if not complex
+ menu.addAction("Plot Time", newPlotterFProxy)
+ menu.addAction("Plot PSD", newPlotterPsdFProxy)
+
+ # displays available if complex
+ menu.addAction("Plot Time (cpx)", newPlotterCProxy)
+ menu.addAction("Plot Constellation", newPlotterConstProxy)
+ menu.addAction("Plot PSD cpx", newPlotterPsdCProxy)
+
+ 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 = knobprop.min.value
+ pmax = knobprop.max.value
+ if pmin == []:
+ pmin = None
+ else:
+ pmin = 1.1*pmin
+ if pmax == []:
+ pmax = None
+ else:
+ pmax = 1.1*pmax
+
+ if(knobprop.display == GNURadio.DisplayType.DISPXYSCATTER):
+ self.newPlotConst(tag, uid, title, pmin, pmax)
+ elif(knobprop.display == GNURadio.DisplayType.DISPTIMESERIESF):
+ self.newPlotF(tag, uid, title, pmin, pmax)
+ elif(knobprop.display == GNURadio.DisplayType.DISPTIMESERIESC):
+ self.newPlotC(tag, uid, title, pmin, pmax)
+
+ def createPlot(self, plot, uid, title):
+ plot.start()
+ self.plots[uid].append(plot)
+
+ self.mdiArea.addSubWindow(plot.qwidget())
+ plot.qwidget().setWindowTitle("{0}: {1}".format(title, plot.name()))
+ self.connect(plot.qwidget(),
+ QtCore.SIGNAL('destroyed(QObject*)'),
+ self.destroyPlot)
+ plot.qwidget().show()
+
+
+ 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):
+ plot = GrDataPlotterConst(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotF(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrDataPlotterF(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotC(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrDataPlotterC(tag, 32e6, pmin, pmax)
+ 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 update(self, knobs, uid):
+ #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
+ for plot in self.plots[uid]:
+ data = knobs[plot.name()].value
+ 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.newAct = QtGui.QAction(QtGui.QIcon(':/images/new.png'), "&New Plot",
+ self.newPlotAct = QtGui.QAction("&New Plot",
+ self,
+ statusTip="Create a new file", triggered=self.newPlotF)
+
+ 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)
+
+
+ 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.newPlotAct)
+ 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 createToolBars(self):
+ self.fileToolBar = self.addToolBar("File")
+ self.fileToolBar.addAction(self.newConAct)
+ self.fileToolBar.addAction(self.newPlotAct)
+
+ 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
+ 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])
+ self.textInput.setText(str(rv[key].value))
+ 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)
+
+ 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())
+ else:
+ sys.stderr.write("set type not supported! ({0})\n".format(type(self.sv.value)))
+ sys.exit(-1)
+
+ self.sv.value = val
+ km = {}
+ km[self.key] = self.sv
+ self.radio.set(km)
+ self._set_slider_value(self.sv.value)
+
+ def _set(self):
+ self._apply()
+ self.done(0)
+
+
+class MForm(QtGui.QWidget):
+ def update(self):
+ try:
+ st = time.time();
+ knobs = self.radio.get([]);
+ 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:
+ self.table.updateItems(knobs, self.knobprops)
+
+ #UPDATE PLOTS
+ self.parent.update(knobs, self.uid)
+
+
+ def __init__(self, radio=None, port=None, uid=0, parent=None):
+
+ super(MForm, self).__init__()
+
+ if(radio == None or port == None):
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ host = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+ radio = parent.interface.getRadio(host, port)
+ else:
+ self.radio = None
+ return
+
+ self.uid = uid
+ self.parent = parent
+ self.horizontalLayout = QtGui.QVBoxLayout(self)
+ self.gridLayout = QtGui.QGridLayout()
+
+ self.radio = radio
+ self.knobprops = self.radio.properties([])
+ 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.timer.start(1000)
+
+ # 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);
+
+ 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)
+
+
+class MyClient(IceRadioClient):
+ def __init__(self):
+ IceRadioClient.__init__(self, MAINWindow)
+
+sys.exit(MyClient().main(sys.argv))
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/icon.png b/gnuradio-core/src/python/gnuradio/ctrlport/icon.png
new file mode 100644
index 0000000000..4beb204428
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/icon.png
Binary files differ
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/monitor.py b/gnuradio-core/src/python/gnuradio/ctrlport/monitor.py
new file mode 100644
index 0000000000..1e74a814f0
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/monitor.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 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.
+#
+
+import sys, subprocess, re, signal, time, atexit, os
+from gnuradio import gr
+
+class monitor:
+ def __init__(self):
+ print "ControlPort Monitor running."
+ self.started = False
+ self.start()
+ atexit.register(self.shutdown)
+
+ def __del__(self):
+ if(self.started):
+ self.stop()
+
+ def start(self):
+ print "monitor::endpoints() = %s" % (gr.rpcmanager_get().endpoints())
+ try:
+ self.proc = subprocess.Popen(map(lambda a: ["gr-ctrlport-monitor",
+ re.search("\d+\.\d+\.\d+\.\d+",a).group(0),
+ re.search("-p (\d+)",a).group(1)],
+ gr.rpcmanager_get().endpoints())[0])
+ self.started = True
+ except:
+ self.proc = None
+ print "failed to to start ControlPort Monitor on specified port"
+
+ def stop(self):
+ if(self.proc):
+ if(self.proc.returncode == None):
+ print "\tcalling stop on shutdown"
+ self.proc.terminate()
+ else:
+ print "\tno proc to shut down, exiting"
+
+ def shutdown(self):
+ print "ctrlport.monitor received shutdown signal"
+ if(self.started):
+ self.stop()
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/qa_cpp_py_binding.py b/gnuradio-core/src/python/gnuradio/ctrlport/qa_cpp_py_binding.py
new file mode 100755
index 0000000000..d4db8e76da
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/qa_cpp_py_binding.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 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.
+#
+
+#
+# This program tests mixed python and c++ ctrlport exports in a single app
+#
+
+import Ice
+import sys, time, random, numpy
+from gnuradio import gr, gr_unittest
+
+from gnuradio.ctrlport import GNURadio
+from gnuradio import ctrlport
+
+def get1():
+ return "success"
+
+def get2():
+ return "failure"
+
+class inc_class:
+ def __init__(self):
+ self.val = 1
+ def pp(self):
+ self.val = self.val+1
+ return self.val
+
+get3 = inc_class()
+
+def get4():
+ random.seed(0)
+ rv = random.random()
+ return rv
+
+def get5():
+ numpy.random.seed(0)
+ samp_t = numpy.random.randn(24)+1j*numpy.random.randn(24);
+ samp_f = numpy.fft.fft(samp_t);
+ log_pow_f = 20*numpy.log10(numpy.abs(samp_f))
+ rv = list(log_pow_f)
+ return rv;
+
+def get6():
+ numpy.random.seed(0)
+ samp_t = numpy.random.randn(1024)+1j*numpy.random.randn(1024);
+ rv = list(samp_t)
+ return rv;
+
+class test_cpp_py_binding(gr_unittest.TestCase):
+
+ def setUp(self):
+ self.tb = gr.top_block()
+
+ def tearDown(self):
+ self.tb = None
+
+ def test_001(self):
+ v1 = gr.RPC_get_string("pyland", "v1", "unit_1_string",
+ "Python Exported String", "", "", "",
+ gr.DISPNULL)
+ v1.activate(get1)
+
+ v2 = gr.RPC_get_string("pyland", "v2", "unit_2_string",
+ "Python Exported String", "", "", "",
+ gr.DISPNULL)
+ v2.activate(get2)
+
+ v3 = gr.RPC_get_int("pyland", "v3", "unit_3_int",
+ "Python Exported Int", 0, 100, 1,
+ gr.DISPNULL)
+ v3.activate(get3.pp)
+
+ v4 = gr.RPC_get_double("pyland", "time", "unit_4_time_double",
+ "Python Exported Double", 0, 1000, 1,
+ gr.DISPNULL)
+ v4.activate(get4)
+
+ v5 = gr.RPC_get_vector_float("pyland", "fvec", "unit_5_float_vector",
+ "Python Exported Float Vector", [], [], [],
+ gr.DISPTIMESERIESC)
+ v5.activate(get5)
+
+ v6 = gr.RPC_get_vector_gr_complex("pyland", "cvec", "unit_6_gr_complex_vector",
+ "Python Exported Complex Vector", [], [], [],
+ gr.DISPXYSCATTER)
+ v6.activate(get6)
+
+ # print some variables locally
+ val = get1()
+ rval = v1.get()
+ self.assertEqual(val, rval)
+
+ val = get2()
+ rval = v2.get()
+ self.assertEqual(val, rval)
+
+ val = get3.pp()
+ rval = v3.get()
+ self.assertEqual(val+1, rval)
+
+ val = get4()
+ rval = v4.get()
+ self.assertEqual(val, rval)
+
+ val = get5()
+ rval = v5.get()
+ self.assertComplexTuplesAlmostEqual(val, rval, 5)
+
+ val = get6()
+ rval = v6.get()
+ self.assertComplexTuplesAlmostEqual(val, rval, 5)
+
+ def test_002(self):
+ data = range(1,9)
+
+ self.src = gr.vector_source_c(data)
+ self.p1 = gr.ctrlport_probe_c("aaa","C++ exported variable")
+ self.p2 = gr.ctrlport_probe_c("bbb","C++ exported variable")
+
+ self.tb.connect(self.src, self.p1)
+ self.tb.connect(self.src, self.p2)
+ self.tb.start()
+
+ # Probes return complex values as list of floats with re, im
+ # Imaginary parts of this data set are 0.
+ expected_result = [1, 0, 2, 0, 3, 0, 4, 0,
+ 5, 0, 6, 0, 7, 0, 8, 0]
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+
+ # Initialize a simple Ice client from endpoint
+ ic = Ice.initialize(sys.argv)
+ base = ic.stringToProxy(ep)
+ radio = GNURadio.ControlPortPrx.checkedCast(base)
+
+ # Get all exported knobs
+ ret = radio.get([])
+ for name in ret.keys():
+ result = ret[name].value
+ self.assertEqual(result, expected_result)
+
+ self.tb.stop()
+
+if __name__ == '__main__':
+ gr_unittest.run(test_cpp_py_binding, "test_cpp_py_binding.xml")
+
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/qa_cpp_py_binding_set.py b/gnuradio-core/src/python/gnuradio/ctrlport/qa_cpp_py_binding_set.py
new file mode 100755
index 0000000000..fe7e5bcb60
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/qa_cpp_py_binding_set.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 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.
+#
+
+#
+# This program tests mixed python and c++ GRCP sets in a single app
+#
+
+import Ice
+import sys, time, random, numpy
+from gnuradio import gr, gr_unittest
+
+from gnuradio.ctrlport import GNURadio
+from gnuradio import ctrlport
+
+class inc_class:
+ def __init__(self,val):
+ self.val = val;
+
+ def _get(self):
+ #print "returning get (val = %s)"%(str(self.val));
+ return self.val;
+
+ def _set(self,val):
+ #print "updating val to %s"%(str(val));
+ self.val = val;
+ return;
+
+getset1 = inc_class(10);
+getset2 = inc_class(100.0);
+getset3 = inc_class("test");
+
+class test_cpp_py_binding_set(gr_unittest.TestCase):
+ def setUp(self):
+ self.tb = gr.top_block()
+
+ def tearDown(self):
+ self.tb = None
+
+ def test_001(self):
+
+ g1 = gr.RPC_get_int("pyland", "v1", "unit_1_int",
+ "Python Exported Int", 0, 100, 10,
+ gr.DISPNULL)
+ g1.activate(getset1._get)
+ s1 = gr.RPC_get_int("pyland", "v1", "unit_1_int",
+ "Python Exported Int", 0, 100, 10,
+ gr.DISPNULL)
+ s1.activate(getset1._set)
+ time.sleep(0.01)
+
+ # test int variables
+ getset1._set(21)
+ val = getset1._get()
+ rval = g1.get()
+ self.assertEqual(val, rval)
+
+ g2 = gr.RPC_get_float("pyland", "v2", "unit_2_float",
+ "Python Exported Float", -100, 1000.0, 100.0,
+ gr.DISPNULL)
+ g2.activate(getset2._get)
+ s2 = gr.RPC_get_float("pyland", "v2", "unit_2_float",
+ "Python Exported Float", -100, 1000.0, 100.0,
+ gr.DISPNULL)
+ s2.activate(getset2._set)
+ time.sleep(0.01)
+
+ # test float variables
+ getset2._set(123.456)
+ val = getset2._get()
+ rval = g2.get()
+ self.assertAlmostEqual(val, rval, 4)
+
+ g3 = gr.RPC_get_string("pyland", "v3", "unit_3_string",
+ "Python Exported String", "", "", "",
+ gr.DISPNULL)
+ g3.activate(getset3._get)
+ s3 = gr.RPC_get_string("pyland", "v3", "unit_3_string",
+ "Python Exported String", "", "", "",
+ gr.DISPNULL)
+ s3.activate(getset3._set)
+ time.sleep(0.01)
+
+ # test string variables
+ getset3._set("third test")
+ val = getset3._get()
+ rval = g3.get()
+ self.assertEqual(val, rval)
+
+
+ def test_002(self):
+ data = range(1, 10)
+
+ self.src = gr.vector_source_c(data, True)
+ self.p = gr.nop(gr.sizeof_gr_complex)
+ self.p.set_ctrlport_test(0);
+ probe_info = "{0}{1}".format(self.p.name(), self.p.unique_id())
+
+ self.tb.connect(self.src, self.p)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+
+ # Initialize a simple Ice client from endpoint
+ ic = Ice.initialize(sys.argv)
+ base = ic.stringToProxy(ep)
+ radio = GNURadio.ControlPortPrx.checkedCast(base)
+
+ self.tb.start()
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get all exported knobs
+ key_name_test = probe_info+"::test"
+ ret = radio.get([key_name_test,])
+
+ ret[key_name_test].value = 10
+ radio.set({key_name_test: ret[key_name_test]})
+
+ ret = radio.get([])
+ result_test = ret[key_name_test].value
+ self.assertEqual(result_test, 10)
+
+ self.tb.stop()
+ self.tb.wait()
+
+if __name__ == '__main__':
+ gr_unittest.run(test_cpp_py_binding_set, "test_cpp_py_binding_set.xml")
+