Pulling radio data out of thin air

Yuval Adam

Yuval Adam

  • Full stack developer and systems architecture consultant
  • Using Python for everything for 10+ years
  • https://yuv.al
  • @yuvadm

This talk

Radio

static/waves.gif

Hardware Radio

static/radio.jpg

Hardware Radio

static/gsm.jpg

Software-Defined Radio

rtlsdr.jpg

What's it good for

  • Digital TV broadcast ("Idan+", DVB-T)
  • Airplane tracking (ADS-B)
  • Weather satellites
  • IoT project integration
  • FM radio broadcast

Airplane Location Tracking (ADSB)

static/adsb.png

Weather Satellites

static/noaa.jpg

I/Q Sampling

static/cosample.png

Source: http://whiteboard.ping.se/SDR/IQ

I/Q Sampling

static/corkscrew.png

Source: http://whiteboard.ping.se/SDR/IQ

GQRX

In [ ]:
# pyrtlsdr provides us bindings to work with the RTL-SDR driver

from rtlsdr import RtlSdr

sdr = RtlSdr()
sdr.sample_rate = 1.2e6       # 1,200,000 samples per second
sdr.center_freq = 91.8e6      # 91,800,000 Hz frequency for the radio station
sdr.gain = 'auto'             # tune the gain (AKA "volume") automatically

samples = sdr.read_samples(1.2e6 * 10)
sdr.close()
In [1]:
# Alternatively, load previously captured samples from rtl_sdr
# and convert them from unsigned 8-bit samples to a complex IQ array

import numpy as np

raw = np.fromfile('/tmp/samples.in', np.uint8).astype(np.float64)
raw += -127
raw /= 2**7
samples = (raw[0::2] + 1j * raw[1::2]).astype(np.complex64)
In [2]:
# We captured too many samples so we need to apply a low pass filter
# In other words, we have a too large window and we want to make it smaller

import scipy.signal as signal

BANDWIDTH = 200e3
DECIMATION_RATE = int(1.2e6 / BANDWIDTH)  # Decimation factor of 6

samples = signal.decimate(samples, DECIMATION_RATE)
In [3]:
# Apply the Frequency DEmodulation
# We use something called a polar discriminator

samples = np.angle(samples[1:] * np.conj(samples[:-1]))
In [4]:
# De-emphasis filter
# Even I'm not sure exactly how/why this works

# MAKE THINGS SOUND BETTER

d = BANDWIDTH * 75e-6
x = np.exp(-1/d)
b, a = [1-x], [1,-x]
samples = signal.lfilter(b, a, samples)
In [5]:
# Decimate the signal down to something an audio driver can handle
# We only catch the mono part of the signal

AUDIO_RATE = 50e3
DECIMATION_RATE = int(BANDWIDTH / AUDIO_RATE)  # Decimation factor of 4

samples = signal.decimate(samples, DECIMATION_RATE)

# Amplify the signal volume
samples *= 10000
samples = samples.astype('int16')
In [8]:
# HIT IT

import alsaaudio

device = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, device='default')
device.setchannels(1)
device.setrate(50000)
device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
device.setperiodsize(120)

# Write data in chunks
for s in np.array_split(samples, 120):
    device.write(s)

Review

  • Took a fixed set of samples, did some math on them
  • Python, NumPy and SciPy are very good at doing fast processing of static data
  • When handling real-time data, buffering becomes a serious issue
    • How do you synchronize different input/ouput sample rates on the same flow?

GNU Radio

  • Comprehensive signal processing framework
  • Implemented many DSP primivites
  • Has a great scheduling engine
  • Actually generates flowgraphs in Python

Where to go from here

Thank you! Questions?

In [ ]: