diff options
Diffstat (limited to 'gnuradio-runtime/python/gnuradio/ctrlport')
-rw-r--r-- | gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt | 97 | ||||
-rw-r--r-- | gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py | 428 | ||||
-rw-r--r-- | gnuradio-runtime/python/gnuradio/ctrlport/IceRadioClient.py | 102 | ||||
-rw-r--r-- | gnuradio-runtime/python/gnuradio/ctrlport/__init__.py | 30 | ||||
-rwxr-xr-x | gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-curses | 268 | ||||
-rwxr-xr-x | gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor | 721 | ||||
-rwxr-xr-x | gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitor | 591 | ||||
-rwxr-xr-x | gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx | 727 | ||||
-rw-r--r-- | gnuradio-runtime/python/gnuradio/ctrlport/icon.png | bin | 0 -> 1532 bytes | |||
-rw-r--r-- | gnuradio-runtime/python/gnuradio/ctrlport/monitor.py | 59 |
10 files changed, 3023 insertions, 0 deletions
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt b/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt new file mode 100644 index 0000000000..c68694785f --- /dev/null +++ b/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt @@ -0,0 +1,97 @@ +# 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) + +EXECUTE_PROCESS( + COMMAND ${ICE_SLICE2PY} -I${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib + --output-dir=${CMAKE_BINARY_DIR}/gnuradio-runtime/python + ${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib/gnuradio.ice +) + +EXECUTE_PROCESS( + COMMAND ${ICE_SLICE2PY} -I${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib + --output-dir=${CMAKE_BINARY_DIR}/gnuradio-runtime/python + ${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib/frontend.ice +) + +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_BINARY_DIR}/gnuradio-runtime/python/gnuradio_ice.py + ${CMAKE_BINARY_DIR}/gnuradio-runtime/python/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}/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}/gr-ctrlport-monitor + ${CMAKE_CURRENT_SOURCE_DIR}/gr-ctrlport-curses + DESTINATION ${GR_RUNTIME_DIR} + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + COMPONENT "core_python" +) diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py b/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py new file mode 100644 index 0000000000..8597ca6497 --- /dev/null +++ b/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py @@ -0,0 +1,428 @@ +#!/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 +from gnuradio import blocks +from gnuradio import filter +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 GrDataPlotParent(gr.top_block, QtGui.QWidget): + # Setup signals + plotupdated = QtCore.pyqtSignal(QtGui.QWidget) + + def __init__(self, name, rate, pmin=None, pmax=None): + gr.top_block.__init__(self) + QtGui.QWidget.__init__(self, None) + + self._name = name + self._npts = 500 + self._rate = rate + self.knobnames = [name,] + + self.layout = QtGui.QVBoxLayout() + self.setLayout(self.layout) + + self.setAcceptDrops(True) + + def _setup(self, nconnections): + self.stop() + self.wait() + + if(self.layout.count() > 0): + # Remove and disconnect. Making sure no references to snk + # remain so that the plot gets deleted. + self.layout.removeWidget(self.py_window) + self.disconnect(self.thr, (self.snk, 0)) + self.disconnect(self.src[0], self.thr) + for n in xrange(1, self._ncons): + self.disconnect(self.src[n], (self.snk,n)) + + self._ncons = nconnections + self._data_len = self._ncons*[0,] + + self.thr = blocks.throttle(self._datasize, self._rate) + self.snk = self.get_qtsink() + + self.connect(self.thr, (self.snk, 0)) + + self._last_data = [] + self.src = [] + for n in xrange(self._ncons): + self.set_line_label(n, self.knobnames[n]) + + self._last_data.append(int(self._npts)*[0,]) + self.src.append(self.get_vecsource()) + + if(n == 0): + self.connect(self.src[n], self.thr) + else: + self.connect(self.src[n], (self.snk,n)) + + self.py_window = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget) + + self.layout.addWidget(self.py_window) + + def __del__(self): + pass + + def close(self): + self.snk.close() + + def qwidget(self): + return self.py_window + + def name(self): + return self._name + + def semilogy(self, en=True): + self.snk.enable_semilogy(en) + + def dragEnterEvent(self, e): + e.acceptProposedAction() + + def dropEvent(self, e): + if(e.mimeData().hasFormat("text/plain")): + data = str(e.mimeData().text()) + + #"PlotData:{0}:{1}".format(tag, iscomplex) + datalst = data.split(":::") + tag = datalst[0] + name = datalst[1] + cpx = datalst[2] != "0" + + if(tag == "PlotData" and cpx == self._iscomplex): + self.knobnames.append(name) + + # create a new qwidget plot with the new data stream. + self._setup(len(self.knobnames)) + + # emit that this plot has been updated with a new qwidget. + self.plotupdated.emit(self) + + e.acceptProposedAction() + + def data_to_complex(self, data): + if(self._iscomplex): + data_r = data[0::2] + data_i = data[1::2] + data = [complex(r,i) for r,i in zip(data_r, data_i)] + return data + + def update(self, data): + # Ask GUI if there has been a change in nsamps + npts = self.get_npts() + if(self._npts != npts): + + # Adjust buffers to accomodate new settings + for n in xrange(self._ncons): + if(npts < self._npts): + if(self._data_len[n] < npts): + self._last_data[n] = self._last_data[n][0:npts] + else: + self._last_data[n] = self._last_data[n][self._data_len[n]-npts:self._data_len[n]] + self._data_len[n] = npts + else: + self._last_data[n] += (npts - self._npts)*[0,] + self._npts = npts + self.snk.reset() + + if(self._stripchart): + # Update the plot data depending on type + for n in xrange(self._ncons): + if(type(data[n]) == list): + data[n] = self.data_to_complex(data[n]) + if(len(data[n]) > self._npts): + self.src[n].set_data(data[n]) + self._last_data[n] = data[n][-self._npts:] + else: + newdata = self._last_data[n][-(self._npts-len(data)):] + newdata += data[n] + self.src[n].set_data(newdata) + self._last_data[n] = newdata + + else: # single value update + if(self._iscomplex): + data[n] = complex(data[n][0], data[n][1]) + if(self._data_len[n] < self._npts): + self._last_data[n][self._data_len[n]] = data[n] + self._data_len[n] += 1 + else: + self._last_data[n] = self._last_data[n][1:] + self._last_data[n].append(data[n]) + self.src[n].set_data(self._last_data[n]) + else: + for n in xrange(self._ncons): + if(type(data[n]) != list): + data[n] = [data[n],] + data[n] = self.data_to_complex(data[n]) + self.src[n].set_data(data[n]) + + + +class GrDataPlotterC(GrDataPlotParent): + def __init__(self, name, rate, pmin=None, pmax=None, stripchart=False): + GrDataPlotParent.__init__(self, name, rate, pmin, pmax) + + self._stripchart = stripchart + self._datasize = gr.sizeof_gr_complex + self._iscomplex = True + + self._setup(1) + + def stem(self, en=True): + self.snk.enable_stem_plot(en) + + def get_qtsink(self): + snk = qtgui.time_sink_c(self._npts, 1.0, + self._name, self._ncons) + snk.enable_autoscale(True) + return snk + + def get_vecsource(self): + return blocks.vector_source_c([]) + + def get_npts(self): + self._npts = self.snk.nsamps() + return self._npts + + def set_line_label(self, n, name): + self.snk.set_line_label(2*n+0, "Re{" + self.knobnames[n] + "}") + self.snk.set_line_label(2*n+1, "Im{" + self.knobnames[n] + "}") + + +class GrDataPlotterF(GrDataPlotParent): + def __init__(self, name, rate, pmin=None, pmax=None, stripchart=False): + GrDataPlotParent.__init__(self, name, rate, pmin, pmax) + + self._stripchart = stripchart + self._datasize = gr.sizeof_float + self._iscomplex = False + + self._setup(1) + + def stem(self, en=True): + self.snk.enable_stem_plot(en) + + def get_qtsink(self): + snk = qtgui.time_sink_f(self._npts, 1.0, + self._name, self._ncons) + snk.enable_autoscale(True) + return snk + + def get_vecsource(self): + return blocks.vector_source_f([]) + + def get_npts(self): + self._npts = self.snk.nsamps() + return self._npts + + def set_line_label(self, n, name): + self.snk.set_line_label(n, self.knobnames[n]) + + +class GrDataPlotterConst(GrDataPlotParent): + def __init__(self, name, rate, pmin=None, pmax=None): + GrDataPlotParent.__init__(self, name, rate, pmin, pmax) + + self._datasize = gr.sizeof_gr_complex + self._stripchart = False + self._iscomplex = True + + self._setup(1) + + def get_qtsink(self): + snk = qtgui.const_sink_c(self._npts, + self._name, + self._ncons) + snk.enable_autoscale(True) + return snk + + def get_vecsource(self): + return blocks.vector_source_c([]) + + def get_npts(self): + self._npts = self.snk.nsamps() + return self._npts + + def scatter(self, en=True): + if(en): + self.snk.set_line_style(0, 0) + else: + self.snk.set_line_style(0, 1) + + def set_line_label(self, n, name): + self.snk.set_line_label(n, self.knobnames[n]) + + +class GrDataPlotterPsdC(GrDataPlotParent): + def __init__(self, name, rate, pmin=None, pmax=None): + GrDataPlotParent.__init__(self, name, rate, pmin, pmax) + + self._datasize = gr.sizeof_gr_complex + self._stripchart = True + self._iscomplex = True + + self._npts = 2048 + self._wintype = filter.firdes.WIN_BLACKMAN_hARRIS + self._fc = 0 + + self._setup(1) + + def get_qtsink(self): + snk = qtgui.freq_sink_c(self._npts, self._wintype, + self._fc, 1.0, + self._name, + self._ncons) + snk.enable_autoscale(True) + return snk + + def get_vecsource(self): + return blocks.vector_source_c([]) + + def get_npts(self): + self._npts = self.snk.fft_size() + return self._npts + + def set_line_label(self, n, name): + self.snk.set_line_label(n, self.knobnames[n]) + + +class GrDataPlotterPsdF(GrDataPlotParent): + def __init__(self, name, rate, pmin=None, pmax=None): + GrDataPlotParent.__init__(self, name, rate, pmin, pmax) + + self._datasize = gr.sizeof_float + self._stripchart = True + self._iscomplex = False + + self._npts = 2048 + self._wintype = filter.firdes.WIN_BLACKMAN_hARRIS + self._fc = 0 + + self._setup(1) + + def get_qtsink(self): + snk = qtgui.freq_sink_f(self._npts, self._wintype, + self._fc, 1.0, + self._name, + self._ncons) + snk.enable_autoscale(True) + return snk + + def get_vecsource(self): + return blocks.vector_source_f([]) + + def get_npts(self): + self._npts = self.snk.fft_size() + return self._npts + + def set_line_label(self, n, name): + self.snk.set_line_label(n, self.knobnames[n]) + + +class GrTimeRasterF(GrDataPlotParent): + def __init__(self, name, rate, pmin=None, pmax=None): + GrDataPlotParent.__init__(self, name, rate, pmin, pmax) + + self._npts = 10 + self._rows = 40 + + self._datasize = gr.sizeof_float + self._stripchart = False + self._iscomplex = False + + self._setup(1) + + def get_qtsink(self): + snk = qtgui.time_raster_sink_f(1.0, self._npts, self._rows, + [], [], self._name, + self._ncons) + return snk + + def get_vecsource(self): + return blocks.vector_source_f([]) + + def get_npts(self): + self._npts = self.snk.num_cols() + return self._npts + + def set_line_label(self, n, name): + self.snk.set_line_label(n, self.knobnames[n]) + +class GrTimeRasterB(GrDataPlotParent): + def __init__(self, name, rate, pmin=None, pmax=None): + GrDataPlotParent.__init__(self, name, rate, pmin, pmax) + + self._npts = 10 + self._rows = 40 + + self._datasize = gr.sizeof_char + self._stripchart = False + self._iscomplex = False + + self._setup(1) + + def get_qtsink(self): + snk = qtgui.time_raster_sink_b(1.0, self._npts, self._rows, + [], [], self._name, + self._ncons) + return snk + + def get_vecsource(self): + return blocks.vector_source_b([]) + + def get_npts(self): + self._npts = self.snk.num_cols() + return self._npts + + def set_line_label(self, n, name): + self.snk.set_line_label(n, self.knobnames[n]) + + +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-runtime/python/gnuradio/ctrlport/IceRadioClient.py b/gnuradio-runtime/python/gnuradio/ctrlport/IceRadioClient.py new file mode 100644 index 0000000000..0964b5a4ba --- /dev/null +++ b/gnuradio-runtime/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-runtime/python/gnuradio/ctrlport/__init__.py b/gnuradio-runtime/python/gnuradio/ctrlport/__init__.py new file mode 100644 index 0000000000..031c3b424e --- /dev/null +++ b/gnuradio-runtime/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-runtime/python/gnuradio/ctrlport/gr-ctrlport-curses b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-curses new file mode 100755 index 0000000000..1bee3b1a1e --- /dev/null +++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-curses @@ -0,0 +1,268 @@ +#!/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 threading +import curses +import os, sys, time +from optparse import OptionParser + +import Ice +from gnuradio.ctrlport import GNURadio + +ENTER = chr(10) +UP_ARROW = chr(65) +DOWN_ARROW = chr(66) + +class modem_monitor(threading.Thread): + def __init__(self, cb_live, cb_exit, interface): + threading.Thread.__init__(self) + self.cb_live = cb_live + self.cb_exit = cb_exit + + self.running = True + + def __del__(self): + rx.close() + + def run(self): + while self.running: + time.sleep(0.5) + + def shutdown(self): + self.running = False + self.rx.close() + + def cb(self,contents): + (op, sep, payload) = contents.partition(":") + if(op == "live"): + print "live" + self.cb_live(payload) + elif(op == "exit"): + self.cb_exit(payload) + else: + print "unknown op arrived! garbage on multicast adx?" + +class modem_window(threading.Thread): + def __init__(self, locator): + threading.Thread.__init__(self) + self.locator = locator + + # curses init + self.win = curses.newwin(30,100,4,4) + + # Ice/GRCP init + self.comm = Ice.initialize() + proxy = self.comm.stringToProxy(locator) + self.radio = GNURadio.ControlPortPrx.checkedCast(proxy) + self.updateKnobs() + + # GUI init + self.running = True + self.ssel = 0 + self.start() + #self.updateGUI() + + # input loop + while(self.running): + self.getInput() + + # wait for update thread exit + self.join() + + def updateKnobs(self): + self.knobs = self.radio.get([]) + + def getInput(self): + a = self.win.getch() + if(a <= 256): + a = chr(a) + if(a == 'q'): + self.running = False + elif(a == UP_ARROW): + self.ssel = max(self.ssel-1, 0) + self.updateGUI() + elif(a == DOWN_ARROW): + self.ssel = max(min(self.ssel+1, len(self.knobs.keys())-1),0) + self.updateGUI() + self.updateGUI() + + def updateGUI(self): + self.win.clear() + self.win.border(0) + self.win.addstr(1, 2, "Modem Statistics :: %s"%(self.locator)) + self.win.addstr(2, 2, "---------------------------------------------------") + + maxnb = 0 + maxk = 0 + for k in self.knobs.keys(): + (nb,k) = k.split("::", 2) + maxnb = max(maxnb,len(nb)) + maxk = max(maxk,len(k)) + + offset = 3 + keys = self.knobs.keys() + keys.sort() + for k in keys: + (nb,sk) = k.split("::", 2) + v = self.knobs[k].value + sv = str(v) + if(len(sv) > 20): + sv = sv[0:20] + props = 0 + if(self.ssel == offset-3): + props = props | curses.A_REVERSE + self.win.addstr(offset, 2, "%s %s %s" % \ + (nb.rjust(maxnb," "), sk.ljust(maxk," "), sv),props) + offset = offset + 1 + self.win.refresh() + + def run(self): + while(self.running): + self.updateKnobs() + self.updateGUI() + time.sleep(1) + +class monitor_gui: + def __init__(self, interfaces, options): + + locator = None + + # Extract options into a locator + if(options.host and options.port): + locator = "{0} -t:{1} -h {2} -p {3}".format( + options.app, options.protocol, + options.host, options.port) + + # Set up GUI + self.locators = {} + + self.mode = 0 # modem index screen (modal keyboard input) + self.lsel = 0 # selected locator + self.scr = curses.initscr() + self.updateGUI() + + # Kick off initial monitors + self.monitors = [] + for i in interfaces: + self.monitors.append( modem_monitor(self.addModem, self.delModem, i) ) + self.monitors[-1].start() + + if not ((locator == None) or (locator == "")): + self.addModem(locator) + + # wait for user input + while(True): + self.getInput() + + def addModem(self, locator): + if(not self.locators.has_key(locator)): + self.locators[locator] = {} + self.locators[locator]["update_time"] = time.time() + self.locators[locator]["status"] = "live" + + self.updateGUI(); + + def delModem(self, locator): + #if(self.locators.has_key(locator)): + if(not self.locators.has_key(locator)): + self.locators[locator] = {} + self.locators[locator]["update_time"] = time.time() + self.locators[locator]["status"] = "exit" + + self.updateGUI() + + def updateGUI(self): + if(self.mode == 0): #redraw locators + self.scr.clear() + self.scr.border(0) + self.scr.addstr(1, 2, " GRCP-Curses Modem Monitor :: (A)dd (R)efresh, (Q)uit, ...") + for i in range(len(self.locators.keys())): + locator = self.locators.keys()[i] + lhash = self.locators[locator] + #self.scr.addstr(3+i, 5, locator + str(lhash)) + props = 0 + if(self.lsel == i): + props = props | curses.A_REVERSE + self.scr.addstr(3+i, 5, locator + str(lhash), props) + self.scr.refresh() + + def connectGUI(self): + self.scr.clear() + self.scr.addstr(1, 1, "Connect to radio:") + locator = self.scr.getstr(200) + self.addModem(locator) + self.updateGUI() + + def getInput(self): + a = self.scr.getch() + self.scr.addstr(20, 2, "got key (%d) " % (int(a))) + if(a <= 256): + a = chr(a) + if(a =='r'): + self.updateGUI() + elif(a == 'q'): + self.shutdown() + elif(a == 'a'): + self.connectGUI() + elif(a == UP_ARROW): + self.lsel = max(self.lsel-1, 0) + self.updateGUI() + elif(a == DOWN_ARROW): + self.lsel = max(min(self.lsel+1, len(self.locators.keys())-1),0) + self.updateGUI() + elif(a == ENTER): + try: + locator = self.locators.keys()[self.lsel] + self.mode = 1 + mwin = modem_window(locator) + self.mode = 0 + # pop up a new modem display ... + self.updateGUI() + except: + pass + + def shutdown(self): + curses.endwin() + os._exit(0) + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option("-H", "--host", type="string", + help="Hostname of ControlPort server.") + parser.add_option("-p", "--port", type="int", + help="Port number of host's ControlPort endpoint.") + parser.add_option("-i", "--interfaces", type="string", + action="append", default=["lo"], + help="Interfaces to use. [default=%default]") + parser.add_option("-P", "--protocol", type="string", default="tcp", + help="Type of protocol to use (usually tcp). [default=%default]") + parser.add_option("-a", "--app", type="string", default="gnuradio", + help="Name of application [default=%default]") + (options, args) = parser.parse_args() + + if((options.host == None) ^ (options.port == None)): + print "Please set both a hostname and a port number.\n" + parser.print_help() + sys.exit(1) + + mg = monitor_gui(options.interfaces, options) + diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor new file mode 100755 index 0000000000..e71cd92ab7 --- /dev/null +++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor @@ -0,0 +1,721 @@ +#!/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 + +import Ice +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, radio, port, interface): + + super(MAINWindow, self).__init__() + self.updateRate = 1000; + 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) + + # 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, radio=None, port=None): + child = MForm(radio, port, len(self.conns), self.updateRate, 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 + + # 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) + + 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 = 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 + + 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): + plot = GrDataPlotterConst(tag, 32e6, pmin, pmax) + 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]: + data = [] + for n in plot.knobnames: + data.append(knobs[n].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) + + 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.newPlotAct) + 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.newPlotAct) + 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 + 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, updateRate=2000, 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.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) + + +class MyClient(IceRadioClient): + def __init__(self): + IceRadioClient.__init__(self, MAINWindow) + +sys.exit(MyClient().main(sys.argv)) diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitor b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitor new file mode 100755 index 0000000000..f2c01691a1 --- /dev/null +++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitor @@ -0,0 +1,591 @@ +#!/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,Qwt5 +import PyQt4.QtGui as QtGui +import sys, time, re, pprint +import itertools +import scipy + +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 Performance 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)) + horizbar = QtGui.QScrollArea() + horizbar.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + horizbar.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + horizbar.setWidget(child) + self.mdiArea.addSubWindow(horizbar) + self.mdiArea.currentSubWindow().showMaximized() + + self.conns.append(child) + self.plots.append([]) + + def newUpdater(self, key, radio): + updater = UpdaterWindow(key, radio, None) + updater.setWindowTitle("Updater: " + key) + updater.setModal(False) + updater.exec_() + + 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.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.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 = 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) + + 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) + + # 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) + + +def build_edge_graph(sources, sinks, edges): + ''' + Starting from the sources, walks through all of the edges to find + the next connected block. The output is stored in 'allblocks' + where each row starts with a source and follows one path down + until it terminates in either a sink or as an input to a block + that is part of another chain. + ''' + def find_edge(src, sinks, edges, row, col): + #print "\n\nAll blocks: " + #printer.pprint(allblocks) + #print "\nLooking for: ", src + + src0 = src.split(":")[0] + if(src0 in sinks): + if(len(allblocks) <= row): + allblocks.append(col*[""]) + allblocks[row].append(src) + return row+1 + + for edge in edges: + if(re.match(src0, edge)): + s = edge.split("->")[0] + b = edge.split("->")[1] + if(len(allblocks) <= row): + allblocks.append(col*[""]) + allblocks[row].append(s) + #print "Source: {0} Sink: {1}".format(s, b) + row = find_edge(b, sinks, edges, row, col+1) + return row + + # Recursively get all edges as a matrix of source->sink + n = 0 + allblocks = [] + for src in sources: + n = find_edge(src, sinks, edges, n, 0) + + # Sort by longest list + allblocks = sorted(allblocks, key=len) + allblocks.reverse() + + # Make all rows same length by padding '' in front of sort rows + maxrowlen = len(allblocks[0]) + for i,a in enumerate(allblocks): + rowlen = len(a) + allblocks[i] = (maxrowlen-rowlen)*[''] + a + + # Dedup rows + allblocks = sorted(allblocks) + allblocks = list(k for k,_ in itertools.groupby(allblocks)) + allblocks.reverse() + + return allblocks + + +class MForm(QtGui.QWidget): + def update(self): + try: + st = time.time() + knobs = self.radio.get([b[0] for b in self.block_dict]) + + 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 + + #UPDATE TABLE: + self.updateItems(knobs) + + #UPDATE PLOTS + self.parent.update(knobs, self.uid) + + def updateItems(self, knobs): + for b in self.block_dict: + if(knobs[b[0]].ice_id.im_class == GNURadio.KnobVecF): + b[1].setText("{0:.4f}".format(knobs[b[0]].value[b[2]])) + else: + b[1].setText("{0:.4f}".format(knobs[b[0]].value)) + + 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.layout = QtGui.QGridLayout(self) + self.layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) + + 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 + + + # Set up the graph of blocks + input_name = lambda x: x+"::avg input % full" + output_name = lambda x: x+"::avg output % full" + wtime_name = lambda x: x+"::avg work time" + nout_name = lambda x: x+"::avg noutput_items" + nprod_name = lambda x: x+"::avg nproduced" + + tmplist = [] + knobs = self.radio.get([]) + edgelist = None + for k in knobs: + propname = k.split("::") + blockname = propname[0] + keyname = propname[1] + if(keyname == "edge list"): + edgelist = knobs[k].value + elif(blockname not in tmplist): + # only take gr_blocks (no hier_block2) + if(knobs.has_key(input_name(blockname))): + tmplist.append(blockname) + + if not edgelist: + sys.stderr.write("Could not find list of edges from flowgraph. " + \ + "Make sure the option 'edges_list' is enabled " + \ + "in the ControlPort configuration.\n\n") + sys.exit(1) + + edges = edgelist.split("\n")[0:-1] + producers = [] + consumers = [] + for e in edges: + _e = e.split("->") + producers.append(_e[0]) + consumers.append(_e[1]) + + # Get producers and consumers as sets while ignoring the + # ports. + prods = set(map(lambda x: x.split(":")[0], producers)) + cons = set(map(lambda x: x.split(":")[0], consumers)) + + # Split out all blocks, sources, and sinks based on how they + # appear as consumers and/or producers. + blocks = prods.intersection(cons) + sources = prods.difference(blocks) + sinks = cons.difference(blocks) + + nblocks = len(prods) + len(cons) + + allblocks = build_edge_graph(sources, sinks, edges) + nrows = len(allblocks) + ncols = len(allblocks[0]) + + col_width = 120 + + self.block_dict = [] + + for row, blockrow in enumerate(allblocks): + for col, block in enumerate(blockrow): + if(block == ''): + continue + + bgroup = QtGui.QGroupBox(block) + playout = QtGui.QFormLayout() + bgroup.setLayout(playout) + self.layout.addWidget(bgroup, row, 2*col) + + blockname = block.split(":")[0] + + name = wtime_name(blockname) + wtime = knobs[name].value + newtime = QtGui.QLineEdit() + newtime.setMinimumWidth(col_width) + newtime.setText("{0:.4f}".format(wtime)) + self.block_dict.append((name, newtime)) + + name = nout_name(blockname) + nout = knobs[name].value + newnout = QtGui.QLineEdit() + newnout.setText("{0:.4f}".format(nout)) + newnout.setMinimumWidth(col_width) + self.block_dict.append((name, newnout)) + + name = nprod_name(blockname) + nprod = knobs[name].value + newnprod = QtGui.QLineEdit() + newnprod.setMinimumWidth(col_width) + newnprod.setText("{0:.4f}".format(nprod)) + self.block_dict.append((name, newnprod)) + + playout.addRow("Work time", newtime) + playout.addRow("noutput_items", newnout) + playout.addRow("nproduced", newnprod) + + if blockname in blocks or blockname in sources: + # Add a buffer between blocks + buffgroup = QtGui.QGroupBox("Buffer") + bufflayout = QtGui.QFormLayout() + buffgroup.setLayout(bufflayout) + self.layout.addWidget(buffgroup, row, 2*col+1) + + i = int(block.split(":")[1]) + name = output_name(blockname) + obuff = knobs[name].value + for i,o in enumerate(obuff): + newobuff = QtGui.QLineEdit() + newobuff.setMinimumWidth(col_width) + newobuff.setText("{0:.4f}".format(o)) + self.block_dict.append((name, newobuff, i)) + bufflayout.addRow("Out Buffer {0}".format(i), + newobuff) + + if blockname in blocks or blockname in sinks: + item = self.layout.itemAtPosition(row, 2*col-1) + if(item): + buffgroup = item.widget() + bufflayout = buffgroup.layout() + else: + buffgroup = QtGui.QGroupBox("Buffer") + bufflayout = QtGui.QFormLayout() + buffgroup.setLayout(bufflayout) + self.layout.addWidget(buffgroup, row, 2*col-1) + + i = int(block.split(":")[1]) + name = input_name(blockname) + ibuff = knobs[name].value[i] + newibuff = QtGui.QLineEdit() + newibuff.setMinimumWidth(col_width) + newibuff.setText("{0:.4f}".format(ibuff)) + self.block_dict.append((name, newibuff, i)) + bufflayout.addRow("In Buffer {0}".format(i), + newibuff) + + # set up timer + self.timer = QtCore.QTimer() + self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update) + self.timer.start(1000) + + 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-runtime/python/gnuradio/ctrlport/gr-perf-monitorx b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx new file mode 100755 index 0000000000..a65b0406e4 --- /dev/null +++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx @@ -0,0 +1,727 @@ +#!/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. +# + +import random,math,operator +import networkx as nx; +import matplotlib.pyplot as plt + +from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar +from matplotlib.figure import Figure + +from gnuradio import gr, ctrlport + +from PyQt4 import QtCore,Qt,Qwt5 +import PyQt4.QtGui as QtGui +import sys, time, re, pprint +import itertools +import scipy +from scipy import spatial + +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 Performance Monitor") + self.setUnifiedTitleAndToolBarOnMac(True) + + self.newCon(radio, port) + icon = QtGui.QIcon(ctrlport.__path__[0] + "/icon.png" ) + self.setWindowIcon(icon) + + + def newSubWindow(self, window, title): + child = window; + child.setWindowTitle(title) + self.mdiArea.addSubWindow(child) + self.conns.append(child) + child.show(); + self.mdiArea.currentSubWindow().showMaximized() + + + 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)) +# horizbar = QtGui.QScrollArea() +# horizbar.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) +# horizbar.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) +# horizbar.setWidget(child) +# self.mdiArea.addSubWindow(horizbar) + self.mdiArea.addSubWindow(child) + self.mdiArea.currentSubWindow().showMaximized() + + self.conns.append(child) + self.plots.append([]) + + 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.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.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 = 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 DataTable(QtGui.QWidget): + def update(self): + print "update" + + def __init__(self, radio, G): + QtGui.QWidget.__init__( self) + + self.layout = QtGui.QVBoxLayout(self); + self.hlayout = QtGui.QHBoxLayout(); + self.layout.addLayout(self.hlayout); + + self.G = G; + self.radio = radio; + + self._keymap = None + + # Create a combobox to set the type of statistic we want. + self._statistic = "Instantaneous" + self._statistics_table = {"Instantaneous": "", + "Average": "avg ", + "Variance": "var "} + self.stattype = QtGui.QComboBox() + self.stattype.addItem("Instantaneous") + self.stattype.addItem("Average") + self.stattype.addItem("Variance") + self.stattype.setMaximumWidth(200) + self.hlayout.addWidget(self.stattype); + self.stattype.currentIndexChanged.connect(self.stat_changed) + + # Create a checkbox to toggle sorting of graphs + self._sort = False + self.checksort = QtGui.QCheckBox("Sort") + self.checksort.setCheckState(self._sort) + self.hlayout.addWidget(self.checksort); + self.checksort.stateChanged.connect(self.checksort_changed) + + # set up table + self.perfTable = Qt.QTableWidget(); + self.perfTable.setColumnCount(2) + self.perfTable.verticalHeader().hide(); + self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Runtime"] ); + self.perfTable.horizontalHeader().setStretchLastSection(True); + self.perfTable.setSortingEnabled(True) + nodes = self.G.nodes(data=True) + + # set up plot + self.f = plt.figure(figsize=(10,8), dpi=90) + self.sp = self.f.add_subplot(111); + self.sp.autoscale_view(True,True,True); + self.sp.set_autoscale_on(True) + self.canvas = FigureCanvas(self.f) + + # set up tabs + self.tabber = QtGui.QTabWidget(); + self.layout.addWidget(self.tabber); + self.tabber.addTab(self.perfTable,"Table View"); + self.tabber.addTab(self.canvas, "Graph View"); + + # set up timer + self.timer = QtCore.QTimer() + self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update) + self.timer.start(500) + + for i in range(0,len(nodes)): + self.perfTable.setItem( + i,0, + Qt.QTableWidgetItem(nodes[i][0])) + + def table_update(self,data): + for k in data.keys(): + weight = data[k] + existing = self.perfTable.findItems(str(k),QtCore.Qt.MatchFixedString) + if(len(existing) == 0): + i = self.perfTable.rowCount(); + self.perfTable.setRowCount( i+1) + self.perfTable.setItem( i,0, Qt.QTableWidgetItem(str(k))) + self.perfTable.setItem( i,1, Qt.QTableWidgetItem(str(weight))) + else: + self.perfTable.setItem( self.perfTable.row(existing[0]),1, Qt.QTableWidgetItem(str(weight))) + + def stat_changed(self, index): + self._statistic = str(self.stattype.currentText()) + + def checksort_changed(self, state): + self._sort = state > 0 + +class DataTableBuffers(DataTable): + def __init__(self, radio, G): + DataTable.__init__(self,radio,G) + self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Buffer Full"] ); + + def update(self): + nodes = self.G.nodes(); + + # get buffer fullness for all blocks + kl = map(lambda x: "%s::%soutput %% full" % \ + (x, self._statistics_table[self._statistic]), + nodes); + buf_knobs = self.radio.get(kl) + + # strip values out of ctrlport response + buffer_fullness = dict(zip( + map(lambda x: x.split("::")[0], buf_knobs.keys()), + map(lambda x: x.value, buf_knobs.values()))) + + blockport_fullness = {} + for blk in buffer_fullness: + for port in range(0,len(buffer_fullness[blk])): + blockport_fullness["%s:%d"%(blk,port)] = buffer_fullness[blk][port]; + + self.table_update(blockport_fullness); + + if(self._sort): + sorted_fullness = sorted(blockport_fullness.iteritems(), key=operator.itemgetter(1)) + self._keymap = map(operator.itemgetter(0), sorted_fullness) + else: + if self._keymap: + sorted_fullness = len(self._keymap)*['',] + for b in blockport_fullness: + sorted_fullness[self._keymap.index(b)] = (b, blockport_fullness[b]) + else: + sorted_fullness = blockport_fullness.items() + + self.sp.clear(); + plt.figure(self.f.number) + plt.subplot(111); + self.sp.bar(range(0,len(sorted_fullness)), map(lambda x: x[1], sorted_fullness), + alpha=0.5) + self.sp.set_ylabel("% Buffers Full"); + self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_fullness)))) + self.sp.set_xticklabels( map(lambda x: " " + x, map(lambda x: x[0], sorted_fullness)), + rotation="vertical", verticalalignment="bottom" ) + self.canvas.draw(); + self.canvas.show(); + +class DataTableRuntimes(DataTable): + def __init__(self, radio, G): + DataTable.__init__(self,radio,G) + #self.perfTable.setRowCount(len( self.G.nodes() )) + + def update(self): + nodes = self.G.nodes(); + + # get work time for all blocks + kl = map(lambda x: "%s::%swork time" % \ + (x, self._statistics_table[self._statistic]), + nodes); + wrk_knobs = self.radio.get(kl) + + # strip values out of ctrlport response + total_work = sum(map(lambda x: x.value, wrk_knobs.values())) + work_times = dict(zip( + map(lambda x: x.split("::")[0], wrk_knobs.keys()), + map(lambda x: x.value/total_work, wrk_knobs.values()))) + + # update table view + self.table_update(work_times) + + if(self._sort): + sorted_work = sorted(work_times.iteritems(), key=operator.itemgetter(1)) + self._keymap = map(operator.itemgetter(0), sorted_work) + else: + if self._keymap: + sorted_work = len(self._keymap)*['',] + for b in work_times: + sorted_work[self._keymap.index(b)] = (b, work_times[b]) + else: + sorted_work = work_times.items() + + self.sp.clear(); + plt.figure(self.f.number) + plt.subplot(111); + self.sp.bar(range(0,len(sorted_work)), map(lambda x: x[1], sorted_work), + alpha=0.5) + self.sp.set_ylabel("% Runtime"); + self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_work)))) + self.sp.set_xticklabels( map(lambda x: " " + x[0], sorted_work), + rotation="vertical", verticalalignment="bottom" ) + + self.canvas.draw(); + self.canvas.show(); + +class MForm(QtGui.QWidget): + def update(self): + try: + + nodes = self.G.nodes(); + + # get current buffer depths of all output buffers + kl = map(lambda x: "%s::%soutput %% full" % \ + (x, self._statistics_table[self._statistic]), + nodes); + + st = time.time() + buf_knobs = self.radio.get(kl) + td1 = time.time() - st; + + # strip values out of ctrlport response + buf_vals = dict(zip( + map(lambda x: x.split("::")[0], buf_knobs.keys()), + map(lambda x: x.value, buf_knobs.values()))) + + # get work time for all blocks + kl = map(lambda x: "%s::%swork time" % \ + (x, self._statistics_table[self._statistic]), + nodes); + st = time.time() + wrk_knobs = self.radio.get(kl) + td2 = time.time() - st; + + # strip values out of ctrlport response + total_work = sum(map(lambda x: x.value, wrk_knobs.values())) + work_times = dict(zip( + map(lambda x: x.split("::")[0], wrk_knobs.keys()), + map(lambda x: x.value/total_work, wrk_knobs.values()))) + + for n in nodes: + # ne is the list of edges away from this node! + ne = self.G.edges([n],True); + for e in ne: # iterate over edges from this block + # get the right output buffer/port weight for each edge + sourceport = e[2]["sourceport"]; + newweight = buf_vals[n][sourceport] + e[2]["weight"] = newweight; + + # set updated weights + self.node_weights = map(lambda x: 20+2000*work_times[x], nodes); + self.edge_weights = map(lambda x: 100.0*x[2]["weight"], self.G.edges(data=True)); + + # draw graph updates + self.updateGraph(); + + latency = td1 + td2; + 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) + + # 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 + + def rtt(self): + self.parent.newSubWindow( DataTableRuntimes(self.radio, self.G), "Runtime Table" ); + + def bpt(self): + self.parent.newSubWindow( DataTableBuffers(self.radio, self.G), "Buffers Table" ); + + def stat_changed(self, index): + self._statistic = str(self.stattype.currentText()) + + 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.layoutTop = QtGui.QVBoxLayout(self) + self.ctlBox = QtGui.QHBoxLayout(); + self.layout = QtGui.QHBoxLayout() + + self.layoutTop.addLayout(self.ctlBox); + self.layoutTop.addLayout(self.layout); + + self.rttAct = QtGui.QAction("Runtime Table", + self, statusTip="Runtime Table", triggered=self.rtt) + self.rttBut = Qt.QToolButton() + self.rttBut.setDefaultAction(self.rttAct); + self.ctlBox.addWidget(self.rttBut); + + self.bptAct = QtGui.QAction("Buffer Table", + self, statusTip="Buffer Table", triggered=self.bpt) + self.bptBut = Qt.QToolButton() + self.bptBut.setDefaultAction(self.bptAct); + self.ctlBox.addWidget(self.bptBut); + + self._statistic = "Instantaneous" + self._statistics_table = {"Instantaneous": "", + "Average": "avg ", + "Variance": "var "} + self.stattype = QtGui.QComboBox() + self.stattype.addItem("Instantaneous") + self.stattype.addItem("Average") + self.stattype.addItem("Variance") + self.stattype.setMaximumWidth(200) + self.ctlBox.addWidget(self.stattype); + self.stattype.currentIndexChanged.connect(self.stat_changed) + +# self.setLayout(self.layout); + + self.radio = radio + self.knobprops = self.radio.properties([]) + self.parent.knobprops.append(self.knobprops) + + self.timer = QtCore.QTimer() + self.constupdatediv = 0 + self.tableupdatediv = 0 + plotsize=250 + + + # Set up the graph of blocks + input_name = lambda x: x+"::avg input % full" + output_name = lambda x: x+"::avg output % full" + wtime_name = lambda x: x+"::avg work time" + nout_name = lambda x: x+"::avg noutput_items" + nprod_name = lambda x: x+"::avg nproduced" + + tmplist = [] + knobs = self.radio.get([]) + edgelist = None + for k in knobs: + propname = k.split("::") + blockname = propname[0] + keyname = propname[1] + if(keyname == "edge list"): + edgelist = knobs[k].value + elif(blockname not in tmplist): + # only take gr_blocks (no hier_block2) + if(knobs.has_key(input_name(blockname))): + tmplist.append(blockname) + + if not edgelist: + sys.stderr.write("Could not find list of edges from flowgraph. " + \ + "Make sure the option 'edges_list' is enabled " + \ + "in the ControlPort configuration.\n\n") + sys.exit(1) + + edges = edgelist.split("\n")[0:-1] + edgepairs = []; + for e in edges: + _e = e.split("->") + edgepairs.append( (_e[0].split(":")[0], _e[1].split(":")[0], + {"sourceport":int(_e[0].split(":")[1])}) ); + + self.G = nx.MultiDiGraph(); + self.G.add_edges_from(edgepairs); + + n_edges = self.G.edges(data=True); + for e in n_edges: + e[2]["weight"] = 5+random.random()*10; + + self.G.clear(); + self.G.add_edges_from(n_edges); + + + self.f = plt.figure(figsize=(10,8), dpi=90) + self.sp = self.f.add_subplot(111); + self.sp.autoscale_view(True,True,True); + self.sp.set_autoscale_on(True) + + self.canvas = FigureCanvas(self.f) + self.layout.addWidget(self.canvas); + + self.pos = nx.graphviz_layout(self.G); + #self.pos = nx.pygraphviz_layout(self.G); + #self.pos = nx.spectral_layout(self.G); + #self.pos = nx.circular_layout(self.G); + #self.pos = nx.shell_layout(self.G); + #self.pos = nx.spring_layout(self.G); + + # generate weights and plot + self.update(); + + # set up timer + self.timer = QtCore.QTimer() + self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update) + self.timer.start(1000) + + # Set up mouse callback functions to move blocks around. + self._grabbed = False + self._current_block = '' + self.f.canvas.mpl_connect('button_press_event', + self.button_press) + self.f.canvas.mpl_connect('motion_notify_event', + self.mouse_move) + self.f.canvas.mpl_connect('button_release_event', + self.button_release) + + def button_press(self, event): + x, y = event.xdata, event.ydata + thrsh = 100 + + if(x is not None and y is not None): + nearby = map(lambda z: spatial.distance.euclidean((x,y), z), self.pos.values()) + i = nearby.index(min(nearby)) + if(abs(self.pos.values()[i][0] - x) < thrsh and + abs(self.pos.values()[i][1]-y) < thrsh): + self._current_block = self.pos.keys()[i] + #print "MOVING BLOCK: ", self._current_block + #print "CUR POS: ", self.pos.values()[i] + self._grabbed = True + + def mouse_move(self, event): + if self._grabbed: + x, y = event.xdata, event.ydata + if(x is not None and y is not None): + #print "NEW POS: ", (x,y) + self.pos[self._current_block] = (x,y) + self.updateGraph(); + + def button_release(self, event): + self._grabbed = False + + + 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 updateGraph(self): + + self.canvas.updateGeometry() + self.sp.clear(); + plt.figure(self.f.number) + plt.subplot(111); + nx.draw(self.G, self.pos, + edge_color=self.edge_weights, + node_color='#A0CBE2', + width=map(lambda x: 3+math.log(x), self.edge_weights), + node_shape="s", + node_size=self.node_weights, + #edge_cmap=plt.cm.Blues, + edge_cmap=plt.cm.Reds, + ax=self.sp, + arrows=False + ) + nx.draw_networkx_labels(self.G, self.pos, + font_size=12) + + self.canvas.draw(); + self.canvas.show(); + +class MyClient(IceRadioClient): + def __init__(self): + IceRadioClient.__init__(self, MAINWindow) + +sys.exit(MyClient().main(sys.argv)) diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/icon.png b/gnuradio-runtime/python/gnuradio/ctrlport/icon.png Binary files differnew file mode 100644 index 0000000000..4beb204428 --- /dev/null +++ b/gnuradio-runtime/python/gnuradio/ctrlport/icon.png diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py b/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py new file mode 100644 index 0000000000..53a571a698 --- /dev/null +++ b/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py @@ -0,0 +1,59 @@ +#!/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 + 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() |