{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# COS 463 Wireless Networks - Spring 2019\n", "# Lab 2: Synchronization and MAC protocols\n", "\n", "In class we've learned about different MAC (medium access control) protocols that determine when devices get to transmit (these protocols *control* *access* to the *medium*). For all but the simplest MAC protocols, the participating devices need to be synchronized in time. The way this is done in practice is to have one node transmit a signal (called a \"preamble\" or \"beacon\") and the other nodes will listen for this signal. Since wireless signals travel at the speed of light, all nodes can agree that they will hear the preamble at nearly exactly the same time.\n", "\n", "First, we will explore how nodes can listen for a specific preamble to achieve time synchronization. Then, we will use this method to synchronize to a preamble using the HackRF hardware. Finally, we will wrap up with a Monte Carlo simulation of the capacities of the slotted-ALOHA MAC protocol." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%matplotlib notebook\n", "\n", "from matplotlib import pyplot\n", "import numpy\n", "import SoapySDR\n", "from SoapySDR import SOAPY_SDR_RX, SOAPY_SDR_CF32" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Part 1: Correlation and Preambles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A preamble is a waveform sent at the beginning of a packet that is used to indicate the start of a packet. The preamble waveform is agreed to ahead-of-time, so the signal carries no data. Receivers will listen for the preamble, and when it's detected the receivers will start to demodulate the rest of the packet. Preambles can also be used to synchronize multiple clients for MAC protocols that require synchronization.\n", "\n", "In this part, we will explore how to detect and synchronize to a preamble. Both detection and synchronization can be done using a **correlation**. A correlation of two discrete signals $x$ and $y$ is an infinite-length discrete signal which is defined as\n", "$$ (x \\star y)[k] = \\sum_{n = -\\infty}^{\\infty}x^*[n] \\cdot y[n + k]$$\n", "In other words, at each index $k$, you take conjugate of the first signal $x$ and offset the second signal $y$ by $k$ indices and then sum the resulting two signals. Intuitively, the correlation measures the similarity of two signals for every offset of the one signal relative to the other. For signals with finite length, the correlation is zero for sufficiently large $|k|$, so often we'll consider the correlation to be finite as well." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem 1.1: Autocorrelation (5 pts)\n", "The autocorrelation of a signal $x$ is the correlation of $x$ with itself. For a uniform random signal $x$ think about what the autocorrelation would look like.\n", "\n", "- **Plot the signal and its autocorrelation. Use `numpy.correlate` with `mode='full'`, and plot the absolute value of the autocorrelation.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_len = 1000\n", "X = numpy.random.random(X_len) + 1j * numpy.random.random(X_len)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So how do we use the correlation to detect a preamble? Let $x$ be the preamble signal, and let $y$ be the signal that the receiver receives. For now, we'll let $y$ be finite length. If the preamble is not received, then we can model $y$ as a random signal, and the correlation between $x$ and $y$ will also be random. However, if the receiver did receive the preamble in the signal $y$, then we can model $y$ as $x$ with some offset plus noise. The correlation between $x$ and $y$ will then have a large amplitude at the time in $y$ when the preamble started. So if we look for large peaks in the correlation we can detect and then also synchronize to a given preamble." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Problem 1.2: Preamble detection using correlation (10 pts)\n", "Implement preamble detection and synchronization using the correlation.\n", "\n", "- **Fill in the `detect_preamble` function below. The function should take two signals and return `None` if the preamble is not found, otherwise it should return the index where the preamble starts.**" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "# Compare the correlation magnitude against this value to determine whether there is a preamble or not\n", "threshold = 100\n", "def detect_preamble(preamble, signal):\n", " pass" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [], "source": [ "# This cell will test your implementation of `detect_preamble`\n", "preamble_length = 100\n", "signal_length = 1000\n", "preamble = (numpy.random.random(preamble_length) + 1j * numpy.random.random(preamble_length))\n", "signalA = numpy.random.random(signal_length) + 1j * numpy.random.random(signal_length)\n", "signalB = numpy.random.random(signal_length) + 1j * numpy.random.random(signal_length)\n", "preamble_start_idx = 123\n", "signalB[preamble_start_idx:preamble_start_idx + preamble_length] += preamble\n", "\n", "numpy.testing.assert_equal(detect_preamble(preamble, signalA), None)\n", "numpy.testing.assert_equal(detect_preamble(preamble, signalB), preamble_start_idx)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Handling Frequency Offsets\n", "In practice, radios will disagree on their center frequencies. This is because a radio's center frequency is determined by a local oscillator (which is fed into the \"mixer\"), and these oscillators have some error. What this means is that a signal that's transmitted by one radio will be received with a \"frequency offset\" by another radio.\n", "\n", "Unfortunately, if the receiving radio doesn't know what the frequency offset is, then this can cause the correlation to fail. Consider the demonstration below:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "def shift_frequency(signal, freq_shift):\n", " \"\"\"\n", " Applies a frequency shift to a signal. `freq_shift` is in radians per sample, and should be between 0 and 2𝛑.\n", " \n", " A frequency offset is simply an addition of a linearly increasing phase. Note that you could have taken the DFT,\n", " circularly shifted the spectrum, and then taken the IDFT, and you would have gotten the same result.\n", " \"\"\"\n", " return signal * numpy.exp(1j * freq_shift * numpy.arange(len(signal)))" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support.' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " if (mpl.ratio != 1) {\n", " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", " }\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " this.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var backingStore = this.context.backingStorePixelRatio ||\n", "\tthis.context.webkitBackingStorePixelRatio ||\n", "\tthis.context.mozBackingStorePixelRatio ||\n", "\tthis.context.msBackingStorePixelRatio ||\n", "\tthis.context.oBackingStorePixelRatio ||\n", "\tthis.context.backingStorePixelRatio || 1;\n", "\n", " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width * mpl.ratio);\n", " canvas.attr('height', height * mpl.ratio);\n", " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
')\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('