From f263caa2db965ded17cbeb02280dc0174bd73587 Mon Sep 17 00:00:00 2001 From: Nao Pross Date: Sat, 27 Nov 2021 14:46:47 +0100 Subject: Move network code and update nix files --- src/gui.py | 42 ++---------------------------------------- src/net.py | 35 +++++++++++++++++++++++++++++++++++ src/nix/numpy-ringbuffer.nix | 12 ++++++++++++ src/shell.nix | 10 +++++++++- 4 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 src/nix/numpy-ringbuffer.nix diff --git a/src/gui.py b/src/gui.py index a0db566..b2cbebb 100755 --- a/src/gui.py +++ b/src/gui.py @@ -13,7 +13,6 @@ import signal # Mathematics import numpy as np -from numpy_ringbuffer import RingBuffer # For debugging import logging @@ -37,42 +36,6 @@ setup_dearpygui() # Show demo for dev show_demo() -#================================================ -# Network classes - -class network_plot(net.udpsource): - def __init__(self, url, nsamples, **kwargs): - net.udpsource.__init__(self, url) - - self.nsamples = nsamples - self.plot = plot(**kwargs) - - # create buffer and fill with zeroes - self.buffer = RingBuffer(capacity=nsamples, dtype=(np.float, 2)) - for i in range(nsamples): - # TODO: remove random data used for testing - self.buffer.append(np.array([i, 1 + np.random.rand() / 5])) - - self.bind() - - def __enter__(self): - return self.plot.__enter__() - - def __exit__(self, t, val, tb): - self.plot.__exit__(t, val, tb) - - @property - def x_data(self): - return np.array(self.buffer[:,0]) - - @property - def y_data(self): - return np.array(self.buffer[:,1]) - - def refresh(self, series_tag): - # set_value(series_tag, [self.x_data, self.y_data]) - pass - #================================================ # GUI Callback functions @@ -135,8 +98,7 @@ with window(label="RX DSP Flow Graph", width=800, height=400, pos=(25,25), tag=" #================================================ # Network plots Window -recv_plot = network_plot(url="udp://localhost:31415", nsamples=100, label="Test", height=300, width=800) - +recv_plot = net.network_plot(url="udp://localhost:31415", nsamples=100, label="Test", height=300, width=800) plots = { recv_plot: "plt_ampl" @@ -158,7 +120,7 @@ show_viewport() # Main loop while is_dearpygui_running(): for plt, tag in plots.items(): - plt.refresh(tag) + plt.refresh_series(tag) render_dearpygui_frame() diff --git a/src/net.py b/src/net.py index 6bd71ac..2c91bb8 100644 --- a/src/net.py +++ b/src/net.py @@ -3,6 +3,8 @@ import socket from urllib.parse import urlparse import numpy as np +from numpy_ringbuffer import RingBuffer +import dearpygui.dearpygui as dpg class udpsource: @@ -31,3 +33,36 @@ class udpsource: else: return None + +class network_plot(udpsource): + def __init__(self, url, nsamples, **kwargs): + udpsource.__init__(self, url) + + self.nsamples = nsamples + self.plot = dpg.plot(**kwargs) + + # create buffer and fill with zeroes + self.buffer = RingBuffer(capacity=nsamples, dtype=(float, 2)) + for i in range(nsamples): + # TODO: remove random data used for testing + self.buffer.append(np.array([i, 1 + np.random.rand() / 5])) + + self.bind() + + def __enter__(self): + return self.plot.__enter__() + + def __exit__(self, t, val, tb): + self.plot.__exit__(t, val, tb) + + @property + def x_data(self): + return np.array(self.buffer[:,0]) + + @property + def y_data(self): + return np.array(self.buffer[:,1]) + + def refresh_series(self, tag): + dpg.set_value(tag, [self.x_data, self.y_data]) + pass diff --git a/src/nix/numpy-ringbuffer.nix b/src/nix/numpy-ringbuffer.nix new file mode 100644 index 0000000..b97b01a --- /dev/null +++ b/src/nix/numpy-ringbuffer.nix @@ -0,0 +1,12 @@ +{ lib, pkgs, buildPythonPackage, fetchPypi, isPy38, autoPatchelfHook }: + +buildPythonPackage rec { + pname = "numpy_ringbuffer"; + version = "0.2.1"; + src = fetchPypi { + inherit pname version; + sha256 = "1vrw38jb3cy9m0c1xxvkk5sf1hpgv58x649a2nnqi9ljdl5wcydc"; + }; + + buildInputs = (with pkgs.python3Packages; [ numpy ]); +} diff --git a/src/shell.nix b/src/shell.nix index 666bab3..22771e2 100644 --- a/src/shell.nix +++ b/src/shell.nix @@ -7,10 +7,18 @@ let isPy38 = pkgs.python38Packages.isPy38; }; + numpy-ringbuffer = callPackage ./nix/numpy-ringbuffer.nix { + buildPythonPackage = pkgs.python38Packages.buildPythonPackage; + fetchPypi = pkgs.python38Packages.fetchPypi; + isPy38 = pkgs.python38Packages.isPy38; + }; + in mkShell { - buildInputs = [ dearpygui ] ++ (with pkgs; [ + buildInputs = [ dearpygui numpy-ringbuffer ] ++ (with pkgs; [ gnuradio python38Packages.setuptools + python38Packages.matplotlib + python38Packages.numpy # gnuradio block dev dependencies cmake ninja pkg-config log4cpp mpir boost175 gmp volk doxygen python38Packages.pybind11 -- cgit v1.2.1 From 6912b6c68d9e1f16926210af477d39d34a859a72 Mon Sep 17 00:00:00 2001 From: Nao Pross Date: Sat, 27 Nov 2021 14:51:15 +0100 Subject: Update access code processing Fixed: FIR filter samples need to be complex conj --- tests/correlator/acgen.dat | Bin 5120 -> 5120 bytes tests/correlator/acgen.grc | 6 +++-- tests/correlator/acgen.py | 13 ++++++---- tests/correlator/acproc.py | 7 +++--- tests/correlator/correlator.grc | 47 +++++++++++++++++++++++++----------- tests/correlator/correlator.py | 51 ++++++++++++++++++++++------------------ 6 files changed, 77 insertions(+), 47 deletions(-) diff --git a/tests/correlator/acgen.dat b/tests/correlator/acgen.dat index a6ad193..537dcc5 100644 Binary files a/tests/correlator/acgen.dat and b/tests/correlator/acgen.dat differ diff --git a/tests/correlator/acgen.grc b/tests/correlator/acgen.grc index b0aa685..ec7c1e8 100644 --- a/tests/correlator/acgen.grc +++ b/tests/correlator/acgen.grc @@ -1,6 +1,7 @@ options: parameters: author: Naoki Pross + catch_exceptions: 'True' category: '[GRC Hier Blocks]' cmake_opt: '' comment: '' @@ -22,7 +23,6 @@ options: sizing_mode: fixed thread_safe_setters: '' title: Access Code Symbols Generator - window_size: '' states: bus_sink: false bus_source: false @@ -36,7 +36,7 @@ blocks: id: variable parameters: comment: '' - value: '[ 0xaa, 0xff, 0xff ]' + value: '[ 0xaa, 0xff, 0x0a ]' states: bus_sink: false bus_source: false @@ -50,6 +50,7 @@ blocks: comment: '' const_points: '[-1-1j, -1+1j, 1+1j, 1-1j]' dims: '1' + normalization: digital.constellation.AMPLITUDE_NORMALIZATION precision: '8' rot_sym: '4' soft_dec_lut: None @@ -168,6 +169,7 @@ blocks: maxoutbuf: '0' minoutbuf: '0' samples_per_symbol: sps + truncate: 'False' verbose: 'False' states: bus_sink: false diff --git a/tests/correlator/acgen.py b/tests/correlator/acgen.py index f4486e4..5fbdbb4 100755 --- a/tests/correlator/acgen.py +++ b/tests/correlator/acgen.py @@ -7,12 +7,13 @@ # GNU Radio Python Flow Graph # Title: Access Code Symbols Generator # Author: Naoki Pross -# GNU Radio version: 3.8.2.0 +# GNU Radio version: 3.9.2.0 from gnuradio import blocks from gnuradio import digital from gnuradio import gr from gnuradio.filter import firdes +from gnuradio.fft import window import sys import signal from argparse import ArgumentParser @@ -20,10 +21,12 @@ from gnuradio.eng_arg import eng_float, intx from gnuradio import eng_notation + + class acgen(gr.top_block): def __init__(self): - gr.top_block.__init__(self, "Access Code Symbols Generator") + gr.top_block.__init__(self, "Access Code Symbols Generator", catch_exceptions=True) ################################################## # Variables @@ -32,7 +35,7 @@ class acgen(gr.top_block): self.samp_rate = samp_rate = 32000 self.excess_bw = excess_bw = 1 self.const = const = digital.constellation_qpsk().base() - self.access_code = access_code = [ 0xaa, 0xff, 0xff ] + self.access_code = access_code = [ 0xaa, 0xff, 0x0a ] ################################################## # Blocks @@ -44,7 +47,8 @@ class acgen(gr.top_block): pre_diff_code=True, excess_bw=excess_bw, verbose=False, - log=False) + log=False, + truncate=False) self.blocks_vector_source_x_0 = blocks.vector_source_b([0x00] * 10 + access_code * 10, False, 1, []) self.blocks_throttle_0 = blocks.throttle(gr.sizeof_char*1, samp_rate,True) self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_gr_complex*1, 'acgen.dat', False) @@ -95,7 +99,6 @@ class acgen(gr.top_block): - def main(top_block_cls=acgen, options=None): tb = top_block_cls() diff --git a/tests/correlator/acproc.py b/tests/correlator/acproc.py index 5f6ace3..e119520 100755 --- a/tests/correlator/acproc.py +++ b/tests/correlator/acproc.py @@ -49,8 +49,9 @@ ax2.plot(ac.imag, ".-") ax2.set_title("Symbols of Access Code (time)") plt.show() +fir = list(np.conj(ac[::-1])) + # print the symbols print(f"Generated {len(ac)} symbols from a {aclen} byte sequence") -print(list(ac)) -print("Reversed symbols (for FIR filter)") -print(list(ac[::-1])) +print("Reversed symbols (for FIR filter):") +print(fir) diff --git a/tests/correlator/correlator.grc b/tests/correlator/correlator.grc index a5a8c89..cc57e18 100644 --- a/tests/correlator/correlator.grc +++ b/tests/correlator/correlator.grc @@ -1,6 +1,7 @@ options: parameters: author: Naoki Pross + catch_exceptions: 'True' category: '[GRC Hier Blocks]' cmake_opt: '' comment: '' @@ -22,7 +23,6 @@ options: sizing_mode: fixed thread_safe_setters: '' title: Correlator Test - window_size: '' states: bus_sink: false bus_source: false @@ -38,6 +38,7 @@ blocks: comment: '' const_points: '[-1-1j, -1+1j, 1+1j, 1-1j]' dims: '1' + normalization: digital.constellation.AMPLITUDE_NORMALIZATION precision: '8' rot_sym: '4' soft_dec_lut: None @@ -122,6 +123,22 @@ blocks: coordinate: [696, 420.0] rotation: 0 state: true +- name: blocks_complex_to_mag_0 + id: blocks_complex_to_mag + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1584, 80.0] + rotation: 0 + state: true - name: blocks_null_sink_0 id: blocks_null_sink parameters: @@ -169,7 +186,7 @@ blocks: repeat: 'False' tags: '[]' type: byte - vector: '[0x00] * 10 + [0xaa, 0xff, 0xff] + [0x00] * 10' + vector: ([0x00] * 10 + [0xaa, 0xff, 0x0a] + [0x00] * 10) * 20 vlen: '1' states: bus_sink: false @@ -196,7 +213,7 @@ blocks: bus_structure: null coordinate: [992, 188.0] rotation: 0 - state: true + state: enabled - name: digital_constellation_decoder_cb_0 id: digital_constellation_decoder_cb parameters: @@ -226,6 +243,7 @@ blocks: maxoutbuf: '0' minoutbuf: '0' samples_per_symbol: sps + truncate: 'False' verbose: 'False' states: bus_sink: false @@ -267,17 +285,17 @@ blocks: maxoutbuf: '0' minoutbuf: '0' samp_delay: '0' - taps: '[(1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), - (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (-1.4142197+1.4142197j), - (-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (1.4142197+1.4142197j), - (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), - (1.4142197+1.4142197j)]' + taps: '[(-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (1.4142197-1.4142197j), + (1.4142197-1.4142197j), (1.4142197-1.4142197j), (1.4142197-1.4142197j), (-1.4142197-1.4142197j), + (-1.4142197-1.4142197j), (-1.4142197-1.4142197j), (-1.4142197-1.4142197j), (-1.4142197-1.4142197j), + (-1.4142197-1.4142197j), (-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (1.4142197-1.4142197j), + (1.4142197-1.4142197j)]' type: ccc states: bus_sink: false bus_source: false bus_structure: null - coordinate: [1240, 76.0] + coordinate: [1328, 68.0] rotation: 0 state: enabled - name: qtgui_time_sink_x_0 @@ -354,7 +372,7 @@ blocks: tr_mode: qtgui.TRIG_MODE_FREE tr_slope: qtgui.TRIG_SLOPE_POS tr_tag: '""' - type: complex + type: float update_time: '0.10' width1: '1' width10: '1' @@ -367,14 +385,14 @@ blocks: width8: '1' width9: '1' ylabel: Amplitude - ymax: '2' - ymin: '-2' + ymax: '50' + ymin: '0' yunit: '""' states: bus_sink: false bus_source: false bus_structure: null - coordinate: [1488, 68.0] + coordinate: [1792, 60.0] rotation: 0 state: true - name: qtgui_time_sink_x_1 @@ -476,6 +494,7 @@ blocks: state: true connections: +- [blocks_complex_to_mag_0, '0', qtgui_time_sink_x_0, '0'] - [blocks_throttle_0, '0', digital_constellation_modulator_0, '0'] - [blocks_vector_source_x_0, '0', blocks_throttle_0, '0'] - [digital_cma_equalizer_cc_0, '0', digital_constellation_decoder_cb_0, '0'] @@ -484,7 +503,7 @@ connections: - [digital_constellation_modulator_0, '0', digital_pfb_clock_sync_xxx_0, '0'] - [digital_constellation_modulator_0, '0', qtgui_time_sink_x_1, '0'] - [digital_pfb_clock_sync_xxx_0, '0', digital_cma_equalizer_cc_0, '0'] -- [fir_filter_xxx_1, '0', qtgui_time_sink_x_0, '0'] +- [fir_filter_xxx_1, '0', blocks_complex_to_mag_0, '0'] metadata: file_format: 1 diff --git a/tests/correlator/correlator.py b/tests/correlator/correlator.py index b0cef0e..79fa3f8 100755 --- a/tests/correlator/correlator.py +++ b/tests/correlator/correlator.py @@ -7,7 +7,7 @@ # GNU Radio Python Flow Graph # Title: Correlator Test # Author: Naoki Pross -# GNU Radio version: 3.8.2.0 +# GNU Radio version: 3.9.2.0 from distutils.version import StrictVersion @@ -29,18 +29,21 @@ from gnuradio import blocks from gnuradio import digital from gnuradio import filter from gnuradio import gr +from gnuradio.fft import window import sys import signal from argparse import ArgumentParser from gnuradio.eng_arg import eng_float, intx from gnuradio import eng_notation + + from gnuradio import qtgui class correlator(gr.top_block, Qt.QWidget): def __init__(self): - gr.top_block.__init__(self, "Correlator Test") + gr.top_block.__init__(self, "Correlator Test", catch_exceptions=True) Qt.QWidget.__init__(self) self.setWindowTitle("Correlator Test") qtgui.util.check_set_qss() @@ -88,7 +91,8 @@ class correlator(gr.top_block, Qt.QWidget): 1024, #size samp_rate, #samp_rate "", #name - 1 #number of inputs + 1, #number of inputs + None # parent ) self.qtgui_time_sink_x_1.set_update_time(0.10) self.qtgui_time_sink_x_1.set_y_axis(-2, 2) @@ -133,15 +137,16 @@ class correlator(gr.top_block, Qt.QWidget): self.qtgui_time_sink_x_1.set_line_alpha(i, alphas[i]) self._qtgui_time_sink_x_1_win = sip.wrapinstance(self.qtgui_time_sink_x_1.pyqwidget(), Qt.QWidget) - self.top_grid_layout.addWidget(self._qtgui_time_sink_x_1_win) - self.qtgui_time_sink_x_0 = qtgui.time_sink_c( + self.top_layout.addWidget(self._qtgui_time_sink_x_1_win) + self.qtgui_time_sink_x_0 = qtgui.time_sink_f( 1024, #size samp_rate, #samp_rate "", #name - 1 #number of inputs + 1, #number of inputs + None # parent ) self.qtgui_time_sink_x_0.set_update_time(0.10) - self.qtgui_time_sink_x_0.set_y_axis(-2, 2) + self.qtgui_time_sink_x_0.set_y_axis(0, 50) self.qtgui_time_sink_x_0.set_y_label('Amplitude', "") @@ -168,12 +173,9 @@ class correlator(gr.top_block, Qt.QWidget): -1, -1, -1, -1, -1] - for i in range(2): + for i in range(1): if len(labels[i]) == 0: - if (i % 2 == 0): - self.qtgui_time_sink_x_0.set_line_label(i, "Re{{Data {0}}}".format(i/2)) - else: - self.qtgui_time_sink_x_0.set_line_label(i, "Im{{Data {0}}}".format(i/2)) + self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i)) else: self.qtgui_time_sink_x_0.set_line_label(i, labels[i]) self.qtgui_time_sink_x_0.set_line_width(i, widths[i]) @@ -183,8 +185,8 @@ class correlator(gr.top_block, Qt.QWidget): self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i]) self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget) - self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win) - self.fir_filter_xxx_1 = filter.fir_filter_ccc(1, [(1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j), (1.4142197+1.4142197j)]) + self.top_layout.addWidget(self._qtgui_time_sink_x_0_win) + self.fir_filter_xxx_1 = filter.fir_filter_ccc(1, [(-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (1.4142197-1.4142197j), (1.4142197-1.4142197j), (1.4142197-1.4142197j), (1.4142197-1.4142197j), (-1.4142197-1.4142197j), (-1.4142197-1.4142197j), (-1.4142197-1.4142197j), (-1.4142197-1.4142197j), (-1.4142197-1.4142197j), (-1.4142197-1.4142197j), (-1.4142197+1.4142197j), (-1.4142197+1.4142197j), (1.4142197-1.4142197j), (1.4142197-1.4142197j)]) self.fir_filter_xxx_1.declare_sample_delay(0) self.digital_pfb_clock_sync_xxx_0 = digital.pfb_clock_sync_ccf(sps, timing_loop_bw, rrc_taps, nfilts, 16, 1.5, 1) self.digital_constellation_modulator_0 = digital.generic_mod( @@ -194,18 +196,21 @@ class correlator(gr.top_block, Qt.QWidget): pre_diff_code=True, excess_bw=excess_bw, verbose=False, - log=False) + log=False, + truncate=False) self.digital_constellation_decoder_cb_0 = digital.constellation_decoder_cb(const) self.digital_cma_equalizer_cc_0 = digital.cma_equalizer_cc(15, 1, .002, 1) - self.blocks_vector_source_x_0 = blocks.vector_source_b([0x00] * 10 + [0xaa, 0xff, 0xff] + [0x00] * 10, False, 1, []) + self.blocks_vector_source_x_0 = blocks.vector_source_b(([0x00] * 10 + [0xaa, 0xff, 0x0a] + [0x00] * 10) * 20, False, 1, []) self.blocks_throttle_0 = blocks.throttle(gr.sizeof_char*1, samp_rate,True) self.blocks_null_sink_0 = blocks.null_sink(gr.sizeof_char*1) + self.blocks_complex_to_mag_0 = blocks.complex_to_mag(1) ################################################## # Connections ################################################## + self.connect((self.blocks_complex_to_mag_0, 0), (self.qtgui_time_sink_x_0, 0)) self.connect((self.blocks_throttle_0, 0), (self.digital_constellation_modulator_0, 0)) self.connect((self.blocks_vector_source_x_0, 0), (self.blocks_throttle_0, 0)) self.connect((self.digital_cma_equalizer_cc_0, 0), (self.digital_constellation_decoder_cb_0, 0)) @@ -214,12 +219,15 @@ class correlator(gr.top_block, Qt.QWidget): self.connect((self.digital_constellation_modulator_0, 0), (self.digital_pfb_clock_sync_xxx_0, 0)) self.connect((self.digital_constellation_modulator_0, 0), (self.qtgui_time_sink_x_1, 0)) self.connect((self.digital_pfb_clock_sync_xxx_0, 0), (self.digital_cma_equalizer_cc_0, 0)) - self.connect((self.fir_filter_xxx_1, 0), (self.qtgui_time_sink_x_0, 0)) + self.connect((self.fir_filter_xxx_1, 0), (self.blocks_complex_to_mag_0, 0)) def closeEvent(self, event): self.settings = Qt.QSettings("GNU Radio", "correlator") self.settings.setValue("geometry", self.saveGeometry()) + self.stop() + self.wait() + event.accept() def get_sps(self): @@ -275,7 +283,6 @@ class correlator(gr.top_block, Qt.QWidget): - def main(top_block_cls=correlator, options=None): if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"): @@ -290,6 +297,9 @@ def main(top_block_cls=correlator, options=None): tb.show() def sig_handler(sig=None, frame=None): + tb.stop() + tb.wait() + Qt.QApplication.quit() signal.signal(signal.SIGINT, sig_handler) @@ -299,11 +309,6 @@ def main(top_block_cls=correlator, options=None): timer.start(500) timer.timeout.connect(lambda: None) - def quitting(): - tb.stop() - tb.wait() - - qapp.aboutToQuit.connect(quitting) qapp.exec_() if __name__ == '__main__': -- cgit v1.2.1 From 2ad286aa3ae3d09dcebf59058565625e08f1c6bc Mon Sep 17 00:00:00 2001 From: Nao Pross Date: Sat, 27 Nov 2021 14:53:56 +0100 Subject: Very small rewording --- doc/thesis/chapters/implementation.tex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/thesis/chapters/implementation.tex b/doc/thesis/chapters/implementation.tex index 27a8d41..d524d5c 100644 --- a/doc/thesis/chapters/implementation.tex +++ b/doc/thesis/chapters/implementation.tex @@ -1,4 +1,4 @@ -% vim: set ts=2 sw=2 noet: +% vim: set ts=2 sw=2 noet spell: \chapter{Implementation} @@ -47,7 +47,7 @@ From the complex space the constellation points are decode to bits. } \end{figure} -To compute the empirical \emph{bit error rate} (BER) of the setup, the data has to be framed on by the sender and the bitstream synchronized on the receiver side. The structure of a data packed used in the implementation is shown in \figref{fig:dataframe}. A frame begins with an user specified \(k\)-byte preamble, that in the current implementation serves as synchronization pattern. Another use case for the preamble sequence could be to introduce channel estimation pilot symbols. Following the preamble are 4 bytes encoded using a (31, 26) Hamming code (plus 1 padding bit), that contain metadata about the packet, namely payload ID and payload length. Because the payload length in bytes is encoded in 21 bits, the maximum payload size is 2 MiB, which together with \(2^5 = 32\) possible unique IDs gives a maximum data transfer with unique frame headers of 64 MiB. These constraints are a result of decisions made to keep the implementation simple. +To compute the empirical \emph{bit error rate} (BER) of the setup, the data has to be framed on by the sender and the bitstream synchronized on the receiver side. The structure of a data packed used in the implementation is shown in \figref{fig:dataframe}. A frame begins with an user specified \(k\)-byte preamble, that in the current implementation serves as synchronization pattern. Another use case for the preamble sequence could be to introduce channel estimation pilot symbols. Following the preamble are 4 bytes encoded using a (31, 26) Hamming code (plus 1 padding bit), that contain metadata about the packet, namely payload ID and payload length. Because the payload length in bytes is encoded in 21 bits, the maximum payload size is 2 MiB, which together with 32 possible unique IDs gives a maximum data transfer with unique frame headers of 64 MiB. These constraints are a result of decisions made to keep the implementation simple. On the receiver side the bitstream is synchronized using a XOR correlator. To find the synchronization delay \(d\) (in samples) the XOR correlator computes the binary cross correlation \begin{equation} \label{eqn:binary-xcorr} -- cgit v1.2.1