summaryrefslogtreecommitdiff
path: root/gnuradio-runtime/python/gnuradio
diff options
context:
space:
mode:
authorJohnathan Corgan <johnathan@corganlabs.com>2015-04-15 14:03:44 -0700
committerJohnathan Corgan <johnathan@corganlabs.com>2015-04-15 14:03:44 -0700
commit527c21cb6a3b1b32610c4acf0bec8956ace2c5bf (patch)
tree5fdb9e755f77d2493d8851f956b074c6f11cb8ce /gnuradio-runtime/python/gnuradio
parent7765798c48b9ec4b1cda43367e97eb778a8ad758 (diff)
parentb092142302bcf8c771ec68e61da7781eb406c86f (diff)
Merge branch 'master' into next
Diffstat (limited to 'gnuradio-runtime/python/gnuradio')
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt43
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py132
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py6
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/RPCConnection.py115
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/RPCConnectionThrift.py208
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor771
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx856
-rw-r--r--gnuradio-runtime/python/gnuradio/ctrlport/monitor.py6
8 files changed, 2131 insertions, 6 deletions
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt b/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt
index 1d5a292429..f40f253a72 100644
--- a/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/CMakeLists.txt
@@ -27,10 +27,53 @@ install(
COMPONENT "runtime_python"
)
+
GR_PYTHON_INSTALL(
FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
${CMAKE_CURRENT_SOURCE_DIR}/GrDataPlotter.py
${CMAKE_CURRENT_SOURCE_DIR}/monitor.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/GNURadioControlPortClient.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/RPCConnection.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/
+ COMPONENT "runtime_python"
+)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/gr-perf-monitorx
+ ${CMAKE_CURRENT_SOURCE_DIR}/gr-ctrlport-monitor
+ DESTINATION ${GR_RUNTIME_DIR}
+ PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
+ COMPONENT "runtime_python"
+)
+
+if(THRIFT_FOUND)
+
+EXECUTE_PROCESS(
+ COMMAND ${THRIFT_BIN} --gen py -out ${CMAKE_CURRENT_BINARY_DIR}/ ${CMAKE_SOURCE_DIR}/gnuradio-runtime/lib/controlport/thrift/gnuradio.thrift
+ OUTPUT_VARIABLE THRIFT_PY_OUTPUT
+ ERROR_VARIABLE THRIFT_PY_ERROR
+ )
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/RPCConnectionThrift.py
DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/
COMPONENT "runtime_python"
)
+
+GR_PYTHON_INSTALL(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/__init__.py
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/constants.py
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/ControlPort.py
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/ControlPort-remote
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/StreamReceiver.py
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/StreamReceiver-remote
+ ${CMAKE_CURRENT_BINARY_DIR}/GNURadio/ttypes.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/ctrlport/GNURadio
+ COMPONENT "runtime_python"
+)
+
+endif(THRIFT_FOUND)
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py b/gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py
new file mode 100644
index 0000000000..87d2cf5658
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py
@@ -0,0 +1,132 @@
+#
+# Copyright 2015 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+"""
+Python Client classes for interfacing with the GNU Radio ControlPort interface
+and for accessing Performance Counters.
+
+While ControlPort and these client classes are designed to support multiple
+Remote Procedure Call (RPC) transports, the Apache Thrift middle-ware RPC
+is currently the only supported transport.
+
+"""
+
+import exceptions
+
+"""
+GNURadioControlPortClient is the main class for creating a GNU Radio
+ControlPort client application for all transports.
+
+Two constructors are provided for creating a connection to ControlPort.
+
+"""
+
+class GNURadioControlPortClient():
+ """
+ Constructor for creating a ControlPort connection to a specified host / port
+
+ Args:
+ host: hostname of the connection. Specifying None (default) will
+ select the loopback interface.
+
+ port: port number to use for the connection. Specifying None (default)
+ will select the specified RPC transport's default port number, if
+ the transport has a default.
+
+ rpcmethod: This string specifies the RPC transport to use for the
+ client connection. The default implementation currently uses
+ the Apache Thrift RPC transport. The value specified here must
+ be one of the transport keys listed in the RPCMethods dictionary
+ above
+
+ callback: This optional parameter is a callback function that will be passed
+ a reference to the Client implementation for the RPC transport specified
+ by rpcmethod. The callback will be executed after the client has been
+ constructed, but before __init__ returns.
+
+ blockingcallback: This optional parameter is a callback function with
+ no parameters that will be executed after callback() is executed,
+ but before __init__ returns. It is useful if your application
+ requires that a blocking function be called to start the application,
+ such as QtGui.QApplication.exec_
+
+ """
+
+ def __init__(self, host = None, port = None, rpcmethod = 'thrift', callback = None, blockingcallback = None):
+ __init__([host, port], rpcmethod, callback, blockingcallback)
+
+ """
+ Constructor for creating a ControlPort from a tuple of command line arguments (i.e. sys.argv)
+
+ Args:
+ argv: List of command line arguments. Future implementations may parse the argument list
+ for OptionParser style key / value pairs, however the current implementation
+ simply takes argv[1] and argv[2] as the connection hostname and port, respectively.
+
+ Example Usage:
+
+ In the following QT client example, the ControlPort host and port are specified to
+ the Client application as the first two command line arguments. The MAINWindow class is
+ of the type QtGui.QMainWindow, and is the main window for the QT application. MyApp
+ is a simple helper class for starting the application.
+
+ class MAINWindow(QtGui.QMainWindow):
+ ... QT Application implementation ...
+
+ class MyApp(object):
+ def __init__(self, args):
+ from GNURadioControlPortClient import GNURadioControlPortClient
+ GNURadioControlPortClient(args, 'thrift', self.run, QtGui.QApplication(sys.argv).exec_)
+
+ def run(self, client):
+ MAINWindow(client).show()
+
+ MyApp(sys.argv)
+
+
+ """
+
+ def __init__(self, argv = [], rpcmethod = 'thrift', callback = None, blockingcallback = None):
+ if len(argv) > 1: host = argv[1]
+ else: host = None
+
+ if len(argv) > 2: port = argv[2]
+ else: port = None
+
+ self.client = None
+
+ from gnuradio.ctrlport.RPCConnection import RPCMethods
+ if RPCMethods.has_key(rpcmethod):
+ from gnuradio.ctrlport.RPCConnectionThrift import RPCConnectionThrift
+ if rpcmethod == 'thrift':
+ #print("making RPCConnectionThrift")
+ self.client = RPCConnectionThrift(host, port)
+ #print("made %s" % self.client)
+
+ #print("making callback call")
+ if not callback is None:
+ callback(self.client)
+
+ #print("making blockingcallback call")
+ if not blockingcallback is None:
+ blockingcallback()
+ else:
+ print("Unsupported RPC method: ", rpcmethod)
+ raise exceptions.ValueError()
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py b/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py
index 661705d613..c5bfd0a8cb 100644
--- a/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/GrDataPlotter.py
@@ -23,7 +23,7 @@
from gnuradio import gr
from gnuradio import blocks
from gnuradio import filter
-from gnuradio.ctrlport import GNURadio
+from gnuradio.ctrlport.GNURadio import ControlPort
import sys, time, struct
try:
@@ -442,7 +442,7 @@ class GrDataPlotterValueTable:
units = str(knobprops[itemKey].units)
descr = str(knobprops[itemKey].description)
- if(type(v) == GNURadio.complex):
+ if(type(v) == ControlPort.complex):
v = v.re + v.im*1j
# If it's a byte stream, Python thinks it's a string.
# Unpack and convert to floats for plotting.
@@ -468,7 +468,7 @@ class GrDataPlotterValueTable:
for k in knobs.keys():
if k not in foundKeys:
v = knobs[k].value
- if(type(v) == GNURadio.complex):
+ if(type(v) == ControlPort.complex):
v = v.re + v.im*1j
# If it's a byte stream, Python thinks it's a string.
# Unpack and convert to floats for plotting.
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/RPCConnection.py b/gnuradio-runtime/python/gnuradio/ctrlport/RPCConnection.py
new file mode 100644
index 0000000000..e14cc0cea7
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/RPCConnection.py
@@ -0,0 +1,115 @@
+#
+# Copyright 2015 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import exceptions
+
+"""
+RPCMethods is a dictionary listing RPC transports currently supported
+by this client.
+
+Args:
+ function: the function whose parameter list will be examined
+ excluded_args: function arguments that are NOT to be added to the dictionary (sequence of strings)
+ options: result of command argument parsing (optparse.Values)
+"""
+
+RPCMethods = {'thrift': 'Apache Thrift',
+ #'ice': 'Zeroc ICE'
+ }
+
+
+"""
+Base class for RPC transport clients
+
+Methods that all RPC clients should implement include:
+
+ def newConnection(host,port): Method for re-establishing a new client
+ connection to a different host / port
+
+ def properties([]): Given a list of ControlPort property names,
+ or an empty list to specify all currently registered properties,
+ this method returns a dictionary of metadata describing the
+ the specified properties. The dictionary key contains the name
+ of each returned properties.
+
+ def getKnobs([]): Given a list of ControlPort property names,
+ or an empty list to specify all currently registered properties,
+ this method returns a dictionary of the current value of
+ the specified properties.
+
+ def getRe([]): Given a list of regular expression strings,
+ this method returns a dictionary of the current value of
+ the all properties with names that match the specified
+ expressions.
+
+ def setKnobs({}): Given a dictionary of ControlPort property
+ key / value pairs, this method requests that ControlPort
+ attempt to set the specified named properties to the
+ value given. Success in setting each property to the
+ value specified requires that the property be registered
+ as a 'setable' ControlPort property, that the client have the
+ requisite privilege level to set the property, and
+ the underlying Block's implementation in handling
+ the set request.
+
+Args:
+ method: name of the RPC transport
+ port: port number of the connection
+ host: hostname of the connection
+"""
+
+class RPCConnection(object):
+ def __init__(self, method, port, host=None):
+ (self.method, self.port) = (method, port)
+ if host is None: self.host = '127.0.0.1'
+ else: self.host = host
+
+ def __str__(self):
+ return "%s connection on %s:%s"%(self.getName(), self.getHost(), self.getPort())
+
+ def getName(self):
+ return RPCMethods[self.method]
+
+ def getHost(self):
+ return self.host
+
+ def getPort(self):
+ return self.port
+
+ def newConnection(self, host=None, port=None):
+ raise exceptions.NotImplementedError()
+
+ def properties(self, *args):
+ raise exceptions.NotImplementedError()
+
+ def getKnobs(self, *args):
+ raise exceptions.NotImplementedError()
+
+ def getRe(self,*args):
+ raise exceptions.NotImplementedError()
+
+ def setKnobs(self,*args):
+ raise exceptions.NotImplementedError()
+
+ def shutdown(self):
+ raise exceptions.NotImplementedError()
+
+ def printProperties(self, props):
+ raise exceptions.NotImplementedError()
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/RPCConnectionThrift.py b/gnuradio-runtime/python/gnuradio/ctrlport/RPCConnectionThrift.py
new file mode 100644
index 0000000000..9a2a302af5
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/RPCConnectionThrift.py
@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from thrift import Thrift
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+from gnuradio.ctrlport.GNURadio import ControlPort
+from gnuradio.ctrlport import RPCConnection
+from gnuradio import gr
+import sys
+
+class ThriftRadioClient:
+ def __init__(self, host, port):
+ self.tsocket = TSocket.TSocket(host, port)
+ self.transport = TTransport.TBufferedTransport(self.tsocket)
+ self.protocol = TBinaryProtocol.TBinaryProtocol(self.transport)
+
+ self.radio = ControlPort.Client(self.protocol)
+ self.transport.open()
+
+ def __del__(self):
+ self.radio.shutdown()
+ self.transport.close()
+
+ def getRadio(self, host, port):
+ return self.radio
+
+"""
+RPC Client interface for the Apache Thrift middle-ware RPC transport.
+
+Args:
+ port: port number of the connection
+ host: hostname of the connection
+"""
+
+class RPCConnectionThrift(RPCConnection.RPCConnection):
+ class Knob():
+ def __init__(self, key, value=None, ktype=0):
+ (self.key, self.value, self.ktype) = (key, value, ktype)
+
+ def __repr__(self):
+ return "({0} = {1})".format(self.key, self.value)
+
+ def __init__(self, host=None, port=None):
+ from gnuradio.ctrlport.GNURadio import ttypes
+ self.BaseTypes = ttypes.BaseTypes
+ self.KnobBase = ttypes.KnobBase
+
+ # If not set by the user, get the port number from the thrift
+ # config file, if one is set. Defaults to 9090 otherwise.
+ if port is None:
+ p = gr.prefs()
+ thrift_config_file = p.get_string("ControlPort", "config", "");
+ if(len(thrift_config_file) > 0):
+ p.add_config_file(thrift_config_file)
+ port = p.get_long("thrift", "port", 9090)
+ else:
+ port = 9090
+ else:
+ port = int(port)
+
+ super(RPCConnectionThrift, self).__init__(method='thrift', port=port, host=host)
+ self.newConnection(host, port)
+
+ self.unpack_dict = {
+ self.BaseTypes.BOOL: lambda k,b: self.Knob(k, b.value.a_bool, self.BaseTypes.BOOL),
+ self.BaseTypes.BYTE: lambda k,b: self.Knob(k, b.value.a_byte, self.BaseTypes.BYTE),
+ self.BaseTypes.SHORT: lambda k,b: self.Knob(k, b.value.a_short, self.BaseTypes.SHORT),
+ self.BaseTypes.INT: lambda k,b: self.Knob(k, b.value.a_int, self.BaseTypes.INT),
+ self.BaseTypes.LONG: lambda k,b: self.Knob(k, b.value.a_long, self.BaseTypes.LONG),
+ self.BaseTypes.DOUBLE: lambda k,b: self.Knob(k, b.value.a_double, self.BaseTypes.DOUBLE),
+ self.BaseTypes.STRING: lambda k,b: self.Knob(k, b.value.a_string, self.BaseTypes.STRING),
+ self.BaseTypes.COMPLEX: lambda k,b: self.Knob(k, b.value.a_complex, self.BaseTypes.COMPLEX),
+ self.BaseTypes.F32VECTOR: lambda k,b: self.Knob(k, b.value.a_f32vector, self.BaseTypes.F32VECTOR),
+ self.BaseTypes.F64VECTOR: lambda k,b: self.Knob(k, b.value.a_f64vector, self.BaseTypes.F64VECTOR),
+ self.BaseTypes.S64VECTOR: lambda k,b: self.Knob(k, b.value.a_s64vector, self.BaseTypes.S64VECTOR),
+ self.BaseTypes.S32VECTOR: lambda k,b: self.Knob(k, b.value.a_s32vector, self.BaseTypes.S32VECTOR),
+ self.BaseTypes.S16VECTOR: lambda k,b: self.Knob(k, b.value.a_s16vector, self.BaseTypes.S16VECTOR),
+ self.BaseTypes.S8VECTOR: lambda k,b: self.Knob(k, b.value.a_s8vector, self.BaseTypes.S8VECTOR),
+ self.BaseTypes.C32VECTOR: lambda k,b: self.Knob(k, b.value.a_c32vector, self.BaseTypes.C32VECTOR),
+ }
+
+ self.pack_dict = {
+ self.BaseTypes.BOOL: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_bool = k.value)),
+ self.BaseTypes.BYTE: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_byte = k.value)),
+ self.BaseTypes.SHORT: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_short = k.value)),
+ self.BaseTypes.INT: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_int = k.value)),
+ self.BaseTypes.LONG: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_long = k.value)),
+ self.BaseTypes.DOUBLE: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_double = k.value)),
+ self.BaseTypes.STRING: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_string = k.value)),
+ self.BaseTypes.COMPLEX: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_complex = k.value)),
+ self.BaseTypes.F32VECTOR: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_f32vector = k.value)),
+ self.BaseTypes.F64VECTOR: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_f64vector = k.value)),
+ self.BaseTypes.S64VECTOR: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_s64vector = k.value)),
+ self.BaseTypes.S32VECTOR: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_s32vector = k.value)),
+ self.BaseTypes.S16VECTOR: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_s16vector = k.value)),
+ self.BaseTypes.S8VECTOR: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_s8vector = k.value)),
+ self.BaseTypes.C32VECTOR: lambda k: ttypes.Knob(type=k.ktype, value=ttypes.KnobBase(a_c32vector = k.value)),
+ }
+
+ def unpackKnob(self, key, knob):
+ f = self.unpack_dict.get(knob.type, None)
+ if(f):
+ return f(key, knob)
+ else:
+ sys.stderr.write("unpackKnobs: Incorrect Knob type: {0}\n".format(knob.type))
+ raise exceptions.ValueError
+
+ def packKnob(self, knob):
+ f = self.pack_dict.get(knob.ktype, None)
+ if(f):
+ return f(knob)
+ else:
+ sys.stderr.write("packKnobs: Incorrect Knob type: {0}\n".format(knob.type))
+ raise exceptions.ValueError
+
+ def newConnection(self, host=None, port=None):
+ try:
+ self.thriftclient = ThriftRadioClient(self.getHost(), self.getPort())
+ except TTransport.TTransportException:
+ sys.stderr.write("Could not connect to ControlPort endpoint at {0}:{1}.\n\n".format(host, port))
+ sys.exit(1)
+
+ def properties(self, *args):
+ knobprops = self.thriftclient.radio.properties(*args)
+ for key, knobprop in knobprops.iteritems():
+ #print("key:", key, "value:", knobprop, "type:", knobprop.type)
+ knobprops[key].min = self.unpackKnob(key, knobprop.min)
+ knobprops[key].max = self.unpackKnob(key, knobprop.max)
+ knobprops[key].defaultvalue = self.unpackKnob(key, knobprop.defaultvalue)
+ return knobprops
+
+ def getKnobs(self, *args):
+ result = {}
+ for key, knob in self.thriftclient.radio.getKnobs(*args).iteritems():
+ #print("key:", key, "value:", knob, "type:", knob.type)
+ result[key] = self.unpackKnob(key, knob)
+
+ # If complex, convert to Python complex
+ # FIXME: better list iterator way to handle this?
+ if(knob.type == self.BaseTypes.C32VECTOR):
+ for i in xrange(len(result[key].value)):
+ result[key].value[i] = complex(result[key].value[i].re,
+ result[key].value[i].im)
+ return result
+
+ def getKnobsRaw(self, *args):
+ result = {}
+ for key, knob in self.thriftclient.radio.getKnobs(*args).iteritems():
+ #print("key:", key, "value:", knob, "type:", knob.type)
+ result[key] = knob
+ return result
+
+ def getRe(self,*args):
+ result = {}
+ for key, knob in self.thriftclient.radio.getRe(*args).iteritems():
+ result[key] = self.unpackKnob(key, knob)
+ return result
+
+ def setKnobs(self, *args):
+ if(type(*args) == dict):
+ a = dict(*args)
+ result = {}
+ for key, knob in a.iteritems():
+ result[key] = self.packKnob(knob)
+ self.thriftclient.radio.setKnobs(result)
+ elif(type(*args) == list or type(*args) == tuple):
+ a = list(*args)
+ result = {}
+ for k in a:
+ result[k.key] = self.packKnob(k)
+ self.thriftclient.radio.setKnobs(result)
+ else:
+ sys.stderr.write("setKnobs: Invalid type; must be dict, list, or tuple\n")
+
+ def shutdown(self):
+ self.thriftclient.radio.shutdown()
+
+ def printProperties(self, props):
+ info = ""
+ info += "Item:\t\t{0}\n".format(props.description)
+ info += "units:\t\t{0}\n".format(props.units)
+ info += "min:\t\t{0}\n".format(props.min.value)
+ info += "max:\t\t{0}\n".format(props.max.value)
+ info += "default:\t\t{0}\n".format(props.defaultvalue.value)
+ info += "Type Code:\t0x{0:x}\n".format(props.type)
+ info += "Disp Code:\t0x{0:x}\n".format(props.display)
+ return info
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor
new file mode 100644
index 0000000000..c866776355
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-ctrlport-monitor
@@ -0,0 +1,771 @@
+#!/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 PyQt4 import QtCore,Qt
+import PyQt4.QtGui as QtGui
+import os, sys, time, struct
+
+from gnuradio import gr, ctrlport
+from gnuradio.ctrlport.GrDataPlotter import *
+
+class RateDialog(QtGui.QDialog):
+ def __init__(self, delay, parent=None):
+ super(RateDialog, self).__init__(parent)
+ self.gridLayout = QtGui.QGridLayout(self)
+ self.setWindowTitle("Update Delay (ms)");
+ self.delay = QtGui.QLineEdit(self);
+ self.delay.setText(str(delay));
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
+ self.gridLayout.addWidget(self.delay);
+ self.gridLayout.addWidget(self.buttonBox);
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+ def accept(self):
+ self.done(1);
+ def reject(self):
+ self.done(0);
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return Qtgui.QSize(800,600)
+
+ def __init__(self, radioclient):
+
+ super(MAINWindow, self).__init__()
+ self.radioclient = radioclient
+ self.updateRate = 1000;
+ self.conns = []
+ self.plots = []
+ self.knobprops = []
+
+ self.mdiArea = QtGui.QMdiArea()
+ self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.setCentralWidget(self.mdiArea)
+
+ self.mdiArea.subWindowActivated.connect(self.updateMenus)
+ self.windowMapper = QtCore.QSignalMapper(self)
+ self.windowMapper.mapped[QtGui.QWidget].connect(self.setActiveSubWindow)
+
+ self.createActions()
+ self.createMenus()
+ self.createToolBars()
+ self.createStatusBar()
+ self.updateMenus()
+
+ self.setWindowTitle("GNU Radio Control Port Monitor")
+ self.setUnifiedTitleAndToolBarOnMac(True)
+
+ self.newCon(radioclient)
+ icon = QtGui.QIcon(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, csomeBool):
+ child = MForm(self.radioclient, len(self.conns), parent = self, dialogprompt = not csomeBool)
+ if(child.radioclient is not None):
+ child.setWindowTitle(str(child.radioclient))
+ self.mdiArea.addSubWindow(child)
+ self.mdiArea.currentSubWindow().showMaximized()
+ self.conns.append(child)
+ self.plots.append([])
+
+ def propertiesMenu(self, key, radio, uid):
+ title = str(radio)
+
+ props = radio.properties([key])
+
+ pmin,pmax = get_minmax(props[key])
+
+ # Use display option mask of item to set up available plot
+ # types and default options.
+ disp = self.knobprops[uid][key].display
+ cplx = disp & gr.DISPOPTCPLX | disp & gr.DISPXY
+ strip = disp & gr.DISPOPTSTRIP
+ stem = disp & gr.DISPOPTSTEM
+ log = disp & gr.DISPOPTLOG
+ scatter = disp & gr.DISPOPTSCATTER
+
+ def newUpdaterProxy():
+ self.newUpdater(key, radio)
+
+ def newPlotterFProxy():
+ self.newPlotF(key, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ def newPlotterCProxy():
+ self.newPlotC(key, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ def newPlotterConstProxy():
+ self.newPlotConst(key, uid, title, pmin, pmax,
+ scatter, strip)
+
+ def newPlotterPsdFProxy():
+ self.newPlotPsdF(key, uid, title)
+
+ def newPlotterPsdCProxy():
+ self.newPlotPsdC(key, uid, title)
+
+ def newPlotterRasterFProxy():
+ self.newPlotRasterF(key, uid, title, pmin, pmax)
+
+ def newPlotterRasterBProxy():
+ self.newPlotRasterB(key, uid, title, pmin, pmax)
+
+ menu = QtGui.QMenu(self)
+ menu.setTitle("Item Actions")
+ menu.setTearOffEnabled(False)
+
+ # object properties
+ menu.addAction("Properties", newUpdaterProxy)
+
+ # displays available
+ if(cplx == 0):
+ menu.addAction("Plot Time", newPlotterFProxy)
+ menu.addAction("Plot PSD", newPlotterPsdFProxy)
+ menu.addAction("Plot Raster (real)", newPlotterRasterFProxy)
+ #menu.addAction("Plot Raster (bits)", newPlotterRasterBProxy)
+ else:
+ menu.addAction("Plot Time", newPlotterCProxy)
+ menu.addAction("Plot PSD", newPlotterPsdCProxy)
+ menu.addAction("Plot Constellation", newPlotterConstProxy)
+
+ menu.popup(QtGui.QCursor.pos())
+
+ def newUpdater(self, key, radio):
+ updater = UpdaterWindow(key, radio, None)
+ updater.setWindowTitle("Updater: " + key)
+ updater.setModal(False)
+ updater.exec_()
+
+ def newSub(self, e):
+ tag = str(e.text(0))
+ tree = e.treeWidget().parent()
+ uid = tree.uid
+ knobprop = self.knobprops[uid][tag]
+
+ strr = str(tree.radioclient)
+ print(strr)
+# r = strr.split(" ")
+ title = strr #title = "{0}:{1}".format(r[3], r[5])
+ pmin,pmax = get_minmax(knobprop)
+
+ disp = knobprop.display
+ if(disp & gr.DISPTIME):
+ strip = disp & gr.DISPOPTSTRIP
+ stem = disp & gr.DISPOPTSTEM
+ log = disp & gr.DISPOPTLOG
+ if(disp & gr.DISPOPTCPLX == 0):
+ self.newPlotF(tag, uid, title, pmin, pmax,
+ log, strip, stem)
+ else:
+ self.newPlotC(tag, uid, title, pmin, pmax,
+ log, strip, stem)
+
+ elif(disp & gr.DISPXY):
+ scatter = disp & gr.DISPOPTSCATTER
+ self.newPlotConst(tag, uid, title, pmin, pmax, scatter)
+
+ elif(disp & gr.DISPPSD):
+ if(disp & gr.DISPOPTCPLX == 0):
+ self.newPlotPsdF(tag, uid, title)
+ else:
+ self.newPlotPsdC(tag, uid, title)
+
+ def startDrag(self, e):
+ drag = QtGui.QDrag(self)
+ mime_data = QtCore.QMimeData()
+
+ tag = str(e.text(0))
+ tree = e.treeWidget().parent()
+ knobprop = self.knobprops[tree.uid][tag]
+ disp = knobprop.display
+ iscomplex = (disp & gr.DISPOPTCPLX) or (disp & gr.DISPXY)
+
+ if(disp != gr.DISPNULL):
+ data = "PlotData:::{0}:::{1}".format(tag, iscomplex)
+ else:
+ data = "OtherData:::{0}:::{1}".format(tag, iscomplex)
+
+ mime_data.setText(data)
+ drag.setMimeData(mime_data)
+
+ drop = drag.start()
+
+ def createPlot(self, plot, uid, title):
+ plot.start()
+ self.plots[uid].append(plot)
+
+ self.mdiArea.addSubWindow(plot)
+ plot.setWindowTitle("{0}: {1}".format(title, plot.name()))
+ self.connect(plot.qwidget(),
+ QtCore.SIGNAL('destroyed(QObject*)'),
+ self.destroyPlot)
+
+ # when the plot is updated via drag-and-drop, we need to be
+ # notified of the new qwidget that's created so we can
+ # properly destroy it.
+ plot.plotupdated.connect(self.plotUpdated)
+
+ plot.show()
+
+ def plotUpdated(self, q):
+ # the plot has been updated with a new qwidget; make sure this
+ # gets dies to the destroyPlot function.
+ for i, plots in enumerate(self.plots):
+ for p in plots:
+ if(p == q):
+ #plots.remove(p)
+ #plots.append(q)
+ self.connect(q.qwidget(),
+ QtCore.SIGNAL('destroyed(QObject*)'),
+ self.destroyPlot)
+ break
+
+ def destroyPlot(self, obj):
+ for plots in self.plots:
+ for p in plots:
+ if p.qwidget() == obj:
+ plots.remove(p)
+ break
+
+ def newPlotConst(self, tag, uid, title="", pmin=None, pmax=None,
+ scatter=False, stripchart=False):
+ plot = GrDataPlotterConst(tag, 32e6, pmin, pmax, stripchart)
+ plot.scatter(scatter)
+ self.createPlot(plot, uid, title)
+
+ def newPlotF(self, tag, uid, title="", pmin=None, pmax=None,
+ logy=False, stripchart=False, stem=False):
+ plot = GrDataPlotterF(tag, 32e6, pmin, pmax, stripchart)
+ plot.semilogy(logy)
+ plot.stem(stem)
+ self.createPlot(plot, uid, title)
+
+ def newPlotC(self, tag, uid, title="", pmin=None, pmax=None,
+ logy=False, stripchart=False, stem=False):
+ plot = GrDataPlotterC(tag, 32e6, pmin, pmax, stripchart)
+ plot.semilogy(logy)
+ plot.stem(stem)
+ self.createPlot(plot, uid, title)
+
+ def newPlotPsdF(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrDataPlotterPsdF(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotPsdC(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrDataPlotterPsdC(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotRasterF(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrTimeRasterF(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def newPlotRasterB(self, tag, uid, title="", pmin=None, pmax=None):
+ plot = GrTimeRasterB(tag, 32e6, pmin, pmax)
+ self.createPlot(plot, uid, title)
+
+ def update(self, knobs, uid):
+ #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
+ for plot in self.plots[uid]:
+ data = []
+ for n in plot.knobnames:
+ d = knobs[n].value
+ # TODO: FIX COMPLEX!
+# if(type(d) == GNURadio.complex):
+# d = [d.re, d.im]
+
+ # If it's a byte stream, Python thinks it's a string.
+ # Unpack and convert to floats for plotting.
+ if(type(d) == str and n.find('probe2_b') == 0):
+ d = struct.unpack(len(d)*'b', d)
+ d = [float(di) for di in d]
+
+ data.append(d)
+ plot.update(data)
+ plot.stop()
+ plot.wait()
+ plot.start()
+
+ def setActiveSubWindow(self, window):
+ if window:
+ self.mdiArea.setActiveSubWindow(window)
+
+
+ def createActions(self):
+ self.newConAct = QtGui.QAction("&New Connection",
+ self, shortcut=QtGui.QKeySequence.New,
+ statusTip="Create a new file", triggered=self.newCon)
+
+ self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
+ statusTip="Exit the application",
+ triggered=QtGui.qApp.closeAllWindows)
+
+ self.closeAct = QtGui.QAction("Cl&ose", self, shortcut="Ctrl+F4",
+ statusTip="Close the active window",
+ triggered=self.mdiArea.closeActiveSubWindow)
+
+ self.closeAllAct = QtGui.QAction("Close &All", self,
+ statusTip="Close all the windows",
+ triggered=self.mdiArea.closeAllSubWindows)
+
+ self.urAct = QtGui.QAction("Update Rate", self, shortcut="F5",
+ statusTip="Change Update Rate",
+ triggered=self.updateRateShow)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T);
+ self.tileAct = QtGui.QAction("&Tile", self,
+ statusTip="Tile the windows",
+ triggered=self.mdiArea.tileSubWindows,
+ shortcut=qks)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C);
+ self.cascadeAct = QtGui.QAction("&Cascade", self,
+ statusTip="Cascade the windows", shortcut=qks,
+ triggered=self.mdiArea.cascadeSubWindows)
+
+ self.nextAct = QtGui.QAction("Ne&xt", self,
+ shortcut=QtGui.QKeySequence.NextChild,
+ statusTip="Move the focus to the next window",
+ triggered=self.mdiArea.activateNextSubWindow)
+
+ self.previousAct = QtGui.QAction("Pre&vious", self,
+ shortcut=QtGui.QKeySequence.PreviousChild,
+ statusTip="Move the focus to the previous window",
+ triggered=self.mdiArea.activatePreviousSubWindow)
+
+ self.separatorAct = QtGui.QAction(self)
+ self.separatorAct.setSeparator(True)
+
+ self.aboutAct = QtGui.QAction("&About", self,
+ statusTip="Show the application's About box",
+ triggered=self.about)
+
+ self.aboutQtAct = QtGui.QAction("About &Qt", self,
+ statusTip="Show the Qt library's About box",
+ triggered=QtGui.qApp.aboutQt)
+
+ def createMenus(self):
+ self.fileMenu = self.menuBar().addMenu("&File")
+ self.fileMenu.addAction(self.newConAct)
+ self.fileMenu.addAction(self.urAct)
+ self.fileMenu.addSeparator()
+ self.fileMenu.addAction(self.exitAct)
+
+ self.windowMenu = self.menuBar().addMenu("&Window")
+ self.updateWindowMenu()
+ self.windowMenu.aboutToShow.connect(self.updateWindowMenu)
+
+ self.menuBar().addSeparator()
+
+ self.helpMenu = self.menuBar().addMenu("&Help")
+ self.helpMenu.addAction(self.aboutAct)
+ self.helpMenu.addAction(self.aboutQtAct)
+
+ def updateRateShow(self):
+ askrate = RateDialog(self.updateRate, self);
+ if askrate.exec_():
+ ur = float(str(askrate.delay.text()));
+ self.setUpdateRate(ur);
+ return;
+ else:
+ return;
+
+ def createToolBars(self):
+ self.fileToolBar = self.addToolBar("File")
+ self.fileToolBar.addAction(self.newConAct)
+ self.fileToolBar.addAction(self.urAct)
+
+ self.fileToolBar = self.addToolBar("Window")
+ self.fileToolBar.addAction(self.tileAct)
+ self.fileToolBar.addAction(self.cascadeAct)
+
+ def createStatusBar(self):
+ self.statusBar().showMessage("Ready")
+
+
+ def activeMdiChild(self):
+ activeSubWindow = self.mdiArea.activeSubWindow()
+ if activeSubWindow:
+ return activeSubWindow.widget()
+ return None
+
+ def updateMenus(self):
+ hasMdiChild = (self.activeMdiChild() is not None)
+ self.closeAct.setEnabled(hasMdiChild)
+ self.closeAllAct.setEnabled(hasMdiChild)
+ self.tileAct.setEnabled(hasMdiChild)
+ self.cascadeAct.setEnabled(hasMdiChild)
+ self.nextAct.setEnabled(hasMdiChild)
+ self.previousAct.setEnabled(hasMdiChild)
+ self.separatorAct.setVisible(hasMdiChild)
+
+ def updateWindowMenu(self):
+ self.windowMenu.clear()
+ self.windowMenu.addAction(self.closeAct)
+ self.windowMenu.addAction(self.closeAllAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.tileAct)
+ self.windowMenu.addAction(self.cascadeAct)
+ self.windowMenu.addSeparator()
+ self.windowMenu.addAction(self.nextAct)
+ self.windowMenu.addAction(self.previousAct)
+ self.windowMenu.addAction(self.separatorAct)
+
+ def about(self):
+ about_info = \
+'''Copyright 2012 Free Software Foundation, Inc.\n
+This program is part of GNU Radio.\n
+GNU Radio is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.\n
+GNU Radio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n
+You should have received a copy of the GNU General Public License along with GNU Radio; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301, USA.'''
+
+ QtGui.QMessageBox.about(None, "gr-ctrlport-monitor", about_info)
+
+
+class ConInfoDialog(QtGui.QDialog):
+ def __init__(self, parent=None):
+ super(ConInfoDialog, self).__init__(parent)
+
+ self.gridLayout = QtGui.QGridLayout(self)
+
+
+ self.host = QtGui.QLineEdit(self);
+ self.port = QtGui.QLineEdit(self);
+ self.host.setText("localhost");
+ self.port.setText("43243");
+
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
+
+ self.gridLayout.addWidget(self.host);
+ self.gridLayout.addWidget(self.port);
+ self.gridLayout.addWidget(self.buttonBox);
+
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.reject)
+
+
+ def accept(self):
+ self.done(1);
+
+ def reject(self):
+ self.done(0);
+
+
+class UpdaterWindow(QtGui.QDialog):
+ def __init__(self, key, radio, parent):
+ QtGui.QDialog.__init__(self, parent)
+
+ self.key = key;
+ self.radio = radio
+
+ self.resize(300,200)
+ self.layout = QtGui.QVBoxLayout()
+
+ self.props = radio.properties([key])[key]
+ info = radio.printProperties(self.props)
+
+ self.infoLabel = QtGui.QLabel(info)
+ self.layout.addWidget(self.infoLabel)
+
+ # Test here to make sure that a 'set' function exists
+ try:
+ radio.setKnobs(radio.getKnobs([key]))
+ has_set = True
+ except:
+ 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.getKnobs([key])
+ val = rv[key].value
+ if(type(val) == ControlPort.complex):
+ val = val.re + val.im*1j
+
+ self.textInput.setText(str(val))
+ self.sv = rv[key]
+
+ self.applyButton.connect(self.applyButton, QtCore.SIGNAL('clicked()'), self._apply)
+ self.setButton.connect(self.setButton, QtCore.SIGNAL('clicked()'), self._set)
+ self.cancelButton.connect(self.cancelButton, QtCore.SIGNAL('clicked()'), self.reject)
+
+ self.is_num = ((type(self.sv.value)==float) or (type(self.sv.value)==int))
+ if(self.is_num):
+ self.sliderlayout = QtGui.QHBoxLayout()
+
+ self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
+
+ self.sliderlayout.addWidget(QtGui.QLabel(str(self.props.min.value)))
+ self.sliderlayout.addWidget(self.slider)
+ self.sliderlayout.addWidget(QtGui.QLabel(str(self.props.max.value)))
+
+ self.steps = 10000
+ self.valspan = self.props.max.value - self.props.min.value
+
+ self.slider.setRange(0, 10000)
+ self._set_slider_value(self.sv.value)
+
+ self.connect(self.slider, QtCore.SIGNAL("sliderReleased()"), self._slide)
+
+ self.layout.addLayout(self.sliderlayout)
+ else:
+ self._set_slider_value = None
+
+ self.buttonlayout = QtGui.QHBoxLayout()
+ self.buttonlayout.addWidget(self.applyButton)
+ self.buttonlayout.addWidget(self.setButton)
+ self.buttonlayout.addWidget(self.cancelButton)
+ self.layout.addLayout(self.buttonlayout)
+
+ # set layout and go...
+ self.setLayout(self.layout)
+
+ def _set_slider_value(self, val):
+ self.slider.setValue(self.steps*(val-self.props.min.value)/self.valspan)
+
+ def _slide(self):
+ val = (self.slider.value()*self.valspan + self.props.min.value)/float(self.steps)
+ self.textInput.setText(str(val))
+
+ def _apply(self):
+ if(type(self.sv.value) == str):
+ val = str(self.textInput.text())
+ elif(type(self.sv.value) == int):
+ val = int(round(float(self.textInput.text())))
+ elif(type(self.sv.value) == float):
+ val = float(self.textInput.text())
+ elif(type(self.sv.value) == ControlPort.complex):
+ t = str(self.textInput.text())
+ t = complex(t.strip("(").strip(")").replace(" ", ""))
+ val = ControlPort.complex()
+ val.re = t.real
+ val.im = t.imag
+ else:
+ sys.stderr.write("set type not supported! ({0})\n".format(type(self.sv.value)))
+ return
+
+ self.sv.value = val
+ km = {}
+ km[self.key] = self.sv
+ self.radio.setKnobs(km)
+ if self._set_slider_value:
+ self._set_slider_value(self.sv.value)
+
+ def _set(self):
+ self._apply()
+ self.done(0)
+
+
+class MForm(QtGui.QWidget):
+ def update(self):
+ # TODO: revisit this try-except block, figure out what it's doing, and if we need to keep it. at very lease makes debugging dificult
+ if True: #try:
+ st = time.time();
+ knobs = self.radioclient.getKnobs([])
+ ft = time.time();
+ latency = ft-st;
+ self.parent.statusBar().showMessage("Current GNU Radio Control Port Query Latency: %f ms"%(latency*1000))
+
+# except Exception, e:
+# sys.stderr.write("ctrlport-monitor: radio.get threw exception ({0}).\n".format(e))
+# if(type(self.parent) is MAINWindow):
+# # Find window of connection
+# remove = []
+# for p in self.parent.mdiArea.subWindowList():
+# if self.parent.conns[self.uid] == p.widget():
+# remove.append(p)
+#
+# # Find any subplot windows of connection
+# for p in self.parent.mdiArea.subWindowList():
+# for plot in self.parent.plots[self.uid]:
+# if plot.qwidget() == p.widget():
+# remove.append(p)
+#
+# # Clean up local references to these
+# self.parent.conns.remove(self.parent.conns[self.uid])
+# self.parent.plots.remove(self.parent.plots[self.uid])
+#
+# # Remove subwindows for connection and plots
+# for r in remove:
+# self.parent.mdiArea.removeSubWindow(r)
+#
+# # Clean up self
+# self.close()
+# else:
+# sys.exit(1)
+# return
+
+ tableitems = knobs.keys()
+
+ #UPDATE TABLE:
+ #try:
+ self.table.updateItems(knobs, self.knobprops)
+ #except:
+ # self.knobprops = self.radioclient.properties([])
+ # print("knobsprops1:", len(self.knobprops))
+
+ #UPDATE PLOTS
+ self.parent.update(knobs, self.uid)
+
+
+ def __init__(self, radioclient, uid=0, updateRate=2000, parent=None, dialogprompt = False):
+
+ super(MForm, self).__init__()
+ self.radioclient = radioclient
+# print("before radioclient.getHost()", radioclient.getHost(), radioclient.getPort(), "prompt", prompt)
+ if(dialogprompt or radioclient.getHost() is None or radioclient.getPort() is None):
+# print("before ConInfoDialog")
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ host = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+# print("before radioclient.newConnection host: %s port: %s"%(host,port))
+ newradio = self.radioclient.newConnection(host, port)
+ if newradio is None:
+ print("Error making a %s connection to %s:%s from %s" % (radioclient.getName(), host, port, radioclient))
+ else:
+ self.radioclient = newradio
+
+ else:
+ self.radioclient = Nonclient = None
+ return
+
+
+ self.uid = uid
+ self.parent = parent
+ self.horizontalLayout = QtGui.QVBoxLayout(self)
+ self.gridLayout = QtGui.QGridLayout()
+
+ self.knobprops = self.radioclient.properties([])
+ #print("props5:", self.knobprops)
+ self.parent.knobprops.append(self.knobprops)
+ self.resize(775,500)
+ self.timer = QtCore.QTimer()
+ self.constupdatediv = 0
+ self.tableupdatediv = 0
+ plotsize=250
+
+ # make table
+ self.table = GrDataPlotterValueTable(uid, self, 0, 0, 400, 200)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
+ self.table.treeWidget.setSizePolicy(sizePolicy)
+ self.table.treeWidget.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed)
+ self.table.treeWidget.setSortingEnabled(True)
+ self.table.treeWidget.setDragEnabled(True)
+
+ # add things to layouts
+ self.horizontalLayout.addWidget(self.table.treeWidget)
+
+ # set up timer
+ self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
+ self.updateRate = updateRate;
+ self.timer.start(self.updateRate)
+
+ # set up context menu ..
+ self.table.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.table.treeWidget.customContextMenuRequested.connect(self.openMenu)
+
+ # Set up double-click to launch default plotter
+ self.connect(self.table.treeWidget,
+ QtCore.SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'),
+ self.parent.newSub);
+
+ # Allow drag/drop event from table item to plotter
+ self.connect(self.table.treeWidget,
+ QtCore.SIGNAL('itemPressed(QTreeWidgetItem*, int)'),
+ self.parent.startDrag)
+
+ def openMenu(self, pos):
+ index = self.table.treeWidget.selectedIndexes()
+ item = self.table.treeWidget.itemFromIndex(index[0])
+ itemname = str(item.text(0))
+ self.parent.propertiesMenu(itemname, self.radioclient, self.uid)
+
+
+def get_minmax(p):
+ pmin = p.min.value
+ pmax = p.max.value
+
+ # Find min/max or real or imag for GNURadio::complex
+ # TODO: fix complex
+ if(type(pmin) == ControlPort.complex):
+ pmin = min(pmin.re, pmin.im)
+ if(type(pmax) == ControlPort.complex):
+ pmax = max(pmax.re, pmax.im)
+
+ # If it's a byte stream, Python thinks it's a string.
+ try:
+ if(type(pmin) == str):
+ pmin = struct.unpack('b', pmin)[0]
+ if(type(pmax) == str):
+ pmax = struct.unpack('b', pmax)[0]
+ except struct.error:
+ pmin = []
+ pmax = []
+
+ if pmin == []:
+ pmin = None
+ else:
+ pmin = 1.1*float(pmin)
+ if pmax == []:
+ pmax = None
+ else:
+ pmax = 1.1*float(pmax)
+
+ return pmin, pmax
+
+class MyApp(object):
+ def __init__(self, args):
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ GNURadioControlPortClient(args, 'thrift', self.run, QtGui.QApplication(sys.argv).exec_)
+
+ def run(self, client):
+ MAINWindow(client).show()
+
+MyApp(sys.argv)
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
new file mode 100644
index 0000000000..23e11d4174
--- /dev/null
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
@@ -0,0 +1,856 @@
+#!/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
+matplotlib.use("QT4Agg")
+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 PyQt4 import QtCore,Qt,Qwt5
+import PyQt4.QtGui as QtGui
+import sys, time, re, pprint
+import itertools
+
+from gnuradio import gr, ctrlport
+from gnuradio.ctrlport.GrDataPlotter import *
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return QtGui.QSize(800,600)
+
+ def __init__(self, radioclient):
+
+ super(MAINWindow, self).__init__()
+ self.radioclient = radioclient
+ self.conns = []
+ self.plots = []
+ self.knobprops = []
+
+ self.mdiArea = QtGui.QMdiArea()
+ self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.setCentralWidget(self.mdiArea)
+
+ self.mdiArea.subWindowActivated.connect(self.updateMenus)
+ self.windowMapper = QtCore.QSignalMapper(self)
+ self.windowMapper.mapped[QtGui.QWidget].connect(self.setActiveSubWindow)
+
+ self.createActions()
+ self.createMenus()
+ self.createToolBars()
+ self.createStatusBar()
+ self.updateMenus()
+
+ self.setWindowTitle("GNU Radio Performance Monitor")
+ self.setUnifiedTitleAndToolBarOnMac(True)
+
+ self.newCon(radioclient)
+
+ 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, csomeBool):
+ child = MForm(self.radioclient, len(self.conns), self, dialogprompt = not csomeBool)
+ if(child.radioclient is not None):
+ child.setWindowTitle(str(child.radioclient))
+ self.mdiArea.addSubWindow(child)
+ self.mdiArea.currentSubWindow().showMaximized()
+
+ self.conns.append(child)
+ self.plots.append([])
+
+ def 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-perf-monitorx", 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, radioclient, G):
+ QtGui.QWidget.__init__( self)
+
+ self.layout = QtGui.QVBoxLayout(self);
+ self.hlayout = QtGui.QHBoxLayout();
+ self.layout.addLayout(self.hlayout);
+
+ self.G = G;
+ self.radioclient = radioclient;
+
+ 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, radioclient, G):
+ super(DataTableBuffers, self).__init__(radioclient, 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.radioclient.getKnobs(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:
+ bdata = buffer_fullness[blk]
+ if bdata:
+ for port in range(0,len(bdata)):
+ blockport_fullness["%s:%d"%(blk,port)] = bdata[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();
+ 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, radioclient, G):
+ super(DataTableRuntimes, self).__init__( radioclient, 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.radioclient.getKnobs(kl)
+
+ # strip values out of ctrlport response
+ total_work = sum(map(lambda x: x.value, wrk_knobs.values()))
+ if(total_work == 0):
+ total_work = 1
+ 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:
+ try:
+ # update current clock type
+ self.prevent_clock_change = True;
+ kl1 = None;
+ if(self.clockKey == None):
+ kl1 = self.radioclient.getRe([".*perfcounter_clock"])
+ else:
+ kl1 = self.radioclient.getKnobs([self.clockKey])
+ self.clockKey = kl1.keys()[0]
+ self.currClock = kl1[self.clockKey].value
+ self.clockSelIdx = self.clocks.values().index(self.currClock)
+ self.clockSel.setCurrentIndex(self.clockSelIdx)
+ self.prevent_clock_change = False
+ except:
+ print "WARNING: Failed to get current clock setting!"
+
+ nodes_stream = self.G_stream.nodes()
+ nodes_msg = self.G_msg.nodes()
+
+ # get current buffer depths of all output buffers
+ kl = map(lambda x: "%s::%soutput %% full" % \
+ (x, self._statistics_table[self._statistic]),
+ nodes_stream);
+
+ st = time.time()
+ buf_knobs = self.radioclient.getKnobs(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_stream);
+ st = time.time()
+ wrk_knobs = self.radioclient.getKnobs(kl)
+ td2 = time.time() - st;
+
+ # strip values out of ctrlport response
+ total_work = sum(map(lambda x: x.value, wrk_knobs.values()))
+ if(total_work == 0):
+ total_work = 1
+ work_times = dict(zip(
+ map(lambda x: x.split("::")[0], wrk_knobs.keys()),
+ map(lambda x: x.value/total_work, wrk_knobs.values())))
+ work_times_padded = dict(zip(
+ self.G.nodes(),
+ [0.1]*len(self.G.nodes())))
+ work_times_padded.update(work_times)
+
+ for n in nodes_stream:
+ # 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
+ for e in ne: # iterate over edges from this block
+ # get the right output buffer/port weight for each edge
+ sourceport = e[2]["sourceport"];
+ if(e[2]["type"] == "stream"):
+ newweight = buf_vals[n][sourceport]
+ e[2]["weight"] = newweight;
+
+ for n in nodes_msg:
+ ne = self.G.edges([n],True);
+ for e in ne: # iterate over edges from this block
+ sourceport = e[2]["sourceport"];
+ if(e[2]["type"] == "msg"):
+ newweight = 0.01;
+ e[2]["weight"] = newweight;
+
+ # set updated weights
+ #self.node_weights = map(lambda x: 20+2000*work_times[x], nodes_stream);
+ self.node_weights = map(lambda x: 20+2000*work_times_padded[x], self.G.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("gr-perf-monitorx: radio.getKnobs 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.radioclient, self.G_stream), "Runtime Table" );
+
+ def bpt(self):
+ self.parent.newSubWindow( DataTableBuffers(self.radioclient, self.G_stream), "Buffers Table" );
+
+ def resetPCs(self):
+ knobs = []
+ for b in self.blocks_list:
+ knobs += [self.radioclient.Knob(b + "::reset_perf_counters"),]
+ k = self.radioclient.setKnobs(knobs)
+
+ def toggleFlowgraph(self):
+ if self.pauseFGAct.isChecked():
+ self.pauseFlowgraph()
+ else:
+ self.unpauseFlowgraph()
+
+ def pauseFlowgraph(self):
+ knobs = [self.radioclient.Knob(self.top_block + "::lock"),
+ self.radioclient.Knob(self.top_block + "::stop")]
+ k = self.radioclient.setKnobs(knobs)
+
+ def unpauseFlowgraph(self):
+ knobs = [self.radioclient.Knob(self.top_block + "::unlock")]
+ k = self.radioclient.setKnobs(knobs)
+
+ def stat_changed(self, index):
+ self._statistic = str(self.stattype.currentText())
+
+ def update_clock(self, clkidx):
+ if(self.prevent_clock_change):
+ return;
+ idx = self.clockSel.currentIndex();
+ clk = self.clocks.values()[idx]
+# print "UPDATE CLOCK!!! %d -> %d"%(idx,clk);
+ k = self.radioclient.getKnobs([self.clockKey]);
+ k[self.clockKey].value = clk;
+ km = {};
+ km[self.clockKey] = k[self.clockKey];
+ self.radioclient.setKnobs(km);
+
+ def __init__(self, radioclient, uid=0, parent=None, dialogprompt = False):
+
+ super(MForm, self).__init__()
+ self.radioclient = radioclient
+# print("before radioclient.getHost()", radioclient.getHost(), radioclient.getPort(), "prompt", prompt)
+ if(dialogprompt or radioclient.getHost() is None or radioclient.getPort() is None):
+# print("before ConInfoDialog")
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ host = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+# print("before radioclient.newConnection host: %s port: %s"%(host,port))
+ newradio = self.radioclient.newConnection(host, port)
+ if newradio is None:
+ print("Error making a %s connection to %s:%s from %s" % (radioclient.getName(), host, port, radioclient))
+ else:
+ self.radioclient = newradio
+
+ else:
+ self.radioclient = 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.resetPCsAct = QtGui.QAction("Reset", self,
+ statusTip="Reset all Performance Counters",
+ triggered=self.resetPCs)
+ self.resetPCsBut = Qt.QToolButton()
+ self.resetPCsBut.setDefaultAction(self.resetPCsAct);
+ self.ctlBox.addWidget(self.resetPCsBut);
+
+ self.pauseFGAct = QtGui.QAction("Pause", self,
+ statusTip="Pause the Flowgraph",
+ triggered=self.toggleFlowgraph)
+ self.pauseFGAct.setCheckable(True)
+ self.pauseFGBut = Qt.QToolButton()
+ self.pauseFGBut.setDefaultAction(self.pauseFGAct);
+ self.ctlBox.addWidget(self.pauseFGBut);
+
+ self.prevent_clock_change = True;
+ self.clockKey = None;
+ self.clocks = {"MONOTONIC":1, "THREAD":3};
+ self.clockSel = QtGui.QComboBox(self);
+ map(lambda x: self.clockSel.addItem(x), self.clocks.keys());
+ self.ctlBox.addWidget(self.clockSel);
+ self.clockSel.currentIndexChanged.connect(self.update_clock);
+ self.prevent_clock_change = False;
+
+ 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 = radioclient
+ 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.getKnobs([])
+ edgelist = None
+ msgedgelist = None
+ for k in knobs:
+ propname = k.split("::")
+ blockname = propname[0]
+ keyname = propname[1]
+ if(keyname == "edge list"):
+ edgelist = knobs[k].value
+ self.top_block = blockname
+ elif(keyname == "msg edges list"):
+ msgedgelist = 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)
+
+ self.blocks_list = tmplist
+ edges = edgelist.split("\n")[0:-1]
+ msgedges = msgedgelist.split("\n")[0:-1]
+
+ edgepairs_stream = [];
+ edgepairs_msg = [];
+
+ # add stream connections
+ for e in edges:
+ _e = e.split("->")
+ edgepairs_stream.append( (_e[0].split(":")[0], _e[1].split(":")[0],
+ {"type":"stream", "sourceport":int(_e[0].split(":")[1])}) );
+
+ # add msg connections
+ for e in msgedges:
+ _e = e.split("->")
+ edgepairs_msg.append( (_e[0].split(":")[0], _e[1].split(":")[0],
+ {"type":"msg", "sourceport":_e[0].split(":")[1]}) );
+
+ self.G = nx.MultiDiGraph();
+ self.G_stream = nx.MultiDiGraph();
+ self.G_msg = nx.MultiDiGraph();
+
+ self.G.add_edges_from(edgepairs_stream);
+ self.G.add_edges_from(edgepairs_msg);
+
+ self.G_stream.add_edges_from(edgepairs_stream);
+ self.G_msg.add_edges_from(edgepairs_msg);
+
+ 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: math.sqrt( math.pow(x-z[0],2) + math.pow(y-z[1],2)), 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.radioclient, 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 MyApp(object):
+ def __init__(self, args):
+ p = gr.prefs()
+ cp_on = p.get_bool("ControlPort", "on", None)
+ cp_edges = p.get_bool("ControlPort", "edges_list", None)
+ pcs_on = p.get_bool("PerfCounters", "on", None)
+ pcs_exported = p.get_bool("PerfCounters", "export", None)
+ if(not (pcs_on and cp_on and pcs_exported and cp_edges)):
+ print("Configuration has not turned on all of the appropriate ControlPort features:")
+ print("\t[ControlPort] on = {0}".format(cp_on))
+ print("\t[ControlPort] edges_list = {0}".format(cp_edges))
+ print("\t[PerfCounters] on = {0}".format(pcs_on))
+ print("\t[PerfCounters] export = {0}".format(pcs_exported))
+ exit(1)
+
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ GNURadioControlPortClient(args, 'thrift', self.run, QtGui.QApplication(sys.argv).exec_)
+
+ def run(self, client):
+ MAINWindow(client).show()
+
+MyApp(sys.argv)
diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py b/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py
index 8bb26d93a1..f651be2449 100644
--- a/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/monitor.py
@@ -48,9 +48,9 @@ class monitor:
print "monitor::endpoints() = %s" % (gr.rpcmanager_get().endpoints())
try:
cmd = map(lambda a: [self.tool,
- re.search("\d+\.\d+\.\d+\.\d+",a).group(0),
- re.search("-p (\d+)",a).group(1)],
- gr.rpcmanager_get().endpoints())[0]
+ re.search("-h (\S+|\d+\.\d+\.\d+\.\d+)",a).group(1),
+ re.search("-p (\d+)",a).group(1)],
+ gr.rpcmanager_get().endpoints())[0]
print "running: %s"%(str(cmd))
self.proc = subprocess.Popen(cmd);
self.started = True