diff options
Diffstat (limited to 'docs/doxygen/other')
-rw-r--r-- | docs/doxygen/other/ctrlport.dox | 280 | ||||
-rw-r--r-- | docs/doxygen/other/extra_pages.dox | 4 | ||||
-rw-r--r-- | docs/doxygen/other/logger.dox | 31 | ||||
-rw-r--r-- | docs/doxygen/other/main_page.dox | 46 | ||||
-rw-r--r-- | docs/doxygen/other/metadata.dox | 45 | ||||
-rw-r--r-- | docs/doxygen/other/msg_passing.dox | 16 | ||||
-rw-r--r-- | docs/doxygen/other/pfb_intro.dox | 14 | ||||
-rw-r--r-- | docs/doxygen/other/pmt.dox | 190 | ||||
-rw-r--r-- | docs/doxygen/other/tagged_stream_blocks.dox | 271 | ||||
-rw-r--r-- | docs/doxygen/other/thread_affinity.dox | 8 |
10 files changed, 741 insertions, 164 deletions
diff --git a/docs/doxygen/other/ctrlport.dox b/docs/doxygen/other/ctrlport.dox new file mode 100644 index 0000000000..69f467b288 --- /dev/null +++ b/docs/doxygen/other/ctrlport.dox @@ -0,0 +1,280 @@ +/*! \page page_ctrlport ControlPort + +\section Introduction + +This is the gr-ctroport package. It is a tool to create distributed +contol applications for GNU Radio. It provides blocks that can be +connected to an output stream to plot the signal remotely. It also +provides an API that allows blocks to export variables that can be +set, monitored, and plotted remotely. + +The Python namespace is in gnuradio.ctrlport, which would be normally +imported as: + +\code + from gnuradio import ctrlport +\endcode + + +See the Doxygen documentation for details about the blocks available +in this package. A quick listing of the details can be found in Python +after importing by using: + +\code + help(ctrlport) +\endcode + +\section Dependencies + +ControlPort requires ZeroC's ICE and associated +libraries/headers/programs. ICE is generally installed into the +standard paths if using a software repo (like apt-get, yum, etc.). If +installed by hand, GNU Radio assumes ICE is installed into +/opt/Ice-3.4.2. If this is not the case, you can tell GNU Radio where +to find ICE by passing to cmake the following: + + -DICE_MANUAL_INSTALL_PATH=\<your path here\> + +\section conf Configuration + +ControlPort is configured using two files. The first is the GNU Radio +preferences configuration while the second file is specific to the +type of transport engine used. Since we are focusing on using ICE, the +configuration file is the ICE configuration file and format. + +The GNU Radio preferences file allows you to enable or disable +ControlPort. If enabled and a configuration file is used, this file +also specifies the location of the configuration file. + +\code + [ControlPort] + on = True + config = ctrlport.conf +\endcode + +The 'ctrlport.conf' holds specific properties related to the transport +engine. If using ICE, more information can be found here: +http://doc.zeroc.com/display/Ice/Properties+and+Configuration + +An example ICE config file is installed with GNU Radio to show how to +change the exposed endpoint of ControlPort. This file is installed +as ${prefix}/etc/gnuradio/ctrlport.conf.example. + + +\section 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. If the block +defines and exports variables using <b>setup_rpc()</b>, then they are +now all available over ControlPort. + +The new block simply declares that it is overloading +<b>setup_rpc()</b> in its header file. In the source file, it defines +any setter and/or getting for a variable it wants. + +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 declaration 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): + gr_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 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 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 gr-ctrlport-monitor 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). + +*/ diff --git a/docs/doxygen/other/extra_pages.dox b/docs/doxygen/other/extra_pages.dox index 94c741864d..8d9b69a87b 100644 --- a/docs/doxygen/other/extra_pages.dox +++ b/docs/doxygen/other/extra_pages.dox @@ -122,11 +122,11 @@ built or installed. The -DENABLE_DEFAULT=False can be used to disable all components. Individual components can then be selectively turned back -on. For example, just buidling the Volk and Gruel libraries can be +on. For example, just buidling the Volk library can be done with this: \code -cmake -DENABLE_DEFAULT=Off -DENABLE_VOLK=True -DENABLE_GRUEL=True <srcdir> +cmake -DENABLE_DEFAULT=Off -DENABLE_VOLK=True <srcdir> \endcode diff --git a/docs/doxygen/other/logger.dox b/docs/doxygen/other/logger.dox index 22458051db..f5228cfc55 100644 --- a/docs/doxygen/other/logger.dox +++ b/docs/doxygen/other/logger.dox @@ -24,8 +24,9 @@ different levels. These levels are: The order here determines the level of output. These levels are hierarchical in that specifying any level also includes any level -above it. For example, when using the WARN level, all WARN and -higher messages are logged while DEBUG and INFO are ignored. +above it. For example, when using the INFO level, all INFO and +higher messages are logged and DEBUG is ignored. A level NOTSET is provided +to disable a logger. \subsection configfile Logging Configuration @@ -143,7 +144,7 @@ provided with GNU Radio can be used. After installation, the default configuration script is located at: <pre> - $prefix/etc/gnuradio/gr_log_default.xml + $prefix/etc/gnuradio/gr_log_default.conf </pre> For the following examples, we will assume that our local @@ -151,12 +152,12 @@ For the following examples, we will assume that our local \code [LOG] -log_config = /opt/gr/etc/gnuadio/gr_log_default.xml +log_config = /opt/gr/etc/gnuadio/gr_log_default.conf log_level = debug debug_level = Off \endcode -Inside of the XML default configuration file, we define the parameters +Inside of the default configuration file, we define the parameters for the two logger's, the standard logger the separate debug logger. If the levels of the two loggers are specified in our configuration @@ -202,4 +203,24 @@ This creates a pointer called LOG (which is instantiated as a log4cpp:LoggerPtr in the macro) that we can now use locally as the input to our logging macros like 'GR_LOG_INFO(LOG, "message")'. +\section logPy Logging from Python + +The logging capability has been brought out python via swig. The configuration +of the logger can be manipulated via the following calls: +\code + from gnuradio import gr + gr.logger_config(filename,watch_period) # Configures the logger with conf file filename + names = gr.logger_get_logger_names() # Returns the names of all loggers + gr.logger_reset_config() # Resets logger config by removing all appenders +\endcode + +Once the logger is configured you can manipulate a logger via a wrapper class gr.logger(). +You can isntantiate this by the following. (Reference gr_logger.h for list of methods) +\code + from gnuradio import gr + log=gr.logger("nameOfLogger") + log.debug("Log a debug message") + log.set_level("INFO"); + +\endcode */ diff --git a/docs/doxygen/other/main_page.dox b/docs/doxygen/other/main_page.dox index 9353bfb315..3ad9e68021 100644 --- a/docs/doxygen/other/main_page.dox +++ b/docs/doxygen/other/main_page.dox @@ -70,7 +70,7 @@ done. A single source and sink are used with a FIR filter between them. \code - from gnuradio import gr, filter + from gnuradio import gr, filter, analog class my_topblock(gr.top_block): def __init__(self): @@ -79,9 +79,9 @@ them. amp = 1 taps = filter.firdes.low_pass(1, 1, 0.1, 0.01) - self.src = gr.noise_source_c(gr.GR_GAUSSIAN, amp) + self.src = analog.noise_source_c(gr.GR_GAUSSIAN, amp) self.flt = filter.fir_filter_ccf(1, taps) - self.snk = gr.null_sink(gr.sizeof_gr_complex) + self.snk = blocks.null_sink(gr.sizeof_gr_complex) self.connect(self.src, self.flt, self.snk) @@ -203,7 +203,7 @@ buffer, which may be different than what was initially specified. It is possible to reconfigure the flowgraph at runtime. The reconfiguration is meant for changes in the flowgraph structure, not individual parameter settings of the blocks. For example, changing the -constant in a gr_add_const_cc block can be done while the flowgraph is +constant in a gr::blocks::add_const_cc block can be done while the flowgraph is running using the 'set_k(k)' method. Reconfiguration is done by locking the flowgraph, which stops it from @@ -211,23 +211,24 @@ running and processing data, performing the reconfiguration, and then restarting the graph by unlocking it. The following example code shows a graph that first adds two -gr_noise_source_c blocks and then replaces the gr_add_cc block with a -gr_sub_cc block to then subtract the sources. +gr::analog::noise_source_c blocks and then replaces the +gr::blocks::add_cc block with a gr::blocks::sub_cc block to then +subtract the sources. \code -from gnuradio import gr +from gnuradio import gr, analog, blocks import time class mytb(gr.top_block): def __init__(self): gr.top_block.__init__(self) - self.src0 = gr.noise_source_c(gr.GR_GAUSSIAN, 1) - self.src1 = gr.noise_source_c(gr.GR_GAUSSIAN, 1) - self.add = gr.add_cc() - self.sub = gr.sub_cc() - self.head = gr.head(gr.sizeof_gr_complex, 1000000) - self.snk = gr.file_sink(gr.sizeof_gr_complex, "output.32fc") + self.src0 = analog.noise_source_c(analog.GR_GAUSSIAN, 1) + self.src1 = analog.noise_source_c(analog.GR_GAUSSIAN, 1) + self.add = blocks.add_cc() + self.sub = blocks.sub_cc() + self.head = blocks.head(gr.sizeof_gr_complex, 1000000) + self.snk = blocks.file_sink(gr.sizeof_gr_complex, "output.32fc") self.connect(self.src0, (self.add,0)) self.connect(self.src1, (self.add,1)) @@ -270,19 +271,19 @@ The following example expands the previous example but sets and resets the max noutput_items both locally and globally. \code -from gnuradio import gr +from gnuradio import gr, analog, blocks import time class mytb(gr.top_block): def __init__(self): gr.top_block.__init__(self) - self.src0 = gr.noise_source_c(gr.GR_GAUSSIAN, 1) - self.src1 = gr.noise_source_c(gr.GR_GAUSSIAN, 1) - self.add = gr.add_cc() - self.sub = gr.sub_cc() - self.head = gr.head(gr.sizeof_gr_complex, 1000000) - self.snk = gr.file_sink(gr.sizeof_gr_complex, "output.32fc") + self.src0 = analog.noise_source_c(analog.GR_GAUSSIAN, 1) + self.src1 = analog.noise_source_c(analog.GR_GAUSSIAN, 1) + self.add = blocks.add_cc() + self.sub = blocks.sub_cc() + self.head = blocks.head(gr.sizeof_gr_complex, 1000000) + self.snk = blocks.file_sink(gr.sizeof_gr_complex, "output.32fc") self.connect(self.src0, (self.add,0)) self.connect(self.src1, (self.add,1)) @@ -327,8 +328,9 @@ The \ref volk_guide page provides an overview of how to incorporate and use Volk in GNU Radio blocks. Many blocks have already been converted to use Volk in their calls, so -they can also serve as examples. See the gr_complex_to_xxx.h file for -examples of various blocks that make use of Volk. +they can also serve as examples. See the +gr::blocks::complex_to_<type>.h files for examples of various blocks +that make use of Volk. \section prefs Configuration / Preference Files diff --git a/docs/doxygen/other/metadata.dox b/docs/doxygen/other/metadata.dox index b527b21008..03ebe591e4 100644 --- a/docs/doxygen/other/metadata.dox +++ b/docs/doxygen/other/metadata.dox @@ -106,7 +106,7 @@ interface. The file metadata consists of a static mandatory header and a dynamic optional extras header. Each header is a separate PMT dictionary. Headers are created by building a PMT dictionary -(pmt::pmt_make_dict) of key:value pairs, then the dictionary is +(pmt::make_dict) of key:value pairs, then the dictionary is serialized into a string to be written to file. The header is always the same length that is predetermined by the version of the header (this must be known already). The header will then indicate if there @@ -117,27 +117,26 @@ we use PMT operators. For example, we create a simplified version of the header in C++ like this: \code - using namespace pmt; const char METADATA_VERSION = 0x0; - pmt_t header; - header = pmt_make_dict(); - header = pmt_dict_add(header, mp("version"), mp(METADATA_VERSION)); - header = pmt_dict_add(header, mp("rx_rate"), mp(samp_rate)); - std::string hdr_str = pmt_serialize_str(header); + pmt::pmt_t header; + header = pmt::make_dict(); + header = pmt::dict_add(header, pmt::mp("version"), pmt::mp(METADATA_VERSION)); + header = pmt::dict_add(header, pmt::mp("rx_rate"), pmt::mp(samp_rate)); + std::string hdr_str = pmt::serialize_str(header); \endcode -The call to pmt::pmt_dict_add adds a new key:value pair to the +The call to pmt::dict_add adds a new key:value pair to the dictionary. Notice that it both takes and returns the 'header' variable. This is because we are actually creating a new dictionary with this function, so we just assign it to the same variable. The 'mp' functions are convenience functions provided by the PMT library. They interpret the data type of the value being inserted and -call the correct 'pmt_from_xxx' function. For more direct control over +call the correct 'pmt::from_xxx' function. For more direct control over the data type, see PMT functions in pmt.h, such as -pmt::pmt_from_uint64 or pmt::pmt_from_double. +pmt::from_uint64 or pmt::from_double. -We finish this off by using pmt::pmt_serialize_str to convert the PMT +We finish this off by using pmt::serialize_str to convert the PMT dictionary into a specialized string format that makes it easy to write to a file. @@ -153,10 +152,10 @@ Assuming that 'std::string str' contains the full string as read from a file, we can access the dictionary in C++ like this: \code - pmt_t hdr = pmt_deserialize_str(str); - if(pmt_dict_has_key(hdr, pmt_string_to_symbol("strt"))) { - pmt_t r = pmt_dict_ref(hdr, pmt_string_to_symbol("strt"), PMT_NIL); - uint64_t seg_start = pmt_to_uint64(r); + pmt::pmt_t hdr = pmt::deserialize_str(str); + if(pmt::dict_has_key(hdr, pmt::string_to_symbol("strt"))) { + pmt::pmt_t r = pmt::dict_ref(hdr, pmt::string_to_symbol("strt"), pmt::PMT_NIL); + uint64_t seg_start = pmt::to_uint64(r); uint64_t extra_len = seg_start - METADATA_HEADER_SIZE; } \endcode @@ -221,7 +220,7 @@ a PMT dictionary of key:value pairs. The extras header can contain anything and can grow while a program is running. We can insert extra data into the header at the beginning if we -wish. All we need to do is use the pmt::pmt_dict_add function to insert +wish. All we need to do is use the pmt::dict_add function to insert our hand-made metadata. This can be useful to add our own markers and information. @@ -239,7 +238,7 @@ When reading out data from the extras, we do not necessarily know the data type of the PMT value. The key is always a PMT symbol, but the value can be any other PMT type. There are PMT functions that allow us to query the PMT to test if it is a particular type. We also have the -ability to do pmt::pmt_print on any PMT object to print it to +ability to do pmt::print on any PMT object to print it to screen. Before converting from a PMT to it's natural data type, it is necessary to know the data type. @@ -310,15 +309,15 @@ into the metadata to keep track of later. The date in this case is encoded as a vector of uint16 with [day, month, year]. \code - from gruel import pmt + import pmt from gnuradio import blocks - key = pmt.pmt_intern("date") - val = pmt.pmt_init_u16vector(3, [13,12,2012]) + key = pmt.intern("date") + val = pmt.init_u16vector(3, [13,12,2012]) - extras = pmt.pmt_make_dict() - extras = pmt.pmt_dict_add(extras, key, val) - extras_str = pmt.pmt_serialize_str(extras) + extras = pmt.make_dict() + extras = pmt.dict_add(extras, key, val) + extras_str = pmt.serialize_str(extras) self.sink = blocks.file_meta_sink(gr.sizeof_gr_complex, "/tmp/metadat_file.out", samp_rate, 1, diff --git a/docs/doxygen/other/msg_passing.dox b/docs/doxygen/other/msg_passing.dox index aea0ac94ae..7035f291e4 100644 --- a/docs/doxygen/other/msg_passing.dox +++ b/docs/doxygen/other/msg_passing.dox @@ -83,7 +83,7 @@ received by \b dbg on port \a print. Note here how we are just using strings to define the ports, not PMT symbols. This is a convenience to the user to be able to more easily type in the port names (for reference, you can create a PMT symbol in Python using the -pmt::pmt_intern function as pmt.pmt_intern("string")). +pmt::intern function as pmt.intern("string")). Users can also query blocks for the names of their input and output ports using the following API calls: @@ -157,13 +157,13 @@ void gr_message_debug::print(pmt::pmt_t msg) { std::cout << "***** MESSAGE DEBUG PRINT ********\n"; - pmt::pmt_print(msg); + pmt::print(msg); std::cout << "**********************************\n"; } \endcode The function simply takes in the PMT message and prints it. The method -pmt::pmt_print is a function in the PMT library to print the +pmt::print is a function in the PMT library to print the PMT in a friendly, (mostly) pretty manner. The gr_tagged_stream_to_pdu block only defines a single @@ -196,11 +196,11 @@ gr_tagged_stream_to_pdu::send_message function that is shown below. void gr_tagged_stream_to_pdu::send_meassage() { - if(pmt::pmt_length(d_pdu_vector) != d_pdu_length) { + if(pmt::length(d_pdu_vector) != d_pdu_length) { throw std::runtime_error("msg length not correct"); } - pmt::pmt_t msg = pmt::pmt_cons(d_pdu_meta, + pmt::pmt_t msg = pmt::cons(d_pdu_meta, d_pdu_vector); message_port_pub(pdu_port_id, msg); @@ -247,9 +247,9 @@ flowgraph. These PDUs could come from another block or flowgraph, but here, we will create and insert them by hand. \code - port = pmt.pmt_intern("pdus") - msg = pmt.pmt_cons(pmt.PMT_NIL, - pmt.pmt_make_u8vector(16, 0xFF)) + port = pmt.intern("pdus") + msg = pmt.cons(pmt.PMT_NIL, + pmt.make_u8vector(16, 0xFF)) src.to_basic_block()._post(port, msg) \endcode diff --git a/docs/doxygen/other/pfb_intro.dox b/docs/doxygen/other/pfb_intro.dox index 2d285f0027..d3c3ee63be 100644 --- a/docs/doxygen/other/pfb_intro.dox +++ b/docs/doxygen/other/pfb_intro.dox @@ -29,9 +29,9 @@ filter that is then split up between the \p N channels of the PFB. self._fs = 9000 # input sample rate self._M = 9 # Number of channels to channelize - self._taps = gr.firdes.low_pass_2(1, self._fs, 475.50, 50, - attenuation_dB=100, - window=gr.firdes.WIN_BLACKMAN_hARRIS) + self._taps = filter.firdes.low_pass_2(1, self._fs, 475.50, 50, + attenuation_dB=100, + window=filter.firdes.WIN_BLACKMAN_hARRIS) \endcode In this example, the signal into the channelizer is sampled at 9 ksps @@ -63,9 +63,9 @@ defined to use a sample rate of \p filter_size times the signal's sampling rate. A helpful wrapper for the arbitrary resampler is found in -<b>gnuradio-core/src/python/gnuradio/blks2impl/pfb_arb_resampler.py</b>, -which is exposed in Python as <b>blks2.pfb_arb_resampler_ccf</b> and -<b>blks2.pfb_arb_resampler_fff</b>. This block is set up so that the +<b>gr-filter/python/pfb.py</b>, +which is exposed in Python as <b>filter.pfb.arb_resampler_ccf</b> and +<b>filter.pfb.arb_resampler_fff</b>. This block is set up so that the user only needs to pass it the real number \p rate as the resampling rate. With just this information, this hierarchical block automatically creates a filter that fully passes the signal bandwidth @@ -90,6 +90,6 @@ channels. NOTE: you need the Scipy and Matplotlib Python modules installed to run this example. -\include gnuradio-core/src/examples/pfb/channelize.py +\include gr-filter/examples/channelize.py */ diff --git a/docs/doxygen/other/pmt.dox b/docs/doxygen/other/pmt.dox index 61b73bca13..04f58aafc8 100644 --- a/docs/doxygen/other/pmt.dox +++ b/docs/doxygen/other/pmt.dox @@ -66,25 +66,25 @@ underneath is still a C++ typed object, and so the right type of set/get function must be used for the data type. Typically, a PMT object can be made from a scalar item using a call -like "pmt::pmt_from_<type>". Similarly, when getting data out of a -PMT, we use a call like "pmt::pmt_to_<type>". For example: +like "pmt::from_<type>". Similarly, when getting data out of a +PMT, we use a call like "pmt::to_<type>". For example: \code double a = 1.2345; -pmt::pmt_t pmt_a = pmt::pmt_from_double(a); -double b = pmt::pmt_to_double(pmt_a); +pmt::pmt_t pmt_a = pmt::from_double(a); +double b = pmt::to_double(pmt_a); int c = 12345; -pmt::pmt_t pmt_c = pmt::pmt_from_long(c); -int d = pmt::pmt_to_long(pmt_c); +pmt::pmt_t pmt_c = pmt::from_long(c); +int d = pmt::to_long(pmt_c); \endcode As a side-note, making a PMT from a complex number is not obvious: \code std::complex<double> a(1.2, 3.4); -pmt::pmt_t pmt_a = pmt::pmt_make_rectangular(a.real(), b.imag()); -std::complex<double> b = pmt::pmt_to_complex(pmt_a); +pmt::pmt_t pmt_a = pmt::make_rectangular(a.real(), b.imag()); +std::complex<double> b = pmt::to_complex(pmt_a); \endcode Pairs, dictionaries, and vectors have different constructors and ways @@ -100,17 +100,17 @@ new symbol from a string, if that string already exists in the hash table, the constructor will return a reference to the existing PMT. We create strings with the following functions, where the second -function, pmt::pmt_intern, is simply an alias of the first. +function, pmt::intern, is simply an alias of the first. \code -pmt::pmt_t str0 = pmt::pmt_string_to_symbol(std::string("some string")); -pmt::pmt_t str1 = pmt::pmt_intern(std::string("some string")); +pmt::pmt_t str0 = pmt::string_to_symbol(std::string("some string")); +pmt::pmt_t str1 = pmt::intern(std::string("some string")); \endcode The string can be retrieved using the inverse function: \code -std::string s = pmt::pmt_symbol_to_string(str0); +std::string s = pmt::symbol_to_string(str0); \endcode @@ -118,28 +118,28 @@ std::string s = pmt::pmt_symbol_to_string(str0); The PMT library comes with a number of functions to test and compare PMT objects. In general, for any PMT data type, there is an equivalent -"pmt::pmt_is_<type>". We can use these to test the PMT before trying +"pmt::is_<type>". We can use these to test the PMT before trying to access the data inside. Expanding our examples above, we have: \code -pmt::pmt_t str0 = pmt::pmt_string_to_symbol(std::string("some string")); -if(pmt::pmt_is_symbol(str0)) - std::string s = pmt::pmt_symbol_to_string(str0); +pmt::pmt_t str0 = pmt::string_to_symbol(std::string("some string")); +if(pmt::is_symbol(str0)) + std::string s = pmt::symbol_to_string(str0); double a = 1.2345; -pmt::pmt_t pmt_a = pmt::pmt_from_double(a); -if(pmt::pmt_is_double(pmt_a)) - double b = pmt::pmt_to_double(pmt_a); +pmt::pmt_t pmt_a = pmt::from_double(a); +if(pmt::is_double(pmt_a)) + double b = pmt::to_double(pmt_a); int c = 12345; -pmt::pmt_t pmt_c = pmt::pmt_from_long(c); -if(pmt::pmt_is_long(pmt_a)) - int d = pmt::pmt_to_long(pmt_c); +pmt::pmt_t pmt_c = pmt::from_long(c); +if(pmt::is_long(pmt_a)) + int d = pmt::to_long(pmt_c); \\ This will fail the test. Otherwise, trying to coerce \b pmt_c as a \\ double when internally it is a long will result in an exception. -if(pmt::pmt_is_double(pmt_a)) - double d = pmt::pmt_to_double(pmt_c); +if(pmt::is_double(pmt_a)) + double d = pmt::to_double(pmt_c); \endcode @@ -156,15 +156,15 @@ returned dictionary is a new PMT with the changes made there. The following is a list of PMT dictionary functions. Click through to get more information on what each does. -- bool pmt::pmt_is_dict(const pmt_t &obj) -- pmt_t pmt::pmt_make_dict() -- pmt_t pmt::pmt_dict_add(const pmt_t &dict, const pmt_t &key, const pmt_t &value) -- pmt_t pmt::pmt_dict_delete(const pmt_t &dict, const pmt_t &key) -- bool pmt::pmt_dict_has_key(const pmt_t &dict, const pmt_t &key) -- pmt_t pmt::pmt_dict_ref(const pmt_t &dict, const pmt_t &key, const pmt_t ¬_found) -- pmt_t pmt::pmt_dict_items(pmt_t dict) -- pmt_t pmt::pmt_dict_keys(pmt_t dict) -- pmt_t pmt::pmt_dict_values(pmt_t dict) +- bool pmt::is_dict(const pmt_t &obj) +- pmt_t pmt::make_dict() +- pmt_t pmt::dict_add(const pmt_t &dict, const pmt_t &key, const pmt_t &value) +- pmt_t pmt::dict_delete(const pmt_t &dict, const pmt_t &key) +- bool pmt::dict_has_key(const pmt_t &dict, const pmt_t &key) +- pmt_t pmt::dict_ref(const pmt_t &dict, const pmt_t &key, const pmt_t ¬_found) +- pmt_t pmt::dict_items(pmt_t dict) +- pmt_t pmt::dict_keys(pmt_t dict) +- pmt_t pmt::dict_values(pmt_t dict) This example does some basic manipulations of PMT dictionaries in Python. Notice that we pass the dictionary \a a and return the results @@ -173,41 +173,41 @@ reference to the old dictionary. This just keeps our number of variables small. \code -from gruel import pmt +import pmt -key0 = pmt.pmt_intern("int") -val0 = pmt.pmt_from_long(123) -val1 = pmt.pmt_from_long(234) +key0 = pmt.intern("int") +val0 = pmt.from_long(123) +val1 = pmt.from_long(234) -key1 = pmt.pmt_intern("double") -val2 = pmt.pmt_from_double(5.4321) +key1 = pmt.tern("double") +val2 = pmt.om_double(5.4321) # Make an empty dictionary -a = pmt.pmt_make_dict() +a = pmt.make_dict() # Add a key:value pair to the dictionary -a = pmt.pmt_dict_add(a, key0, val0) -pmt.pmt_print(a) +a = pmt.dict_add(a, key0, val0) +print a # Add a new value to the same key; # new dict will still have one item with new value -a = pmt.pmt_dict_add(a, key0, val1) -pmt.pmt_print(a) +a = pmt.dict_add(a, key0, val1) +print a # Add a new key:value pair -a = pmt.pmt_dict_add(a, key1, val2) -pmt.pmt_print(a) +a = pmt.dict_add(a, key1, val2) +print a # Test if we have a key, then delete it -print pmt.pmt_dict_has_key(a, key1) -a = pmt.pmt_dict_delete(a, key1) -print pmt.pmt_dict_has_key(a, key1) +print pmt.dict_has_key(a, key1) +a = pmt.dict_delete(a, key1) +print pmt.dict_has_key(a, key1) -ref = pmt.pmt_dict_ref(a, key0, pmt.PMT_NIL) -pmt.pmt_print(ref) +ref = pmt.dict_ref(a, key0, pmt.PMT_NIL) +print ref # The following should never print -if(pmt.pmt_dict_has_key(a, key0) and pmt.pmt_eq(ref, pmt.PMT_NIL)): +if(pmt.dict_has_key(a, key0) and pmt.eq(ref, pmt.PMT_NIL)): print "Trouble! We have key0, but it returned PMT_NIL" \endcode @@ -232,29 +232,29 @@ both signed and unsigned. Vectors have a well-defined interface that allows us to make, set, get, and fill them. We can also get the length of a vector with -pmt::pmt_length. +pmt::length. For standard vectors, these functions look like: -- bool pmt::pmt_is_vector(pmt_t x) -- pmt_t pmt::pmt_make_vector(size_t k, pmt_t fill) -- pmt_t pmt::pmt_vector_ref(pmt_t vector, size_t k) -- void pmt::pmt_vector_set(pmt_t vector, size_t k, pmt_t obj) -- void pmt::pmt_vector_fill(pmt_t vector, pmt_t fill) +- bool pmt::is_vector(pmt_t x) +- pmt_t pmt::make_vector(size_t k, pmt_t fill) +- pmt_t pmt::vector_ref(pmt_t vector, size_t k) +- void pmt::vector_set(pmt_t vector, size_t k, pmt_t obj) +- void pmt::vector_fill(pmt_t vector, pmt_t fill) Uniform vectors have the same types of functions, but they are data type-dependent. The following list tries to explain them where you substitute the specific data type prefix for \a dtype (prefixes being: u8, u16, u32, u64, s8, s16, s32, s64, f32, f64, c32, c64). -- bool pmt::pmt_is_(dtype)vector(pmt_t x) -- pmt_t pmt::pmt_make_(dtype)vector(size_t k, (dtype) fill) -- pmt_t pmt::pmt_init_(dtype)vector(size_t k, const (dtype*) data) -- pmt_t pmt::pmt_init_(dtype)vector(size_t k, const std::vector<dtype> data) -- pmt_t pmt::pmt_(dtype)vector_ref(pmt_t vector, size_t k) -- void pmt::pmt_(dtype)vector_set(pmt_t vector, size_t k, (dtype) x) -- const dtype* pmt::pmt_(dtype)vector_elements(pmt_t vector, size_t &len) -- dtype* pmt::pmt_(dtype)vector_writable_elements(pmt_t vector, size_t &len) +- bool pmt::is_(dtype)vector(pmt_t x) +- pmt_t pmt::make_(dtype)vector(size_t k, (dtype) fill) +- pmt_t pmt::init_(dtype)vector(size_t k, const (dtype*) data) +- pmt_t pmt::init_(dtype)vector(size_t k, const std::vector<dtype> data) +- pmt_t pmt::(dtype)vector_ref(pmt_t vector, size_t k) +- void pmt::(dtype)vector_set(pmt_t vector, size_t k, (dtype) x) +- const dtype* pmt::(dtype)vector_elements(pmt_t vector, size_t &len) +- dtype* pmt::(dtype)vector_writable_elements(pmt_t vector, size_t &len) \b Note: We break the contract with vectors. The 'set' functions actually change the data underneath. It is important to keep track of @@ -276,12 +276,12 @@ Pairs are inspired by LISP 'cons' data types, so you will find the language here comes from LISP. A pair is just a pair of PMT objects. They are manipulated using the following functions: -- bool pmt::pmt_is_pair (const pmt_t &obj): Return true if obj is a pair, else false -- pmt_t pmt::pmt_cons(const pmt_t &x, const pmt_t &y): construct new pair -- pmt_t pmt::pmt_car(const pmt_t &pair): get the car of the pair (first object) -- pmt_t pmt::pmt_cdr(const pmt_t &pair): get the cdr of the pair (second object) -- void pmt::pmt_set_car(pmt_t pair, pmt_t value): Stores value in the car field -- void pmt::pmt_set_cdr(pmt_t pair, pmt_t value): Stores value in the cdr field +- bool pmt::is_pair(const pmt_t &obj): Return true if obj is a pair, else false +- pmt_t pmt::cons(const pmt_t &x, const pmt_t &y): construct new pair +- pmt_t pmt::car(const pmt_t &pair): get the car of the pair (first object) +- pmt_t pmt::cdr(const pmt_t &pair): get the cdr of the pair (second object) +- void pmt::set_car(pmt_t pair, pmt_t value): Stores value in the car field +- void pmt::set_cdr(pmt_t pair, pmt_t value): Stores value in the cdr field \section serdes Serializing and Deserializing @@ -293,38 +293,38 @@ string and then methods to deserialize the string buffer or string back into a PMT. We use this extensively in the metadata files (see \ref page_metadata). -- bool pmt::pmt_serialize(pmt_t obj, std::streambuf &sink) -- std::string pmt::pmt_serialize_str(pmt_t obj) -- pmt_t pmt::pmt_deserialize(std::streambuf &source) -- pmt_t pmt::pmt_deserialize_str(std::string str) +- bool pmt::serialize(pmt_t obj, std::streambuf &sink) +- std::string pmt::serialize_str(pmt_t obj) +- pmt_t pmt::deserialize(std::streambuf &source) +- pmt_t pmt::deserialize_str(std::string str) For example, we will serialize the data above to make it into a string ready to be written to a file and then deserialize it back to its original PMT. \code -from gruel import pmt +import pmt -key0 = pmt.pmt_intern("int") -val0 = pmt.pmt_from_long(123) +key0 = pmt.intern("int") +val0 = pmt.from_long(123) -key1 = pmt.pmt_intern("double") -val1 = pmt.pmt_from_double(5.4321) +key1 = pmt.intern("double") +val1 = pmt.from_double(5.4321) # Make an empty dictionary -a = pmt.pmt_make_dict() +a = pmt.make_dict() # Add a key:value pair to the dictionary -a = pmt.pmt_dict_add(a, key0, val0) -a = pmt.pmt_dict_add(a, key1, val1) +a = pmt.dict_add(a, key0, val0) +a = pmt.dict_add(a, key1, val1) -pmt.pmt_print(a) +print a -ser_str = pmt.pmt_serialize_str(a) +ser_str = pmt.serialize_str(a) print ser_str -b = pmt.pmt_deserialize_str(ser_str) -pmt.pmt_print(b) +b = pmt.deserialize_str(ser_str) +print b \endcode @@ -335,13 +335,17 @@ string. This is only done here as a test. \section printing Printing -We have used the pmt::pmt_print function in these examples to nicely -print the contents of a PMT. Another way to print the contents is -using the overloaded "<<" operator with a stream buffer object. In -C++, we can inline print the contents of a PMT like: +In Python, the __repr__ function of a PMT object is overloaded to call +'pmt::write_string'. This means that any time we call a formatted +printing operation on a PMT object, the PMT library will properly +format the object for display. + +In C++, we can use the 'pmt::print(object)' function or print the +contents is using the overloaded "<<" operator with a stream buffer +object. In C++, we can inline print the contents of a PMT like: \code -pmt::pmt_t a pmt::pmt_from_double(1.0); +pmt::pmt_t a pmt::from_double(1.0); std::cout << "The PMT a contains " << a << std::endl; \endcode diff --git a/docs/doxygen/other/tagged_stream_blocks.dox b/docs/doxygen/other/tagged_stream_blocks.dox new file mode 100644 index 0000000000..33e2bf6437 --- /dev/null +++ b/docs/doxygen/other/tagged_stream_blocks.dox @@ -0,0 +1,271 @@ +/*! \page page_tagged_stream_blocks Tagged Stream Blocks + +\section intro Introduction + +A tagged stream block is a block that works on streamed, but packetized input data. +Think of packet data transmission: A data packet consists of N bytes. However, in +traditional GNU Radio blocks, if we stream N bytes into a block, there's no way of +knowing the packet boundary. This might be relevant: Perhaps the modulator has to +prepend a synchronisation word before every packet, or append a CRC. So while some +blocks don't care about packet boundaries, other blocks do: These are <em>tagged +stream blocks</em>. + +These blocks are different from all the other GNU Radio block types (gr_block, gr_sync_block etc.) in that they are driven by the input: The PDU length tag tells the block how to +operate, whereas other blocks are output-driven (the scheduler tries to fill up the output +buffer are much as possible). + +\subsection howtheywork How do they work? + +As the name implies, tagged stream blocks use tags to identify PDU boundaries. +On the first item of a streamed PDU, there \em must be a tag with a specific +key, which stores the length of the PDU as a pmt_integer. If anything else, or no tag, +is on this first item, this will cause the flow graph to crash! + +The scheduler then takes care of everything. When the work function is called, it +is guaranteed to contain exactly one complete PDU and enough space in the output buffer for +the output. + +\subsection relatestootherblocks How do they relate to other block types (e.g. sync blocks)? + +Tagged stream blocks and sync blocks are really orthogonal concepts, and a block could be +both (gr::digital::ofdm_frame_equalizer_vcvc is such a block). However, because the work +function is defined differently in these block types, there is no way to derive a block +from both gr_tagged_stream_block and gr_sync_block. + +If a block needs the tagged stream mechanism (i.e. knowing about PDU boundaries), it must be +derived from gr_tagged_stream_block. If it's also a sync block, it is still possible to +set gr_block::set_relative_rate(1.0) and/or the fixed rate functions. + +The way gr_tagged_stream_block works, it is still beneficial to specify a relative rate, +if possible. + + +\section creating Creating a tagged stream block + +To create a tagged stream block, the block must be derived from gr_tagged_stream_block. +Here's a minimal example of how the header file could look: +\code +#include <digital/api.h> +#include <gr_tagged_stream_block.h> + +namespace gr { + namespace digital { + + class DIGITAL_API crc32_bb : virtual public gr_tagged_stream_block + { + public: + typedef boost::shared_ptr<crc32_bb> sptr; + + static sptr make(bool check=false, const std::string& len_tag_key="packet_len"); + }; + + } // namespace digital +} // namespace gr + +\endcode + +It is very similar to any other block definition. Two things are stand out: First, +gr_tagged_stream_block.h is included to allow deriving from gr_tagged_stream_block. + +The other thing is in the make function: the second argument is a string containing +the key of the length tags. This is not necessary (the block could get this information +hard-coded), but the usual way is to allow the user to change this tag, but give a +default value (in this case, \p packet_len). + +The implementation header (*_impl.h) also looks a bit different (again this is cropped to the relevant parts): +\code +#include <digital/crc32_bb.h> +namespace gr { + namespace digital { + + class crc32_bb_impl : public crc32_bb + { + public: + crc32_bb_impl(bool check, const std::string& len_tag_key); + ~crc32_bb_impl(); + + int calculate_output_stream_length(const gr_vector_int &ninput_items); + int work(int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + }; + + } // namespace digital +} // namespace gr +\endcode + +First, the \p work function signature is new. The argument list looks like that from +gr_block::general_work() (note: the arguments mean the same, too), but it's called +\p work like with the other derived block types (such as gr_sync_block). Also, there's a new +function: \p calculate_output_stream_length() is, in a sense, the opposite function to +gr_block::forecast(). Given a number of input items, it calculates the required number of +output items. Note how this relates to the fact that these blocks are input-driven. + +These two overrides (\p work() and \p calculate_output_stream_length() ) are what you need +for most tagged stream blocks. There are cases when you don't need to override the latter +because the default behaviour is enough, and other cases where you have to override more +than these two functions. These are discussed in \ref adv_usage. + +Finally, this is part of the actual block implementation (heavily cropped again, to highlight the relevant parts): +\code +#include <gr_io_signature.h> +#include "crc32_bb_impl.h" + +namespace gr { + namespace digital { + + crc32_bb::sptr crc32_bb::make(bool check, const std::string& len_tag_key) + { + return gnuradio::get_initial_sptr (new crc32_bb_impl(check, len_tag_key)); + } + + crc32_bb_impl::crc32_bb_impl(bool check, const std::string& len_tag_key) + : gr_tagged_stream_block("crc32_bb", + gr_make_io_signature(1, 1, sizeof (char)), + gr_make_io_signature(1, 1, sizeof (char)), + len_tag_key), + d_check(check) + { + } + + int + crc32_bb_impl::calculate_output_stream_length(const gr_vector_int &ninput_items) + { + if (d_check) { + return ninput_items[0] - 4; + } else { + return ninput_items[0] + 4; + } + } + + int + crc32_bb_impl::work (int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) + { + const unsigned char *in = (const unsigned char *) input_items[0]; + unsigned char *out = (unsigned char *) output_items[0]; + + // Do all the signal processing... + // Don't call consume! + + return new_packet_length; + } + + } // namespace digital +} // namespace gr +\endcode + +The make function is not different to any other block. The constructor calls +gr_tagged_stream_block::gr_tagged_stream_block() as expected, but note that it passes the +key of the length tag to the parent constructor. + +The block in question is a CRC block, and it has two modes: It can check the CRC (which is +then dropped), or it can append a CRC to a sequence of bytes. The \p calculate_output_stream_length() function +is thus very simple: depending on how the block is configured, the output is either 4 bytes +longer or shorter than the input stream. + +The \p work() function looks very similar to any other work function. When writing the +signal processing code, the following things must be kept in mind: +- The work function is called for exactly one PDU, and no more (or less) may be processed +- \p ninput_items contains the exact number of items in this PDU (at every port). + These items \em will be consumed after \p work() exits. +- Don't call \p consume() or \p consume_each() yourself! gr_tagged_stream_block will do that + for you. +- You can call \p produce() or \p produce_each(), if you're doing something complicated. + Don't forget to return WORK_CALLED_PRODUCE in that case. + + +\subsection note_on_tp A note on tag propagation + +Despite using tags for a special purpose, all tags that are not the length tag are treated +exactly as before: use gr_block::set_tag_propagation_policy() in the constructor. + +In a lot of the cases, though, you will need to specify set_tag_propagation_policy(TPP_DONT) +and manually handle the tag propagation in \p work(). This is because the unknown length of +the PDUs at compile time prohibits us from setting a precise relative rate of the block, +which is a requirement for automatic tag propagation. +Only if the tagged stream block is also a sync block (including interpolators and decimators, +i.e. blocks with an integer rate change), can automatic tag propagation be reliably used. + +The CRC block seems to a very simple block, but it's already complicated enough to confuse +the automatic tag propagation. For example, what happens to tags which are on the CRC? Do +they get removed, or do they get moved to the last item before the CRC? Also, the input to +output rate is different for every PDU length. + +In this case, it is necessary for the developer to define a tag propagation policy and +implement it in \p work(). Also, it is good practice to specify that tag propagation policy +in the blocks documentation. + +The actual length tags \em are treated differently, though. Most importantly, you don't have +to write the new length tag yourself. The key for the output length tag is the same as that +on the input, if you don't want this, you must override gr_tagged_stream_block::update_length_tags(). + + +\section adv_usage Advanced Usage + +It is generally recommended to read the block documentation of gr_tagged_stream_block. + +A few special cases are described here: + +\subsection multiplelentags Multiple length tags + +In some cases, a single tag is not enough. One example is the OFDM receiver: one OFDM frame contains a certain number of OFDM symbols, and another number of bytes--these numbers are only +very loosely related, and one cannot be calculated from the other. + +gr::digital::ofdm_serializer_vcc is such a block. It is driven by the number of OFDM frames, +but the output is determined by the number of complex symbols. In order to use multiple length +tag keys, it overrides update_length_tags(). + +\subsection backtogrblock Falling back to gr_block behaviour + +If, at compile-time, it is uncertain whether or not a block should be a +gr_tagged_stream_block, there is the possibility of falling back to gr_block behaviour. + +To do this, simple don't pass an empty string as length tag key. Instead of crashing, +a tagged stream block will behave like a gr_block. + +This has some consequences: The work function must have all the elements of a +gr_block::general_work() function, including calls to consume(). Because such a block must +allow both modes of operation (PDUs with tags, and infinite-stream), the work function +must check which mode is currently relevant. Checking if +gr_tagged_stream_block::d_length_tag_key_str is empty is a good choice. + +gr::digital::ofdm_cyclic_prefixer implements this. + +\subsection otherwaysdetermineninput Other ways to determine the number of input items + +If the number of input items is not stored as a pmt::pmt_integer, but there is a way to determine +it, gr_tagged_stream_block::parse_length_tags() can be overridden to figure out the length +of the PDU. + +\section examples Examples + +\subsection CRC32 CRC32 + +Block: gr::digital::crc32_bb + +This is a very simple block, and a good example to start with. + +\subsection ofdmeq OFDM Frame Equalizer + +Block: gr::digital::ofdm_frame_equalizer_vcvc + +This block would be a sync block if tagged stream blocks didn't exist. It also uses more +than one tag to determine the output. + +\subsection muxer Tagged Stream Muxer + +Block: gr::blocks::tagged_stream_mux + +Use this to multiplex any number of tagged streams. + +\subsection ofdmprefixer Cyclic Prefixer (OFDM) + +Block: gr::digital::ofdm_cyclic_prefixer + +This block uses the gr_block behaviour fallback. + +*/ diff --git a/docs/doxygen/other/thread_affinity.dox b/docs/doxygen/other/thread_affinity.dox index 235266febd..2f31d9ce53 100644 --- a/docs/doxygen/other/thread_affinity.dox +++ b/docs/doxygen/other/thread_affinity.dox @@ -6,8 +6,8 @@ In the thread-per-block scheduler, you can set the block's core affinity. Each block can be pinned to a group cores or be set back to use the standard kernel scheduler. -The implementation is done by adding new functions to the GRUEL -library: +The implementation is done by adding new functions to the threading +section of the gnuradio-runtime library: \code gr_thread_t get_current_thread_id(); @@ -21,7 +21,7 @@ library: The ability to set a thread's affinity to a core or groups of cores is not implemented in the Boost thread library, and so we have made our -own portability library. In particular, the gruel::gr_thread_t type is +own portability library. In particular, the gr::thread::gr_thread_t type is defined as the thread type for the given system. The other functions are designed to be portable as well by calling the specific implementation for the thread affinity for a particular platform. @@ -43,7 +43,7 @@ Each block has two new data members: - threaded: a boolean value that is true if the block is attached to a thread. -- thread: a gruel::gr_thread_t handle to the block's thread. +- thread: a gr::thread::gr_thread_t handle to the block's thread. A block can set and unset it's affinity at any time using the following member functions: |