{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Pulling radio data out of thin air\n", "\n", "## Yuval Adam" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Yuval Adam\n", "\n", " - Full stack developer and systems architecture consultant\n", " - Using Python for **everything** for 10+ years\n", " - https://yuv.al\n", " - [@yuvadm](https://twitter.com/yuvadm)\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## This talk\n", "\n", " - I never learned physics, RF engineering or signal processing\n", " - But radios are pretty cool!\n", " - Hopefully I can show you some neat things\n", " - Slides and code @ https://github.com/yuvadm/radio-pyconil-2018" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Radio\n", "\n", "![static/waves.gif](static/waves.gif)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Radio Waves\n", "\n", "![static/dipole.gif](static/dipole.gif)\n", "\n", "*Source: https://en.wikipedia.org/wiki/Antenna_(radio)*\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Hardware Radio\n", "\n", "![static/radio.jpg](static/radio.jpg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Hardware Radio\n", "\n", "![static/gsm.jpg](static/gsm.jpg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Software-Defined Radio\n", "\n", "![rtlsdr.jpg](static/rtlsdr.jpg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## What's it good for\n", "\n", " - Digital TV broadcast (\"Idan+\", DVB-T)\n", " - Airplane tracking (ADS-B)\n", " - Weather satellites\n", " - IoT project integration\n", " - FM radio broadcast" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Airplane Location Tracking (ADSB)\n", "\n", "![static/adsb.png](static/adsb.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Weather Satellites\n", "\n", "![static/noaa.jpg](static/noaa.jpg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Internet of Things\n", "\n", "![static/iot.jpg](static/iot.jpg)\n", "\n", "*Source: https://blog.hackster.io/iot-devices-may-be-susceptible-to-replay-attacks-with-a-raspberry-pi-and-rtl-sdr-dongle-de6eca268fbf*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# I/Q Sampling\n", "\n", "![static/cosample.png](static/cosample.png)\n", "\n", "*Source: http://whiteboard.ping.se/SDR/IQ*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## I/Q Sampling\n", "\n", "![static/corkscrew.png](static/corkscrew.png)\n", "\n", "*Source: http://whiteboard.ping.se/SDR/IQ*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Modulations\n", "\n", "![static/amfm2.gif](static/amfm2.gif)\n", "\n", "*Source: http://mriquestions.com/signal-squiggles.html*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## GQRX" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "# pyrtlsdr provides us bindings to work with the RTL-SDR driver\n", "\n", "from rtlsdr import RtlSdr\n", "\n", "sdr = RtlSdr()\n", "sdr.sample_rate = 1.2e6 # 1,200,000 samples per second\n", "sdr.center_freq = 91.8e6 # 91,800,000 Hz frequency for the radio station\n", "sdr.gain = 'auto' # tune the gain (AKA \"volume\") automatically\n", "\n", "samples = sdr.read_samples(1.2e6 * 10)\n", "sdr.close()" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# Alternatively, load previously captured samples from rtl_sdr\n", "# and convert them from unsigned 8-bit samples to a complex IQ array\n", "\n", "import numpy as np\n", "\n", "raw = np.fromfile('/tmp/samples.in', np.uint8).astype(np.float64)\n", "raw += -127\n", "raw /= 2**7\n", "samples = (raw[0::2] + 1j * raw[1::2]).astype(np.complex64)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# We captured too many samples so we need to apply a low pass filter\n", "# In other words, we have a too large window and we want to make it smaller\n", "\n", "import scipy.signal as signal\n", "\n", "BANDWIDTH = 200e3\n", "DECIMATION_RATE = int(1.2e6 / BANDWIDTH) # Decimation factor of 6\n", "\n", "samples = signal.decimate(samples, DECIMATION_RATE)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# Apply the Frequency DEmodulation\n", "# We use something called a polar discriminator\n", "\n", "samples = np.angle(samples[1:] * np.conj(samples[:-1]))" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# De-emphasis filter\n", "# Even I'm not sure exactly how/why this works\n", "\n", "# MAKE THINGS SOUND BETTER\n", "\n", "d = BANDWIDTH * 75e-6\n", "x = np.exp(-1/d)\n", "b, a = [1-x], [1,-x]\n", "samples = signal.lfilter(b, a, samples)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# Decimate the signal down to something an audio driver can handle\n", "# We only catch the mono part of the signal\n", "\n", "AUDIO_RATE = 50e3\n", "DECIMATION_RATE = int(BANDWIDTH / AUDIO_RATE) # Decimation factor of 4\n", "\n", "samples = signal.decimate(samples, DECIMATION_RATE)\n", "\n", "# Amplify the signal volume\n", "samples *= 10000\n", "samples = samples.astype('int16')" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# HIT IT\n", "\n", "import alsaaudio\n", "\n", "device = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, device='default')\n", "device.setchannels(1)\n", "device.setrate(50000)\n", "device.setformat(alsaaudio.PCM_FORMAT_S16_LE)\n", "device.setperiodsize(120)\n", "\n", "# Write data in chunks\n", "for s in np.array_split(samples, 120):\n", " device.write(s)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Review\n", "\n", " - Took a fixed set of samples, did some math on them\n", " - Python, NumPy and SciPy are very good at doing fast processing of **static** data\n", " - When handling **real-time** data, buffering becomes a serious issue\n", " - How do you synchronize different input/ouput sample rates on the same flow?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## GNU Radio\n", "\n", "- Comprehensive signal processing framework\n", "- Implemented many DSP primivites\n", "- Has a great scheduling engine\n", "- *Actually* generates flowgraphs in Python" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Where to go from here\n", "\n", " - Buy an RTL-SDR dongle, they start at less than $10\n", " - eBay, AliExpress\n", " - Learn more about SDR, signal processing\n", " - https://osmocom.org/projects/sdr/wiki/rtl-sdr\n", " - https://www.reddit.com/r/RTLSDR/wiki/index\n", " - Michael Ossmann's video course - https://greatscottgadgets.com/sdr/\n", " - But you don't really need do know math\n", " - Lots of code is already written\n", " - Explore the radio waves!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Thank you! Questions?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }