BlocksCodingGuide

Version 7 (Johnathan Corgan, 06/22/2012 10:41 pm)

1 6 Tom Rondeau
h1. Blocks Coding Style
2 1
{{toc}}
3 1
4 1
h2. Terminology
5 1
6 1
| Block | A functional processing unit with inputs and outputs |
7 1
| Port | A single input or output of a block |
8 1
| Source | A producer of data |
9 1
| Sink | A consumer of data |
10 1
| Connection | A flow of data from output port to input port|
11 1
| Flow graph | A collection of blocks and connections |
12 1
| Item | A unit of data. Ex: baseband sample, fft vector, matrix... |
13 1
| Stream | A continuous flow of consecutive items |
14 1
|IO signature | A description of a blocks input and output ports |
15 1
16 1
----
17 1
18 6 Tom Rondeau
h2. Coding Structure
19 1
20 6 Tom Rondeau
h3. Public Header Files
21 6 Tom Rondeau
22 6 Tom Rondeau
The public header files are defined in *include/foo* and get installed into *$prefix/include/foo*.
23 6 Tom Rondeau
24 6 Tom Rondeau
The accessors (set/get) functions that are to be exported are defined a pure virtual functions in this header.
25 6 Tom Rondeau
26 6 Tom Rondeau
A skeleton of the a common public header file looks like:
27 6 Tom Rondeau
28 6 Tom Rondeau
<pre>
29 6 Tom Rondeau
#ifndef INCLUDED_FOO_BAR_H
30 6 Tom Rondeau
#define INCLUDED_FOO_BAR_H
31 6 Tom Rondeau
32 6 Tom Rondeau
#include <foo/api.h>
33 6 Tom Rondeau
#include <gr_sync_block.h>
34 6 Tom Rondeau
35 6 Tom Rondeau
namespace gr {
36 6 Tom Rondeau
  namespace foo {
37 6 Tom Rondeau
38 6 Tom Rondeau
    class FOO_API bar : virtual public gr_sync_block
39 6 Tom Rondeau
    {
40 6 Tom Rondeau
    public:
41 6 Tom Rondeau
42 6 Tom Rondeau
      // gr::foo::bar::sptr
43 6 Tom Rondeau
      typedef boost::shared_ptr<bar> sptr;
44 6 Tom Rondeau
45 6 Tom Rondeau
      /*!
46 6 Tom Rondeau
       * \class bar
47 6 Tom Rondeau
       * \brief A brief description of what foo::bar does
48 6 Tom Rondeau
       *
49 6 Tom Rondeau
       * \ingroup <some group>_blk
50 6 Tom Rondeau
       *
51 6 Tom Rondeau
       * A more detailed description of the block.
52 6 Tom Rondeau
       * 
53 6 Tom Rondeau
       * \param var explanation of argument var.
54 6 Tom Rondeau
       */
55 7 Johnathan Corgan
      static sptr make(dtype var);
56 6 Tom Rondeau
57 6 Tom Rondeau
      virtual void set_var(dtype var) = 0;
58 6 Tom Rondeau
      virtual dtype var() = 0;
59 6 Tom Rondeau
    };
60 6 Tom Rondeau
61 6 Tom Rondeau
  } /* namespace foo */
62 6 Tom Rondeau
} /* namespace gr */
63 6 Tom Rondeau
64 6 Tom Rondeau
#endif /* INCLUDED_FOO_BAR_H */
65 6 Tom Rondeau
</pre>
66 6 Tom Rondeau
67 6 Tom Rondeau
h3. Implementation Header File
68 6 Tom Rondeau
69 6 Tom Rondeau
The private implementation header files are defined in *lib* and do not get installed. We normally define these files to use the same name as the public file and class with a '_impl' suffix to indicate that this is the implementation file for the class.
70 6 Tom Rondeau
71 6 Tom Rondeau
In some cases, this file might be specific to a very particular implementation and multiple implementations might be available for a given block but with the same public API. A good example is the use of the FFTW library for implementing the *fft_filter* blocks. This is only one of many possible ways to implement an FFT, and so the implementation was named *fft_filter_ccc_fftw*. Another library that implements an FFT specific to a platform or purpose could then be slotted in as a new implementation like *fft_filter_ccc_myfft*.
72 6 Tom Rondeau
73 6 Tom Rondeau
All member variables are declared private and use the prefix 'd_'. As much as possible, all variables should have a set and get function. The set function looks like *void set_var(dtype var)*, and the get function looks like *dtype var()*. It does not always make sense to have a set or get for a particular variable, but all efforts should be made to accommodate it.
74 6 Tom Rondeau
75 6 Tom Rondeau
The Doxygen comments that will be included in the manual are defined in the public header file. There is no need for Doxygen markup in the private files, but of course, any comments or documentation that make sense should always be used.
76 6 Tom Rondeau
77 6 Tom Rondeau
A skeleton of the a common private header file looks like:
78 6 Tom Rondeau
79 6 Tom Rondeau
<pre>
80 6 Tom Rondeau
#ifndef INCLUDED_FOO_BAR_IMPL_H
81 6 Tom Rondeau
#define INCLUDED_FOO_BAR_IMPL_H
82 6 Tom Rondeau
83 6 Tom Rondeau
#include <foo/bar.h>
84 6 Tom Rondeau
85 6 Tom Rondeau
namespace gr {
86 6 Tom Rondeau
  namespace foo {
87 6 Tom Rondeau
88 6 Tom Rondeau
    class FOO_API bar_impl : public bar
89 6 Tom Rondeau
    {
90 6 Tom Rondeau
    private:
91 6 Tom Rondeau
      dtype d_var;
92 6 Tom Rondeau
93 6 Tom Rondeau
    public:
94 6 Tom Rondeau
      bar_impl(dtype var);
95 6 Tom Rondeau
96 6 Tom Rondeau
      ~bar_impl();
97 6 Tom Rondeau
98 6 Tom Rondeau
      void set_var(dtype var);
99 6 Tom Rondeau
      dtype var();
100 6 Tom Rondeau
101 6 Tom Rondeau
      int work(int noutput_items,
102 6 Tom Rondeau
           gr_vector_const_void_star &input_items,
103 6 Tom Rondeau
           gr_vector_void_star &output_items);
104 6 Tom Rondeau
    };
105 6 Tom Rondeau
106 6 Tom Rondeau
  } /* namespace foo */
107 6 Tom Rondeau
} /* namespace gr */
108 6 Tom Rondeau
109 6 Tom Rondeau
#endif /* INCLUDED_FOO_BAR_IMPL_H */
110 6 Tom Rondeau
</pre>
111 6 Tom Rondeau
112 6 Tom Rondeau
h3. Implementation Source File
113 6 Tom Rondeau
114 6 Tom Rondeau
The source file is *lib/bar.cc* and implements the actual code for the class.
115 6 Tom Rondeau
116 6 Tom Rondeau
This file defines the *make* function for the public class. This is a member of the class, which means that we can, if necessary, do interesting things, define multiple factor functions, etc. Most of the time, this simply returns an sptr to the implementation class.
117 6 Tom Rondeau
118 6 Tom Rondeau
<pre>
119 6 Tom Rondeau
#ifdef HAVE_CONFIG_H
120 6 Tom Rondeau
#include "config.h" 
121 6 Tom Rondeau
#endif
122 6 Tom Rondeau
123 6 Tom Rondeau
#include "bar_impl.h" 
124 6 Tom Rondeau
#include <gr_io_signature.h>
125 6 Tom Rondeau
126 6 Tom Rondeau
namespace gr {
127 6 Tom Rondeau
  namespace foo {
128 6 Tom Rondeau
129 6 Tom Rondeau
    bar::sptr bar::make(dtype var)
130 6 Tom Rondeau
    {
131 6 Tom Rondeau
      return gnuradio::get_initial_sptr(new bar_impl(var));
132 6 Tom Rondeau
    }
133 6 Tom Rondeau
134 6 Tom Rondeau
    bar_impl::bar_impl(dtype var)
135 6 Tom Rondeau
      : gr_sync_block("bar",
136 6 Tom Rondeau
              gr_make_io_signature(1, 1, sizeof(in_type)),
137 6 Tom Rondeau
              gr_make_io_signature(1, 1, sizeof(out_type)))
138 6 Tom Rondeau
    {
139 6 Tom Rondeau
      set_var(var);
140 6 Tom Rondeau
    }
141 6 Tom Rondeau
142 6 Tom Rondeau
    bar_impl::~bar_impl()
143 6 Tom Rondeau
    {
144 6 Tom Rondeau
      // any cleanup code here
145 6 Tom Rondeau
    }
146 6 Tom Rondeau
147 6 Tom Rondeau
    dtype
148 6 Tom Rondeau
    bar_impl::var()
149 6 Tom Rondeau
    {
150 6 Tom Rondeau
      return d_var;
151 6 Tom Rondeau
    }
152 6 Tom Rondeau
153 6 Tom Rondeau
    void
154 6 Tom Rondeau
    bar_impl::set_var(dtype var)
155 6 Tom Rondeau
    {
156 6 Tom Rondeau
      d_var = var;
157 6 Tom Rondeau
    }
158 6 Tom Rondeau
159 6 Tom Rondeau
    int
160 6 Tom Rondeau
    bar_impl::work(int noutput_items,
161 6 Tom Rondeau
                   gr_vector_const_void_star &input_items,
162 6 Tom Rondeau
                   gr_vector_void_star &output_items)
163 6 Tom Rondeau
    {
164 6 Tom Rondeau
      const in_type *in = (const in_type*)input_items[0];
165 6 Tom Rondeau
      out_type *out = (out_type*)output_items[0];
166 6 Tom Rondeau
167 6 Tom Rondeau
      // Perform work; read from in, write to out.
168 6 Tom Rondeau
169 6 Tom Rondeau
      return noutput_items;
170 6 Tom Rondeau
    }
171 6 Tom Rondeau
172 6 Tom Rondeau
  } /* namespace foo */
173 6 Tom Rondeau
} /* namespace gr */
174 6 Tom Rondeau
</pre>
175 6 Tom Rondeau
176 6 Tom Rondeau
h3. SWIG Interface File
177 6 Tom Rondeau
178 6 Tom Rondeau
Because of the use of the public header file to describe what we want accessible publicly, we can simple include the headers in the main interface file. So in the directory *swig* is a single interface file *foo_swig.i*:
179 6 Tom Rondeau
180 6 Tom Rondeau
<pre>
181 6 Tom Rondeau
#define FOO_API
182 6 Tom Rondeau
183 6 Tom Rondeau
%include "gnuradio.i" 
184 6 Tom Rondeau
185 6 Tom Rondeau
//load generated python docstrings
186 6 Tom Rondeau
%include "foo_swig_doc.i" 
187 6 Tom Rondeau
188 6 Tom Rondeau
%{
189 6 Tom Rondeau
#include "foo/bar.h" 
190 6 Tom Rondeau
%}
191 6 Tom Rondeau
192 6 Tom Rondeau
%include "foo/bar.h" 
193 6 Tom Rondeau
194 6 Tom Rondeau
GR_SWIG_BLOCK_MAGIC2(foo, bar);
195 6 Tom Rondeau
</pre>
196 6 Tom Rondeau
197 6 Tom Rondeau
*NOTE*: We are using "GR_SWIG_BLOCK_MAGIC2" for the definitions now. When we are completely converted over, this will be replaced by "GR_SWIG_BLOCK_MAGIC".
198 6 Tom Rondeau
199 6 Tom Rondeau
----
200 6 Tom Rondeau
201 6 Tom Rondeau
h2. Block Structure
202 6 Tom Rondeau
203 6 Tom Rondeau
h3. The *work* function
204 6 Tom Rondeau
205 1
To implement processing, the user must write a "work" routine that reads inputs, processes, and writes outputs.
206 5 Tom Rondeau
207 5 Tom Rondeau
An example work function implementing an adder in c++
208 5 Tom Rondeau
<pre>
209 5 Tom Rondeau
int work(int noutput_items,
210 1
         gr_vector_const_void_star &input_items,
211 5 Tom Rondeau
         gr_vector_void_star &output_items)
212 5 Tom Rondeau
{
213 1
  //cast buffers
214 1
  const float* in0 = reinterpret_cast<const float *>(input_items[0]);
215 1
  const float* in1 = reinterpret_cast<const float *>(input_items[1]);
216 1
  float* out = reinterpret_cast<float *>(output_items[0]);
217 1
218 1
  //process data
219 1
  for(size_t i = 0; i < noutput_items; i++) {
220 1
    out[i] = in0[i] + in1[i];
221 1
  }
222 1
223 1
  //return produced
224 1
  return noutput_items;
225 1
}
226 1
</pre>
227 2 Josh Blum
228 2 Josh Blum
Parameter definitions:
229 1
* **noutput_items:** total number of items in each output buffer
230 1
* **input_items:** vector of input buffers, where each element corresponds to an input port
231 1
* **output_items:** vector of output buffers, where each element corresponds to an output port
232 1
233 1
Some observations:
234 1
* Each buffer must be cast from a void* pointer into a usable data type.
235 1
* The number of items in each input buffer is implied by noutput_items
236 1
** More information on this in later sections
237 1
* The number of items produced is returned, this can be less than noutput_items
238 1
239 6 Tom Rondeau
h3. IO signatures
240 1
241 1
When creating a block, the user must communicate the following to the block:
242 1
243 1
* The number of input ports
244 1
* The number of output ports
245 1
* The item size of each port
246 1
247 1
An IO signature describes the number of ports a block may have and the size of each item in bytes. Each block has 2 IO signatures: an input signature, and an output signature.
248 1
249 1
Some example signatures in c++
250 1
<pre>
251 1
252 1
-- A block with 2 inputs and 1 output --
253 1
254 1
gr_sync_block("my adder", gr_make_io_signature(2, 2, sizeof(float)), gr_make_io_signature(1, 1, sizeof(float)))
255 1
256 1
-- A block with no inputs and 1 output --
257 1
258 1
gr_sync_block("my source", gr_make_io_signature(0, 0, 0), gr_make_io_signature(1, 1, sizeof(float)))
259 1
260 1
-- A block with 2 inputs (float and double) and 1 output --
261 1
262 1
std::vector<int> input_sizes;
263 1
input_sizes.push_back(sizeof(float));
264 1
input_sizes.push_back(sizeof(double));
265 1
266 1
gr_sync_block("my block", gr_make_io_signaturev(2, 2, input_sizes), gr_make_io_signature(1, 1, sizeof(float)))
267 1
</pre>
268 1
269 1
Some observations:
270 1
* Use gr_make_io_signature for blocks where all ports are homogenous in size
271 1
* Use gr_make_io_signaturev for blocks that have heterogeneous port sizes
272 6 Tom Rondeau
The first two parameters are min and max number of ports, this allows blocks to have a selectable number of ports at runtime
273 1
274 6 Tom Rondeau
275 1
----
276 1
277 1
h2. Block types
278 1
279 1
To take advantage of the gnuradio framework, users will create various blocks to implement the desired data processing. There are several types of blocks to choose from:
280 1
281 6 Tom Rondeau
* Synchronous Blocks (1:1)
282 6 Tom Rondeau
* Decimation Blocks (N:1)
283 6 Tom Rondeau
* Interpolation Blocks (1:M)
284 6 Tom Rondeau
* General Blocks (N:M)
285 6 Tom Rondeau
286 5 Tom Rondeau
h3. Synchronous Block
287 5 Tom Rondeau
288 1
The sync block allows users to write blocks that consume and produce an equal number of items per port. A sync block may have any number of inputs or outputs. When a sync block has zero inputs, its called a source. When a sync block has zero outputs, its called a sink.
289 5 Tom Rondeau
290 5 Tom Rondeau
An example sync block in c++
291 5 Tom Rondeau
<pre>
292 5 Tom Rondeau
#include <gr_sync_block.h>
293 5 Tom Rondeau
294 5 Tom Rondeau
class my_sync_block : public gr_sync_block
295 1
{
296 5 Tom Rondeau
public:
297 5 Tom Rondeau
  my_sync_block(...):
298 5 Tom Rondeau
    gr_sync_block("my block", 
299 5 Tom Rondeau
                  gr_make_io_signature(1, 1, sizeof(int32_t)),
300 5 Tom Rondeau
                  gr_make_io_signature(1, 1, sizeof(int32_t)))
301 5 Tom Rondeau
  {
302 5 Tom Rondeau
    //constructor stuff
303 1
  }
304 1
305 1
  int work(int noutput_items,
306 1
           gr_vector_const_void_star &input_items,
307 1
           gr_vector_void_star &output_items)
308 1
  {
309 1
    //work stuff...
310 1
    return noutput_items;
311 1
  }
312 1
};
313 1
</pre>
314 1
315 1
Some observations:
316 1
* noutput_items is the length in items of all input and output buffers
317 1
* an input siganture of gr_make_io_signature(0, 0, 0) makes this a source block
318 1
* an output siganture of gr_make_io_signature(0, 0, 0) makes this a sink block
319 5 Tom Rondeau
320 5 Tom Rondeau
h3. Decimation Block
321 1
322 5 Tom Rondeau
The decimation block is another type of fixed rate block where the number of input items is a fixed multiple of the number of output items.
323 5 Tom Rondeau
324 5 Tom Rondeau
An example decimation block in c++
325 5 Tom Rondeau
<pre>
326 5 Tom Rondeau
#include <gr_sync_decimator.h>
327 5 Tom Rondeau
328 5 Tom Rondeau
class my_decim_block : public gr_sync_decimator
329 5 Tom Rondeau
{
330 1
public:
331 5 Tom Rondeau
  my_decim_block(...):
332 1
    gr_sync_decimator("my decim block", 
333 1
                      in_sig,
334 1
                      out_sig,
335 1
                      decimation)
336 1
  {
337 1
    //constructor stuff
338 1
  }
339 1
340 1
  //work function here...
341 1
};
342 1
</pre>
343 1
344 1
Some observations:
345 1
* The gr_sync_decimator constructor takes a 4th parameter, the decimation factor
346 1
* The user should assume that the number of input items = noutput_items*decimation
347 5 Tom Rondeau
348 5 Tom Rondeau
h3. Interpolation Block
349 1
350 5 Tom Rondeau
The interpolation block is another type of fixed rate block where the number of output items is a fixed multiple of the number of input items.
351 5 Tom Rondeau
352 5 Tom Rondeau
An example interpolation block in c++
353 5 Tom Rondeau
<pre>
354 5 Tom Rondeau
#include <gr_sync_interpolator.h>
355 5 Tom Rondeau
356 5 Tom Rondeau
class my_interp_block : public gr_sync_interpolator
357 5 Tom Rondeau
{
358 1
public:
359 5 Tom Rondeau
  my_interp_block(...):
360 1
    gr_sync_interpolator("my interp block", 
361 1
                         in_sig,
362 1
                         out_sig,
363 1
                         interpolation)
364 1
  {
365 1
    //constructor stuff
366 1
  }
367 1
368 1
  //work function here...
369 1
};
370 1
</pre>
371 1
372 1
Some observations:
373 1
* The gr_sync_interpolator constructor takes a 4th parameter, the interpolation factor
374 1
* The user should assume that the number of input items = noutput_items/interpolation
375 1
376 1
h3. Basic Block
377 1
378 1
The basic block provides no relation between the number of input items and the number of output items. All other blocks are just simplifications of the basic block. Users should choose to inherit from basic block when the other blocks are not suitable.
379 1
380 1
The adder revisited as a basic block in c++
381 1
<pre>
382 1
#include <gr_block.h>
383 5 Tom Rondeau
384 5 Tom Rondeau
class my_basic_block : public gr_block
385 1
{
386 5 Tom Rondeau
public:
387 5 Tom Rondeau
  my_basic_adder_block(...):
388 5 Tom Rondeau
    gr_block("another adder block",
389 5 Tom Rondeau
             in_sig,
390 5 Tom Rondeau
             out_sig)
391 5 Tom Rondeau
  {
392 5 Tom Rondeau
    //constructor stuff
393 1
  }
394 5 Tom Rondeau
395 5 Tom Rondeau
  int general_work(int noutput_items,
396 5 Tom Rondeau
                   gr_vector_int &ninput_items,
397 5 Tom Rondeau
                   gr_vector_const_void_star &input_items,
398 5 Tom Rondeau
                   gr_vector_void_star &output_items)
399 5 Tom Rondeau
  {
400 5 Tom Rondeau
    //cast buffers
401 5 Tom Rondeau
    const float* in0 = reinterpret_cast<const float *>(input_items[0]);
402 5 Tom Rondeau
    const float* in1 = reinterpret_cast<const float *>(input_items[1]);
403 1
    float* out = reinterpret_cast<float *>(output_items[0]);
404 5 Tom Rondeau
405 5 Tom Rondeau
    //process data
406 5 Tom Rondeau
    for(size_t i = 0; i < noutput_items; i++) {
407 5 Tom Rondeau
      out[i] = in0[i] + in1[i];
408 1
    }
409 5 Tom Rondeau
410 5 Tom Rondeau
    //consume the inputs
411 5 Tom Rondeau
    this->consume(0, noutput_items); //consume port 0 input
412 5 Tom Rondeau
    this->consume(1, noutput_items); //consume port 1 input
413 1
    //this->consume_each(noutput_items); //or shortcut to consume on all inputs
414 5 Tom Rondeau
415 5 Tom Rondeau
    //return produced
416 5 Tom Rondeau
    return noutput_items;
417 1
  }
418 1
};
419 1
420 1
</pre>
421 1
422 1
Some observations:
423 1
* This class overloads the general_work() method, not work()
424 1
* The general work has a parameter: ninput_items
425 1
** ninput_items is a vector describing the length of each input buffer
426 1
* Before return, general_work must manually consume the used inputs
427 1
* The number of items in the input buffers is assumed to be noutput_items
428 1
** Users may alter this behaviour by overloading the forecast() method
429 1
430 1
----
431 1
432 6 Tom Rondeau
h2. Other Types of Blocks
433 6 Tom Rondeau
434 6 Tom Rondeau
h3. Hierarchical Block
435 6 Tom Rondeau
436 6 Tom Rondeau
Hierarchical blocks are blocks that are made up of other blocks. They instantiate the other GNU Radio blocks (or other hierarchical blocks) and connect them together. A hierarchical block has a “connect” function for this purpose.
437 6 Tom Rondeau
438 6 Tom Rondeau
Hierarchical blocks define an input and output stream much like normal blocks. To connect input *i* to a hierarchical block, the source is (in Python):
439 6 Tom Rondeau
440 6 Tom Rondeau
@self.connect((self, i), <block>)@
441 6 Tom Rondeau
442 6 Tom Rondeau
Similarly, to send the signal out of the block on output stream *o*:
443 6 Tom Rondeau
444 6 Tom Rondeau
@self.connect(<block>, (self, o))@
445 6 Tom Rondeau
446 6 Tom Rondeau
447 6 Tom Rondeau
h3. Top Block
448 6 Tom Rondeau
449 6 Tom Rondeau
The top block is the main data structure of a GNU Radio flowgraph. All blocks are connected under this block. The top block has the functions that control the running of the flowgraph. Generally, we create a class that inherits from a top block:
450 6 Tom Rondeau
451 6 Tom Rondeau
<pre>
452 6 Tom Rondeau
class my_topblock(gr.top_block):
453 6 Tom Rondeau
    def __init__(self, <args>):
454 6 Tom Rondeau
        gr.top_block.__init__(self)
455 6 Tom Rondeau
456 6 Tom Rondeau
        <create and connect blocks>
457 6 Tom Rondeau
458 6 Tom Rondeau
def main():
459 6 Tom Rondeau
    tb = mytb(<args>)
460 6 Tom Rondeau
    tb.run()
461 6 Tom Rondeau
</pre>
462 6 Tom Rondeau
463 6 Tom Rondeau
The top block has a few main member functions:
464 6 Tom Rondeau
465 6 Tom Rondeau
* start(N): starts the flow graph running with N as the maximum noutput_items any block can receive.
466 6 Tom Rondeau
* stop(): stops the top block
467 6 Tom Rondeau
* wait(): blocks until top block is finished
468 6 Tom Rondeau
* run(N): a blocking start(N) (calls start then wait)
469 6 Tom Rondeau
* lock(): locks the flowgraph so we can reconfigure it
470 6 Tom Rondeau
* unlock(): unlocks and resetarts the flowgraph
471 6 Tom Rondeau
472 6 Tom Rondeau
The N concept allows us to adjust the latency of a flowgraph. By default, N is large and blocks pass large chunks of items between eachothre. This is designed to maximize throughput and efficiency. Since large chunks of items incurs latency, we can force these chunks to a maximum size to control the overall latency at the expense of efficiency. A *set_max_noutput_items(N)* method is defined for a top block to change this number, but it only takes effect during a lock/unlock procedure.
473 6 Tom Rondeau
474 6 Tom Rondeau
----
475 6 Tom Rondeau
476 1
h2. Stream Tags
477 1
478 1
A tag decorates a stream with metadata. A tag is associated with a particular item in a stream. An item may have more than one tag associated with it. The association of an item and tag is made through an absolute count. Every item in a stream has an absolute count. Tags use this count to identify which item in a stream to which they are associated.
479 1
480 1
A tag has the following members:
481 1
* **offset:** the unique item count
482 1
* **key:** a PMT key unique to the type of contents
483 1
* **value:** a PMT holding the contents of this tag
484 1
* **srcid:** a PMT id unique to the producer of the tag (optional)
485 1
486 1
A PMT is a special data type in gnuradio to serialize arbitrary data. To learn more about PMTs see gruel/pmt.h
487 1
488 1
h3. Reading stream tags
489 1
490 1
Tags can be read from the work function using get_tags_in_range. Each input port/stream can have associated tags.
491 1
492 1
Example reading tags in c++
493 5 Tom Rondeau
<pre>
494 5 Tom Rondeau
int work(int noutput_items,
495 5 Tom Rondeau
         gr_vector_const_void_star &input_items,
496 5 Tom Rondeau
         gr_vector_void_star &output_items)
497 5 Tom Rondeau
{
498 5 Tom Rondeau
  std::vector<gr_tag_t> tags;
499 5 Tom Rondeau
  const uint64_t nread = this->nitems_read(0); //number of items read on port 0
500 1
  const size_t ninput_items = noutput_items; //assumption for sync block, this can change
501 5 Tom Rondeau
502 5 Tom Rondeau
  //read all tags associated with port 0 for items in this work function
503 1
  this->get_tags_in_range(tags, 0, nread, nread+ninput_items);
504 5 Tom Rondeau
505 1
  //work stuff here...
506 2 Josh Blum
}
507 2 Josh Blum
</pre>
508 1
509 1
h3. Writing stream tags
510 1
511 1
Tags can be written from the work function using add_item_tag. Each output port/stream can have associated tags.
512 1
513 1
Example writing tags in c++
514 5 Tom Rondeau
<pre>
515 5 Tom Rondeau
int work(int noutput_items,
516 5 Tom Rondeau
         gr_vector_const_void_star &input_items,
517 1
         gr_vector_void_star &output_items)
518 5 Tom Rondeau
{
519 5 Tom Rondeau
  const size_t item_index = ? //which output item gets the tag?
520 5 Tom Rondeau
  const uint64_t offset = this->nitems_written(0) + item_index;
521 5 Tom Rondeau
  pmt::pmt_t key = pmt::pmt_string_to_symbol("example_key");
522 5 Tom Rondeau
  pmt::pmt_t value = pmt::pmt_string_to_symbol("example_value");
523 1
524 5 Tom Rondeau
  //write at tag to output port 0 with given absolute item offset
525 5 Tom Rondeau
  this->add_item_tag(0, offset, key, value);
526 1
527 5 Tom Rondeau
  //work stuff here...
528 1
}
529 1
</pre>
530 1
531 1
----
532 1
533 6 Tom Rondeau
h2. Tips and Tricks
534 1
535 1
This is the part of the guide where we give tips and tricks for making blocks that work robustly with the scheduler.
536 1
537 1
h3. Blocking calls
538 1
539 1
If a work function contains a blocking call, it must be written in such a way that it can be interrupted by boost threads. When the flow graph is stopped, all worker threads will be interrupted. Thread interruption occurs when the user calls unlock() or stop() on the flow graph. Therefore, it is only acceptable to block indefinitely on a boost thread call such a sleep or condition variable, or something that uses these boost thread calls internally such as pop_msg_queue(). If you need to block on a resource such as a file descriptor or socket, the work routine should always call into the blocking routine with a timeout. When the operation times out, the work routine should call a boost thread interruption point or check boost thread interrupted and exit it true.
540 1
541 1
h3. Saving state
542 1
543 1
Because work functions can be interrupted, the block's state variables may be indeterminate next time the flow graph is run. To make blocks robust against indeterminate state, users should overload the blocks start() and stop() functions. The start() routine is called when the flow graph is started before the work() thread is spawned. The stop() routine is called when the flow graph is stopped after the work thread has been joined and exited. Users should ensure that the state variables of the block are initialized property in the start() routine.