Sending data from GNU Radio into Linrad using the network

During the last few days I've been experimenting with feeding signals from GNU Radio into Linrad using Linrad's network protocol. Linrad has several network protocols designed to share data between different instances of Linrad, but generally these protocols are only supported by Linrad itself. The only other example I know of is MAP65, which can receive noise-blanked data from Linrad using the timf2 format.

The result of these experiments is a GNU Radio out-of-tree module called gr-linrad which allows to send data from GNU Radio and receive the data in Linrad. Currently, gr-linrad only supports sending a one-channel complex IQ signal using the raw 24 bit format, but I'll probably add more options in the future. The intended application of gr-linrad is to easily add support for SDR hardware to Linrad. Usually GNU Radio has support for most SDR hardware in the market, perhaps through osmocom or other libraries. Linrad has support for a good amount of SDR hardware, but there are some notable exceptions of unsupported hardware, such as the HackRF One. I also want to use my Hermes-Lite 2.0 beta2 in Linrad, and this seems the easiest way to do it.

Another possible use of gr-linrad is as an instrumentation for any kind of GNU Radio flowgraph. It is very easy to stream data into Linrad, so it can be used as a very nice waterfall display or to do any sort of signal processing, such as noise blanking or adaptive polarization. Here I describe how to get the test flowgraph in gr-linrad working and some aspects of the network protocol.

The first step to set up the test flowgraph is to open and run examples/test.grc in gr-linrad with gnuradio-companion. Next, we open Linrad and configure the network input as follows.

From the main menu, we press U, A and Y to select network input and get into the network setup screen. Then we set our receive address to 127.0.0.1 and the RX input data format to raw data, 24 bit. The network setup screen should be as in the image below.

Linrad network setup

Next we press Z to disable the output soundcard (or we select some soundcard with B instead). The RX A/D setup should be as in the image below.

Linrad RX A/D setup

Then we press X and W to exit the setup and save the parameters, and we press D to enter SSB mode. Linrad should start receiving data from GNU Radio in real time and we should see something as in the image below.

Linrad receiving test signal

Linrad's network protocol has two parts. The first is the data streamer, which sends the samples in UDP packets in real time. The different formats use different UDP destination ports, so several formats can be enabled simultaneously. The second part is a TCP server that always listens on port 49812 and is interrogated by the clients on start up to get some parameters concerning the stream.

The contents of the UDP packets have the following structure, which is defined in globdef.h in Linrad's source code:

typedef struct {
double passband_center;        //  8
int time;                      //  4
float userx_freq;              //  4
int ptr;                       //  4
unsigned short int block_no;   //  2
signed char userx_no;                 //  1
signed char passband_direction;       //  1
char buf[NET_MULTICAST_PAYLOAD];
} NET_RX_STRUCT;

There is a header which has some parameters which can change dynamically and then NET_MULTICAST_PAYLOAD bytes worth of samples follow. Everything is in machine endianness. The samples are interleaved for all the channels, so for a single IQ channel in 24 bit raw format the first 3 bytes are the first sample from the I channel, the next 3 bytes are the first sample from the Q channel and so on. passband_center is the centre frequency of the passband in MHz, time is the time of day in milliseconds, and block_no is the packet number. The ptr is really important and depends on the concept of buffer. Linrad has a lot of circular buffers of a fixed size which are used for signal processing. The size of the input buffer is computed in the master and the clients inherit the same buffer size (which they get from the master using the TCP protocol). ptr points to the position of the circular buffer where the samples in the next UDP packet would start. Therefore, ptr is NET_MULTICAST_PAYLOAD % buffer_size for the first packet and then for each subsequent packet it gets incremented by NET_MULTICAST_PAYLOAD, always taking modulo buffer_size. In gr-linrad, the user has to decide an appropriate buffer size, which should be a power of two, although it would be good to decide it from the number of channels and sample rate. The default of 4096 should be good in many cases.

So far I only understand Linrad's TCP protocol enough to implement something that works. The scheme is simple. The client can send a packet with one of several kinds of requests and the server responds with the appropriate data. The most important request is NETMSG_MODE_REQUEST, which is a packet that starts by 0xb8 (and it has some other data I don't understand). The answer is a packet with 8 32-bit integers as follows (code taken from network.c in Linrad's source).

case NETMSG_MODE_REQUEST:
    intbuf[0]=ui.rx_ad_speed;
    intbuf[1]=ui.rx_ad_channels;
    intbuf[2]=ui.rx_rf_channels;
    intbuf[3]=ui.rx_input_mode;                
    intbuf[4]=snd[RXAD].block_bytes;
    intbuf[5]=fft1_size;
    intbuf[6]=fft1_n;
    intbuf[7]=genparm[FIRST_FFT_SINPOW];
    net_write(fd,intbuf,8*sizeof(int));
    if(kill_all_flag)goto netserver_error_exit;
    break;

The important parameters are the following: rx_ad_speed is the sample rate, rx_ad_channels is the number of real channels, rx_rf_channels is the number of RF channels (so, for instance, a single channel IQ signal would have 2 AD channels and 1 RF channel, while a double channel IQ signal would have 4 AD channels and 2 RF channels), rx_input_mode is an OR of several flags (in practice, it has to be 1 for real format and 5 for IQ format), and block_bytes is the buffer size I have mentioned before. The rest of the parameters are not important for raw format streaming (Linrad can also stream FFT transforms instead of raw samples).

There are two other requests which start by 0xb5 and 0xb6. When I tested Linrad to understand how the protocol works, it seems that Linrad responds to these requests with 0x00 always, and that is what gr-linrad does as well. They seem to be for calibration data. In fact, the constants NETMSG_CAL_REQUEST = 0xa2b5 and NETMSG_CALIQ_REQUEST = 0xa2b6 appear in network.c.

The implementation in gr-linrad follows this description of Linrad's protocol. There is a block which implements the TCP server, and the user can set all the important parameters. It is coded in Python for simplicity. There is also a block which implements the UDP streamer and is coded in C++ for performance. There is no callback implemented yet for updating the centre frequency of the passband, which can change dynamically. As you can see below, the test flowgraph in gr-linrad is really simple.

Test flowgraph in gr-linrad

One Reply to “Sending data from GNU Radio into Linrad using the network”

Leave a Reply

Your email address will not be published. Required fields are marked *