summaryrefslogtreecommitdiff
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
parent7765798c48b9ec4b1cda43367e97eb778a8ad758 (diff)
parentb092142302bcf8c771ec68e61da7781eb406c86f (diff)
Merge branch 'master' into next
-rw-r--r--CMakeLists.txt8
-rw-r--r--cmake/Modules/FindThrift.cmake79
-rw-r--r--cmake/Modules/GrTest.cmake3
-rw-r--r--config.h.in6
-rw-r--r--docs/doxygen/other/build_guide.dox13
-rw-r--r--docs/doxygen/other/ctrlport.dox425
-rw-r--r--docs/doxygen/other/perf_counters.dox10
-rw-r--r--gnuradio-runtime/include/gnuradio/CMakeLists.txt10
-rw-r--r--gnuradio-runtime/include/gnuradio/prefs.h32
-rw-r--r--gnuradio-runtime/include/gnuradio/rpcbufferedget.h65
-rw-r--r--gnuradio-runtime/include/gnuradio/rpcmanager.h2
-rw-r--r--gnuradio-runtime/include/gnuradio/rpcpmtconverters_thrift.h74
-rw-r--r--gnuradio-runtime/include/gnuradio/rpcregisterhelpers.h22
-rw-r--r--gnuradio-runtime/include/gnuradio/rpcserver_booter_thrift.h52
-rw-r--r--gnuradio-runtime/include/gnuradio/rpcserver_selector.h10
-rw-r--r--gnuradio-runtime/include/gnuradio/rpcserver_thrift.h230
-rw-r--r--gnuradio-runtime/include/gnuradio/thrift_application_base.h253
-rw-r--r--gnuradio-runtime/include/gnuradio/thrift_server_template.h160
-rw-r--r--gnuradio-runtime/lib/block.cc12
-rw-r--r--gnuradio-runtime/lib/controlport/CMakeLists.txt70
-rw-r--r--gnuradio-runtime/lib/controlport/rpcmanager.cc14
-rw-r--r--gnuradio-runtime/lib/controlport/rpcserver_booter_aggregator.cc3
-rw-r--r--gnuradio-runtime/lib/controlport/rpcserver_selector.cc15
-rw-r--r--gnuradio-runtime/lib/controlport/thrift/gnuradio.thrift108
-rw-r--r--gnuradio-runtime/lib/controlport/thrift/rpcpmtconverters_thrift.cc303
-rw-r--r--gnuradio-runtime/lib/controlport/thrift/rpcserver_booter_thrift.cc137
-rw-r--r--gnuradio-runtime/lib/controlport/thrift/rpcserver_thrift.cc194
-rw-r--r--gnuradio-runtime/lib/controlport/thrift/thrift.conf.example4
-rw-r--r--gnuradio-runtime/lib/controlport/thrift/thrift_application_base.cc23
-rw-r--r--gnuradio-runtime/lib/prefs.cc28
-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
-rw-r--r--gnuradio-runtime/swig/prefs.i4
-rw-r--r--gr-blocks/grc/blocks_block_tree.xml2
-rw-r--r--gr-blocks/grc/blocks_ctrlport_performance.xml48
-rw-r--r--gr-blocks/grc/blocks_ctrlport_viewer.xml48
-rw-r--r--gr-blocks/lib/ctrlport_probe2_b_impl.cc31
-rw-r--r--gr-blocks/lib/ctrlport_probe2_b_impl.h6
-rw-r--r--gr-blocks/lib/ctrlport_probe2_c_impl.cc31
-rw-r--r--gr-blocks/lib/ctrlport_probe2_c_impl.h7
-rw-r--r--gr-blocks/lib/ctrlport_probe2_f_impl.cc32
-rw-r--r--gr-blocks/lib/ctrlport_probe2_f_impl.h7
-rw-r--r--gr-blocks/lib/ctrlport_probe2_i_impl.cc32
-rw-r--r--gr-blocks/lib/ctrlport_probe2_i_impl.h7
-rw-r--r--gr-blocks/lib/ctrlport_probe2_s_impl.cc31
-rw-r--r--gr-blocks/lib/ctrlport_probe2_s_impl.h7
-rw-r--r--gr-blocks/python/blocks/CMakeLists.txt6
-rw-r--r--gr-blocks/python/blocks/qa_cpp_py_binding.py173
-rw-r--r--gr-blocks/python/blocks/qa_cpp_py_binding_set.py151
-rw-r--r--gr-blocks/python/blocks/qa_ctrlport_probes.py210
56 files changed, 5112 insertions, 223 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c1f16e792b..1450908cf8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -408,6 +408,14 @@ add_subdirectory(gr-zeromq)
if(ENABLE_GR_CTRLPORT)
set(GR_CTRLPORT True)
add_definitions(-DGR_CTRLPORT)
+
+ if(CTRLPORT_BACKENDS GREATER 0)
+ set(GR_RPCSERVER_ENABLED True)
+
+ if(THRIFT_FOUND)
+ set(GR_RPCSERVER_THRIFT True)
+ endif(THRIFT_FOUND)
+ endif(CTRLPORT_BACKENDS GREATER 0)
endif(ENABLE_GR_CTRLPORT)
# Install our Cmake modules into $prefix/lib/cmake/gnuradio
diff --git a/cmake/Modules/FindThrift.cmake b/cmake/Modules/FindThrift.cmake
new file mode 100644
index 0000000000..f12bce01e6
--- /dev/null
+++ b/cmake/Modules/FindThrift.cmake
@@ -0,0 +1,79 @@
+INCLUDE(FindPkgConfig)
+PKG_CHECK_MODULES(PC_THRIFT thrift)
+
+set(THRIFT_REQ_VERSION "0.9.2")
+
+# If pkg-config found Thrift and it doesn't meet our version
+# requirement, warn and exit -- does not cause an error; just doesn't
+# enable Thrift.
+if(PC_THRIFT_FOUND AND PC_THRIFT_VERSION VERSION_LESS ${THRIFT_REQ_VERSION})
+ message(STATUS "Could not find appropriate version of Thrift: ${PC_THRIFT_VERSION} < ${THRIFT_REQ_VERSION}")
+ return()
+endif(PC_THRIFT_FOUND AND PC_THRIFT_VERSION VERSION_LESS ${THRIFT_REQ_VERSION})
+
+
+# Else, look for it ourselves
+
+FIND_PATH(THRIFT_INCLUDE_DIRS
+ NAMES thrift/Thrift.h
+ HINTS ${PC_THRIFT_INCLUDE_DIR}
+ ${CMAKE_INSTALL_PREFIX}/include
+ PATHS
+ /usr/local/include
+ /usr/include
+ )
+
+FIND_LIBRARY(THRIFT_LIBRARIES
+ NAMES thrift
+ HINTS ${PC_THRIFT_LIBDIR}
+ ${CMAKE_INSTALL_PREFIX}/lib
+ ${CMAKE_INSTALL_PREFIX}/lib64
+ PATHS
+ ${THRIFT_INCLUDE_DIRS}/../lib
+ /usr/local/lib
+ /usr/lib
+ )
+
+# Get the thrift binary to build our files during cmake
+FIND_PROGRAM(THRIFT_BIN thrift)
+
+# Use binary to get version string and test against THRIFT_REQ_VERSION
+EXECUTE_PROCESS(
+ COMMAND ${THRIFT_BIN} --version
+ OUTPUT_VARIABLE THRIFT_VERSION
+ ERROR_VARIABLE THRIFT_VERSION_ERROR
+ )
+
+if(NOT THRIFT_BIN)
+ message(STATUS "Binary 'thrift' not found.")
+ return()
+endif(NOT THRIFT_BIN)
+
+STRING(REGEX MATCH "[0-9]+.[0-9]+.[0-9]+"
+ THRIFT_VERSION "${THRIFT_VERSION}")
+
+if(THRIFT_VERSION VERSION_LESS THRIFT_REQ_VERSION)
+ message(STATUS "Could not find appropriate version of Thrift: ${THRIFT_VERSION} < ${THRIFT_REQ_VERSION}")
+ return()
+endif(THRIFT_VERSION VERSION_LESS THRIFT_REQ_VERSION)
+
+
+# Check that Thrift for Python is available
+include(GrPython)
+GR_PYTHON_CHECK_MODULE("Thrift" thrift "1" PYTHON_THRIFT_FOUND)
+
+# Set to found if we've made it this far
+if(THRIFT_INCLUDE_DIRS AND THRIFT_LIBRARIES AND PYTHON_THRIFT_FOUND)
+ set(THRIFT_FOUND TRUE CACHE BOOL "If Thift has been found")
+endif(THRIFT_INCLUDE_DIRS AND THRIFT_LIBRARIES AND PYTHON_THRIFT_FOUND)
+
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(THRIFT DEFAULT_MSG
+ THRIFT_LIBRARIES THRIFT_INCLUDE_DIRS
+ THRIFT_BIN PYTHON_THRIFT_FOUND THRIFT_FOUND
+)
+MARK_AS_ADVANCED(
+ THRIFT_LIBRARIES THRIFT_INCLUDE_DIRS
+ THRIFT_BIN PYTHON_THRIFT_FOUND THRIFT_FOUND
+)
diff --git a/cmake/Modules/GrTest.cmake b/cmake/Modules/GrTest.cmake
index 62caab4b51..ff78ed2726 100644
--- a/cmake/Modules/GrTest.cmake
+++ b/cmake/Modules/GrTest.cmake
@@ -66,7 +66,8 @@ function(GR_ADD_TEST test_name)
file(TO_NATIVE_PATH "${GR_TEST_LIBRARY_DIRS}" libpath) #ok to use on dir list?
file(TO_NATIVE_PATH "${GR_TEST_PYTHON_DIRS}" pypath) #ok to use on dir list?
- set(environs "VOLK_GENERIC=1" "GR_DONT_LOAD_PREFS=1" "srcdir=${srcdir}")
+ set(environs "VOLK_GENERIC=1" "GR_DONT_LOAD_PREFS=1" "srcdir=${srcdir}"
+ "GR_CONF_CONTROLPORT_ON=False")
list(APPEND environs ${GR_TEST_ENVIRONS})
#http://www.cmake.org/pipermail/cmake/2009-May/029464.html
diff --git a/config.h.in b/config.h.in
index 82751a3cf7..ad6e3d022c 100644
--- a/config.h.in
+++ b/config.h.in
@@ -30,6 +30,12 @@
#ifndef GR_CTRLPORT
#cmakedefine GR_CTRLPORT
#endif
+#ifndef GR_RPCSERVER_ENABLED
+#cmakedefine GR_RPCSERVER_ENABLED
+#endif
+#ifndef GR_RPCSERVER_THRIFT
+#cmakedefine GR_RPCSERVER_THRIFT
+#endif
#ifndef ENABLE_GR_LOG
#cmakedefine ENABLE_GR_LOG
#endif
diff --git a/docs/doxygen/other/build_guide.dox b/docs/doxygen/other/build_guide.dox
index c21b9874ac..ebf47dc7e2 100644
--- a/docs/doxygen/other/build_guide.dox
+++ b/docs/doxygen/other/build_guide.dox
@@ -63,7 +63,7 @@ first. Most recent systems have these packages available.
\li audio-osx
\li audio-windows
-* Optional but recommended dependencies.
+<b>Optional but recommended dependencies.</b>
It is not necessary to satisfy all of these dependencies; just the
one(s) that are right for your system. On Linux, don't expect
@@ -82,6 +82,17 @@ audio-osx and audio-windows to be either satisfied or built.
\li log4cpp (>= 1.0) http://log4cpp.sourceforge.net/
+<b>Optional</b>
+
+\ref page_ctrlport may use various backends to perform the RPC
+process, and each is its own dependency.
+
+Currently, ControlPort only supports the Apache Thrift backend.
+
+\li thrift (>= 0.9.2) https://thrift.apache.org/
+
+
+
\section build_gr_cmake Building GNU Radio
GNU Radio is built using the CMake build system
diff --git a/docs/doxygen/other/ctrlport.dox b/docs/doxygen/other/ctrlport.dox
index 64bf9f7d38..94a768e429 100644
--- a/docs/doxygen/other/ctrlport.dox
+++ b/docs/doxygen/other/ctrlport.dox
@@ -16,8 +16,427 @@ gnuradio.ctrlport module, imported as:
from gnuradio import ctrlport
\endcode
-ControlPort is currently a temporary stub implementation of a set of
-RPC calls we would like to enable that would allow remote viewing,
-command, and control of GNU Radio flowgraphs and blocks.
+
+\section ctrlport_conf Configuration
+
+ControlPort is configured using two files. The first is the GNU Radio
+preferences file while the second file is specific to the type of
+middleware used.
+
+The GNU Radio preferences file has three options. The 'on' option is
+used to enable or disable the use of ControlPort, and is disabled by
+default. The 'config' option allows a user to specify the
+middleware-specific configuration file. The 'edges_list' is a special
+option that exports the list of nodes and edges of the flowgraph
+across ControlPort. This latter option is mainly used for redrawing
+the flowgraph for the Performance Counter applications.
+
+\code
+ [ControlPort]
+ on = True
+ edges_list = True
+ config = path-to/ctrlport.conf
+\endcode
+
+The ControlPort preferences are installed by default into
+'gnuradio-runtime.conf'. These can always be overridden in the local
+~/.gnuradio/config.conf file.
+
+
+
+\section ctrlport_deps Dependencies
+
+ControlPort is an abstracted remote procedure call tool that. It is
+built on top of other middleware libraries. The following subsections
+explain some details about the use of the particular middleware
+project.
+
+Currently, the only implemented middleware library is the Apache
+Thrift project.
+
+\subsection ctrlport_thrift Apache Thrift
+
+Current version support: >= 0.9.2
+
+Apache Thrift is a middleware layer that defines interfaces of a
+program using its own Thrift language. GNU Radio's interface file is:
+
+gnuradio-runtime/lib/controlport/thrift/gnuradio.thrift
+
+This file defines the interfaces set, get, trigger, and properties. It
+also defines a set of data structure Knobs to allow us to pass any
+type of data over the interfaces.
+
+To use Thrift in ControlPort requires a minimum Thrift version of
+0.9.0. If a Thrift version greater than or equal to this version is
+not found, the Thrift backend to ControlPort will not be installed,
+through ControlPort itself still will be. During cmake configuration
+time, it prints out information about finding Thrift and requires:
+
+\li Thrift header files by looking for thrift/Thrift.h
+\li Thrift C++ libraries: libthrift.so
+\li Thrift Python bindings: "import thrift"
+
+If all of these are not satisfied, the Thrift backend will not be
+installed. Upon completion, cmake outputs a notification of what
+components will be built. You will see this if Thrift was found and
+can be used:
+
+\code
+* gr-ctrlport
+* * thrift
+\endcode
+
+Cmake also uses the Thrift compiler ("thrift") to build the C++ and
+Python files necessary for compiling ControlPort. It runs "thrift
+--gen cpp" the C++ bindings in the build directory, and then
+it runs "thrift --gen py" to build the Python bindings, also in the
+build directory. These are used to compile the Thrift ControlPort
+features and are necessary files to run the Python clients. If cmake
+fails to produce these bindings, it should error out.
+
+
+
+\subsubsection ctrlport_thrift_prefs Configuration
+
+Thrift does not support its own concept of a configuration file, so we
+have built one for our purposes in GNU Radio. The 'config' option in
+the ControlPort section of the preference files tells ControlPort
+where to find the backend-specific file format. GNU Radio's Thrift
+format follows the same "[Section] key = value" scheme used in all of
+its other preference files. Currently supported configuration options
+are:
+
+\code
+[thrift]
+port = 9090
+nthreads = 2
+buffersize = 1434
+init_attempts = 100
+\endcode
+
+
+\subsubsection ctrlport_thrift_issues Thrift: Current Issues
+
+Thrift uses a thread pool system to handle each connection, but it
+will only allow up to a specified number of threads in the server. The
+default value is 10 threads, but the Thrift configuration file allows
+the user to change this value.
+
+Thrift also does not find and use a free ephemeral port when launching
+the server. It must be told explicitly which port to launch on, which
+we set in the configuration file. This makes it difficult to launch
+multiple flowgraphs on the same machine because that will cause a port
+collision. Until this is fixed, a way around this is to use the
+environmental variable GR_CONF_THRIFT_PORT=xxx to set the port number
+for that specific application.
+
+Efficiency issues of Thrift come from the over-the-wire formatting
+done by the transport protocol. It defaults to using 512 byte packets,
+which can lead to a lot of fragmentation of the data over the
+connection. The buffersize configuration allows the user to set this
+value to whatever number fits their network needs. The default 1434 is
+designed around the standard 1500 byte Ethernet frame size limit minus
+the TCP/IP and Ethernet header size.
+
+
+\subsection ctrlport_client_translation Translation Layer for Clients
+
+Different backends will produce different ways to interface with the
+system. ControlPort in the running flowgraph acts as the server by
+exposing interfaces to blocks. The interfaces and API in GNU Radio to
+communicate with ControlPort are all abstracted completely away from
+the backend methods and data types. That is, the code in GNU Radio's
+scheduler and in the blocks that expose their ControlPort interfaces
+will work regardless of the backend used.
+
+We are building better abstractions on the clients sides now, as
+well. Although certain backends will support other features of
+discovery and services that work well with their products, GNU Radio
+wants to make sure that clients can access the data from the
+interfaces in the same way for any backend used. This abstraction is
+done through the GNURadioControlPortClient. This class is told which
+type of backend is used, and defaults to Thrift, and can be passed
+information about the server's endpoint such as the host name and port
+number to attach to. The GNURadioControlPortClient returns a 'radio'
+object that represents the connection to the running flowgraph.
+
+
+\section ctrlport_using Using ControlPort to Export Variables
+
+The ability to export variables from a block is inherited from
+gr::block. Then, when the flowgraph is started, the function
+<b>setup_rpc()</b> is called in turn for each block. By default, this
+is an empty function. A block overloads this function and defines and
+exports variables in it.
+
+Say we have a class <b>gr::blocks::foo</b> that has variables <b>a</b>
+and <b>b</b> that we want to export. Specifically, we want to be able
+to read the values of both <b>a</b> and <b>b</b> and also set the
+value of <b>b</b>. The class <b>gr::blocks::foo</b> has setters and
+getters all set up. So our class implementation header file looks
+something like:
+
+\code
+namespace gr {
+ namespace blocks {
+
+ class foo_impl : public foo
+ {
+ private:
+ float d_a, d_b;
+
+ public:
+ foo_impl(float a, float b);
+ ~foo_impl();
+
+ float a() const { return d_a; }
+ float b() const { return d_a; }
+ void set_a(float a) { d_a = a; }
+ void set_b(float b) { d_b = b; }
+ void setup_rpc();
+ int work(int noutput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items);
+ };
+
+ } /* namespace blocks */
+} /* namespace gr */
+\endcode
+
+The source code then sets up the class and fills in
+<b>setup_rpc()</b>.
+
+\code
+namespace gr {
+ namespace blocks {
+
+ foo_impl::foo_impl(float a, float b):
+ sync_bloc(....),
+ d_a(a), d_b(b)
+ { }
+
+ foo_impl::~foo_impl()
+ { }
+
+ void
+ foo_impl::setup_rpc()
+ {
+#ifdef GR_CTRLPORT
+ add_rpc_variable(
+ rpcbasic_sptr(new rpcbasic_register_get<foo, float>(
+ alias(), "a",
+ &foo::a,
+ pmt::mp(-2.0f), pmt::mp(2.0f), pmt::mp(0.0f),
+ "", "Get value of a", RPC_PRIVLVL_MIN,
+ DISPTIME | DISPOPTSTRIP)));
+
+ add_rpc_variable(
+ rpcbasic_sptr(new rpcbasic_register_get<foo, float>(
+ alias(), "b",
+ &foo::b,
+ pmt::mp(0.0f), pmt::mp(20.0f), pmt::mp(10.0f),
+ "", "Get value of b", RPC_PRIVLVL_MIN,
+ DISPTIME | DISPOPTSTRIP)));
+
+ add_rpc_variable(
+ rpcbasic_sptr(new rpcbasic_register_set<foo, float>(
+ alias(), "b",
+ &foo::set_b,
+ pmt::mp(0.0f), pmt::mp(20.0f), pmt::mp(10.0f),
+ "", "Set value of b", RPC_PRIVLVL_MIN,
+ DISPNULL)));
+#endif /* GR_CTRLPORT */
+ }
+
+ int
+ foo_impl::work(int noutput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items)
+ { .... }
+
+ } /* namespace blocks */
+} /* namespace gr */
+\endcode
+
+In the above example, we're ignoring some of the basic semantics of
+the class as a GNU Radio block and focus just on the call to set up
+the get and set functions over ControlPort. Each block has a function
+that allows us to add a new ControlPort interface object to a list,
+the <b>add_rpc_variable</b>. We don't care about that list anymore;
+that's for ControlPort to worry about. We just add new variables,
+either setters or getters.
+
+Without dissecting every piece of the above calls, notice that we use
+the public class, <b>gr::blocks::foo</b> as the class, not the
+implementation class. We also use the block's alias, which GNU Radio
+uses as a database entry to connect a block by name to the pointer in
+memory. This allows ControlPort to know where the object in memory is
+at any given time to access the setters and getters.
+
+The three PMTs specified are simply an expected minimum, maximum, and
+default value. None of these are strictly enforced and only serve as
+guides. The RPC_PRIVLVL_MIN is currently a placeholder for a
+privilege level setting. In many cases, reading <b>b</b> might be
+fine for everyone, but we want strong restrictions on who has the
+ability to set <b>b</b>.
+
+And finally, we can specify display options to hint at the right way
+to display this variable when remotely plotting it. More on that in
+the following section.
+
+Finally, note that we put \#ifdefs around the code. We always want
+<b>setup_rpc</b> to be there and callable, but if ControlPort was not
+built for GNU Radio, we cannot register any variables with it. This is
+just a nicety to allow us to set up our code for use with ControlPort
+without requiring it.
+
+
+\subsection ctrlport_alt_reg Alternative Registers
+
+If using the concept above, <b>setup_rpc</b> automatically gets called
+when the flowgraph is started. In most instances, this is all we ever
+need since there's nothing interesting going on until then. However,
+if not using a gr::block or needing access before we run the flowgraph,
+the above method won't work (it comes down to when the block's alias
+has meaning).
+
+There are alternate variable registration functions for the sets and
+gets. These take the form:
+
+\code
+ rpcbasic_register_get(const std::string& name,
+ const char* functionbase,
+ T* obj,
+ Tfrom (T::*function)(),
+ const pmt::pmt_t &min, const pmt::pmt_t &max, const pmt::pmt_t &def,
+ const char* units_ = "",
+ const char* desc_ = "",
+ priv_lvl_t minpriv_ = RPC_PRIVLVL_MIN,
+ DisplayType display_ = DISPNULL)
+
+ rpcbasic_register_set(const std::string& name,
+ const char* functionbase,
+ T* obj,
+ void (T::*function)(Tto),
+ const pmt::pmt_t &min, const pmt::pmt_t &max, const pmt::pmt_t &def,
+ const char* units_ = "",
+ const char* desc_ = "",
+ priv_lvl_t minpriv_ = RPC_PRIVLVL_MIN,
+ DisplayType display_ = DISPNULL)
+\endcode
+
+The only thing different about the above code is that instead of
+taking a single 'alias()' name, which provides us access to the
+objects pointer, we instead provide a unique name
+(<b>fucntionbase</b>) and a pointer to the object itself
+(<b>obj</b>). These are templated functions, so the class T is known
+from that.
+
+If using this method, the recommended way is to create a new function
+(not <b>setup_rpc</b>), register the variable using
+<b>add_rpc_variable</b> but with the different <b>register_get/set</b>
+shown here, and then call this function either in the object's
+constructor or make it a public member function to be called when you
+need it.
+
+
+\section ctrlport_disp Display Options
+
+When exporting a new RPC variable over ControlPort, one argument is a
+display options mask. These options are useful to a remote client to
+tell identify activities like default plotters and initial
+conditions. The <b>gr-ctrlport-monitor</b> application uses this
+heavily in determining how to plot ControlPort variables.
+
+The options mask is just a 32-bit value with options OR'd
+together. Certain options are only appropriate for certain types of
+plots. Options on plots where that option is not available will
+simply be ignored.
+
+The main caveat to be aware of is that the DISPXY plot type is
+specific to complex values. Therefore, DISPOPTCPLX is assumed.
+
+These options are specified in rpccallbackregister_base.h and are
+exposed through SWIG to live in the \b gr namespace.
+
+<b>Plot Types</b>
+\li <b>DISPNULL:</b> Nothing specified.
+\li <b>DISPTIME:</b> Time-domain plot.
+\li <b>DISPXY:</b> XY or constellation plot (complex only).
+\li <b>DISPPSD:</b> PSD plot.
+\li <b>DISPSPEC:</b> Spectrogram plot.
+\li <b>DISPRAST:</b> Time raster plot (non-complex only)
+
+<b>Plot Options</b>
+\li <b>DISPOPTCPLX:</b> Signal is complex.
+\li <b>DISPOPTLOG:</b> Start plot in semilog-y mode (time domain only).
+\li <b>DISPOPTSTEM:</b> Start plot in stem mode (time domain only).
+\li <b>DISPOPTSTRIP:</b> Run plot as a stripchart (time domain only).
+\li <b>DISPOPTSCATTER:</b> Do scatter plot instead of lines (XY plot only).
+
+
+\section ctrlport_probes ControlPort Probes
+
+ControlPort provides a set of probes that can be used as sinks that
+pass vectors of data across ControlPort. These probes are used to
+sample or visualize data remotely. We can place a ControlPort probe
+anywhere in the flowgraph to grab the latest sample of data from the
+block it's connected to.
+
+The main ControlPort probe to use is
+<b>blocks.ctrlport_probe2_x</b>. From GRC, this is simply "CtrlPort
+Probe", which can handle complex, floats, ints, shorts, and bytes. The
+blocks are named and given a description to identify them over
+ControlPort. The blocks also take a vector length for how many samples
+to pass back at a time. Finally, these blocks take a display hint,
+as described in the above section. This allows us to specify the
+default behavior for how to display the samples.
+
+Another block that can be used is the <b>fft.ctrlport_probe_psd</b> to
+calculate the PSD and pass that over the ControlPort interface.
+
+\section ctrlport_monitors ControlPort Monitors
+
+There are two main ControlPort monitor applications provided with GNU
+Radio. Both act similarly. The first is a standard ControlPort monitor
+application. This connects to a running flowgraph and displays all
+exported interfaces in a table format. The name, unit, latest sample,
+and description of all interfaces are display in a
+row. Double-clicking will open up the default display. Right clicking
+any item will allow the user to select the type of plot to use to
+display the data.
+
+When a display is active, using the buttons at the top, the subwindows
+can all be tiled or windowed as needed to manage the full
+interface. We can then drag-and-drop any other item on top of a
+currently running display plot.
+
+To launch the ControlPort monitor application, know the IP address and
+port of the ControlPort endpoint established by the flowgraph and run:
+
+<pre>
+gr-ctrlport-monitor \<ip-addr\> -p \<port\>
+</pre>
+
+
+\subsection perfmonitor Performance Monitor
+
+A second application is used to locally redraw the flowgraph and
+display some of the Performance Counters. In this application, the
+nodes are blue boxes where the size of the box is proportional to the
+work time and the color depth and line width are proportional to the
+output buffer fullness.
+
+The controls at the top of the Performance Monitor application allow
+us to select the instantaneous, average, and variance values of the
+Performance Counters. And the work time and buffer fullness can be
+displayed as a table or bar graph.
+
+To launch the Performance Monitor, run:
+
+<pre>
+gr-perf-monitorx \<ip-addr\> -p \<port\>
+</pre>
*/
diff --git a/docs/doxygen/other/perf_counters.dox b/docs/doxygen/other/perf_counters.dox
index 1a5bf40cba..9bca38268a 100644
--- a/docs/doxygen/other/perf_counters.dox
+++ b/docs/doxygen/other/perf_counters.dox
@@ -85,4 +85,14 @@ The options for the [PerfCounters] section are:
\li clock: sets the type of clock used when calculating work_time
('thread' or 'monotonic').
+
+\section pc_perfmonitor Performance Monitor
+
+See \ref perfmonitor for some details of using a ControlPort-based
+monitor application, gr-perf-monitorx, for visualizing the
+counters. This application is particularly useful in learning which
+blocks are the computationally complex blocks that could use extra
+optimization or work to improve their performance. It can also be used
+to understand the current 'health' of the application.
+
*/
diff --git a/gnuradio-runtime/include/gnuradio/CMakeLists.txt b/gnuradio-runtime/include/gnuradio/CMakeLists.txt
index 3fc2fe7bd8..472f91847b 100644
--- a/gnuradio-runtime/include/gnuradio/CMakeLists.txt
+++ b/gnuradio-runtime/include/gnuradio/CMakeLists.txt
@@ -86,6 +86,16 @@ install(FILES
COMPONENT "runtime_devel"
)
+if(THRIFT_FOUND)
+install(FILES
+ rpcserver_booter_thrift.h
+ thrift_application_base.h
+ thrift_server_template.h
+ DESTINATION ${GR_INCLUDE_DIR}/gnuradio
+ COMPONENT "runtime_devel"
+)
+endif(THRIFT_FOUND)
+
##########################################################################
# Configure logger
##########################################################################
diff --git a/gnuradio-runtime/include/gnuradio/prefs.h b/gnuradio-runtime/include/gnuradio/prefs.h
index a9a28586ab..4dc92b3631 100644
--- a/gnuradio-runtime/include/gnuradio/prefs.h
+++ b/gnuradio-runtime/include/gnuradio/prefs.h
@@ -1,6 +1,6 @@
/* -*- c++ -*- */
/*
- * Copyright 2006,2013 Free Software Foundation, Inc.
+ * Copyright 2006,2013,2015 Free Software Foundation, Inc.
*
* This file is part of GNU Radio
*
@@ -47,10 +47,38 @@ namespace gr {
public:
static prefs *singleton();
+ /*!
+ * \brief Creates an object to read preference files.
+ *
+ * \details
+ *
+ * If no file name is given (empty arg list or ""), this opens up
+ * the standard GNU Radio configuration files in
+ * prefix/etc/gnuradio/conf.d as well as ~/.gnuradio/config.conf.
+ *
+ * Only access this through the singleton defined here:
+ * \code
+ * prefs *p = prefs::singleton();
+ * \endcode
+ */
prefs();
+
virtual ~prefs();
/*!
+ * If specifying a file name, this opens that specific
+ * configuration file of the standard form containing sections and
+ * key-value pairs:
+ *
+ * \code
+ * [SectionName]
+ * key0 = value0
+ * key1 = value1
+ * \endcode
+ */
+ void add_config_file(const std::string &configfile);
+
+ /*!
* \brief Returns the configuration options as a string.
*/
std::string to_string();
@@ -137,7 +165,7 @@ namespace gr {
protected:
virtual std::vector<std::string> _sys_prefs_filenames();
- virtual void _read_files();
+ virtual std::string _read_files(const std::vector<std::string> &filenames);
virtual void _convert_to_map(const std::string &conf);
virtual char * option_to_env(std::string section, std::string option);
diff --git a/gnuradio-runtime/include/gnuradio/rpcbufferedget.h b/gnuradio-runtime/include/gnuradio/rpcbufferedget.h
new file mode 100644
index 0000000000..ebd740b1f8
--- /dev/null
+++ b/gnuradio-runtime/include/gnuradio/rpcbufferedget.h
@@ -0,0 +1,65 @@
+/* -*- c++ -*- */
+/*
+ * 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.
+ */
+
+#ifndef RPCBUFFEREDGET_H
+#define RPCBUFFEREDGET_H
+
+#include <boost/thread/condition_variable.hpp>
+#include <boost/thread/mutex.hpp>
+#include <stdio.h>
+
+template<typename TdataType>
+class rpcbufferedget {
+public:
+ rpcbufferedget(const unsigned int init_buffer_size = 4096) :
+ d_data_needed(false), d_data_ready(), d_buffer_lock(), d_buffer(init_buffer_size) {;}
+
+ ~rpcbufferedget() {
+ d_data_ready.notify_all();
+ }
+
+ void offer_data(const TdataType& data) {
+ if (!d_data_needed)
+ return;
+ {
+ boost::mutex::scoped_lock lock(d_buffer_lock);
+ d_buffer = data;
+ d_data_needed = false;
+ }
+ d_data_ready.notify_one();
+ }
+
+ TdataType get() {
+ d_data_needed = true;
+ boost::mutex::scoped_lock lock(d_buffer_lock);
+ d_data_ready.wait(lock);
+ return d_buffer;
+ }
+
+private:
+ bool d_data_needed;
+ boost::condition_variable d_data_ready;
+ boost::mutex d_buffer_lock;
+ TdataType d_buffer;
+};
+
+#endif
diff --git a/gnuradio-runtime/include/gnuradio/rpcmanager.h b/gnuradio-runtime/include/gnuradio/rpcmanager.h
index 5635572a8b..e7ee4c4942 100644
--- a/gnuradio-runtime/include/gnuradio/rpcmanager.h
+++ b/gnuradio-runtime/include/gnuradio/rpcmanager.h
@@ -54,7 +54,7 @@ class GR_RUNTIME_API rpcmanager : public virtual rpcmanager_base
static bool booter_registered;
static bool aggregator_registered;
static void rpcserver_booter_base_sptr_dest(rpcserver_booter_base* b) {;}
- static rpcserver_booter_base* boot;
+ static std::auto_ptr<rpcserver_booter_base> boot;
static std::auto_ptr<rpcserver_booter_aggregator> aggregator;
};
diff --git a/gnuradio-runtime/include/gnuradio/rpcpmtconverters_thrift.h b/gnuradio-runtime/include/gnuradio/rpcpmtconverters_thrift.h
new file mode 100644
index 0000000000..6523165a11
--- /dev/null
+++ b/gnuradio-runtime/include/gnuradio/rpcpmtconverters_thrift.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014,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.
+ */
+
+#ifndef RPCPMTCONVERTERS_THRIFT_H
+#define RPCPMTCONVERTERS_THRIFT_H
+
+#include <pmt/pmt.h>
+#include <boost/noncopyable.hpp>
+#include <boost/ptr_container/ptr_map.hpp>
+#include "thrift/gnuradio_types.h"
+
+
+namespace rpcpmtconverter
+{
+ GNURadio::Knob from_pmt(const pmt::pmt_t& knob);
+
+ struct to_pmt_f {
+ to_pmt_f() {;}
+ virtual pmt::pmt_t operator()(const GNURadio::Knob& knob);
+ };
+
+ struct to_pmt_byte_f : public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_short_f : public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_int_f : public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_long_f : public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_double_f : public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_string_f : public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_bool_f : public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_complex_f: public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_f32vect_f: public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_f64vect_f: public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_s64vect_f: public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_s32vect_f: public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_s16vect_f: public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_s8vect_f : public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+ struct to_pmt_c32vect_f: public to_pmt_f { pmt::pmt_t operator()(const GNURadio::Knob& knob); };
+
+ class To_PMT : private boost::noncopyable {
+ public:
+ static To_PMT instance;
+ template<typename TO_PMT_F> friend struct to_pmt_reg;
+ pmt::pmt_t operator()(const GNURadio::Knob& knob);
+
+ protected:
+ boost::ptr_map<GNURadio::BaseTypes::type, to_pmt_f> to_pmt_map;
+
+ private:
+ To_PMT() {;}
+ };
+
+ template<typename TO_PMT_F> struct to_pmt_reg {
+ to_pmt_reg(To_PMT& instance, const GNURadio::BaseTypes::type type);
+ };
+}
+
+#endif /* RPCPMTCONVERTERS_THRIFT_H */
diff --git a/gnuradio-runtime/include/gnuradio/rpcregisterhelpers.h b/gnuradio-runtime/include/gnuradio/rpcregisterhelpers.h
index 0999dea747..f82f5ed0aa 100644
--- a/gnuradio-runtime/include/gnuradio/rpcregisterhelpers.h
+++ b/gnuradio-runtime/include/gnuradio/rpcregisterhelpers.h
@@ -672,7 +672,7 @@ struct rpcbasic_register_set : public rpcbasic_base
d_minpriv = minpriv_;
d_display = display_;
d_object = dynamic_cast<T*>(global_block_registry.block_lookup(pmt::intern(block_alias)).get());
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
callbackregister_base::configureCallback_t
extractor(new rpcbasic_extractor<T,Tto>(d_object, function),
minpriv_, std::string(units_),
@@ -726,7 +726,7 @@ struct rpcbasic_register_set : public rpcbasic_base
d_minpriv = minpriv_;
d_display = display_;
d_object = obj;
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
callbackregister_base::configureCallback_t
extractor(new rpcbasic_extractor<T,Tto>(d_object, function),
minpriv_, std::string(units_),
@@ -741,7 +741,7 @@ struct rpcbasic_register_set : public rpcbasic_base
~rpcbasic_register_set()
{
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
rpcmanager::get()->i()->unregisterConfigureCallback(d_id);
#endif
}
@@ -830,7 +830,7 @@ struct rpcbasic_register_trigger : public rpcbasic_base
d_desc = desc_;
d_minpriv = minpriv_;
d_object = dynamic_cast<T*>(global_block_registry.block_lookup(pmt::intern(block_alias)).get());
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
callbackregister_base::configureCallback_t
extractor(new rpcbasic_extractor<T,void>(d_object, function),
minpriv_, std::string(desc_));
@@ -870,7 +870,7 @@ struct rpcbasic_register_trigger : public rpcbasic_base
d_desc = desc_;
d_minpriv = minpriv_;
d_object = obj;
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
callbackregister_base::configureCallback_t
extractor(new rpcbasic_extractor<T,void>(d_object, function),
minpriv_, std::string(desc_));
@@ -884,7 +884,7 @@ struct rpcbasic_register_trigger : public rpcbasic_base
~rpcbasic_register_trigger()
{
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
rpcmanager::get()->i()->unregisterConfigureCallback(d_id);
#endif
}
@@ -988,7 +988,7 @@ public:
d_minpriv = minpriv_;
d_display = display_;
d_object = dynamic_cast<T*>(global_block_registry.block_lookup(pmt::intern(block_alias)).get());
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
callbackregister_base::queryCallback_t
inserter(new rpcbasic_inserter<T,Tfrom>(d_object, function),
minpriv_, std::string(units_), display_, std::string(desc_), min, max, def);
@@ -1022,7 +1022,7 @@ public:
d_minpriv = minpriv_;
d_display = display_;
d_object = dynamic_cast<T*>(global_block_registry.block_lookup(pmt::intern(block_alias)).get());
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
callbackregister_base::queryCallback_t
inserter(new rpcbasic_inserter<T,Tfrom>(d_object, (Tfrom (T::*)())function),
minpriv_, std::string(units_), display_, std::string(desc_), min, max, def);
@@ -1076,7 +1076,7 @@ public:
d_minpriv = minpriv_;
d_display = display_;
d_object = obj;
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
callbackregister_base::queryCallback_t
inserter(new rpcbasic_inserter<T,Tfrom>(d_object, function),
minpriv_, std::string(units_), display_, std::string(desc_), min, max, def);
@@ -1111,7 +1111,7 @@ public:
d_minpriv = minpriv_;
d_display = display_;
d_object = obj;
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
callbackregister_base::queryCallback_t
inserter(new rpcbasic_inserter<T,Tfrom>(d_object, (Tfrom (T::*)())function),
minpriv_, std::string(units_), display_, std::string(desc_), min, max, def);
@@ -1125,7 +1125,7 @@ public:
~rpcbasic_register_get()
{
-#ifdef RPCSERVER_ENABLED
+#ifdef GR_RPCSERVER_ENABLED
rpcmanager::get()->i()->unregisterQueryCallback(d_id);
#endif
}
diff --git a/gnuradio-runtime/include/gnuradio/rpcserver_booter_thrift.h b/gnuradio-runtime/include/gnuradio/rpcserver_booter_thrift.h
new file mode 100644
index 0000000000..28900a4670
--- /dev/null
+++ b/gnuradio-runtime/include/gnuradio/rpcserver_booter_thrift.h
@@ -0,0 +1,52 @@
+/* -*- c++ -*- */
+/*
+ * 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.
+ */
+
+#ifndef RPCSERVER_BOOTER_THRIFT_H
+#define RPCSERVER_BOOTER_THRIFT_H
+
+#include <gnuradio/rpcserver_booter_base.h>
+#include <gnuradio/thrift_server_template.h>
+#include "thrift/ControlPort.h"
+
+class rpcserver_base;
+class rpcserver_thrift;
+
+class rpcserver_booter_thrift
+ : public virtual rpcserver_booter_base,
+ public virtual thrift_server_template<rpcserver_base,
+ rpcserver_thrift,
+ rpcserver_booter_thrift,
+ boost::shared_ptr<GNURadio::ControlPortIf> >
+{
+ public:
+ rpcserver_booter_thrift();
+ ~rpcserver_booter_thrift();
+
+ rpcserver_base* i();
+ const std::string & type() {return d_type;}
+ const std::vector<std::string> endpoints();
+
+ private:
+ std::string d_type;
+};
+
+#endif /* RPCSERVER_BOOTER_THRIFT_H */
diff --git a/gnuradio-runtime/include/gnuradio/rpcserver_selector.h b/gnuradio-runtime/include/gnuradio/rpcserver_selector.h
index 8a14f78d99..31ab6cea0b 100644
--- a/gnuradio-runtime/include/gnuradio/rpcserver_selector.h
+++ b/gnuradio-runtime/include/gnuradio/rpcserver_selector.h
@@ -23,10 +23,12 @@
#ifndef RPCSERVER_SELECTOR
#define RPCSERVER_SELECTOR
-//#define RPCSERVER_ENABLED
+#include <gnuradio/config.h>
-//#define RPCSERVER_ICE
-//#define RPCSERVER_ERLANG
-//#define RPCSERVER_XMLRPC
+//#define GR_RPCSERVER_ENABLED
+//#define GR_RPCSERVER_ICE
+//#define GR_RPCSERVER_THRIFT
+//#define GR_RPCSERVER_ERLANG
+//#define GR_RPCSERVER_XMLRPC
#endif
diff --git a/gnuradio-runtime/include/gnuradio/rpcserver_thrift.h b/gnuradio-runtime/include/gnuradio/rpcserver_thrift.h
new file mode 100644
index 0000000000..027a9ea75e
--- /dev/null
+++ b/gnuradio-runtime/include/gnuradio/rpcserver_thrift.h
@@ -0,0 +1,230 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2014,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.
+ */
+
+#ifndef RPCSERVER_THRIFT_H
+#define RPCSERVER_THRIFT_H
+
+#include <gnuradio/rpcserver_base.h>
+#include <gnuradio/rpcpmtconverters_thrift.h>
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <map>
+#include "thrift/ControlPort.h"
+#include "thrift/gnuradio_types.h"
+#include <boost/format.hpp>
+
+#define S(x) #x
+#define S_(x) S(x)
+#define S__LINE__ S_(__LINE__)
+
+class rpcserver_thrift : public virtual rpcserver_base, public GNURadio::ControlPortIf
+{
+public:
+ rpcserver_thrift();
+ virtual ~rpcserver_thrift();
+
+ void registerConfigureCallback(const std::string &id,
+ const configureCallback_t callback);
+ void unregisterConfigureCallback(const std::string &id);
+
+ void registerQueryCallback(const std::string &id,
+ const queryCallback_t callback);
+ void unregisterQueryCallback(const std::string &id);
+
+ void setKnobs(const GNURadio::KnobMap&);
+ void getKnobs(GNURadio::KnobMap&,
+ const GNURadio::KnobIDList&);
+ void getRe(GNURadio::KnobMap&,
+ const GNURadio::KnobIDList&);
+ void properties(GNURadio::KnobPropMap&,
+ const GNURadio::KnobIDList& knobs);
+ virtual void shutdown();
+
+ private:
+ typedef std::map<std::string, configureCallback_t> ConfigureCallbackMap_t;
+ ConfigureCallbackMap_t d_setcallbackmap;
+
+ typedef std::map<std::string, queryCallback_t> QueryCallbackMap_t;
+ QueryCallbackMap_t d_getcallbackmap;
+
+ template<typename T, typename TMap> struct set_f
+ : public std::unary_function<T,void>
+ {
+ set_f(TMap &_setcallbackmap, const priv_lvl_t &_cur_priv)
+ : d_setcallbackmap(_setcallbackmap), cur_priv(_cur_priv)
+ {
+ ;
+ }
+
+ void operator()(const T& p)
+ {
+ ConfigureCallbackMap_t::const_iterator iter(d_setcallbackmap.find(p.first));
+ if(iter != d_setcallbackmap.end()) {
+ if(cur_priv <= iter->second.priv) {
+ (*iter->second.callback).post(pmt::PMT_NIL, rpcpmtconverter::To_PMT::instance(p.second));
+ }
+ else {
+ std::cout << "Key " << p.first << " requires PRIVLVL <= "
+ << iter->second.priv << " to set, currently at: "
+ << cur_priv << std::endl;
+ }
+ }
+ else {
+ throw apache::thrift::TApplicationException(__FILE__ " " S__LINE__);
+ }
+ }
+
+ TMap& d_setcallbackmap;
+ const priv_lvl_t& cur_priv;
+ };
+
+ template<typename T, typename TMap>
+ struct get_f : public std::unary_function<T,void>
+ {
+ get_f(TMap &_getcallbackmap, const priv_lvl_t &_cur_priv, GNURadio::KnobMap &_outknobs) :
+ d_getcallbackmap(_getcallbackmap), cur_priv(_cur_priv), outknobs(_outknobs)
+ {}
+
+ void operator()(const T& p)
+ {
+ QueryCallbackMap_t::const_iterator iter(d_getcallbackmap.find(p));
+ if(iter != d_getcallbackmap.end()) {
+ if(cur_priv <= iter->second.priv) {
+ outknobs[p] = rpcpmtconverter::from_pmt((*iter->second.callback).retrieve());
+ }
+ else {
+ std::cout << "Key " << iter->first << " requires PRIVLVL: <= "
+ << iter->second.priv << " to get, currently at: "
+ << cur_priv << std::endl;
+ }
+ }
+ else {
+ std::stringstream ss;
+ ss << "Ctrlport Key called with unregistered key (" << p << ")\n";
+ std::cout << ss.str();
+ throw apache::thrift::TApplicationException(__FILE__ " " S__LINE__);
+ }
+ }
+
+ TMap& d_getcallbackmap;
+ const priv_lvl_t& cur_priv;
+ GNURadio::KnobMap& outknobs;
+ };
+
+ template<typename T, typename TMap, typename TKnobMap>
+ struct get_all_f : public std::unary_function<T,void>
+ {
+ get_all_f(TMap &_getcallbackmap, const priv_lvl_t &_cur_priv, TKnobMap &_outknobs) :
+ d_getcallbackmap(_getcallbackmap), cur_priv(_cur_priv), outknobs(_outknobs)
+ {;}
+
+ void operator()(const T& p)
+ {
+ if(cur_priv <= p.second.priv) {
+ outknobs[p.first] = rpcpmtconverter::from_pmt(p.second.callback->retrieve());
+ }
+ else {
+ std::cout << "Key " << p.first << " requires PRIVLVL <= "
+ << p.second.priv << " to get, currently at: "
+ << cur_priv << std::endl;
+ }
+ }
+
+ TMap& d_getcallbackmap;
+ const priv_lvl_t& cur_priv;
+ TKnobMap& outknobs;
+ };
+
+ template<typename T, typename TMap, typename TKnobMap>
+ struct properties_all_f : public std::unary_function<T,void>
+ {
+ properties_all_f(QueryCallbackMap_t &_getcallbackmap,
+ const priv_lvl_t &_cur_priv,
+ GNURadio::KnobPropMap &_outknobs)
+ : d_getcallbackmap(_getcallbackmap),
+ cur_priv(_cur_priv),
+ outknobs(_outknobs)
+ {;}
+
+ void operator()(const T& p)
+ {
+ if(cur_priv <= p.second.priv) {
+ GNURadio::KnobProp prop;
+ prop.type = GNURadio::KnobType::KNOBDOUBLE;
+ prop.units = p.second.units;
+ prop.description = p.second.description;
+ prop.min = rpcpmtconverter::from_pmt(p.second.min);
+ prop.max = rpcpmtconverter::from_pmt(p.second.max);
+ prop.display = static_cast<uint32_t>(p.second.display);
+ outknobs[p.first] = prop;
+ }
+ else {
+ std::cout << "Key " << p.first << " requires PRIVLVL <= "
+ << p.second.priv << " to get, currently at: "
+ << cur_priv << std::endl;
+ }
+ }
+
+ TMap& d_getcallbackmap;
+ const priv_lvl_t& cur_priv;
+ TKnobMap& outknobs;
+ };
+
+ template<class T, typename TMap, typename TKnobMap>
+ struct properties_f : public std::unary_function<T,void>
+ {
+ properties_f(TMap &_getcallbackmap, const priv_lvl_t &_cur_priv, TKnobMap &_outknobs) :
+ d_getcallbackmap(_getcallbackmap), cur_priv(_cur_priv), outknobs(_outknobs)
+ {;}
+
+ void operator()(const T& p)
+ {
+ typename TMap::const_iterator iter(d_getcallbackmap.find(p));
+ if(iter != d_getcallbackmap.end()) {
+ if(cur_priv <= iter->second.priv) {
+ GNURadio::KnobProp prop;
+ prop.type = GNURadio::KnobType::KNOBDOUBLE;
+ prop.units = iter->second.units;
+ prop.description = iter->second.description;
+ prop.min = rpcpmtconverter::from_pmt(iter->second.min);
+ prop.max = rpcpmtconverter::from_pmt(iter->second.max);
+ prop.display = static_cast<uint32_t>(iter->second.display);
+ outknobs[p] = prop;
+ }
+ else {
+ std::cout << "Key " << iter->first << " requires PRIVLVL: <= "
+ << iter->second.priv << " to get, currently at: " << cur_priv << std::endl;
+ }
+ }
+ else {
+ throw apache::thrift::TApplicationException(__FILE__ " " S__LINE__);
+ }
+ }
+
+ TMap& d_getcallbackmap;
+ const priv_lvl_t& cur_priv;
+ TKnobMap& outknobs;
+ };
+};
+
+#endif /* RPCSERVER_THRIFT_H */
diff --git a/gnuradio-runtime/include/gnuradio/thrift_application_base.h b/gnuradio-runtime/include/gnuradio/thrift_application_base.h
new file mode 100644
index 0000000000..aa50c55dc8
--- /dev/null
+++ b/gnuradio-runtime/include/gnuradio/thrift_application_base.h
@@ -0,0 +1,253 @@
+/* -*- c++ -*- */
+/*
+ * 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.
+ */
+
+#ifndef THRIFT_APPLICATION_BASE_H
+#define THRIFT_APPLICATION_BASE_H
+
+#include <gnuradio/api.h>
+#include <gnuradio/logger.h>
+#include <gnuradio/prefs.h>
+#include <gnuradio/thread/thread.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace {
+ // Time, in milliseconds, to wait between checks to the Thrift runtime to see if
+ // it has fully initialized.
+ static const unsigned int THRIFTAPPLICATION_ACTIVATION_TIMEOUT_MS(200);
+};
+
+namespace apache { namespace thrift { namespace server { class TServer; } } }
+
+/*!
+ * \brief Class to be statically initialized by thrift_application_base. Used
+ * to store state for thrift_application_base's singleton functions.
+ */
+
+class thrift_application_base_impl
+{
+public:
+ thrift_application_base_impl() :
+ d_application_initilized(false),
+ d_endpointStr(""),
+ d_start_thrift_thread() {;}
+
+ // Used to ensure the Thrift runtime is initialized on the first call to ::i().
+ bool d_application_initilized;
+ // Stores the generated endpoint string after the Thrift runtime has initialized.
+ std::string d_endpointStr;
+ // Thread to execute the Thrift runtime's blocking serve() function.
+ boost::shared_ptr<gr::thread::thread> d_start_thrift_thread;
+};
+
+/*!
+ * \brief Base class for a Thrift application with a singleton with
+ * instance function thrift_application_base::i(). Lazy initialization
+ * is used to start the Thrift runtime, therefore the Thrift runtime
+ * is not started unless thrift_application_base::i() is called at
+ * least once. This typically means that at least one rpc variable
+ * must be registered by a block before the runtime will start.
+ *
+ * \param TserverBase Template parameter naming the type of the server
+ * base, which is typically rpcserverbase.
+ * \param TserverClass Template parameter naming the eventual type of
+ * the fully derived application.
+ * \param _app Reference to the fully derived application instance to
+ * be returned by thrift_application_base::i().
+ */
+
+template<typename TserverBase, typename TserverClass>
+class thrift_application_base
+{
+public:
+ thrift_application_base(TserverClass* _app);
+
+ /*!
+ * Destructor for the application. Since shutdown and cleanup of the
+ * runtime is typically custom to a particular booter
+ * implementation, this must be implemented as a specalized function
+ * for a particular booter. Thus a template implementation is not
+ * provided here.
+ */
+ ~thrift_application_base();
+
+ /*!
+ * The application singleton instance function.
+ */
+ static TserverBase* i();
+
+ /*!
+ * Returns the endpoint string of this application.
+ */
+ static const std::vector<std::string> endpoints();
+
+protected:
+ /*!
+ * Allows this application's booter to set the endpoint string after
+ * the Thrift runtime has initialized.
+ *
+ * \param[in] endpoint The endpoint string reported by this class.
+ */
+ void set_endpoint(const std::string& endpoint);
+
+ virtual TserverBase* i_impl() = 0;
+
+ /*!
+ * Reference to the fully derived application instance.
+ */
+ static TserverClass* d_application;
+
+ /*!
+ * Reference to the Thrift runtime.
+ */
+ std::auto_ptr<apache::thrift::server::TServer> d_thriftserver;
+
+ /*!
+ * Max number of attempts when checking the Thrift runtime for
+ * Initialization before giving up. Set in the Thrift config file
+ * (see \ref ctrlport_thrift_prefs).
+ */
+ static const unsigned int d_default_max_init_attempts;
+
+ /*!
+ * Default port for the runtime to listen on, if a static port is
+ * not specified. Set in the Thrift config file (see \ref
+ * ctrlport_thrift_prefs).
+ */
+ static const unsigned int d_default_thrift_port;
+
+ /*!
+ * Maximum number of threads to create when serving multiple rpc
+ * clients. Set in the Thrift config file (see \ref
+ * ctrlport_thrift_prefs).
+ */
+ static const unsigned int d_default_num_thrift_threads;
+
+ /*!
+ * Default packet size for the IP payload of thrift packets. Set in
+ * the Thrift config file (see \ref ctrlport_thrift_prefs).
+ */
+ static const unsigned int d_default_thrift_buffer_size;
+
+ /*!
+ * \ref page_logger instances.
+ */
+ gr::logger_ptr d_logger, d_debug_logger;
+
+private:
+
+ // Function to be called in a separate thread to invoke the blocking
+ // ThriftServer::serve() function. Must be specialized for a particular
+ // booter implementation, therefore a template implementation is
+ // not provided here.
+ void start_thrift();
+
+ // Non-blocking function that returns true when the Thrift
+ // runtime has finished initialization. Must be implemented
+ // as a specialized template function for a particular booter
+ // implementation, therefore template implementation is not
+ // provided here.
+ bool application_started();
+
+ // Internal function to start the initialization of the runtime.
+ // Since this singleton uses lazy instantiation, this function
+ // will be called on the first call to the instance function ::i(),
+ // and since ::i() is static, this function must be static as well.
+ static void start_application();
+
+ // Pointer to the structure containing staticly allocated
+ // state information for the applicaiton_base singleton.
+ static std::auto_ptr<thrift_application_base_impl > p_impl;
+
+ // Mutex to protect the endpoint string.
+ gr::thread::mutex d_lock;
+
+ // Will be set to true by a the application_started() function,
+ // specialized for a particular booter implementation, once the
+ // thrift runtime has successfully initialized.
+ bool d_thirft_is_running;
+};
+
+template<typename TserverBase, typename TserverClass>
+TserverClass* thrift_application_base<TserverBase, TserverClass>::d_application(0);
+
+template<typename TserverBase, typename TserverClass>
+thrift_application_base<TserverBase, TserverClass>::thrift_application_base(TserverClass* _app)
+ : d_lock(),
+ d_thirft_is_running(false)
+{
+ gr::configure_default_loggers(d_logger, d_debug_logger, "controlport");
+ d_application = _app;
+ //GR_LOG_DEBUG(d_debug_logger, "thrift_application_base: ctor");
+}
+
+template<typename TserverBase, typename TserverClass>
+void thrift_application_base<TserverBase, TserverClass>::start_application()
+{
+ //std::cerr << "thrift_application_base: start_application" << std::endl;
+
+ unsigned int max_init_attempts = \
+ static_cast<unsigned int>(gr::prefs::singleton()->get_long("thrift", "init_attempts",
+ d_default_max_init_attempts));
+
+ if(!p_impl->d_application_initilized) {
+ p_impl->d_start_thrift_thread.reset(
+ (new gr::thread::thread(boost::bind(&thrift_application_base::start_thrift, d_application))));
+
+ bool app_started(false);
+ for(unsigned int attempts(0); (!app_started && attempts < max_init_attempts); ++attempts) {
+ boost::this_thread::sleep(boost::posix_time::milliseconds(THRIFTAPPLICATION_ACTIVATION_TIMEOUT_MS));
+ app_started = d_application->application_started();
+ }
+
+ if(!app_started) {
+ std::cerr << "thrift_application_base::start_application(), timeout waiting to port number might have failed?" << std::endl;
+ }
+
+ p_impl->d_application_initilized = true;
+ }
+}
+
+template<typename TserverBase, typename TserverClass>
+const std::vector<std::string> thrift_application_base<TserverBase, TserverClass>::endpoints()
+{
+ std::vector<std::string> ep;
+ ep.push_back(p_impl->d_endpointStr);
+ return ep;
+}
+
+template<typename TserverBase, typename TserverClass>
+void thrift_application_base<TserverBase, TserverClass>::set_endpoint(const std::string& endpoint)
+{
+ gr::thread::scoped_lock guard(d_lock);
+ p_impl->d_endpointStr = endpoint;
+}
+
+template<typename TserverBase, typename TserverClass>
+TserverBase* thrift_application_base<TserverBase, TserverClass>::i()
+{
+ if(!p_impl->d_application_initilized) {
+ start_application();
+ }
+ return d_application->i_impl();
+}
+
+#endif
diff --git a/gnuradio-runtime/include/gnuradio/thrift_server_template.h b/gnuradio-runtime/include/gnuradio/thrift_server_template.h
new file mode 100644
index 0000000000..1e9059d920
--- /dev/null
+++ b/gnuradio-runtime/include/gnuradio/thrift_server_template.h
@@ -0,0 +1,160 @@
+/* -*- c++ -*- */
+/*
+ * 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.
+ */
+
+#ifndef THRIFT_SERVER_TEMPLATE_H
+#define THRIFT_SERVER_TEMPLATE_H
+
+#include <gnuradio/prefs.h>
+#include <gnuradio/logger.h>
+#include <gnuradio/rpcserver_thrift.h>
+#include <gnuradio/thrift_application_base.h>
+#include <iostream>
+
+#include <thrift/server/TSimpleServer.h>
+#include <thrift/server/TThreadPoolServer.h>
+#include <thrift/concurrency/ThreadManager.h>
+#include <thrift/concurrency/PlatformThreadFactory.h>
+#include <thrift/transport/TServerSocket.h>
+#include <thrift/transport/TBufferTransports.h>
+#include "thrift/ControlPort.h"
+
+using namespace apache;
+
+template<typename TserverBase, typename TserverClass, typename TImplClass, typename TThriftClass>
+class thrift_server_template : public thrift_application_base<TserverBase, TImplClass>
+{
+public:
+ thrift_server_template(TImplClass* _this);
+ ~thrift_server_template();
+
+protected:
+ TserverBase* i_impl();
+ friend class thrift_application_base<TserverBase, TImplClass>;
+
+ TserverBase* d_server;
+
+private:
+ /**
+ * Custom TransportFactory that allows you to override the default Thrift buffer size
+ * of 512 bytes.
+ *
+ */
+ class TBufferedTransportFactory : public thrift::transport::TTransportFactory
+ {
+ public:
+ TBufferedTransportFactory(const unsigned int _bufferSize) : bufferSize(_bufferSize) {;}
+
+ virtual ~TBufferedTransportFactory() {}
+
+ virtual boost::shared_ptr<thrift::transport::TTransport> getTransport(
+ boost::shared_ptr<thrift::transport::TTransport> trans)
+ {
+ return boost::shared_ptr<thrift::transport::TTransport>
+ (new thrift::transport::TBufferedTransport(trans, bufferSize));
+ }
+ private:
+ unsigned int bufferSize;
+ };
+};
+
+template<typename TserverBase, typename TserverClass, typename TImplClass, typename TThriftClass>
+thrift_server_template<TserverBase, TserverClass, TImplClass, TThriftClass>::thrift_server_template
+(TImplClass* _this) : thrift_application_base<TserverBase, TImplClass>(_this)
+{
+ gr::logger_ptr logger, debug_logger;
+ gr::configure_default_loggers(logger, debug_logger, "controlport");
+
+ unsigned int port, nthreads, buffersize;
+ std::string thrift_config_file = gr::prefs::singleton()->get_string("ControlPort", "config", "");
+
+ if(thrift_config_file.length() > 0) {
+ gr::prefs::singleton()->add_config_file(thrift_config_file);
+ }
+
+ // Collect configuration options from the Thrift config file;
+ // defaults if the config file doesn't exist or list the specific
+ // options.
+ port = static_cast<unsigned int>(gr::prefs::singleton()->get_long("thrift", "port",
+ thrift_application_base<TserverBase, TImplClass>::d_default_thrift_port));
+ nthreads = static_cast<unsigned int>(gr::prefs::singleton()->get_long("thrift", "nthreads",
+ thrift_application_base<TserverBase, TImplClass>::d_default_num_thrift_threads));
+ buffersize = static_cast<unsigned int>(gr::prefs::singleton()->get_long("thrift", "buffersize",
+ thrift_application_base<TserverBase, TImplClass>::d_default_thrift_buffer_size));
+
+ boost::shared_ptr<TserverClass> handler(new TserverClass());
+
+ boost::shared_ptr<thrift::TProcessor>
+ processor(new GNURadio::ControlPortProcessor(handler));
+
+ boost::shared_ptr<thrift::transport::TServerTransport>
+ serverTransport(new thrift::transport::TServerSocket(port));
+
+ boost::shared_ptr<thrift::transport::TTransportFactory>
+ transportFactory(new thrift_server_template::TBufferedTransportFactory(buffersize));
+
+ boost::shared_ptr<thrift::protocol::TProtocolFactory>
+ protocolFactory(new thrift::protocol::TBinaryProtocolFactory());
+
+
+ if(nthreads <= 1) {
+ // "Thrift: Single-threaded server"
+ //std::cout << "Thrift Single-threaded server" << std::endl;
+ thrift_application_base<TserverBase, TImplClass>::d_thriftserver.reset(
+ new thrift::server::TSimpleServer(processor, serverTransport,
+ transportFactory, protocolFactory));
+ }
+ else {
+ //std::cout << "Thrift Multi-threaded server : " << nthreads << std::endl;
+ boost::shared_ptr<thrift::concurrency::ThreadManager> threadManager
+ (thrift::concurrency::ThreadManager::newSimpleThreadManager(nthreads));
+
+ boost::shared_ptr<thrift::concurrency::PlatformThreadFactory> threadFactory
+ (boost::shared_ptr<thrift::concurrency::PlatformThreadFactory>
+ (new thrift::concurrency::PlatformThreadFactory()));
+
+ threadManager->threadFactory(threadFactory);
+
+ threadManager->start();
+
+ thrift_application_base<TserverBase, TImplClass>::d_thriftserver.reset(
+ new thrift::server::TThreadPoolServer(processor, serverTransport,
+ transportFactory, protocolFactory,
+ threadManager));
+ }
+
+ d_server = handler.get();
+}
+
+template<typename TserverBase, typename TserverClass, typename TImplClass, typename TThriftClass>
+thrift_server_template<TserverBase, TserverClass,TImplClass, TThriftClass>::~thrift_server_template()
+{
+}
+
+template<typename TserverBase, typename TserverClass, typename TImplClass, typename TThriftClass>
+TserverBase* thrift_server_template<TserverBase, TserverClass, TImplClass, TThriftClass>::i_impl()
+{
+ //std::cerr << "thrift_server_template: i_impl" << std::endl;
+
+ return d_server;
+}
+
+#endif /* THRIFT_SERVER_TEMPLATE_H */
diff --git a/gnuradio-runtime/lib/block.cc b/gnuradio-runtime/lib/block.cc
index f26d6bb933..6edb73966d 100644
--- a/gnuradio-runtime/lib/block.cc
+++ b/gnuradio-runtime/lib/block.cc
@@ -842,42 +842,42 @@ namespace gr {
d_rpc_vars.push_back(
rpcbasic_sptr(new rpcbasic_register_get<block, std::vector<float> >(
alias(), "input \% full", &block::pc_input_buffers_full,
- pmt::make_c32vector(0,0), pmt::make_c32vector(0,1), pmt::make_c32vector(0,0),
+ pmt::make_f32vector(0,0), pmt::make_f32vector(0,1), pmt::make_f32vector(0,0),
"", "how full input buffers are", RPC_PRIVLVL_MIN,
DISPTIME | DISPOPTSTRIP)));
d_rpc_vars.push_back(
rpcbasic_sptr(new rpcbasic_register_get<block, std::vector<float> >(
alias(), "avg input \% full", &block::pc_input_buffers_full_avg,
- pmt::make_c32vector(0,0), pmt::make_c32vector(0,1), pmt::make_c32vector(0,0),
+ pmt::make_f32vector(0,0), pmt::make_f32vector(0,1), pmt::make_f32vector(0,0),
"", "Average of how full input buffers are", RPC_PRIVLVL_MIN,
DISPTIME | DISPOPTSTRIP)));
d_rpc_vars.push_back(
rpcbasic_sptr(new rpcbasic_register_get<block, std::vector<float> >(
alias(), "var input \% full", &block::pc_input_buffers_full_var,
- pmt::make_c32vector(0,0), pmt::make_c32vector(0,1), pmt::make_c32vector(0,0),
+ pmt::make_f32vector(0,0), pmt::make_f32vector(0,1), pmt::make_f32vector(0,0),
"", "Var. of how full input buffers are", RPC_PRIVLVL_MIN,
DISPTIME | DISPOPTSTRIP)));
d_rpc_vars.push_back(
rpcbasic_sptr(new rpcbasic_register_get<block, std::vector<float> >(
alias(), "output \% full", &block::pc_output_buffers_full,
- pmt::make_c32vector(0,0), pmt::make_c32vector(0,1), pmt::make_c32vector(0,0),
+ pmt::make_f32vector(0,0), pmt::make_f32vector(0,1), pmt::make_f32vector(0,0),
"", "how full output buffers are", RPC_PRIVLVL_MIN,
DISPTIME | DISPOPTSTRIP)));
d_rpc_vars.push_back(
rpcbasic_sptr(new rpcbasic_register_get<block, std::vector<float> >(
alias(), "avg output \% full", &block::pc_output_buffers_full_avg,
- pmt::make_c32vector(0,0), pmt::make_c32vector(0,1), pmt::make_c32vector(0,0),
+ pmt::make_f32vector(0,0), pmt::make_f32vector(0,1), pmt::make_f32vector(0,0),
"", "Average of how full output buffers are", RPC_PRIVLVL_MIN,
DISPTIME | DISPOPTSTRIP)));
d_rpc_vars.push_back(
rpcbasic_sptr(new rpcbasic_register_get<block, std::vector<float> >(
alias(), "var output \% full", &block::pc_output_buffers_full_var,
- pmt::make_c32vector(0,0), pmt::make_c32vector(0,1), pmt::make_c32vector(0,0),
+ pmt::make_f32vector(0,0), pmt::make_f32vector(0,1), pmt::make_f32vector(0,0),
"", "Var. of how full output buffers are", RPC_PRIVLVL_MIN,
DISPTIME | DISPOPTSTRIP)));
#endif /* defined(GR_CTRLPORT) && defined(GR_PERFORMANCE_COUNTERS) */
diff --git a/gnuradio-runtime/lib/controlport/CMakeLists.txt b/gnuradio-runtime/lib/controlport/CMakeLists.txt
index 262c5adb8b..f0d2618c01 100644
--- a/gnuradio-runtime/lib/controlport/CMakeLists.txt
+++ b/gnuradio-runtime/lib/controlport/CMakeLists.txt
@@ -19,8 +19,13 @@
if(ENABLE_GR_CTRLPORT)
+# Keep track of the number of backends ControlPort supports
+SET(CTRLPORT_BACKENDS 0)
+
# Add definition so we can compile in ControlPort to the blocks.
-ADD_DEFINITIONS(-DGR_CTRLPORT)
+add_definitions(-DGR_CTRLPORT)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
list(APPEND gnuradio_ctrlport_sources
${CMAKE_CURRENT_SOURCE_DIR}/rpcmanager.cc
@@ -29,14 +34,71 @@ list(APPEND gnuradio_ctrlport_sources
${CMAKE_CURRENT_SOURCE_DIR}/rpcserver_selector.cc
)
+
+OPTION(ENABLE_CTRLPORT_THRIFT "Enable ControlPort Thrift support" ON)
+
+if(ENABLE_CTRLPORT_THRIFT)
+
+# Look if Thrift is installed and use it as a ControlPort backend.
+FIND_PACKAGE(Thrift)
+
+if(THRIFT_FOUND)
+
+MATH(EXPR CTRLPORT_BACKENDS "${CTRLPORT_BACKENDS} + 1")
+
+# Indicate thrift as an installed backend in the cmake summary.
+message(STATUS "Found and enabling Thrift backend to ControlPort")
+GR_APPEND_SUBCOMPONENT("thrift")
+
+# Run Thrrift To compile C++ and Python files
+message(STATUS "Running thrift to build C++ bindings")
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/thrift/)
+EXECUTE_PROCESS(
+ COMMAND ${THRIFT_BIN} --gen cpp -out ${CMAKE_CURRENT_BINARY_DIR}/thrift/ ${CMAKE_CURRENT_SOURCE_DIR}/thrift/gnuradio.thrift
+ OUTPUT_VARIABLE THRIFT_CPP_OUTPUT
+ ERROR_VARIABLE THRIFT_CPP_ERROR
+ )
+
+list(APPEND gnuradio_ctrlport_sources
+ ${CMAKE_CURRENT_SOURCE_DIR}/thrift/rpcserver_thrift.cc
+ ${CMAKE_CURRENT_SOURCE_DIR}/thrift/rpcpmtconverters_thrift.cc
+ ${CMAKE_CURRENT_SOURCE_DIR}/thrift/rpcserver_booter_thrift.cc
+ ${CMAKE_CURRENT_SOURCE_DIR}/thrift/thrift_application_base.cc
+)
+
+# add files built by compiling gnuradio.thrift
+list(APPEND gnuradio_ctrlport_sources
+ ${CMAKE_CURRENT_BINARY_DIR}/thrift/gnuradio_types.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/thrift/gnuradio_constants.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/thrift/ControlPort.cpp
+)
+
+# Add required libraries here
+list(APPEND gnuradio_runtime_libs
+ ${THRIFT_LIBRARIES}
+)
+
+# Add install rule to move example Thrift configuration file into
+# $prefix/etc/gnuradio
+install(
+ FILES ${CMAKE_CURRENT_SOURCE_DIR}/thrift/thrift.conf.example
+ DESTINATION ${SYSCONFDIR}/${CMAKE_PROJECT_NAME}
+ COMPONENT "runtime_runtime"
+)
+
+endif(THRIFT_FOUND)
+endif(ENABLE_CTRLPORT_THRIFT)
+
########################################################################
# Add controlport stuff to gnuradio-runtime
########################################################################
include_directories(${CMAKE_CURRENT_BINARY_DIR})
-# Add any required libraries here
-#list(APPEND gnuradio_runtime_libs
-#)
+# Save the number of backends for testing against later
+set(
+ CTRLPORT_BACKENDS ${CTRLPORT_BACKENDS}
+ CACHE INTERNAL "Number of ControlPort backends available"
+)
endif(ENABLE_GR_CTRLPORT)
diff --git a/gnuradio-runtime/lib/controlport/rpcmanager.cc b/gnuradio-runtime/lib/controlport/rpcmanager.cc
index 0c7bc135be..a67febe386 100644
--- a/gnuradio-runtime/lib/controlport/rpcmanager.cc
+++ b/gnuradio-runtime/lib/controlport/rpcmanager.cc
@@ -26,16 +26,12 @@
bool rpcmanager::booter_registered(false);
bool rpcmanager::aggregator_registered(false);
-rpcserver_booter_base* rpcmanager::boot(0);
+std::auto_ptr<rpcserver_booter_base> rpcmanager::boot(0);
std::auto_ptr<rpcserver_booter_aggregator> rpcmanager::aggregator(0);
rpcmanager::rpcmanager() {;}
-rpcmanager::~rpcmanager()
-{
- if(boot)
- delete boot;
-}
+rpcmanager::~rpcmanager() {;}
rpcserver_booter_base*
rpcmanager::get()
@@ -44,10 +40,10 @@ rpcmanager::get()
return aggregator.get();
}
else if(booter_registered) {
- return boot;
+ return boot.get();
}
assert(booter_registered || aggregator_registered);
- return boot;
+ return boot.get();
}
void
@@ -63,7 +59,7 @@ rpcmanager::register_booter(rpcserver_booter_base* booter)
aggregator->agg()->registerServer(bootreg);
}
else if(!booter_registered) {
- boot = booter;
+ boot.reset(booter);
booter_registered = true;
}
else {
diff --git a/gnuradio-runtime/lib/controlport/rpcserver_booter_aggregator.cc b/gnuradio-runtime/lib/controlport/rpcserver_booter_aggregator.cc
index 201dfb3929..a1983b4ac5 100644
--- a/gnuradio-runtime/lib/controlport/rpcserver_booter_aggregator.cc
+++ b/gnuradio-runtime/lib/controlport/rpcserver_booter_aggregator.cc
@@ -23,7 +23,8 @@
#include <gnuradio/rpcserver_booter_aggregator.h>
rpcserver_booter_aggregator::rpcserver_booter_aggregator() :
- d_type(std::string("aggregator")), server(new rpcserver_aggregator())
+ d_type(std::string("aggregator")),
+ server(new rpcserver_aggregator())
{;}
rpcserver_booter_aggregator::~rpcserver_booter_aggregator()
diff --git a/gnuradio-runtime/lib/controlport/rpcserver_selector.cc b/gnuradio-runtime/lib/controlport/rpcserver_selector.cc
index 692f151958..8f3b4557c2 100644
--- a/gnuradio-runtime/lib/controlport/rpcserver_selector.cc
+++ b/gnuradio-runtime/lib/controlport/rpcserver_selector.cc
@@ -26,14 +26,23 @@
bool rpcmanager::make_aggregator(false);
-#ifdef RPCSERVER_ICE
+#ifdef GR_RPCSERVER_ENABLED
+rpcmanager manager_instance;
+#endif
+
+#ifdef GR_RPCSERVER_ICE
#error TODO ICE
#endif
-#ifdef RPCSERVER_ERLANG
+#ifdef GR_RPCSERVER_THRIFT
+#include <gnuradio/rpcserver_booter_thrift.h>
+rpcmanager::rpcserver_booter_register_helper<rpcserver_booter_thrift> boot_thrift;
+#endif
+
+#ifdef GR_RPCSERVER_ERLANG
#error TODO ERLANG
#endif
-#ifdef RPCSERVER_XMLRPC
+#ifdef GR_RPCSERVER_XMLRPC
#error TODO XMLRPC
#endif
diff --git a/gnuradio-runtime/lib/controlport/thrift/gnuradio.thrift b/gnuradio-runtime/lib/controlport/thrift/gnuradio.thrift
new file mode 100644
index 0000000000..ae7f839a2f
--- /dev/null
+++ b/gnuradio-runtime/lib/controlport/thrift/gnuradio.thrift
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2014,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.
+ */
+
+namespace cpp GNURadio
+namespace py GNURadio
+
+struct complex {
+ 1: double re;
+ 2: double im;
+}
+
+typedef list<bool> VectorB
+typedef binary VectorC
+typedef list<i16> VectorT
+typedef list<i32> VectorI
+typedef list<i64> VectorL
+typedef list<double> VectorF
+typedef list<double> VectorD
+typedef list<string> VectorS
+typedef list<complex> VectorZ
+
+enum BaseTypes { BOOL, BYTE, SHORT, INT, LONG, DOUBLE, STRING, COMPLEX,
+ F32VECTOR, F64VECTOR, S64VECTOR, S32VECTOR, S16VECTOR,
+ S8VECTOR, C32VECTOR }
+
+union KnobBase {
+ 1: bool a_bool;
+ 2: byte a_byte;
+ 3: i16 a_short;
+ 4: i32 a_int;
+ 5: i64 a_long;
+ 6: double a_double;
+ 7: string a_string;
+ 8: complex a_complex;
+ 9: VectorF a_f32vector;
+ 10: VectorD a_f64vector;
+ 11: VectorL a_s64vector;
+ 12: VectorI a_s32vector;
+ 13: VectorT a_s16vector;
+ 14: VectorC a_s8vector;
+ 15: VectorZ a_c32vector;
+}
+
+struct Knob {
+ 1: BaseTypes type;
+ 2: KnobBase value;
+}
+
+enum KnobType { KNOBBOOL, KNOBCHAR, KNOBINT, KNOBDOUBLE, KNOBSTRING,
+ KNOBLONG, KNOBVECBOOL, KNOBVECCHAR, KNOBVECINT,
+ KNOBVECDOUBLE, KNOBVECSTRING, KNOBVECLONG, KNOBSHORT}
+
+const i32 DISPNULL = 0x0000
+const i32 DISPTIME = 0x0001
+const i32 DISPXY = 0x0002
+const i32 DISPPSD = 0x0004
+const i32 DISPSPEC = 0x0008
+const i32 DISPRAST = 0x0010
+const i32 DISPOPTCPLX = 0x0100
+const i32 DISPOPTLOG = 0x0200
+const i32 DISPOPTSTEM = 0x0400
+const i32 DISPOPTSTRIP = 0x0800
+const i32 DISPOPTSCATTER = 0x1000
+
+struct KnobProp {
+ 1: KnobType type,
+ 2: string units,
+ 3: string description,
+ 4: i32 display,
+ 5: Knob min,
+ 6: Knob max,
+ 7: Knob defaultvalue
+}
+
+typedef list<string> KnobIDList
+typedef map<string, Knob> KnobMap
+typedef map<string, KnobProp> KnobPropMap
+typedef map<string, string> WaveformArgMap
+
+service StreamReceiver {
+ void push(1:VectorC data);
+}
+
+service ControlPort {
+ void setKnobs(1:KnobMap knobs);
+ KnobMap getKnobs(1:KnobIDList knobs);
+ KnobMap getRe(1:KnobIDList knobs);
+ KnobPropMap properties(1:KnobIDList knobs);
+ void shutdown();
+}
diff --git a/gnuradio-runtime/lib/controlport/thrift/rpcpmtconverters_thrift.cc b/gnuradio-runtime/lib/controlport/thrift/rpcpmtconverters_thrift.cc
new file mode 100644
index 0000000000..19da05f787
--- /dev/null
+++ b/gnuradio-runtime/lib/controlport/thrift/rpcpmtconverters_thrift.cc
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2014,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.
+ */
+
+#include <boost/assign/ptr_map_inserter.hpp>
+#include <gnuradio/rpcpmtconverters_thrift.h>
+#include <gnuradio/gr_complex.h>
+#include "thrift/gnuradio_types.h"
+#include <iostream>
+
+GNURadio::Knob
+rpcpmtconverter::from_pmt(const pmt::pmt_t& knob)
+{
+ if(pmt::is_real(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::DOUBLE;
+ result.value.__set_a_double(pmt::to_double(knob));
+ return result;
+ }
+ else if(pmt::is_symbol(knob)) {
+ std::string value = pmt::symbol_to_string(knob);
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::STRING;
+ result.value.__set_a_string(value);
+ return result;
+ }
+ else if(pmt::is_integer(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::LONG;
+ result.value.__set_a_long(pmt::to_long(knob));
+ return result;
+ }
+ else if(pmt::is_bool(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::BOOL;
+ result.value.__set_a_bool(pmt::to_bool(knob));
+ return result;
+ }
+ else if(pmt::is_uint64(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::LONG;
+ result.value.__set_a_long(pmt::to_uint64(knob));
+ return result;
+ }
+ else if(pmt::is_complex(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::COMPLEX;
+ std::complex<double> tmp = pmt::to_complex(knob);
+ GNURadio::complex cpx;
+ cpx.re = tmp.real();
+ cpx.im = tmp.imag();
+ result.value.__set_a_complex(cpx);
+ return result;
+ }
+ else if(pmt::is_f32vector(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::F32VECTOR;
+ size_t size(pmt::length(knob));
+ const float* start((const float*)pmt::f32vector_elements(knob,size));
+ result.value.__set_a_f32vector(std::vector<double>(start,start+size));
+ return result;
+ }
+ else if(pmt::is_f64vector(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::F64VECTOR;
+ size_t size(pmt::length(knob));
+ const double* start((const double*)pmt::f64vector_elements(knob,size));
+ result.value.__set_a_f64vector(std::vector<double>(start,start+size));
+ return result;
+ }
+ else if(pmt::is_s64vector(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::S64VECTOR;
+ size_t size(pmt::length(knob));
+ const int64_t* start((const int64_t*)pmt::s64vector_elements(knob,size));
+ result.value.__set_a_s64vector(std::vector<int64_t>(start,start+size));
+ return result;
+ }
+ else if(pmt::is_s32vector(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::S32VECTOR;
+ size_t size(pmt::length(knob));
+ const int32_t* start((const int32_t*)pmt::s32vector_elements(knob,size));
+ result.value.__set_a_s32vector(std::vector<int32_t>(start,start+size));
+ return result;
+ }
+ else if(pmt::is_s16vector(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::S16VECTOR;
+ size_t size(pmt::length(knob));
+ const int16_t* start((const int16_t*)pmt::s16vector_elements(knob,size));
+ result.value.__set_a_s16vector(std::vector<int16_t>(start,start+size));
+ return result;
+ }
+ else if(pmt::is_s8vector(knob)) {
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::S8VECTOR;
+ size_t size(pmt::length(knob));
+ const int8_t* start((const int8_t*)pmt::s8vector_elements(knob,size));
+ result.value.__set_a_s8vector(std::basic_string<char>(start,start+size));
+ return result;
+ }
+ else if(pmt::is_c32vector(knob)) {
+ std::vector< GNURadio::complex > z;
+
+ GNURadio::Knob result;
+ result.type = GNURadio::BaseTypes::C32VECTOR;
+ size_t size(pmt::length(knob));
+ const gr_complex* start((const gr_complex*)pmt::c32vector_elements(knob,size));
+ for(size_t s = 0; s < size; s++) {
+ GNURadio::complex z0;
+ gr_complex z1 = gr_complex(*(start+s));
+ z0.__set_re(z1.real());
+ z0.__set_im(z1.imag());
+ z.push_back(z0);
+ }
+ result.value.__set_a_c32vector(z);
+ return result;
+ }
+ else {
+ std::cerr << "Error: Don't know how to handle Knob Type (from): " << knob << std::endl;
+ assert(0);
+ }
+ return GNURadio::Knob();
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_byte_f::operator()(const GNURadio::Knob& knob)
+{
+ return pmt::mp(knob.value.a_byte);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_short_f::operator()(const GNURadio::Knob& knob)
+{
+ return pmt::mp(knob.value.a_short);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_int_f::operator()(const GNURadio::Knob& knob)
+{
+ return pmt::mp(knob.value.a_int);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_long_f::operator()(const GNURadio::Knob& knob)
+{
+ return pmt::mp(knob.value.a_long);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_double_f::operator()(const GNURadio::Knob& knob)
+{
+ return pmt::mp(knob.value.a_double);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_string_f::operator()(const GNURadio::Knob& knob)
+{
+ return pmt::string_to_symbol(knob.value.a_string);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_bool_f::operator()(const GNURadio::Knob& knob)
+{
+ if(knob.value.a_bool)
+ return pmt::PMT_T;
+ else
+ return pmt::PMT_F;
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_complex_f::operator()(const GNURadio::Knob& knob)
+{
+ gr_complexd cpx(knob.value.a_complex.re, knob.value.a_complex.im);
+ return pmt::from_complex(cpx);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_f32vect_f::operator()(const GNURadio::Knob& knob)
+{
+ std::vector<double> v_double = knob.value.a_f32vector;
+ std::vector<float> v(v_double.begin(), v_double.end());
+ return pmt::init_f32vector(v.size(), v);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_f64vect_f::operator()(const GNURadio::Knob& knob)
+{
+ std::vector<double> v = knob.value.a_f64vector;
+ return pmt::init_f64vector(v.size(), v);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_s64vect_f::operator()(const GNURadio::Knob& knob)
+{
+ std::vector<int64_t> v = knob.value.a_s64vector;
+ return pmt::init_s64vector(v.size(), v);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_s32vect_f::operator()(const GNURadio::Knob& knob)
+{
+ std::vector<int32_t> v = knob.value.a_s32vector;
+ return pmt::init_s32vector(v.size(), v);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_s16vect_f::operator()(const GNURadio::Knob& knob)
+{
+ std::vector<int16_t> v = knob.value.a_s16vector;
+ return pmt::init_s16vector(v.size(), v);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_s8vect_f::operator()(const GNURadio::Knob& knob)
+{
+ std::basic_string<char> v = knob.value.a_s8vector;
+ return pmt::init_s8vector(v.size(), reinterpret_cast<const int8_t*>(v.data()));
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_c32vect_f::operator()(const GNURadio::Knob& knob)
+{
+ std::vector<GNURadio::complex> v0 = knob.value.a_c32vector;
+ std::vector<GNURadio::complex>::iterator vitr;
+ std::vector<gr_complex> v;
+ for(vitr = v0.begin(); vitr != v0.end(); vitr++) {
+ v.push_back(gr_complex(vitr->re, vitr->im));
+ }
+ return pmt::init_c32vector(v.size(), v);
+}
+
+rpcpmtconverter::To_PMT rpcpmtconverter::To_PMT::instance;
+
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_bool_f> reg_bool(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::BOOL);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_byte_f> reg_byte(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::BYTE);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_short_f> reg_short(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::SHORT);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_int_f> reg_int(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::INT);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_long_f> reg_long(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::LONG);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_double_f> reg_double(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::DOUBLE);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_string_f> reg_string(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::STRING);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_complex_f> reg_complex(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::COMPLEX);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_f32vect_f> reg_f32v(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::F32VECTOR);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_f64vect_f> reg_f64v(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::F64VECTOR);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_s64vect_f> reg_s64v(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::S64VECTOR);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_s32vect_f> reg_s32v(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::S32VECTOR);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_s16vect_f> reg_s16v(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::S16VECTOR);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_s8vect_f> reg_s8v(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::S8VECTOR);
+rpcpmtconverter::to_pmt_reg<rpcpmtconverter::to_pmt_c32vect_f> reg_c32v(rpcpmtconverter::To_PMT::instance,
+ GNURadio::BaseTypes::C32VECTOR);
+
+template<typename TO_PMT_F>
+rpcpmtconverter::to_pmt_reg<TO_PMT_F>::to_pmt_reg(To_PMT& instance,
+ const GNURadio::BaseTypes::type type)
+{
+ boost::assign::ptr_map_insert<TO_PMT_F>(instance.to_pmt_map)(type);
+}
+
+pmt::pmt_t
+rpcpmtconverter::to_pmt_f::operator()(const GNURadio::Knob& knob)
+{
+ std::cerr << "Error: Don't know how to handle Knob Type: " << knob.type << std::endl;
+ assert(0);
+ return pmt::pmt_t();
+}
+
+pmt::pmt_t
+rpcpmtconverter::To_PMT::operator()(const GNURadio::Knob& knob)
+{
+ return to_pmt_map[knob.type](knob);
+}
diff --git a/gnuradio-runtime/lib/controlport/thrift/rpcserver_booter_thrift.cc b/gnuradio-runtime/lib/controlport/thrift/rpcserver_booter_thrift.cc
new file mode 100644
index 0000000000..40cfe1a48a
--- /dev/null
+++ b/gnuradio-runtime/lib/controlport/thrift/rpcserver_booter_thrift.cc
@@ -0,0 +1,137 @@
+/* -*- c++ -*- */
+/*
+ * 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.
+ */
+
+#include <gnuradio/rpcserver_thrift.h>
+#include <gnuradio/rpcserver_booter_thrift.h>
+
+#include <boost/asio/ip/host_name.hpp>
+
+namespace {
+ static const char* const CONTROL_PORT_CLASS("thrift");
+ static const unsigned int ETHERNET_HEADER_SIZE(14);
+ static const unsigned int IP_HEADER_SIZE(20);
+ static const unsigned int TCP_HEADER_SIZE(32);
+ static const unsigned int ETHERNET_TYPICAL_MTU(1500);
+ static const unsigned int ALRIGHT_DEFAULT_BUFFER_SIZE(
+ ETHERNET_TYPICAL_MTU - ETHERNET_HEADER_SIZE - IP_HEADER_SIZE - TCP_HEADER_SIZE);
+};
+
+/*!
+ * \brief A booter implementation for a Thrift application class.
+ */
+
+rpcserver_booter_thrift::rpcserver_booter_thrift() :
+ thrift_server_template<rpcserver_base,
+ rpcserver_thrift,
+ rpcserver_booter_thrift,
+ boost::shared_ptr<GNURadio::ControlPortIf> >(this),
+ d_type(std::string(CONTROL_PORT_CLASS))
+{;}
+
+rpcserver_booter_thrift::~rpcserver_booter_thrift()
+{;}
+
+rpcserver_base*
+rpcserver_booter_thrift::i()
+{
+ return thrift_server_template<rpcserver_base, rpcserver_thrift,
+ rpcserver_booter_thrift,
+ GNURadio::ControlPortIf>::i();
+}
+
+/*!
+ * \brief Returns the endpoint string for the application
+ */
+
+const std::vector<std::string>
+rpcserver_booter_thrift::endpoints()
+{
+ return thrift_server_template<rpcserver_base, rpcserver_thrift,
+ rpcserver_booter_thrift,
+ GNURadio::ControlPortIf>::endpoints();
+}
+
+// Specialized thrift_application_base attributes and functions
+// for this rpcserver_booter instance.
+
+template<class rpcserver_base, class rpcserver_booter_thrift>
+const unsigned int thrift_application_base<rpcserver_base, rpcserver_booter_thrift>::d_default_max_init_attempts(100U);
+
+template<class rpcserver_base, class rpcserver_booter_thrift>
+const unsigned int thrift_application_base<rpcserver_base, rpcserver_booter_thrift>::d_default_thrift_port(0U);
+
+template<class rpcserver_base, class rpcserver_booter_thrift>
+const unsigned int thrift_application_base<rpcserver_base, rpcserver_booter_thrift>::d_default_num_thrift_threads(10U);
+
+template<class rpcserver_base, class rpcserver_booter_thrift>
+const unsigned int thrift_application_base<rpcserver_base, rpcserver_booter_thrift>::d_default_thrift_buffer_size(
+ ALRIGHT_DEFAULT_BUFFER_SIZE);
+
+template<class rpcserver_base, class rpcserver_booter_thrift>
+std::auto_ptr<thrift_application_base_impl>
+ thrift_application_base<rpcserver_base, rpcserver_booter_thrift>::p_impl(
+ new thrift_application_base_impl());
+
+template<class rpcserver_base, class rpcserver_booter_thrift>
+thrift_application_base<rpcserver_base, rpcserver_booter_thrift>::~thrift_application_base()
+{
+ GR_LOG_DEBUG(d_debug_logger, "thrift_application_base: shutdown");
+ if(d_thirft_is_running) {
+ d_thriftserver->stop();
+ d_thirft_is_running = false;
+ }
+}
+
+template<class rpcserver_base, class rpcserver_booter_thrift>
+void thrift_application_base<rpcserver_base, rpcserver_booter_thrift>::start_thrift()
+{
+ d_thriftserver->serve();
+}
+
+template<class rpcserver_base, typename rpcserver_booter_thrift>
+bool thrift_application_base<rpcserver_base, rpcserver_booter_thrift>::application_started()
+{
+ if (d_thirft_is_running) return true;
+
+ bool result(false);
+ // Define the endpoint.
+ apache::thrift::transport::TServerTransport *thetransport =
+ d_thriftserver->getServerTransport().get();
+
+ // Determine the specified endpoint port number, or the port number selected by bind() if
+ int used_port = ((apache::thrift::transport::TServerSocket*)thetransport)->getPort();
+
+ if (used_port > 0) {
+ // Determine the hostname of this host
+ const std::string boost_hostname(boost::asio::ip::host_name());
+
+ std::string endpoint = boost::str(boost::format("-h %1% -p %2%") % boost_hostname % used_port);
+ //std::cout << "Thrift endpoint: " << endpoint << " boost hostname: " << boost_hostname << std::endl;
+ set_endpoint(endpoint);
+
+ GR_LOG_INFO(d_logger, "Apache Thrift: " + endpoint);
+ d_thirft_is_running = true;
+ result = true;
+ }
+
+ return result;
+}
diff --git a/gnuradio-runtime/lib/controlport/thrift/rpcserver_thrift.cc b/gnuradio-runtime/lib/controlport/thrift/rpcserver_thrift.cc
new file mode 100644
index 0000000000..c4655d366e
--- /dev/null
+++ b/gnuradio-runtime/lib/controlport/thrift/rpcserver_thrift.cc
@@ -0,0 +1,194 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2014,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.
+ */
+
+#include <gnuradio/rpcserver_thrift.h>
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+#include <pmt/pmt.h>
+#include <thrift/protocol/TBinaryProtocol.h>
+#include <thrift/transport/TSocket.h>
+#include <thrift/transport/TTransportUtils.h>
+#include <boost/xpressive/xpressive.hpp>
+#include "thrift/ControlPort.h"
+
+#define DEBUG 0
+
+using namespace rpcpmtconverter;
+
+rpcserver_thrift::rpcserver_thrift()
+{
+ //std::cerr << "rpcserver_thrift::ctor" << std::endl;
+}
+
+rpcserver_thrift::~rpcserver_thrift()
+{
+ //std::cerr << "rpcserver_thrift::dtor" << std::endl;
+}
+
+void
+rpcserver_thrift::registerConfigureCallback(const std::string &id,
+ const configureCallback_t callback)
+{
+ {
+ ConfigureCallbackMap_t::const_iterator iter(d_setcallbackmap.find(id));
+ if(iter != d_setcallbackmap.end()) {
+ std::stringstream s;
+ s << "rpcserver_thrift:: rpcserver_thrift ERROR registering set, already registered: "
+ << id << std::endl;
+ throw std::runtime_error(s.str().c_str());
+ }
+ }
+
+ if(DEBUG) {
+ std::cerr << "rpcserver_thrift registering set: " << id << std::endl;
+ }
+ d_setcallbackmap.insert(ConfigureCallbackMap_t::value_type(id, callback));
+}
+
+void
+rpcserver_thrift::unregisterConfigureCallback(const std::string &id)
+{
+ ConfigureCallbackMap_t::iterator iter(d_setcallbackmap.find(id));
+ if(iter == d_setcallbackmap.end()) {
+ std::stringstream s;
+ s << "rpcserver_thrift:: rpcserver_thrift ERROR unregistering set, not registered: "
+ << id << std::endl;
+ throw std::runtime_error(s.str().c_str());
+ }
+
+ if(DEBUG)
+ std::cerr << "rpcserver_thrift unregistering set: " << id << std::endl;
+
+ d_setcallbackmap.erase(iter);
+}
+
+void
+rpcserver_thrift::registerQueryCallback(const std::string &id,
+ const queryCallback_t callback)
+{
+ {
+ QueryCallbackMap_t::const_iterator iter(d_getcallbackmap.find(id));
+ if(iter != d_getcallbackmap.end()) {
+ std::stringstream s;
+ s << "rpcserver_thrift:: rpcserver_thrift ERROR registering get, already registered: "
+ << id << std::endl;
+ throw std::runtime_error(s.str().c_str());
+ }
+ }
+
+ if(DEBUG) {
+ std::cerr << "rpcserver_thrift registering get: " << id << std::endl;
+ }
+ d_getcallbackmap.insert(QueryCallbackMap_t::value_type(id, callback));
+}
+
+void
+rpcserver_thrift::unregisterQueryCallback(const std::string &id)
+{
+ QueryCallbackMap_t::iterator iter(d_getcallbackmap.find(id));
+ if(iter == d_getcallbackmap.end()) {
+ std::stringstream s;
+ s << "rpcserver_thrift:: rpcserver_thrift ERROR unregistering get, registered: "
+ << id << std::endl;
+ throw std::runtime_error(s.str().c_str());
+ }
+
+ if(DEBUG) {
+ std::cerr << "rpcserver_thrift unregistering get: " << id << std::endl;
+ }
+
+ d_getcallbackmap.erase(iter);
+}
+
+void
+rpcserver_thrift::setKnobs(const GNURadio::KnobMap& knobs)
+{
+ std::for_each(knobs.begin(), knobs.end(),
+ set_f<GNURadio::KnobMap::value_type,ConfigureCallbackMap_t>
+ (d_setcallbackmap, cur_priv));
+}
+
+
+void
+rpcserver_thrift::getKnobs(GNURadio::KnobMap& _return,
+ const GNURadio::KnobIDList& knobs)
+{
+ if(knobs.size() == 0) {
+ std::for_each(d_getcallbackmap.begin(), d_getcallbackmap.end(),
+ get_all_f<QueryCallbackMap_t::value_type, QueryCallbackMap_t, GNURadio::KnobMap>
+ (d_getcallbackmap, cur_priv, _return));
+ }
+ else {
+ std::for_each(knobs.begin(), knobs.end(),
+ get_f<GNURadio::KnobIDList::value_type, QueryCallbackMap_t>
+ (d_getcallbackmap, cur_priv, _return));
+ }
+}
+
+void
+rpcserver_thrift::getRe(GNURadio::KnobMap& _return, const GNURadio::KnobIDList& knobs)
+{
+ if(knobs.size() == 0) {
+ std::for_each(d_getcallbackmap.begin(), d_getcallbackmap.end(),
+ get_all_f<QueryCallbackMap_t::value_type, QueryCallbackMap_t, GNURadio::KnobMap>
+ (d_getcallbackmap, cur_priv, _return));
+ }
+ else {
+ QueryCallbackMap_t::iterator it;
+ for(it = d_getcallbackmap.begin(); it != d_getcallbackmap.end(); it++){
+ for(size_t j=0; j<knobs.size(); j++) {
+ const boost::xpressive::sregex re(boost::xpressive::sregex::compile(knobs[j]));
+ if(boost::xpressive::regex_match(it->first, re)) {
+ get_f<GNURadio::KnobIDList::value_type, QueryCallbackMap_t>
+ (d_getcallbackmap, cur_priv, _return)(it->first);
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+rpcserver_thrift::properties(GNURadio::KnobPropMap& _return,
+ const GNURadio::KnobIDList& knobs)
+{
+ if(knobs.size() == 0) {
+ std::for_each(d_getcallbackmap.begin(), d_getcallbackmap.end(),
+ properties_all_f<QueryCallbackMap_t::value_type,
+ QueryCallbackMap_t, GNURadio::KnobPropMap>(d_getcallbackmap,
+ cur_priv, _return));
+ }
+ else {
+ std::for_each(knobs.begin(), knobs.end(),
+ properties_f<GNURadio::KnobIDList::value_type,
+ QueryCallbackMap_t, GNURadio::KnobPropMap>(d_getcallbackmap,
+ cur_priv, _return));
+ }
+}
+
+void
+rpcserver_thrift::shutdown() {
+ if (DEBUG) {
+ std::cerr << "Shutting down..." << std::endl;
+ }
+}
diff --git a/gnuradio-runtime/lib/controlport/thrift/thrift.conf.example b/gnuradio-runtime/lib/controlport/thrift/thrift.conf.example
new file mode 100644
index 0000000000..71cc506249
--- /dev/null
+++ b/gnuradio-runtime/lib/controlport/thrift/thrift.conf.example
@@ -0,0 +1,4 @@
+[thrift]
+port = 9090
+nthreads = 2
+buffersize = 1434
diff --git a/gnuradio-runtime/lib/controlport/thrift/thrift_application_base.cc b/gnuradio-runtime/lib/controlport/thrift/thrift_application_base.cc
new file mode 100644
index 0000000000..282ed9fc63
--- /dev/null
+++ b/gnuradio-runtime/lib/controlport/thrift/thrift_application_base.cc
@@ -0,0 +1,23 @@
+/* -*- c++ -*- */
+/*
+ * 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.
+ */
+
+#include <gnuradio/thrift_application_base.h>
diff --git a/gnuradio-runtime/lib/prefs.cc b/gnuradio-runtime/lib/prefs.cc
index b7fcaada9d..b303ffdaf9 100644
--- a/gnuradio-runtime/lib/prefs.cc
+++ b/gnuradio-runtime/lib/prefs.cc
@@ -1,6 +1,6 @@
/* -*- c++ -*- */
/*
- * Copyright 2006,2013 Free Software Foundation, Inc.
+ * Copyright 2006,2013,2015 Free Software Foundation, Inc.
*
* This file is part of GNU Radio
*
@@ -46,7 +46,10 @@ namespace gr {
prefs::prefs()
{
- _read_files();
+ std::string config = _read_files(_sys_prefs_filenames());
+
+ // Convert the string into a map
+ _convert_to_map(config);
}
prefs::~prefs()
@@ -83,13 +86,12 @@ namespace gr {
return fnames;
}
- void
- prefs::_read_files()
+ std::string
+ prefs::_read_files(const std::vector<std::string> &filenames)
{
std::string config;
- std::vector<std::string> filenames = _sys_prefs_filenames();
- std::vector<std::string>::iterator sitr;
+ std::vector<std::string>::const_iterator sitr;
char tmp[1024];
for(sitr = filenames.begin(); sitr != filenames.end(); sitr++) {
fs::ifstream fin(*sitr);
@@ -142,8 +144,7 @@ namespace gr {
fin.close();
}
- // Convert the string into a map
- _convert_to_map(config);
+ return config;
}
void
@@ -188,6 +189,17 @@ namespace gr {
}
}
+ void
+ prefs::add_config_file(const std::string &configfile)
+ {
+ std::vector<std::string> filenames;
+ filenames.push_back(configfile);
+
+ std::string config = _read_files(filenames);
+ _convert_to_map(config);
+ }
+
+
std::string
prefs::to_string()
{
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
diff --git a/gnuradio-runtime/swig/prefs.i b/gnuradio-runtime/swig/prefs.i
index ac5fab7adc..4774146b69 100644
--- a/gnuradio-runtime/swig/prefs.i
+++ b/gnuradio-runtime/swig/prefs.i
@@ -1,6 +1,6 @@
/* -*- c++ -*- */
/*
- * Copyright 2006,2013 Free Software Foundation, Inc.
+ * Copyright 2006,2015 Free Software Foundation, Inc.
*
* This file is part of GNU Radio
*
@@ -25,6 +25,8 @@ class gr::prefs
public:
static gr::prefs *singleton();
+ void add_config_file(const std::string &configfile);
+
virtual ~prefs();
std::string to_string();
diff --git a/gr-blocks/grc/blocks_block_tree.xml b/gr-blocks/grc/blocks_block_tree.xml
index cc7b4b8c6f..fb4e10d1ba 100644
--- a/gr-blocks/grc/blocks_block_tree.xml
+++ b/gr-blocks/grc/blocks_block_tree.xml
@@ -51,6 +51,8 @@
</cat>
<cat>
<name>Control Port</name>
+ <block>blocks_ctrlport_monitor</block>
+ <block>blocks_ctrlport_monitor_performance</block>
<block>blocks_ctrlport_probe2_x</block>
<block>blocks_ctrlport_probe2_c</block>
<block>blocks_ctrlport_probe_c</block>
diff --git a/gr-blocks/grc/blocks_ctrlport_performance.xml b/gr-blocks/grc/blocks_ctrlport_performance.xml
new file mode 100644
index 0000000000..ab17c9e263
--- /dev/null
+++ b/gr-blocks/grc/blocks_ctrlport_performance.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+
+<!--
+ 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.
+-->
+
+<block>
+ <name>CtrlPort Performance Monitor</name>
+ <key>blocks_ctrlport_monitor_performance</key>
+ <import>from gnuradio.ctrlport.monitor import *</import>
+ <make>not $en or monitor("gr-perf-monitorx")</make>
+ <param>
+ <name>Enabled</name>
+ <key>en</key>
+ <value></value>
+ <type>enum</type>
+ <option>
+ <name>True</name>
+ <key>True</key>
+ </option>
+ <option>
+ <name>False</name>
+ <key>False</key>
+ </option>
+ </param>
+
+ <doc>
+ Place this in a graph to launch a QtPy GR CtrlPort Performance Monitor app.
+ </doc>
+
+</block>
diff --git a/gr-blocks/grc/blocks_ctrlport_viewer.xml b/gr-blocks/grc/blocks_ctrlport_viewer.xml
new file mode 100644
index 0000000000..1d5a2e5931
--- /dev/null
+++ b/gr-blocks/grc/blocks_ctrlport_viewer.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+
+<!--
+ 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.
+-->
+
+<block>
+ <name>CtrlPort Monitor</name>
+ <key>blocks_ctrlport_monitor</key>
+ <import>from gnuradio.ctrlport.monitor import *</import>
+ <make>not $en or monitor()</make>
+ <param>
+ <name>Enabled</name>
+ <key>en</key>
+ <value></value>
+ <type>enum</type>
+ <option>
+ <name>True</name>
+ <key>True</key>
+ </option>
+ <option>
+ <name>False</name>
+ <key>False</key>
+ </option>
+ </param>
+
+ <doc>
+ Place this in a graph to launch a QtPy GR CtrlPort Monitor app.
+ </doc>
+
+</block>
diff --git a/gr-blocks/lib/ctrlport_probe2_b_impl.cc b/gr-blocks/lib/ctrlport_probe2_b_impl.cc
index e6e2570ea6..3cf2ae0167 100644
--- a/gr-blocks/lib/ctrlport_probe2_b_impl.cc
+++ b/gr-blocks/lib/ctrlport_probe2_b_impl.cc
@@ -63,26 +63,9 @@ namespace gr {
ninput_items_required[i] = d_len;
}
- // boost::shared_mutex mutex_buffer;
- // mutable boost::mutex mutex_notify;
- // boost::condition_variable condition_buffer_ready;
std::vector<signed char>
- ctrlport_probe2_b_impl::get()
- {
- mutex_buffer.lock();
- d_buffer.clear();
- mutex_buffer.unlock();
-
- // wait for condition
- boost::mutex::scoped_lock lock(mutex_notify);
- condition_buffer_ready.wait(lock);
-
- mutex_buffer.lock();
- std::vector<signed char> buf_copy = d_buffer;
- assert(buf_copy.size() == d_len);
- mutex_buffer.unlock();
-
- return buf_copy;
+ ctrlport_probe2_b_impl::get() {
+ return buffered_get.get();
}
void
@@ -112,7 +95,6 @@ namespace gr {
const char *in = (const char*)input_items[0];
// copy samples to get buffer if we need samples
- mutex_buffer.lock();
if(d_buffer.size() < d_len) {
// copy smaller of remaining buffer space and num inputs to work()
int num_copy = std::min( (int)(d_len - d_buffer.size()), noutput_items );
@@ -121,13 +103,12 @@ namespace gr {
for(int i = 0; i < num_copy; i++) {
d_buffer.push_back(in[i]);
}
+ }
- // notify the waiting get() if we fill up the buffer
- if(d_buffer.size() == d_len) {
- condition_buffer_ready.notify_one();
- }
+ // notify the waiting get() if we fill up the buffer
+ if(d_buffer.size() == d_len) {
+ buffered_get.offer_data(d_buffer);
}
- mutex_buffer.unlock();
return noutput_items;
}
diff --git a/gr-blocks/lib/ctrlport_probe2_b_impl.h b/gr-blocks/lib/ctrlport_probe2_b_impl.h
index 155dd4c23c..490af9b040 100644
--- a/gr-blocks/lib/ctrlport_probe2_b_impl.h
+++ b/gr-blocks/lib/ctrlport_probe2_b_impl.h
@@ -25,7 +25,7 @@
#include <gnuradio/blocks/ctrlport_probe2_b.h>
#include <gnuradio/rpcregisterhelpers.h>
-#include <boost/thread/shared_mutex.hpp>
+#include <gnuradio/rpcbufferedget.h>
namespace gr {
namespace blocks {
@@ -37,11 +37,9 @@ namespace gr {
std::string d_desc;
size_t d_len;
unsigned int d_disp_mask;
- boost::shared_mutex mutex_buffer;
- mutable boost::mutex mutex_notify;
- boost::condition_variable condition_buffer_ready;
std::vector<signed char> d_buffer;
+ rpcbufferedget< std::vector<signed char> > buffered_get;
public:
ctrlport_probe2_b_impl(const std::string &id, const std::string &desc,
diff --git a/gr-blocks/lib/ctrlport_probe2_c_impl.cc b/gr-blocks/lib/ctrlport_probe2_c_impl.cc
index aa038c0765..bd43130be5 100644
--- a/gr-blocks/lib/ctrlport_probe2_c_impl.cc
+++ b/gr-blocks/lib/ctrlport_probe2_c_impl.cc
@@ -64,26 +64,9 @@ namespace gr {
ninput_items_required[i] = d_len;
}
- // boost::shared_mutex mutex_buffer;
- // mutable boost::mutex mutex_notify;
- // boost::condition_variable condition_buffer_ready;
std::vector<gr_complex>
- ctrlport_probe2_c_impl::get()
- {
- mutex_buffer.lock();
- d_buffer.clear();
- mutex_buffer.unlock();
-
- // wait for condition
- boost::mutex::scoped_lock lock(mutex_notify);
- condition_buffer_ready.wait(lock);
-
- mutex_buffer.lock();
- std::vector<gr_complex> buf_copy = d_buffer;
- assert(buf_copy.size() == d_len);
- mutex_buffer.unlock();
-
- return buf_copy;
+ ctrlport_probe2_c_impl::get() {
+ return buffered_get.get();
}
void
@@ -113,7 +96,6 @@ namespace gr {
const gr_complex *in = (const gr_complex*)input_items[0];
// copy samples to get buffer if we need samples
- mutex_buffer.lock();
if(d_buffer.size() < d_len) {
// copy smaller of remaining buffer space and num inputs to work()
int num_copy = std::min( (int)(d_len - d_buffer.size()), noutput_items );
@@ -122,13 +104,12 @@ namespace gr {
for(int i = 0; i < num_copy; i++) {
d_buffer.push_back(in[i]);
}
+ }
- // notify the waiting get() if we fill up the buffer
- if(d_buffer.size() == d_len) {
- condition_buffer_ready.notify_one();
- }
+ // notify the waiting get() if we fill up the buffer
+ if(d_buffer.size() == d_len) {
+ buffered_get.offer_data(d_buffer);
}
- mutex_buffer.unlock();
return noutput_items;
}
diff --git a/gr-blocks/lib/ctrlport_probe2_c_impl.h b/gr-blocks/lib/ctrlport_probe2_c_impl.h
index 15ff0f4ea2..fa74216202 100644
--- a/gr-blocks/lib/ctrlport_probe2_c_impl.h
+++ b/gr-blocks/lib/ctrlport_probe2_c_impl.h
@@ -25,7 +25,7 @@
#include <gnuradio/blocks/ctrlport_probe2_c.h>
#include <gnuradio/rpcregisterhelpers.h>
-#include <boost/thread/shared_mutex.hpp>
+#include <gnuradio/rpcbufferedget.h>
namespace gr {
namespace blocks {
@@ -37,11 +37,9 @@ namespace gr {
std::string d_desc;
size_t d_len;
unsigned int d_disp_mask;
- boost::shared_mutex mutex_buffer;
- mutable boost::mutex mutex_notify;
- boost::condition_variable condition_buffer_ready;
std::vector<gr_complex> d_buffer;
+ rpcbufferedget< std::vector<gr_complex> > buffered_get;
public:
ctrlport_probe2_c_impl(const std::string &id, const std::string &desc,
@@ -66,4 +64,3 @@ namespace gr {
} /* namespace gr */
#endif /* INCLUDED_CTRLPORT_PROBE2_C_IMPL_H */
-
diff --git a/gr-blocks/lib/ctrlport_probe2_f_impl.cc b/gr-blocks/lib/ctrlport_probe2_f_impl.cc
index b53b2dc3cb..05d67da9dd 100644
--- a/gr-blocks/lib/ctrlport_probe2_f_impl.cc
+++ b/gr-blocks/lib/ctrlport_probe2_f_impl.cc
@@ -62,26 +62,9 @@ namespace gr {
ninput_items_required[i] = d_len;
}
- // boost::shared_mutex mutex_buffer;
- // mutable boost::mutex mutex_notify;
- // boost::condition_variable condition_buffer_ready;
std::vector<float>
- ctrlport_probe2_f_impl::get()
- {
- mutex_buffer.lock();
- d_buffer.clear();
- mutex_buffer.unlock();
-
- // wait for condition
- boost::mutex::scoped_lock lock(mutex_notify);
- condition_buffer_ready.wait(lock);
-
- mutex_buffer.lock();
- std::vector<float> buf_copy = d_buffer;
- assert(buf_copy.size() == d_len);
- mutex_buffer.unlock();
-
- return buf_copy;
+ ctrlport_probe2_f_impl::get() {
+ return buffered_get.get();
}
void
@@ -111,7 +94,6 @@ namespace gr {
const float *in = (const float*)input_items[0];
// copy samples to get buffer if we need samples
- mutex_buffer.lock();
if(d_buffer.size() < d_len) {
// copy smaller of remaining buffer space and num inputs to work()
int num_copy = std::min( (int)(d_len - d_buffer.size()), noutput_items );
@@ -120,13 +102,13 @@ namespace gr {
for(int i = 0; i < num_copy; i++) {
d_buffer.push_back(in[i]);
}
+ }
- // notify the waiting get() if we fill up the buffer
- if(d_buffer.size() == d_len) {
- condition_buffer_ready.notify_one();
- }
+
+ // notify the waiting get() if we fill up the buffer
+ if(d_buffer.size() == d_len) {
+ buffered_get.offer_data(d_buffer);
}
- mutex_buffer.unlock();
return noutput_items;
}
diff --git a/gr-blocks/lib/ctrlport_probe2_f_impl.h b/gr-blocks/lib/ctrlport_probe2_f_impl.h
index a4aa099237..8d406db927 100644
--- a/gr-blocks/lib/ctrlport_probe2_f_impl.h
+++ b/gr-blocks/lib/ctrlport_probe2_f_impl.h
@@ -25,7 +25,7 @@
#include <gnuradio/blocks/ctrlport_probe2_f.h>
#include <gnuradio/rpcregisterhelpers.h>
-#include <boost/thread/shared_mutex.hpp>
+#include <gnuradio/rpcbufferedget.h>
namespace gr {
namespace blocks {
@@ -37,11 +37,9 @@ namespace gr {
std::string d_desc;
size_t d_len;
unsigned int d_disp_mask;
- boost::shared_mutex mutex_buffer;
- mutable boost::mutex mutex_notify;
- boost::condition_variable condition_buffer_ready;
std::vector<float> d_buffer;
+ rpcbufferedget< std::vector<float> > buffered_get;
public:
ctrlport_probe2_f_impl(const std::string &id, const std::string &desc,
@@ -66,4 +64,3 @@ namespace gr {
} /* namespace gr */
#endif /* INCLUDED_CTRLPORT_PROBE2_F_IMPL_H */
-
diff --git a/gr-blocks/lib/ctrlport_probe2_i_impl.cc b/gr-blocks/lib/ctrlport_probe2_i_impl.cc
index 77dca2ad0d..086ebe7cf0 100644
--- a/gr-blocks/lib/ctrlport_probe2_i_impl.cc
+++ b/gr-blocks/lib/ctrlport_probe2_i_impl.cc
@@ -64,26 +64,9 @@ namespace gr {
ninput_items_required[i] = d_len;
}
- // boost::shared_mutex mutex_buffer;
- // mutable boost::mutex mutex_notify;
- // boost::condition_variable condition_buffer_ready;
std::vector<int>
- ctrlport_probe2_i_impl::get()
- {
- mutex_buffer.lock();
- d_buffer.clear();
- mutex_buffer.unlock();
-
- // wait for condition
- boost::mutex::scoped_lock lock(mutex_notify);
- condition_buffer_ready.wait(lock);
-
- mutex_buffer.lock();
- std::vector<int> buf_copy = d_buffer;
- assert(buf_copy.size() == d_len);
- mutex_buffer.unlock();
-
- return buf_copy;
+ ctrlport_probe2_i_impl::get() {
+ return buffered_get.get();
}
void
@@ -111,9 +94,7 @@ namespace gr {
gr_vector_void_star &output_items)
{
const int *in = (const int*)input_items[0];
-
// copy samples to get buffer if we need samples
- mutex_buffer.lock();
if(d_buffer.size() < d_len) {
// copy smaller of remaining buffer space and num inputs to work()
int num_copy = std::min( (int)(d_len - d_buffer.size()), noutput_items );
@@ -122,13 +103,12 @@ namespace gr {
for(int i = 0; i < num_copy; i++) {
d_buffer.push_back(in[i]);
}
+ }
- // notify the waiting get() if we fill up the buffer
- if(d_buffer.size() == d_len) {
- condition_buffer_ready.notify_one();
- }
+ // notify the waiting get() if we fill up the buffer
+ if(d_buffer.size() == d_len) {
+ buffered_get.offer_data(d_buffer);
}
- mutex_buffer.unlock();
return noutput_items;
}
diff --git a/gr-blocks/lib/ctrlport_probe2_i_impl.h b/gr-blocks/lib/ctrlport_probe2_i_impl.h
index 06493ac23a..3a976550eb 100644
--- a/gr-blocks/lib/ctrlport_probe2_i_impl.h
+++ b/gr-blocks/lib/ctrlport_probe2_i_impl.h
@@ -25,7 +25,7 @@
#include <gnuradio/blocks/ctrlport_probe2_i.h>
#include <gnuradio/rpcregisterhelpers.h>
-#include <boost/thread/shared_mutex.hpp>
+#include <gnuradio/rpcbufferedget.h>
namespace gr {
namespace blocks {
@@ -37,11 +37,9 @@ namespace gr {
std::string d_desc;
size_t d_len;
unsigned int d_disp_mask;
- boost::shared_mutex mutex_buffer;
- mutable boost::mutex mutex_notify;
- boost::condition_variable condition_buffer_ready;
std::vector<int> d_buffer;
+ rpcbufferedget< std::vector<int> > buffered_get;
public:
ctrlport_probe2_i_impl(const std::string &id, const std::string &desc,
@@ -66,4 +64,3 @@ namespace gr {
} /* namespace gr */
#endif /* INCLUDED_CTRLPORT_PROBE2_I_IMPL_H */
-
diff --git a/gr-blocks/lib/ctrlport_probe2_s_impl.cc b/gr-blocks/lib/ctrlport_probe2_s_impl.cc
index 6a4ade462c..d6a15faaef 100644
--- a/gr-blocks/lib/ctrlport_probe2_s_impl.cc
+++ b/gr-blocks/lib/ctrlport_probe2_s_impl.cc
@@ -64,26 +64,9 @@ namespace gr {
ninput_items_required[i] = d_len;
}
- // boost::shared_mutex mutex_buffer;
- // mutable boost::mutex mutex_notify;
- // boost::condition_variable condition_buffer_ready;
std::vector<short>
- ctrlport_probe2_s_impl::get()
- {
- mutex_buffer.lock();
- d_buffer.clear();
- mutex_buffer.unlock();
-
- // wait for condition
- boost::mutex::scoped_lock lock(mutex_notify);
- condition_buffer_ready.wait(lock);
-
- mutex_buffer.lock();
- std::vector<short> buf_copy = d_buffer;
- assert(buf_copy.size() == d_len);
- mutex_buffer.unlock();
-
- return buf_copy;
+ ctrlport_probe2_s_impl::get() {
+ return buffered_get.get();
}
void
@@ -113,7 +96,6 @@ namespace gr {
const short *in = (const short*)input_items[0];
// copy samples to get buffer if we need samples
- mutex_buffer.lock();
if(d_buffer.size() < d_len) {
// copy smaller of remaining buffer space and num inputs to work()
int num_copy = std::min( (int)(d_len - d_buffer.size()), noutput_items );
@@ -122,13 +104,12 @@ namespace gr {
for(int i = 0; i < num_copy; i++) {
d_buffer.push_back(in[i]);
}
+ }
- // notify the waiting get() if we fill up the buffer
- if(d_buffer.size() == d_len) {
- condition_buffer_ready.notify_one();
- }
+ // notify the waiting get() if we fill up the buffer
+ if(d_buffer.size() == d_len) {
+ buffered_get.offer_data(d_buffer);
}
- mutex_buffer.unlock();
return noutput_items;
}
diff --git a/gr-blocks/lib/ctrlport_probe2_s_impl.h b/gr-blocks/lib/ctrlport_probe2_s_impl.h
index 078dd56b73..49533ced6f 100644
--- a/gr-blocks/lib/ctrlport_probe2_s_impl.h
+++ b/gr-blocks/lib/ctrlport_probe2_s_impl.h
@@ -25,7 +25,7 @@
#include <gnuradio/blocks/ctrlport_probe2_s.h>
#include <gnuradio/rpcregisterhelpers.h>
-#include <boost/thread/shared_mutex.hpp>
+#include <gnuradio/rpcbufferedget.h>
namespace gr {
namespace blocks {
@@ -37,11 +37,9 @@ namespace gr {
std::string d_desc;
size_t d_len;
unsigned int d_disp_mask;
- boost::shared_mutex mutex_buffer;
- mutable boost::mutex mutex_notify;
- boost::condition_variable condition_buffer_ready;
std::vector<short> d_buffer;
+ rpcbufferedget< std::vector<short> > buffered_get;
public:
ctrlport_probe2_s_impl(const std::string &id, const std::string &desc,
@@ -66,4 +64,3 @@ namespace gr {
} /* namespace gr */
#endif /* INCLUDED_CTRLPORT_PROBE2_S_IMPL_H */
-
diff --git a/gr-blocks/python/blocks/CMakeLists.txt b/gr-blocks/python/blocks/CMakeLists.txt
index 44977313cf..19d808b1dd 100644
--- a/gr-blocks/python/blocks/CMakeLists.txt
+++ b/gr-blocks/python/blocks/CMakeLists.txt
@@ -43,14 +43,14 @@ if(ENABLE_TESTING)
include(GrTest)
file(GLOB py_qa_test_files "qa_*.py")
- # Force out the controlport QA tests if we've disabled it.
- if(NOT ENABLE_GR_CTRLPORT)
+ # Force out the controlport QA tests if we have no backends to use.
+ if(CTRLPORT_BACKENDS EQUAL 0)
list(REMOVE_ITEM py_qa_test_files
${CMAKE_CURRENT_SOURCE_DIR}/qa_cpp_py_binding.py
${CMAKE_CURRENT_SOURCE_DIR}/qa_cpp_py_binding_set.py
${CMAKE_CURRENT_SOURCE_DIR}/qa_ctrlport_probes.py
)
- endif(NOT ENABLE_GR_CTRLPORT)
+ endif(CTRLPORT_BACKENDS EQUAL 0)
foreach(py_qa_test_file ${py_qa_test_files})
get_filename_component(py_qa_test_name ${py_qa_test_file} NAME_WE)
diff --git a/gr-blocks/python/blocks/qa_cpp_py_binding.py b/gr-blocks/python/blocks/qa_cpp_py_binding.py
new file mode 100644
index 0000000000..23a5c9b826
--- /dev/null
+++ b/gr-blocks/python/blocks/qa_cpp_py_binding.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+#
+# Copyright 2012,2013,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.
+#
+
+#
+# This program tests mixed python and c++ ctrlport exports in a single app
+#
+
+import sys, time, random, numpy, re
+from gnuradio import gr, gr_unittest, blocks
+
+from gnuradio.ctrlport import GNURadio
+from gnuradio import ctrlport
+import os
+
+def get1():
+ return "success"
+
+def get2():
+ return "failure"
+
+class inc_class:
+ def __init__(self):
+ self.val = 1
+ def pp(self):
+ self.val = self.val+1
+ return self.val
+
+get3 = inc_class()
+
+def get4():
+ random.seed(0)
+ rv = random.random()
+ return rv
+
+def get5():
+ numpy.random.seed(0)
+ samp_t = numpy.random.randn(24)+1j*numpy.random.randn(24);
+ samp_f = numpy.fft.fft(samp_t);
+ log_pow_f = 20*numpy.log10(numpy.abs(samp_f))
+ rv = list(log_pow_f)
+ return rv;
+
+def get6():
+ numpy.random.seed(0)
+ samp_t = numpy.random.randn(1024)+1j*numpy.random.randn(1024);
+ rv = list(samp_t)
+ return rv;
+
+class test_cpp_py_binding(gr_unittest.TestCase):
+
+ def setUp(self):
+ self.tb = gr.top_block()
+ os.environ['GR_CONF_CONTROLPORT_ON'] = 'True'
+
+ def tearDown(self):
+ self.tb = None
+
+ def test_001(self):
+ v1 = gr.RPC_get_string("pyland", "v1", "unit_1_string",
+ "Python Exported String", "", "", "",
+ gr.DISPNULL)
+ v1.activate(get1)
+
+ v2 = gr.RPC_get_string("pyland", "v2", "unit_2_string",
+ "Python Exported String", "", "", "",
+ gr.DISPNULL)
+ v2.activate(get2)
+
+ v3 = gr.RPC_get_int("pyland", "v3", "unit_3_int",
+ "Python Exported Int", 0, 100, 1,
+ gr.DISPNULL)
+ v3.activate(get3.pp)
+
+ v4 = gr.RPC_get_double("pyland", "time", "unit_4_time_double",
+ "Python Exported Double", 0, 1000, 1,
+ gr.DISPNULL)
+ v4.activate(get4)
+
+ v5 = gr.RPC_get_vector_float("pyland", "fvec", "unit_5_float_vector",
+ "Python Exported Float Vector", [], [], [],
+ gr.DISPTIME | gr.DISPOPTCPLX)
+ v5.activate(get5)
+
+ v6 = gr.RPC_get_vector_gr_complex("pyland", "cvec", "unit_6_gr_complex_vector",
+ "Python Exported Complex Vector", [], [], [],
+ gr.DISPXY | gr.DISPOPTSCATTER)
+ v6.activate(get6)
+
+ # print some variables locally
+ val = get1()
+ rval = v1.get()
+ self.assertEqual(val, rval)
+
+ val = get2()
+ rval = v2.get()
+ self.assertEqual(val, rval)
+
+ val = get3.pp()
+ rval = v3.get()
+ self.assertEqual(val+1, rval)
+
+ val = get4()
+ rval = v4.get()
+ self.assertEqual(val, rval)
+
+ val = get5()
+ rval = v5.get()
+ self.assertComplexTuplesAlmostEqual(val, rval, 5)
+
+ val = get6()
+ rval = v6.get()
+ self.assertComplexTuplesAlmostEqual(val, rval, 5)
+
+ def test_002(self):
+ data = range(1,9)
+
+ self.src = blocks.vector_source_c(data)
+ self.p1 = blocks.ctrlport_probe_c("aaa","C++ exported variable")
+ self.p2 = blocks.ctrlport_probe_c("bbb","C++ exported variable")
+ probe_name = self.p2.alias()
+
+ self.tb.connect(self.src, self.p1)
+ self.tb.connect(self.src, self.p2)
+ self.tb.start()
+
+ # Probes return complex values as list of floats with re, im
+ # Imaginary parts of this data set are 0.
+ expected_result = [1, 2, 3, 4,
+ 5, 6, 7, 8]
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+ hostname = re.search("-h (\S+|\d+\.\d+\.\d+\.\d+)", ep).group(1)
+ portnum = re.search("-p (\d+)", ep).group(1)
+ argv = [None, hostname, portnum]
+
+ # Initialize a simple ControlPort client from endpoint
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ radiosys = GNURadioControlPortClient(argv=argv, rpcmethod='thrift')
+ radio = radiosys.client
+
+ # Get all exported knobs
+ ret = radio.getKnobs([probe_name + "::bbb"])
+ for name in ret.keys():
+ result = ret[name].value
+ self.assertEqual(result, expected_result)
+
+ self.tb.stop()
+
+if __name__ == '__main__':
+ gr_unittest.run(test_cpp_py_binding, "test_cpp_py_binding.xml")
diff --git a/gr-blocks/python/blocks/qa_cpp_py_binding_set.py b/gr-blocks/python/blocks/qa_cpp_py_binding_set.py
new file mode 100644
index 0000000000..5b81de08f9
--- /dev/null
+++ b/gr-blocks/python/blocks/qa_cpp_py_binding_set.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python
+#
+# Copyright 2012,2013,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.
+#
+
+#
+# This program tests mixed python and c++ GRCP sets in a single app
+#
+
+import sys, time, random, numpy, re
+from gnuradio import gr, gr_unittest, blocks
+
+from gnuradio.ctrlport import GNURadio
+from gnuradio import ctrlport
+import os
+
+class inc_class:
+ def __init__(self,val):
+ self.val = val;
+
+ def _get(self):
+ #print "returning get (val = %s)"%(str(self.val));
+ return self.val;
+
+ def _set(self,val):
+ #print "updating val to %s"%(str(val));
+ self.val = val;
+ return;
+
+getset1 = inc_class(10);
+getset2 = inc_class(100.0);
+getset3 = inc_class("test");
+
+class test_cpp_py_binding_set(gr_unittest.TestCase):
+ def setUp(self):
+ self.tb = gr.top_block()
+ os.environ['GR_CONF_CONTROLPORT_ON'] = 'True'
+
+ def tearDown(self):
+ self.tb = None
+
+ def test_001(self):
+
+ g1 = gr.RPC_get_int("pyland", "v1", "unit_1_int",
+ "Python Exported Int", 0, 100, 10,
+ gr.DISPNULL)
+ g1.activate(getset1._get)
+ s1 = gr.RPC_get_int("pyland", "v1", "unit_1_int",
+ "Python Exported Int", 0, 100, 10,
+ gr.DISPNULL)
+ s1.activate(getset1._set)
+ time.sleep(0.01)
+
+ # test int variables
+ getset1._set(21)
+ val = getset1._get()
+ rval = g1.get()
+ self.assertEqual(val, rval)
+
+ g2 = gr.RPC_get_float("pyland", "v2", "unit_2_float",
+ "Python Exported Float", -100, 1000.0, 100.0,
+ gr.DISPNULL)
+ g2.activate(getset2._get)
+ s2 = gr.RPC_get_float("pyland", "v2", "unit_2_float",
+ "Python Exported Float", -100, 1000.0, 100.0,
+ gr.DISPNULL)
+ s2.activate(getset2._set)
+ time.sleep(0.01)
+
+ # test float variables
+ getset2._set(123.456)
+ val = getset2._get()
+ rval = g2.get()
+ self.assertAlmostEqual(val, rval, 4)
+
+ g3 = gr.RPC_get_string("pyland", "v3", "unit_3_string",
+ "Python Exported String", "", "", "",
+ gr.DISPNULL)
+ g3.activate(getset3._get)
+ s3 = gr.RPC_get_string("pyland", "v3", "unit_3_string",
+ "Python Exported String", "", "", "",
+ gr.DISPNULL)
+ s3.activate(getset3._set)
+ time.sleep(0.01)
+
+ # test string variables
+ getset3._set("third test")
+ val = getset3._get()
+ rval = g3.get()
+ self.assertEqual(val, rval)
+
+
+ def test_002(self):
+ data = range(1, 10)
+
+ self.src = blocks.vector_source_c(data, True)
+ self.p = blocks.nop(gr.sizeof_gr_complex)
+ self.p.set_ctrlport_test(0);
+ probe_info = self.p.alias()
+
+ self.tb.connect(self.src, self.p)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+ hostname = re.search("-h (\S+|\d+\.\d+\.\d+\.\d+)", ep).group(1)
+ portnum = re.search("-p (\d+)", ep).group(1)
+ argv = [None, hostname, portnum]
+
+ # Initialize a simple ControlPort client from endpoint
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ radiosys = GNURadioControlPortClient(argv=argv, rpcmethod='thrift')
+ radio = radiosys.client
+
+ self.tb.start()
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get all exported knobs
+ key_name_test = probe_info+"::test"
+ ret = radio.getKnobs([key_name_test,])
+
+ ret[key_name_test].value = 10
+ radio.setKnobs({key_name_test: ret[key_name_test]})
+
+ ret = radio.getKnobs([])
+ result_test = ret[key_name_test].value
+ self.assertEqual(result_test, 10)
+
+ self.tb.stop()
+ self.tb.wait()
+
+if __name__ == '__main__':
+ gr_unittest.run(test_cpp_py_binding_set, "test_cpp_py_binding_set.xml")
diff --git a/gr-blocks/python/blocks/qa_ctrlport_probes.py b/gr-blocks/python/blocks/qa_ctrlport_probes.py
index 91d96010fd..c678846df0 100644
--- a/gr-blocks/python/blocks/qa_ctrlport_probes.py
+++ b/gr-blocks/python/blocks/qa_ctrlport_probes.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright 2013 Free Software Foundation, Inc.
+# Copyright 2013,2015 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -22,33 +22,225 @@
import sys, time, random, numpy
from gnuradio import gr, gr_unittest, blocks
+import os, struct, re
-import os, struct
+from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
class test_ctrlport_probes(gr_unittest.TestCase):
def setUp(self):
- self.tb = gr.top_block()
os.environ['GR_CONF_CONTROLPORT_ON'] = 'True'
+ self.tb = gr.top_block()
def tearDown(self):
self.tb = None
- def xtest_001(self):
- pass
+ def test_001(self):
+ data = range(1,9)
+
+ self.src = blocks.vector_source_c(data, True)
+ self.probe = blocks.ctrlport_probe2_c("samples","Complex",
+ len(data), gr.DISPNULL)
+ probe_name = self.probe.alias()
+
+ self.tb.connect(self.src, self.probe)
+ self.tb.start()
+
+
+ # Probes return complex values as list of floats with re, im
+ # Imaginary parts of this data set are 0.
+ expected_result = [1, 2, 3, 4,
+ 5, 6, 7, 8]
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+ hostname = re.search("-h (\S+|\d+\.\d+\.\d+\.\d+)", ep).group(1)
+ portnum = re.search("-p (\d+)", ep).group(1)
+ argv = [None, hostname, portnum]
+
+ # Initialize a simple ControlPort client from endpoint
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ radiosys = GNURadioControlPortClient(argv=argv, rpcmethod='thrift')
+ radio = radiosys.client
+
+ # Get all exported knobs
+ ret = radio.getKnobs([probe_name + "::samples"])
+ for name in ret.keys():
+ # Get data in probe, which might be offset; find the
+ # beginning and unwrap.
+ result = ret[name].value
+ i = result.index(complex(1.0, 0.0))
+ result = result[i:] + result[0:i]
+ self.assertComplexTuplesAlmostEqual(expected_result, result, 4)
+
+ self.tb.stop()
+ self.tb.wait()
+
def test_002(self):
- pass
+ data = range(1,9)
+
+ self.src = blocks.vector_source_f(data, True)
+ self.probe = blocks.ctrlport_probe2_f("samples","Floats",
+ len(data), gr.DISPNULL)
+ probe_name = self.probe.alias()
+
+ self.tb.connect(self.src, self.probe)
+ self.tb.start()
+
+ expected_result = [1, 2, 3, 4, 5, 6, 7, 8,]
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+ hostname = re.search("-h (\S+|\d+\.\d+\.\d+\.\d+)", ep).group(1)
+ portnum = re.search("-p (\d+)", ep).group(1)
+ argv = [None, hostname, portnum]
+ # Initialize a simple ControlPort client from endpoint
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ radiosys = GNURadioControlPortClient(argv=argv, rpcmethod='thrift')
+ radio = radiosys.client
+
+ # Get all exported knobs
+ ret = radio.getKnobs([probe_name + "::samples"])
+ for name in ret.keys():
+ # Get data in probe, which might be offset; find the
+ # beginning and unwrap.
+ result = ret[name].value
+ i = result.index(1.0)
+ result = result[i:] + result[0:i]
+ self.assertEqual(expected_result, result)
+
+ self.tb.stop()
+ self.tb.wait()
def test_003(self):
- pass
+ data = range(1,9)
+
+ self.src = blocks.vector_source_i(data, True)
+ self.probe = blocks.ctrlport_probe2_i("samples","Integers",
+ len(data), gr.DISPNULL)
+ probe_name = self.probe.alias()
+
+ self.tb.connect(self.src, self.probe)
+ self.tb.start()
+
+ expected_result = [1, 2, 3, 4, 5, 6, 7, 8,]
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+ hostname = re.search("-h (\S+|\d+\.\d+\.\d+\.\d+)", ep).group(1)
+ portnum = re.search("-p (\d+)", ep).group(1)
+ argv = [None, hostname, portnum]
+
+ # Initialize a simple ControlPort client from endpoint
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ radiosys = GNURadioControlPortClient(argv=argv, rpcmethod='thrift')
+ radio = radiosys.client
+
+ # Get all exported knobs
+ ret = radio.getKnobs([probe_name + "::samples"])
+ for name in ret.keys():
+ # Get data in probe, which might be offset; find the
+ # beginning and unwrap.
+ result = ret[name].value
+ i = result.index(1.0)
+ result = result[i:] + result[0:i]
+ self.assertEqual(expected_result, result)
+
+ self.tb.stop()
+ self.tb.wait()
+
def test_004(self):
- pass
+ data = range(1,9)
+
+ self.src = blocks.vector_source_s(data, True)
+ self.probe = blocks.ctrlport_probe2_s("samples","Shorts",
+ len(data), gr.DISPNULL)
+ probe_name = self.probe.alias()
+
+ self.tb.connect(self.src, self.probe)
+ self.tb.start()
+
+ expected_result = [1, 2, 3, 4, 5, 6, 7, 8,]
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+ hostname = re.search("-h (\S+|\d+\.\d+\.\d+\.\d+)", ep).group(1)
+ portnum = re.search("-p (\d+)", ep).group(1)
+ argv = [None, hostname, portnum]
+
+ # Initialize a simple ControlPort client from endpoint
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ radiosys = GNURadioControlPortClient(argv=argv, rpcmethod='thrift')
+ radio = radiosys.client
+
+ # Get all exported knobs
+ ret = radio.getKnobs([probe_name + "::samples"])
+ for name in ret.keys():
+ # Get data in probe, which might be offset; find the
+ # beginning and unwrap.
+ result = ret[name].value
+ i = result.index(1.0)
+ result = result[i:] + result[0:i]
+ self.assertEqual(expected_result, result)
+
+ self.tb.stop()
+ self.tb.wait()
def test_005(self):
- pass
+ data = range(1,9)
+
+ self.src = blocks.vector_source_b(data, True)
+ self.probe = blocks.ctrlport_probe2_b("samples","Bytes",
+ len(data), gr.DISPNULL)
+ probe_name = self.probe.alias()
+
+ self.tb.connect(self.src, self.probe)
+ self.tb.start()
+
+ expected_result = [1, 2, 3, 4, 5, 6, 7, 8,]
+
+ # Make sure we have time for flowgraph to run
+ time.sleep(0.1)
+
+ # Get available endpoint
+ ep = gr.rpcmanager_get().endpoints()[0]
+ hostname = re.search("-h (\S+|\d+\.\d+\.\d+\.\d+)", ep).group(1)
+ portnum = re.search("-p (\d+)", ep).group(1)
+ argv = [None, hostname, portnum]
+
+ # Initialize a simple ControlPort client from endpoint
+ from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
+ radiosys = GNURadioControlPortClient(argv=argv, rpcmethod='thrift')
+ radio = radiosys.client
+
+ # Get all exported knobs
+ ret = radio.getKnobs([probe_name + "::samples"])
+ for name in ret.keys():
+ # Get data in probe, which might be offset; find the
+ # beginning and unwrap.
+ result = ret[name].value
+ result = list(struct.unpack(len(result)*'b', result))
+ i = result.index(1)
+ result = result[i:] + result[0:i]
+ self.assertEqual(expected_result, result)
+
+ self.tb.stop()
+ self.tb.wait()
if __name__ == '__main__':
gr_unittest.run(test_ctrlport_probes, "test_ctrlport_probes.xml")