Tutorial: Consuming RF Data in Python

The Renni Software Defined Radio System comes pre-loaded with a series of useful applications. This tutorial focuses on the clearBOX application. The clearBOX application continuously captures RF data. The IQ data is combined with relevant metadata to form a data packet. The data packets are broadcast over a UDP Multicast channel and can be consumed using a wide variety of programming languages.

In this tutorial, we're going to show you how to consume real-time data packets from a Renni System in Python. We'll start by configuring the Renni Receiver. Then we'll write a Python script to consume and plot the captured waveform.

The code for this tutorial is available on our GitHub page.

Prerequisites

1. Your Renni system is powered up

2a. You are using you Renni as a host (with an attached monitor, keyboard, and mouse)

OR

2b. Your Renni is connected to the same network as your development machine

Note: If you're using Renni as a network appliance, you'll need to install Python on your development machine

Settings

All of the RF interfaces on the Renni SDR can be configured using a web browser. This allows you to interface with a Renni on the Operating System (OS) of your choice. If your OS comes with a semi-modern web browser (which is probably does) then the you'll be able to use Renn without installing any software on your development machine (note: you can also use a Renni with a monitor, mouse and keyboard attached to the box).

First, we must make sure the clearBOX application is selected. We can do this by directing any web browser to the Renni System.

If you're using Renni as a host

  • Point a web browser to 'http://localhost/cfg'

If you're using Renni as a network appliance on a development machine WITH an mDNS/Avahi client

  • Point a web browser to 'http://renni.local/cfg' (note: some mDNS clients use the 'home' nomenclature over 'local'; for example, 'http://renni.home/cfg')

If you're using Renni as a network appliance on a development machine WITHOUT an mDNS client

  • Determine the IP address of the Renni System. Renni ships with DHCP enabled as well as a virtual IP Address
    • Determining the IP Address using DHCP
      • Connect a monitor to the system, open a terminal and type ifconfig
      • The negotiated inet address should be listed under the device that it used to connect to you network
    • Configure your development machine onto the same virtual network as you Renni (192.168.0.25/24)
  • Point a web browser to 'http://<IP_ADDRESS>/cfg' where <IP_ADDRESS> is the configured IP Address of your Renni System.

This will pull up the Application Configuration Page 

Application Settings

This configuration page allows you to select which application is active. Note: your selection will saved and persisted across reboots. Select the Packetizer from the drop down menu. Then click Open Application. You'll be re-directed to the clearBOX Settings Page 

clearBOX settings page

 

Configure your receiver with your desired settings and lets switch to Python. 

Python 

Note: Source code for this tutorial is available from our GitHub page. It is licensed under the MIT license. So feel free to re-use and profit!

First, let's create our Packet objects


packet = BasisPacket.BasisPacket()
packetSize = packet.packetSize()

The BasisPacket class will deserialize the data packets into metadata and IQ pairs.

Now lets setup the UDP Multicast settings. The Renni clearBOX Application is running on '224.12.34.56:9083'.


mcast_group = "224.12.34.56"
mcast_port = 9083

Time to connect to the broadcast


sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', mcast_port))
group = socket.inet_aton(mcast_group)
mreq = struct.pack('4sL', group, socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

We're ready to receive packets. We previously determined the packet size (in bytes) from the BasisPacket class. We saved the packet size into the variable packet_size. So we are ready to receive a packet


(raw,address) = sock.recvfrom(packet_size)

We just received a packet of data. We can deserialize the packet using the unpack method in the BasisPacket class and get the IQ pairs


packet.unpack(raw)
iq_samples = packet.data

Now, let perform a windowed FFT and setup our plotting


yf = np.fft.fftshift(fft(iq_samples * hanning(num_samples)) / num_samples)
xf = 1e-6*np.linspace(-0.5*packet.fs, 0.5*packet.fs, num_samples)
xt = np.linspace(0, num_samples/packet.fs, num_samples)
max_range = 1.2*math.ceil(max(max(abs(packet.data.real)), max(abs(packet.data.imag))))

Finally, plot 


plt.figure(1)
plt.suptitle('Renni Capture')
plt.subplot(211)
plt.plot(xt, packet.data.real, 'r', label='real')
plt.plot(xt, packet.data.imag, 'k', label='imag')
legend = plt.legend(loc='upper right', shadow=True)
plt.title('Time Domain')
plt.xlabel('Time (secs)')
plt.ylabel('Amplitude (ADC counts)')
plt.axis([0, xt[-1], -max_range, max_range])
plt.grid()
plt.subplot(212)
plt.plot(xf, 20*np.log10(np.abs(yf)))
plt.title('Frequency Domain (Center Frequency: ' + str(packet.cf*1e-6) + ' MHz)')
plt.xlabel('Frequency (MHz)')
plt.ylabel('Power (dBc)')
plt.xlim([xf[0], xf[-1]])
plt.grid()
plt.show()


For any comments or questions on this tutorial, please reach out to support@basisfunctional.com

Leave a comment

Please note, comments must be approved before they are published