{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Pulling radio data out of thin air\n", "\n", "## Pycon Israel 2018" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Yuval Adam\n", "\n", " - Full stack developer and systems architecture consultant\n", " - But I also like to play with radios\n", " - https://yuv.al\n", " - @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 in 25 minutes 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": [ "## Agenda\n", "\n", " - What are radio waves?\n", " - Hardware vs software radio" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Radio Waves\n", "\n", "![static/dipole.gif](static/dipole3.gif)\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## RTL-SDR\n", "\n", "![rtlsdr.jpg](static/rtlsdr.jpg)" ] }, { "cell_type": "markdown", "metadata": {}, "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": "slide" } }, "source": [ "# I/Q Sampling\n", "\n", "![static/cosample.png](static/cosample.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## I/Q Sampling\n", "\n", "![static/corkscrew.png](static/corkscrew.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Modulations\n", "\n", "![static/amfm2.gif](static/amfm2.gif)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "modulator_frequency = 4.0\n", "carrier_frequency = 40.0\n", "modulation_index = 1.0\n", "size = 44100.0 * 2\n", "\n", "time = np.arange(size) / size\n", "modulator = np.sin(2.0 * np.pi * modulator_frequency * time) * modulation_index\n", "carrier = np.sin(2.0 * np.pi * carrier_frequency * time)\n", "\n", "product = np.zeros_like(modulator)\n", "for i, t in enumerate(time):\n", " product[i] = np.sin(2. * np.pi * (carrier_frequency * t + modulator[i]))\n", "\n", "modulator = np.sin(2.0 * np.pi * modulator_frequency * time) * modulation_index\n", "amp = np.zeros_like(modulator)\n", "for i, t in enumerate(time):\n", " amp[i] = np.sin(2. * np.pi * (carrier_frequency * t * modulator[i]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "plt.subplot(4, 1, 1)\n", "plt.title('Frequency Modulation')\n", "plt.plot(modulator)\n", "plt.ylabel('Amplitude')\n", "plt.xlabel('Modulator signal')\n", "\n", "plt.subplot(4, 1, 2)\n", "plt.plot(carrier)\n", "plt.ylabel('Amplitude')\n", "plt.xlabel('Carrier signal')\n", "\n", "plt.subplot(4, 1, 3)\n", "plt.plot(product)\n", "plt.ylabel('Amplitude')\n", "plt.xlabel('Output signal')\n", "\n", "plt.subplot(4, 1, 4)\n", "plt.plot(modulator * carrier)\n", "plt.ylabel('Amplitude')\n", "plt.xlabel('Output signal')\n", "plt.show()" ] }, { "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,00 Hz frequency for the radio station\n", "sdr.gain = 'auto' # tune the gain (AKA \"volume\") automatically\n", "\n", "samples = sdr.read_samples(1.2e6 * 8) # collect samples during 8 seconds\n", "sdr.close()\n", "\n", "print(samples[:5])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# Load the samples into a numpy array\n", "\n", "import numpy as np\n", "\n", "samples = np.array(samples).astype('complex64')" ] }, { "cell_type": "code", "execution_count": null, "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)\n", "\n", "samples = signal.decimate(samples, DECIMATION_RATE)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# Apply the demodulation, we use a polar discriminator\n", "\n", "samples = np.angle(samples[1:] * np.conj(samples[:-1]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# De-emphasis filter - too \"sciency\"\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": null, "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(1.2e6 / BANDWIDTH / AUDIO_RATE)\n", "\n", "samples = signal.decimate(samples, DECIMATION_RATE)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# HIT IT\n", "\n", "whatever.play(samples)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Caveats\n", "\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", " - GNU Radio\n", " - top-notch 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", " - Michael Ossmann's video course - https://greatscottgadgets.com/sdr/\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 }