GNU Radio Stream Lambda Blocks and Dynamic Runtime Updates

Since adding message based lambda blocks the other day, it seemed an obvious pairing to include a stream equivalent block as well for really rapid prototyping needs. The interface is a bit less simple because we can have N input ports and M output ports, I settled on defining a lambda function which takes a list of input vectors (the same input_items list of numpy arrays normal python blocks get, and an integer output port index). To implement a simplish stream block now that consumes N input streams and produces M output streams, this function prototype simply needs to be implemented. This won’t let you do everything, for instance output streams still need to produce the same number of items, and they are assumed to consume at this same rate. Extending this to a sync decimate or interpolater should be straight forward, but the function prototype would need to become a bit more complex for a “general work” kind of interface which allows producing/consuming each port at different rates, so I chose not to address that for now.

Example Stream Lambda Block  Flowgraph

A simple example graph which demonstrates the use of these blocks is shown below, this graph takes a simple Gaussian noise source in, throttles it to a fixed throughput rate, and then feeds it through a 2-in 2-out stream lambda block. In this case, we provide the following lambda function for the block,

lambda input_items, output_index: (input_items[output_index][::2]**2)*[0.1,0.2][output_index] + [-1,1][output_index]

That is to say:

output_items[0] = input_items[0][::2]**2*0.1 – 1

output_items[1] = input_items[1][::2]**2*0.2 + 1

Here, each stream is independent of the other output, and they could have been implemented as two seperate blocks, but this is just provided as an illustration.

The second block then is a 2-in 1-out stream lambda block which merges the two streams into one output stream, this mapping is given by:

lambda input_items, output_index: (input_items[0][:] * numpy.conj(input_items[1][:]))

This one is a bit simpler, as we will simply have a single evaluation:

output_items[0] = input_items[0]*conj(input_items[1])

The resulting output signal is then shown in the output plot.

stream_lambda_blocks

Dynamic Runtime Updates

What’s really fun is these lambda functions can be updated at runtime to change the block’s functionality. I’ve added a variable_text_input block to gr-pyqt which allows for runtime input of new lambda functions to each block which are passed to the GRC setter callback for each stream lambda block. By running this graph, you can now fiddle with the block’s algorithms at run time and immediately see the output effect on the stream signal. Just don’t type any invalid python at run-time or you’ll crash the block’s thread context.

The dynamic update version of the stream lambda block demo is shown below.

stream_lambda_variable_text

These dynamic function updates should work with the message lambda block as well, but that is not shown here.

The GRC graph for this example can be found @ https://github.com/osh/gr-pyqt/blob/master/apps/test_stream_lambda.grc

GNU Radio Message Lambda Blocks

It occurred randomly tonight that both python blocks and passing around lambda functions are awesome. The clear conclusion of this was that we should add python lambda blocks to GNU Radio. Since ~15 lines of python and all the installing, importing and xml wrapping is a bit lengthy for writing new python blocks, it occurred to me that in many of these blocks, the only thing that is changing is really the mapping from input vector to output vector of a PDU.

Therefore I introduce to you the new Python PDU Lambda message block. Now from GRC you can make up a completely new message block by simply writing a lambda function in a block parameter field which defines the mapping from input vector to output vector. Since pmt’s to_python and to_pmt methods handle typing for you, this works for any PMT vector type and you can generally use any python or numpy calls within your lambda argument to the block to quickly add completely new functionality from GRC with really minimal effort.

grc-plot

Testing with Such Samples

Dropping a lambda block into the such samples waveform, we make a new block which simply computes a log-power over time signal of a decimated input signal. Dropping this into a standard plotting block, we immediately have promising looking results. Any desired transform of the input data set that can be represented as a lambda function using numpy/etc can now be used to plot random segments of data from our input file now!

window

Also note that the gr-pyqt plots now support adding markers with “shift + left-click” as well as bring up a context menu (which allows you to clear markers) with “middle-click”.

This pdu_lambda block is now readily available in the gr-pyqt out-of-tree module.  The potential damage of new monolithic Balint-style GRC graphs using this block is frightening.

Such Samples: A GNU Radio Tool for Rapid Sample Data File Visualization with Message Passing

GNU Radio has always been a great stream processing system for building signal processing systems. Less noticed perhaps is that GNU Radio’s QTGui component matured, it came along with a handful of useful little tools such as gr_time_plot_c which allowed for inspection of sample data files in the time and frequency domain using the same plot widgets we typically use for streaming data. Since this makes use of a handful of GNU Radio stream blocks to perform file reading, sample dropping and alignment, and vector sources to stream data into the stream sink, and we have recently done quite a bit of work with message based plotting, I wondered if it was time to start trying to re-think these a bit. Aside from that, recently I was trying to look at some really large simulated data sets using this tool and getting frustrated with the slugish nature of the tool every time I wanted to move around in time.

So using message passing in GNU Radio I tried to re-think what a sample data analysis tool might look like, and how it might behave responsively to a use interface, accessing only the data of interest and not wasting time reading and trimming large streams using the traditional GNU Radio stream operations on the fixed data file.

Tool Composition

The tool, which I call “Such samples” is actually quite simple when built using GNU Radio Companion. It really only represents two new blocks aside from using the existing gr-pyqt based PDU message plotters, these blocks are:

1. A message based file source, which takes in a tuple message of (start item, item length) and reads out a PDU containing the requested item range after performing a low level fseek to align correctly (never reading in items outside that range).  Reading data is then conducted transparently through numpy’s from_file and pmt’s to_pmt methods which are quite fast and able to handle large chunks.

2. A message based range tool, which allows for a user to manipulate the location and sample range to view within the file. This really just sends out the (start item, item length) tuples to the message file source and doesn’t do much else.

Both of these are initially implemented as pretty concise simple python blocks, but connecting them up in GNU Radio Companion we see that it’s created a pretty simple but nicely performing tool for looking at data files.

The flowgraph is shown below, and could be easily tailored to include all kinds of message operations and plots that one might want to add or include in such a tool.

fg

Tool Operation

The tools is pretty primitive right now, but operation is simple – a command line parameter, or GNU Radio Companion variable default specified which sample data file to open, and by default the sample range [0,4096] is displayed in a time and frequency plot. Moving a slider around allows you to move the start sample throughout the file, and changing the “sample length” field allows you to change the number of complex samples plotted at a time down below. When the file is opened, it tells the range widget the start and end sample within the file so that the bar correctly spans the range of the file.

A screenshot of the tool in operation can be seen below.

plots

Still to do…

There is still lots of clean up remaining, the file source is hard wired for complx64 type samples right now, the gr-pyqt plots still have relatively meaningless labels, no nice context menus, and generally things look a little bit sloppy, but the basic architecture is in place and feels solid and responsive to me. If anyone wants to help with these other items please send pull requests on github!

I would love to switch to using the normal gr-qtgui plotters, once they support message based inputs, but for now these PyQWT based plotters aren’t too bad when passing numpy vectors around in python by reference.

The tool can be found within the gr-pyqt repo’s app’s directory on github @ https://github.com/osh/gr-pyqt/tree/master/apps

A0W2Er7

Burst PSK Modem with LDPC Coding in GNU Radio using Message Passing and Eventstream

Bursty wireless communications are widely used as a multi-user channel access sharing technique in the real world.   This has long been a frustration in GNU Radio as it has traditionally focused heavily on continuous stream processing blocks and applications.   More recently the message passing additions to GNU Radio and gr-eventstream’s burst schedulers have made building burst radio transmission and reception much simpler and more logical to implement than was possible before.   In this post we detail a demonstration of a burst QPSK modem with LDPC coding and a minimal packet header which is implemented in C++ and Python blocks, and can be put together and run easily in the GNU Radio Companion tool (GRC).

Transmit Waveform

The transmitter waveform is comprised of two processing domains which are joined by a an eventstream source block.   The first is a message passing framework which uses PDUs to pass around discrete “bursts” in various forms, and the second which is a traditional GNU Radio stream processing graph.   The eventstream source block provides the insertion of a discrete length “burst” of samples into the continuous, unbounded, stream of samples.

Transmit Waveform: Message Domain

In the message domain we start off with a “Message Strobe” block, this generates a PDU periodically ever 1000ms which triggers the creation of a random data PDU corresponding to each of these.   These random data PDUs are simply a vector of bits with random length and an empty dictionary which could hold information about the burst.

In reality an applicaiton might implement a higher layer MAC here, or simply use a TUNTAP or SOCKET block to allow real data PDUs to flow into the graph, but this is a convenient model for testing.

We pass these random bits into the framer block which adds a length field, a header checksum, and a payload checksum – which will allow us to verify correct reception and determine packet length upon receipt of the burst at the downstream receiver.

After this framer we pass the PDU through a “burst padder” block which simply adds zero bits to the end of the frame until the length reaches an integer multiple of the uncoded forward error correction (FEC) block size.   Since we must encode entire FEC blocks at a time this is a required step, when operating without FEC this could be easily removed.   At this point we go through a randomizer which XORs a random sequence onto the data to whiten the payload bits and pass it through the forward error correction encoder.

We then insert a known binary preamble sequence onto the front of each burst, and pass the burst bits through a QPSK bit to symbol mapper which converts the uint8_t bit vector into a complex float32 sample vector ready to be inserted into our transmission stream.

The image below shows the total transmit flow-graph as described above.

psk_burst_ldpc_tx.grc

Transmit Waveform: Bridging Domains

The last message block is the burst scheduler which decides when in time to schedule the burst.   It is simply a slotted aloha type of scheduler in this case which drops the burst in as soon as it can along some fixed slot boundary.   It sends bursts annotated with a sample-time to schedule them into the stream on to the eventstream source block, and receives asynchronous feedback from the same eventstream source block letting it know where in the stream it is “now”.   Out of this eventstream source block, we get a sample of complex zero samples with events copped into the correct offsets specified by their event time.

Transmit Waveform: Stream Domain

The stream domain then for this transmitter is quite simple, we go through a throttle block to limit the speed of the outgoing sample stream, go through an interpolating RRC filter to convert from 1 sample per symbol up to 2 samples per symbol for transmission, go through a standard channel model block, and finally stream the resulting continuous sample stream containing bursty transmissions out into a file sink and some QtGui plotters.

The graphical display below shows the plot of a single burst’s samples on the left side, and the mixed burst and off states with noise added in the standard stream plotters on the top and right side.   The bottom text area shows the contents of the PDU dictionary as it travels from the burst scheduler to the eventstream source, in this case the only key value pair is the event_time to schedule the burst at.

tx_waveform

Receive Waveform

The receive waveform similarly spans a message and a stream domain, bridged by using an eventstream sink block to extract discrete events from the continuous sample stream at appropriate times.

Receive Waveform: Stream Domain

The receive waveform begins with a sample stream, either from a radio or out of a stored sample file in this case.   To simulate real time operation, we throttle the data stream at the intended sample rate and then run through a matched filter for the presence of a preamble.   The output of this filter is then further filtered through a local comparison to a moving average, in the correlator filter block, and then the resulting detection metric is run into the eventstream trigger rising edge block.

This block detects whenever the detection metric rises over a certain threshold at the beginning of a burst and sends an asynchronous message to the eventstream sink block to extract a burst event beginning at that time in the sample stream.

Receive Waveform: Bridging Domains

 

The eventstream sink block, when triggered by the threshold detector, schedules a stream event which fires off the PDU handler.   This is kind of a dummy handler which simply converts the extracted samples into a standard PDU format message to be handled by any message passing blocks downstream.

Below you can see the flowgraph for the receive waveform as described.

psk_burst_ldpc_rx.grc

Receive Waveform: Message Domain

In the message domain, we start off with a chunk of samples extracted from the continuous stream containing the burst somewhere in it.   We have not done a fine synchronization and we do not yet know the length of the burst, so for now we pull out some upper bound on the length of all bursts worth of samples.    This is immediately plotted as power over time, and then run through a length detector block which attempts to trim some of the noise off the end of the burst based on its power envelope fall off / trailing edge.

At this point we have hopefully minimized the number of samples to run through our synchronization algorithm, which is of non trivial compute complexity.   We plot the constellation three places, before synchronization, after CFO correction, and after equalization and timing correction.   Since the synchronization block is operating on a whole burst atomically we are no longer restricted to using tracking loops which take time to converge at the beginning of a burst.   Instead we can produce an ML estimate for the CFO over the length of the burst, compute optimal timing and equalizer taps over the length of the burst, and then apply them atomically to the entire burst worth of samples.    The lower left hand plot below shows the correlation peak obtained during timing synchronization within this block,  with a clearly observable peak at the preamble.

Coming out of the synchronization block we have the burst’s symbols lined up in the correct rotation based on the preamble, at this point we can go through a soft demapper block, which translates all complex float symbols in the burst into a series of floating point soft bits in a PDU.   We can then use a “burst frame align” block which first strips the preamble bits off the front of the burst, and ensures that we trim all the remaining bits to a multiple of the coded FEC block size (in this case 271 bits).   We pass these soft bit vectors through an FEC decoder block to perform LDPC decoding and output hard bits, and then finally through a burst randomizer to remove the XORed whitening sequence.     The bottom middle plot shows the soft bits of a single frame before this decoding process, we can see that there is a bi-modal distribution representing clusters centered around the soft values for 0 and 1 bits for the useful portion of the frame, which is what we would hope to see.

At this point we can pass the decoded, derandomized hard bits through a deframer which provides a header CRC check, a payload length field to ensure we throw away bits past the end of the frame, and a payload CRC check which ensures none of the bits in the payload were corrupted.

Lastly we send the resulting PDU to a “meta text output” GUI widget, which allows us to look at the PDU’s dictionary values for each burst in a nice clean and easy way.    This is shown in the top right of the GUI below and contains a handful of dictionary entries which have been appended through event scheduling, synchronization, decoding, and deframing.   These include event time, center frequency offset, number of FEC iterations to decode, number of frames passed and failed, and header and payload pass rates, among other things.

rx_waveform

Future Use

The hope of this modem is largely that the message based components allow for very simple implementation of atomic burst operations, without needing to worry about book-keeping complex bursty state information within stream processing blocks.   A lot of effort has been put into trying to develop components which are NOT tightly coupled in any way, and have general applicability to many configurations with the same modem architecture.   I’m quite excited about having this available as a baseline because I think it will be a great starting point for building many projects in the future which seek to tailor various bits of the modem stack for specific situations without having to reinvent anything monolithic.   For the moment this does not represent an “adaptive” modem, but many of the components are already ready for that.   The demapper block can switch per burst based on a “modulation” dictionary entry, and a header deframer could easily schedule the extraction of an additional payload burst region back into the eventstream sink block if this were necessary, so I believe the architecture is already very well set up to handle these sorts of challenges which we are likely to throw at in the near future.

Waveform Information

This waveform both with and without LDPC can be put together using the GRC graphs in the gr-psk-burst repository which can be installed via pybombs.   This is available on github at https://github.com/osh/gr-psk-burst.   It leverages a number of tools from gr-vt as well as gr-pyqt to provide transmission, reception, and plotting.

I should also acknowledge the LDPC encoder and decoder was the work of Manu TS, originally in an out of tree module (https://github.com/manuts/gr-ldpc) developed as part of Google Summer of Code.   This work has now been integrated into GNU Radio’s gr-fec API and has been moved in-tree.   Additionally much of the underlying  work on burst syncrhonization and other burst modem blocks has been made available from Virginia Tech in the gr-vt repo, https://github.com/gr-vt, and represents the work of a handful of very talented people there.

Visualizing Radio Propagation in GNU Radio with gr-uhdgps, gr-eventstream and Google Earth

Radio propagation is a complex process in the real world, elaborate models are often used to predict expected propagation effects over known terrain, but in many cases there is no substitute for measuring ground truth.  This can now be accomplished quite easily, quickly, and cheaply using GNU Radio for a wide range of protocols.   Using gr-uhdgps (https://github.com/osh/gr-uhdgps), gr-eventstream (https://github.com/osh/gr-eventstream) and Google Earth, we demonstrate a tool to measure broadcast VHF FM Radio received signal strength over a metropolitan area. In this case we use WAMU 88.5 MHz, the local NPR radio station in Washington D.C., to demonstrate the tool and plot the results in Google Earth.

Building the GNU Radio Flowgraph

Looking at the WAMU FM Broadcast channel in a QtGui frequency and time sink plot is a good first sanity check.  Since FM broadcast is typically allocated a 200khz channel, we set up a USRP B210 to tune to 88.5 MHz with a complex sample rate of 200 ksamp/s shown below, the expected time and frequency spectrum behavior of the FM audio signal can be seen below.

      FM_WAMU_Plot   uhdgps_rssi_log.grc

In this flow graph we extract and event of 512 samples exactly every 1 second’s worth of samples from the incoming complex sample stream off the UHD source block.  Since we are throwing away the rest of the samples not extracted, this should be a low duty cycle, low CPU load process to run for the most part.   The trigger sample timer event allows us to trigger events at this sample-periodicity of a fixed length and pass them downstream to the “Handler PDU” block, this block simply converts the eventstream events into normal complex float PDUs which can be handled by any message passing block.

At this point we introduce a simple python message block called “CPDU Average Power” which computes the average sample power within the event’s samples, tags a “power” key and value to the PDU’s metadata structue and passes it downstream.    The functionality of this python block is contained within this simple python/numpy handler function which computes the power, adds the tag, and sends the output message — for now we are throwing away the PDU data field and sending PMT_NIL in its place since we don’t do anything with it downstream.

def handler(self, pdu):
    data = pmt.to_python(pmt.cdr(pdu))
    meta = pmt.car(pdu)
    data = data - numpy.mean(data) # remove DC
    mag_sq = numpy.mean(numpy.real(data*numpy.conj(data))) #compute average Mag Sq
    p = self.k + 10*numpy.log10(mag_sq)
    meta = pmt.dict_add(meta, pmt.intern("power"), pmt.from_float( p ) )
    self.message_port_pub( pmt.intern("cpdus"), pmt.cons( meta, pmt.PMT_NIL ) );

At this point we remove the “event_buffer” item from each PDU’s metadata structure using the “PDU Remove” block since it is also large and we don’t really want to keep it around downstream for any reason — trying to serialize this into our JSON storage would waste a lot of space and makes the message debug print outs massive.

Since the B210 is set up with an internal GPSDO board, we have both a GPS disciplined clock and oscillator as well as GPS NMEA fix data available from the UHD host driver.  By using the uhdgps gps probe block, we can poll the USRP source block for the NMEA fix data every time we need to store an updated signal strength record as shown in the flow graph above.  Passing our CPDUS through this GPS Probe block appends the NMEA data and a bunch of current USRP state information from the device which we can look at later.

Finally, we store a sequence of the JSON’ized PDUs to two files, one which has been filtered for records where “gps_locked” is “true” and one which simply stores all records regardless of GPS lock state.

Record Format

Each JSON’ized record corresponding to a single PDU from the flowgraph and a single eventstream extracted sample event, now looks like this in our output files.

We see that this structure now contains gps state information pulled from the GPS probe block, the stream tags which were added to the event/PDU by eventstream, and our power value computed by the python CPDU power block.

{
 "es::event_length": 512,
 "es::event_time": 20201024,
 "es::event_type": "sample_timer_event",
 "gain": 30.0,
 "gps_gpgga": "$GPGGA,003340.00,3852.6453,N,07707.3960,W,0,10,1.2,85.4,M,-34.7,M,,*6E",
 "gps_gprmc": "$GPRMC,003340.00,V,3852.6453,N,07707.3960,W,0.0,0.0,290315,,*3D",
 "gps_locked": "true",
 "gps_present": true,
 "gps_servo": "15-03-29 307 47943 -469.10 -3.21E-09 15 13 2 0x20",
 "gps_time": "1427589220",
 "power": -119.32362365722656,
 "ref_locked": "true",
 "rx_freq": [
 0,
 88499999.9999999
 ],
 "rx_rate": [
 0,
 199999.99937668347
 ],
 "rx_time": [
 0,
 [
 892243202,
 0.5858168473168757
 ]
 ]
}

Drive Testing

To test this graph I securely fasten a macbook into the passenger seat, wedge a B210 between the center console and passenger seat of the Subaru test vehicle, and plunk a GPS antenna and a poorly band matched magnetic duck antenna onto the trunk of the car and go for a drive.   After launching the application, it takes a while for the GPSDO to obtain GPS lock since it is not network assisted like many devices these days.  After a few minutes of waiting and impatiently moving the GPS antenna to the roof of the car, I obtained lock and pretty much kept it for the entirety of my drive.

seat-scaled  antenna-scaled

Inspecting Results

After driving a thorough route around town and past the broadcast tower, I dump JSON file through uhggps’s json_to_kml.py tool to convert the rss measurements into a Google Earth friendly KML file with vertical lines of height proportional to the RSS at each event location. This provides a nice intuitive visual way to look at and understand received signal strength effects over various terrain changes, obstacles, urban canyons and other such phenomena.   An overview of the route can be seen here.

plot_overview

A few notable spots around the route are shown below.

Driving through Rosslyn, VA we can see massive variation in received signal strength at each sample point likely due to massive amounts of urban multi-path fading resulting in large amounts of constructive and destructive interference.

rosslyn_variance

Driving from the George Washington Parkway North onto the US 1 memorial bridge on the far side of the river, we can see that going from a lower elevation shadowed by a small hill and a handful of trees up onto the elevated and unobstructed bridge resulted in a significant sustained climb in signal strength level.

memorial_bridge

Driving north on Connecticut Avenue, taking the tunnel underneath Dupont Circle, we briefly lose GPS lock, but upon reacquiring show a very low received signal strength down in the lowered, partially covered, and heavily shadowed roadway coming out of the tunnel.   To deal with this kind of GPS loss of lock we may need to implement some kind of host based interpolation of fixes occurring on either side of the positioning outage, but for now the samples are discarded.

dupont_tunnel

There are numerous other effects that can be observed in the KML plot from the drive which is linked below.

KML Issues

One issue that I’m still considering is how exactly to map RSSI to amplitude, there are two modes which KML supports and each has their own issue.   These plots are using “aboveGroundLevel” which maps “0” elevation to the ground elevation at a specific LAT/LON, this is good because we can just use 0 for our base height and not worry about putting a line underground somewhere, however its annoying in that the line elevation tracks with the ground contours distorting your perception of RSSI a bit.   Absolute doesnt have this problem, since 0 is (I think) locked at sea level, however in places farther from the coast this is annoying because low elevations wind up underground.  Ultimately it might be neccisary to do some kind of aboveAverageGroundLevel for a local area, but this is as far as I know not supported in Google Earth / KML.

References

The resulting KML file can be opened in Google Earth from https://github.com/osh/kml/blob/master/WAMU_RSS.locked_1427589112.17.json.kml.   Countless hours of fun can be had zooming around inspecting fades that occur passing various obstacles along the way and signal strength variation going up and down hills in various areas.

This flow graph is available at https://github.com/osh/gr-uhdgps/blob/master/examples/uhdgps_rssi_log.grc   To use it make sure you have gr-eventstream, gr-uhdgps, and relevent python modules installed.

Burst Transmission in GNU Radio Sample Streams with Eventstream

There are a handful of ways to transmit bursts of information using GNU Radio.   Perhaps most widely known is when using Ettus UHD based devices, burst transmit mode may be used to schedule bursts of samples within a properly tagged stream.   This results in accurately timed bursts and low latency transmission, but it also relies upon specific Ettus hardware and makes software only loopback simulation impossible.

This alternative method of burst transmission using the eventstream (http://github.com/osh/gr-eventstream) source block to interleave bursts into a sample stream is hardware independent, and allows precise timing of bursts in the sample stream in a hardware independent way which works well in loopback software simulation.   The performance cost is a slightly higher latency penalty than the UHD burst mode due to reliance on back-pressure within the GNU Radio transmit stream.  But it does have the nice side effect of resolving the ugly “flushing the end of a burst” nastiness which may be present when sending the end of a tagged stream burst through GR blocks and/or transfer to a UHD device.

Signal Source

Our burst signal source in this case is the random pdu generator block.   The message strobe block sends it a message every 500ms telling it to generate a new burst of between 50 and 2000 bits in length.   We ensure that each byte contains a single bit by anding a mask of 0x01 in this generator block.   Finally we go through a PDU PSK burst mapper block to map the random bits into a complex constellation points in our new complex float PDU.

These bursts are plotted using the gr-pyqt burst plotters for reference before continuing down the chain.   They are burst only and and no “off” time exists at this point in the graph.

Eventstream Source / Burst Scheduling

The eventstream source block, void of any events, simply produces zero valued samples at a rate limited by downstream back-pressure.   A variety of transmit handlers may be used to produce output into this sample stream at precise times and lengths.   One convenience handler which is provided is the PDU Handler, which is internal to the eventstream source block, and allows you to simply pass a PDU of samples into the block to be sequenced into the output stream without worrying about the more complex trigger/handler paradigm.

Once we pass a complex float PDU into the schedule_event port, the block checks for an “event_time” tag on the event to tell it what precies sample index time to schedule the burst at.   When this value is not provided, a time of 0 is assumed, which is the case in this example.   The source block defines and “Early Behavior” which defines what should happen if an event arrives with a time which is too early for the current stream nitems_written(0) value (number of samples which have been produced).   The options here are to DISCARD, throw away events which are too early, BALK, or throw an exeption and stop running when such event arrive (useful for debugging), or ASAP, to simply schedule them at the earliest possible time available.   In this case we use ASAP to schedule the events as soon as possible.

flowgraph_es_tx

Plotting

Coming out of the eventstream source block, we throttle the stream to limit production rate to our desired sample rate, add some gaussian noise to make plots more interesting, and then plot the power of the resulting sample stream with roughly time periodic bursts in it.   We can see that plotting a burst tirggered roughly every 500ms does result in a burst being scheduled into the stream roughly every 500ms.   If we wanted this to be exactly 500ms in samples, we could set the “event_time” tag on the event to the proper sample index desired, but this exercise will be left to a future example.

plots

Simple GNU Radio Eventstream Based Burst Extraction and Plotting

Both gr-eventstream and gr-pyqt have been around and available now for a while on my github page, but not enough good documentation material exists for either.   This article aims to address that and show a simple example of how they can both be used to plot synchronous windowed burst events occurring within a standard sample stream.   In order to run this example you will need to install both gr-eventstream (https://github.com/osh/gr-eventstream) and gr-pyqt (https://github.com/osh/gr-pyqt) for plotting.

Signal Source

The signal source for this example is a simple complex sinusoid with amplitude 1 added to complex Gaussian noise with mean amplitude 0.01.   This gives us a nice well understood signal to play with, but adds a bit of noise so plots aren’t identical each burst or sine period.

Detecting Bursts

Using eventstream, finite time-windowed events or bursts are scheduled by blocks known as “trigger” blocks.   There are many ways to do this such as a matched filter correlators, cyclostationary detectors, or otherwise; in this example we will use a generic “Rising edge trigger” block which simply picks up on a float stream rising over a constant threshold value.   Instead of implementing a new custom trigger block for you application, you may be able to use stream operators to produce a detector of interest and then simply pass the derived metric into the trigger block to perform detection.   In the case of this example, we use the real component of the complex sinusoid as our detection metric since it should be quite intuitive as to what is going on here to most readers.   This trigger block allows you to specify a threshold value, an event length (the number of samples contained in each event), a “lookback” length (how many samples before the actual rising edge should the event start), and a minimum trigger distance (a debouncing parameter to allow you to disallow subsequent events for N samples, incase of a noisy rising edge).   These can be seen in the flowgraph image below; upon meeting the threshold criteria, an event description is sent to the eventstream sink block.

Extracting Events

The Eventstream Sink block performs a seemingly simple, but tedious function.   It takes in “event” descriptions (event type, event time, event length) extracts the samples from a stream corresponding with the event time and length specified, and then sends the populated event to all “handlers” downstream which are associated with this type of event to operate.

Handling Bursts

Eventstream burst handlers are blocks which implement a special c++ GR Block interface which implements a handler( pmt_t msg, gr_vector_void_star buf ) method.  Since you probably don’t want to write a custom c++ handler function/class, the easiest thing to do in many applications is to use a “Handler to PDU” block.   This block takes the incoming event parameters and sample buffer from the event and converts it to a standard GNU Radio PDU formatted PMT asynchronous message to send downstream.  Standard GNU Radio asynchronous message blocks which operate on PDUs may then be used to operate on it.  That is the method we will use in this example.

Handling PDUS

Now that we have a standard PDU with complex floats contained in its data vector, we can send it downstream to two message blocks using fan-out of standard GNU Radio message ports.  The first connection is to a simple message debug block, this simply prints the message contents to the console for the user’s viewing pleasure.   The second is to a gr-pyqwt complex time plot, which plots the event as a single atomic plot every time a new message comes in.   These plotters were implemented to allow plotting of an entire event at once regardless of its sample length, as opposed to the existing gr-qtgui plots which are as of this writing, intended for stream plotting and by default plot 1024 samples every time, not variable as these are to the event buffer’s length.  This can be very useful for diagnosing the behavior of various burst waveforms.

Connecting Everything Up

Connecting everything up, we connect our raw sample stream input into the pass-through port on the trigger rising edge block, and we connect our detection metric (the complex to real float stream output) into the thresh_input port.

We then connect our trigger block’s passthrough_out port to the es.sink input block (We keep the sink downstream simply to ensure that the sink does not consume anything that the trigger has not yet, otherwise there would be no need to go through the trigger block), and the trigger’s “which stream” message port to the sink’s “schedule_event” port, this tells the trigger where to schedule the burst extraction when a trigger has been fired.

Finally, we connect the “edge_event” port on the trigger to the “handle_event” port on the “handler”.   This is perhaps the most confusing connection, it specifies that events of the type “rising edge” shall be handled by the “to pdu” handler, however it is a logical connection and does not represent the actual data path at runtime!  At runtime, the event goes to the eventstream sink, is populated with samples, and then passed to the handler to be converted to a PDU.   However, this direct connection is used to provide a more intuitive GRC connection line to show logically which events go to which handlers.   In reality when start() is called in the flowgraph, trigger blocks indicate their connections downstream to their corresponding es.sink blocks which which event types correspond to which handlers so that they then known at runtime which handler blocks to forward populated events to.

The final completed flowgraph can be seen below.

flowgraph

Plotting the results

When we run this flowgraph, we now expect to trigger every time the signal’s real component rises past a value of zero.   Since we have a bunch of added noise, we will actually also see a noisy “rise” past zero when we pass zero both on the way up and down, as you will see when you run the flowgraph.   You will see on the X axis below that each event is 1000 samples in length, which was specified in our trigger block’s “event length”.  In this case it is a constant size, but a trigger can specify a different event length for each burst if desired.  By playing with the lookback value to 500, you could center the zero crossing in the event if desired here.  Lastly since we have a debounce window of 300 samples. we only get one event per crossing even though the noise on top of the signal causes several local crossings in the area for each of these in reality.

Our event shown below, shows the real (green) and imaginary (red) portions of our complex sinusoid with the exact sample of the trigger occurrence being the first (leftmost) sample in the plot.

burst_plot

Example Source

The GRC source file for this example can be downloaded at: https://github.com/osh/gr-eventstream/blob/master/examples/demo_01_burst_rx.grc

Asynchronous Message Synchronization

It occurred to me the other day when playing with a message based application that it would be nice to have a general purpose way to perform message fan-in synchronization of asynchronous messages arriving on N ports. This is a relatively trivial convenience block, but can be used widely to simplify the logic of any block seeking to operate on N arriving messages at a time, allowing it to instead operate on an N-tuple produced by this block removing the need for any custom buffering or synchronization logic within an application or algorithm specific block. Hopefully this will be useful to others as well. The only remaining issues with this block are that it currently does not support providing back-pressure upstream, instead the std::deque’s currently grow unbounded. There is unfortunately not a great way to provide back-pressure when implementing message handlers with boost::bind, blocking in the handler is not a good option as I believe it would result in deadlock, this may be an interesting case where we need a work function operating directly on the native pmt message queues to allow them to provide back-pressure. However in the current scheduler we do not provide support for calling work() in the block thread context in the absence of any stream ports. This should be a good toy problem to help push the scheduler forward. Possible resolutions are, allowing some kind of work function which is called without any stream ports to allow checking async input buffers, or allowing the definition of some kind of handler function which allows checking or not consuming an item from an input message buffer.

Message Synch Example
Message Synch Example

Pull request @ https://github.com/gnuradio/gnuradio/pull/330

GNU Radio ZeroMQ Block Enhancements

Since some time this summer, GNU Radio has merged gr-zeromq into the mainline master branch, these handy I/O blocks have since then provided nice mappings to allow GNU Radio stream ports to use a library called ZeroMQ as a network transport layer for distributing stream items between processes, or between separate processing nodes over a network layer. This is a great addition beyond the original GNU Radio socket based blocks as it provides additional features such as session re-establishment, point to multi-point distribution, and some other message passing models for I/O.
Over this winter break I finally had a bit of free time to finishing working on some enhancements for these blocks which I started working on this year at the GNU Radio Conference 2014 hack-fest. These are elaborated below.

Stream Tag Passing Support

The first major addition to these blocks was the ability to transport stream-tags over ZMQ connections along with the raw stream item contents. This is a major help when working with distribution of GNU Radio flowgraphs as it allows stream tags to be preserved over network connections or between processes with minimal effort allowing them to be generated in one application, and referenced or consumed in a completely separate application elsewhere. In order to introduce stream tag passing support for ZMQ stream connections in GNU Radio, we introduce a new ZMQ message header which encodes a small header, stream offset, and any stream tags within the relevant range. The PMT library’s serialization capability is used to map it into a raw buffer and to recover the PMT data structure upon reception. As can be seen in the simple example below, this successfully allows for tags to be preserved as expected.

The two example graphs below show one flowgraph generating a stream with a sinuoid, a stream to tagged stream block appending a “packet_len” tag to this stream every 1024 items, and then a ZeroMQ Publish sink block to end the waveform.   The second waveform begins with a ZeroMQ Subscribe source block which receives the stream and tags from the other waveform, and sends the output to a QTGui frequency plot sink where we can see the sine wave is in tact, and a tag debug block where we can see the tags are making it through the connection ok!

Stream Tags over ZMQ Connection
GNU Radio ZeroMQ Tagged Stream + Sinusoid Passing Example

Message Passing Support

The second new addition was simply to introduce a new set of message passing blocks which simply allowed the transport of GNU Radio message connections over the primitive ZeroMQ transport interfaces. In contrast to the stream port versions of these blocks, the implementation is quite trivial involving simply the serialization of a single GR Message PMT type into a ZMQ message and the inverse operation upon reception. No additional buffers or context is required since a single PMT constitutes a message between GNU Radio blocks.

The simple example below shows a message strobe block sending a single pmt string/symbol containing “TEST” through a ZeroMQ publish/subscribe message block to a message debug block once every second.

GNU Radio ZMQ Message Passing Example
GNU Radio ZeroMQ Message Passing Example

Until this work is approved and merged, it is available from my github branch at https://github.com/osh/gnuradio/commits/zmqtags

Five Orders of Magnitude Improvement in GNU Radio Stream-Tag Propagation Performance

Recently at GNU Radio Conference 2014 I presented a performance benchmark (https://github.com/osh/gr-chunky) comparing the maximum throughput and expected latency of several simple burst processing graphs.   These included comparing functionally identical graphs based on a message passing design using PDUs to carry around data vectors with a stream tag based design using normal GNU Radio stream buffers and burst-demarcating tags to pass data vectors around.   The initial performance results were somewhat of a landslide performance difference in favor of PDUs, so much so that it was clear that something was wrong, tagged stream item blocks should perform better than this.  To address this Eric Blossom, Doug Geiger, and I set about profiling the root cause and updating the GNU Radio runtime to mitigate the performance issue.

Original Profiling Results

Upon benchmarking the original tag propagation code, we can see that the prune tags method is completely dominating run time.

TSB Profiling Deque Impl
TSB Profiling Deque Impl

Diving into the code for prune_tags method, we see the following which appears to be something of a O(n^2) linear list search and erase within the deque data structure.

 std::deque<tag_t> buffer::d_item_tags;

 void
 buffer::prune_tags(uint64_t max_time)
 {
 std::deque<tag_t>::iterator itr = d_item_tags.begin();
 uint64_t item_time;
 while(itr != d_item_tags.end()) {
   item_time = (*itr).offset;
   if(item_time+d_max_reader_delay + bufsize() < max_time) {
     d_item_tags.erase(itr);
     itr = d_item_tags.begin();
   }
   else
     itr++;
   }
 }

We update the deque to a stl multimap implementation as shown below which should give us something more like an O(log n) access time instead.

std::multimap<uint64_t,tag_t>  buffer::d_item_tags;

 void
 buffer::prune_tags(uint64_t max_time)
 {
 std::multimap<uint64_t, tag_t>::iterator end_itr = d_item_tags.lower_bound(max_time);
 std::multimap<uint64_t, tag_t>::iterator begin_itr = d_item_tags.begin();
 d_item_tags.erase(begin_itr, end_itr);
 }

Profiling After Updates

After updating the tag storage class to be a std multimap, keyed on a uint64_t offset, and the gr_tag_t value, we can update all of our core tag functions to access the data store using more efficient RB-tree access inefficiencies.   Re-running the same benchmark on the updated code we observe the following, a much more reasonable split of processing time than we stated with.

TSB Profiling Multimap Impl
TSB Profiling Multimap Impl

Final Results

The final benchmark performance measurement results are shown below for the following three cases.

  • TSB – Original Stream-Tag Deque Implementation
  • TSB2 – Updated Stream-Tag Multimap Implementation
  • PDU – Message Port Implementation

b2_thru

Graph Throughput
b2_latency
Graph Latency

PDU Latency is probably largely dominated by message port queue depth resulting in back-pressure at a specific depth in these experiments.   We can see that through these data structure updates, our TSB throughput has increased by roughly two orders of magnitude while our TSB latency measure has decreased by almost three orders of magnitude in this case!   We can see this is a major improvement in the unthrottled TSB performance case tested here!