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.
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.
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.
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.
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.
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.
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.
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