diff options
author | Johnathan Corgan <johnathan@corganlabs.com> | 2015-04-15 14:03:44 -0700 |
---|---|---|
committer | Johnathan Corgan <johnathan@corganlabs.com> | 2015-04-15 14:03:44 -0700 |
commit | 527c21cb6a3b1b32610c4acf0bec8956ace2c5bf (patch) | |
tree | 5fdb9e755f77d2493d8851f956b074c6f11cb8ce | |
parent | 7765798c48b9ec4b1cda43367e97eb778a8ad758 (diff) | |
parent | b092142302bcf8c771ec68e61da7781eb406c86f (diff) |
Merge branch 'master' into next
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") |