summaryrefslogtreecommitdiff
path: root/gr-digital/python
diff options
context:
space:
mode:
Diffstat (limited to 'gr-digital/python')
-rw-r--r--gr-digital/python/.gitignore3
-rw-r--r--gr-digital/python/Makefile.am70
-rw-r--r--gr-digital/python/__init__.py46
-rw-r--r--gr-digital/python/bpsk.py171
-rw-r--r--gr-digital/python/cpm.py247
-rw-r--r--gr-digital/python/crc.py38
-rw-r--r--gr-digital/python/generic_mod_demod.py387
-rw-r--r--gr-digital/python/gmsk.py276
-rw-r--r--gr-digital/python/modulation_utils.py102
-rw-r--r--gr-digital/python/ofdm.py318
-rw-r--r--gr-digital/python/ofdm_packet_utils.py454
-rw-r--r--gr-digital/python/ofdm_receiver.py151
-rw-r--r--gr-digital/python/ofdm_sync_fixed.py50
-rw-r--r--gr-digital/python/ofdm_sync_ml.py165
-rw-r--r--gr-digital/python/ofdm_sync_pn.py123
-rw-r--r--gr-digital/python/ofdm_sync_pnac.py125
-rw-r--r--gr-digital/python/packet_utils.py457
-rw-r--r--gr-digital/python/pkt.py167
-rw-r--r--gr-digital/python/psk.py122
-rwxr-xr-xgr-digital/python/qa_binary_slicer_fb.py55
-rwxr-xr-xgr-digital/python/qa_clock_recovery_mm.py176
-rwxr-xr-xgr-digital/python/qa_cma_equalizer.py50
-rwxr-xr-xgr-digital/python/qa_constellation.py204
-rwxr-xr-xgr-digital/python/qa_constellation_decoder_cb.py76
-rwxr-xr-xgr-digital/python/qa_constellation_receiver.py134
-rwxr-xr-xgr-digital/python/qa_correlate_access_code.py84
-rwxr-xr-xgr-digital/python/qa_costas_loop_cc.py151
-rwxr-xr-xgr-digital/python/qa_cpm.py91
-rwxr-xr-xgr-digital/python/qa_crc32.py60
-rwxr-xr-xgr-digital/python/qa_digital.py35
-rwxr-xr-xgr-digital/python/qa_fll_band_edge.py83
-rwxr-xr-xgr-digital/python/qa_lms_equalizer.py53
-rwxr-xr-xgr-digital/python/qa_mpsk_receiver.py121
-rwxr-xr-xgr-digital/python/qa_ofdm_insert_preamble.py180
-rw-r--r--gr-digital/python/qam.py229
-rw-r--r--gr-digital/python/qpsk.py176
-rw-r--r--gr-digital/python/run_tests.in10
-rw-r--r--gr-digital/python/utils/.gitignore3
-rw-r--r--gr-digital/python/utils/Makefile.am34
-rw-r--r--gr-digital/python/utils/__init__.py21
-rw-r--r--gr-digital/python/utils/alignment.py139
-rw-r--r--gr-digital/python/utils/gray_code.py66
-rw-r--r--gr-digital/python/utils/mod_codes.py33
43 files changed, 5736 insertions, 0 deletions
diff --git a/gr-digital/python/.gitignore b/gr-digital/python/.gitignore
new file mode 100644
index 000000000..604b402c5
--- /dev/null
+++ b/gr-digital/python/.gitignore
@@ -0,0 +1,3 @@
+/Makefile
+/Makefile.in
+/run_tests
diff --git a/gr-digital/python/Makefile.am b/gr-digital/python/Makefile.am
new file mode 100644
index 000000000..ead6f7dfd
--- /dev/null
+++ b/gr-digital/python/Makefile.am
@@ -0,0 +1,70 @@
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+include $(top_srcdir)/Makefile.common
+
+TESTS =
+EXTRA_DIST += run_tests.in
+
+if PYTHON
+SUBDIRS = utils
+TESTS += run_tests
+
+digitaldir = $(grpythondir)/digital
+
+noinst_PYTHON = \
+ qa_digital.py \
+ qa_binary_slicer_fb.py \
+ qa_clock_recovery_mm.py \
+ qa_cma_equalizer.py \
+ qa_cpm.py \
+ qa_constellation.py \
+ qa_constellation_receiver.py \
+ qa_constellation_decoder_cb.py \
+ qa_correlate_access_code.py \
+ qa_costas_loop_cc.py \
+ qa_crc32.py \
+ qa_fll_band_edge.py \
+ qa_lms_equalizer.py \
+ qa_mpsk_receiver.py \
+ qa_ofdm_insert_preamble.py
+
+digital_PYTHON = \
+ __init__.py \
+ bpsk.py \
+ cpm.py \
+ crc.py \
+ generic_mod_demod.py \
+ gmsk.py \
+ modulation_utils.py \
+ ofdm.py \
+ ofdm_packet_utils.py \
+ ofdm_receiver.py \
+ ofdm_sync_fixed.py \
+ ofdm_sync_ml.py \
+ ofdm_sync_pnac.py \
+ ofdm_sync_pn.py \
+ packet_utils.py \
+ pkt.py \
+ psk.py \
+ qam.py \
+ qpsk.py
+endif
diff --git a/gr-digital/python/__init__.py b/gr-digital/python/__init__.py
new file mode 100644
index 000000000..7c76183c9
--- /dev/null
+++ b/gr-digital/python/__init__.py
@@ -0,0 +1,46 @@
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+'''
+This is the gr-digital package. It contains all of the blocks,
+utilities, and examples for doing digital modulation and demodulation.
+'''
+
+# The presence of this file turns this directory into a Python package
+
+from digital_swig import *
+from psk import *
+from qam import *
+from bpsk import *
+from qpsk import *
+from gmsk import *
+from cpm import *
+from pkt import *
+from crc import *
+from modulation_utils import *
+from ofdm import *
+from ofdm_receiver import *
+from ofdm_sync_fixed import *
+from ofdm_sync_ml import *
+from ofdm_sync_pnac import *
+from ofdm_sync_pn import *
+
+import packet_utils
+import ofdm_packet_utils
diff --git a/gr-digital/python/bpsk.py b/gr-digital/python/bpsk.py
new file mode 100644
index 000000000..0d8f05c4c
--- /dev/null
+++ b/gr-digital/python/bpsk.py
@@ -0,0 +1,171 @@
+#
+# Copyright 2005,2006,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+"""
+BPSK modulation and demodulation.
+"""
+
+from math import pi, log
+from cmath import exp
+
+from gnuradio import gr
+from gnuradio.digital.generic_mod_demod import generic_mod, generic_demod
+import digital_swig
+import modulation_utils
+
+# Default number of points in constellation.
+_def_constellation_points = 2
+# Whether differential coding is used.
+_def_differential = False
+
+# /////////////////////////////////////////////////////////////////////////////
+# BPSK constellation
+# /////////////////////////////////////////////////////////////////////////////
+
+def bpsk_constellation(m=_def_constellation_points):
+ if m != _def_constellation_points:
+ raise ValueError("BPSK can only have 2 constellation points.")
+ return digital_swig.constellation_bpsk()
+
+# /////////////////////////////////////////////////////////////////////////////
+# BPSK modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class bpsk_mod(generic_mod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ differential=False, *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered BPSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_mod block for list of parameters.
+ """
+
+ constellation_points = _def_constellation_points
+ constellation = digital_swig.constellation_bpsk()
+ if constellation_points != 2:
+ raise ValueError('Number of constellation points must be 2 for BPSK.')
+ super(bpsk_mod, self).__init__(constellation=constellation,
+ differential=differential, *args, **kwargs)
+
+# /////////////////////////////////////////////////////////////////////////////
+# BPSK demodulator
+#
+# /////////////////////////////////////////////////////////////////////////////
+
+class bpsk_demod(generic_demod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ differential=False, *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered BPSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_demod block for list of parameters.
+ """
+
+ constellation_points = _def_constellation_points
+ constellation = digital_swig.constellation_bpsk()
+ if constellation_points != 2:
+ raise ValueError('Number of constellation points must be 2 for BPSK.')
+ super(bpsk_demod, self).__init__(constellation=constellation,
+ differential=differential, *args, **kwargs)
+
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# DBPSK constellation
+# /////////////////////////////////////////////////////////////////////////////
+
+def dbpsk_constellation(m=_def_constellation_points):
+ if m != _def_constellation_points:
+ raise ValueError("DBPSK can only have 2 constellation points.")
+ return digital_swig.constellation_dbpsk()
+
+# /////////////////////////////////////////////////////////////////////////////
+# DBPSK modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class dbpsk_mod(generic_mod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ differential=True, *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered DBPSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_mod block for list of parameters.
+ """
+
+ constellation_points = _def_constellation_points
+ constellation = digital_swig.constellation_bpsk()
+ if constellation_points != 2:
+ raise ValueError('Number of constellation points must be 2 for DBPSK.')
+ super(dbpsk_mod, self).__init__(constellation=constellation,
+ differential=True,
+ *args, **kwargs)
+
+# /////////////////////////////////////////////////////////////////////////////
+# DBPSK demodulator
+#
+# /////////////////////////////////////////////////////////////////////////////
+
+class dbpsk_demod(generic_demod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ differential=True, *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered DBPSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_demod block for list of parameters.
+ """
+
+ constellation_points = _def_constellation_points
+ constellation = digital_swig.constellation_bpsk()
+ if constellation_points != 2:
+ raise ValueError('Number of constellation points must be 2 for DBPSK.')
+ super(dbpsk_demod, self).__init__(constellation=constellation,
+ differential=True,
+ *args, **kwargs)
+
+#
+# Add these to the mod/demod registry
+#
+modulation_utils.add_type_1_mod('bpsk', bpsk_mod)
+modulation_utils.add_type_1_demod('bpsk', bpsk_demod)
+modulation_utils.add_type_1_constellation('bpsk', bpsk_constellation)
+modulation_utils.add_type_1_mod('dbpsk', dbpsk_mod)
+modulation_utils.add_type_1_demod('dbpsk', dbpsk_demod)
+modulation_utils.add_type_1_constellation('dbpsk', dbpsk_constellation)
diff --git a/gr-digital/python/cpm.py b/gr-digital/python/cpm.py
new file mode 100644
index 000000000..05032336d
--- /dev/null
+++ b/gr-digital/python/cpm.py
@@ -0,0 +1,247 @@
+#
+# CPM modulation and demodulation.
+#
+#
+# Copyright 2005-2007,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+# See gnuradio-examples/python/digital for examples
+
+from gnuradio import gr, blks2
+from math import pi
+import numpy
+
+import digital_swig
+import modulation_utils
+
+# default values (used in __init__ and add_options)
+_def_samples_per_symbol = 2
+_def_bits_per_symbol = 1
+_def_h_numerator = 1
+_def_h_denominator = 2
+_def_cpm_type = 0 # 0=CPFSK, 1=GMSK, 2=RC, 3=GENERAL
+_def_bt = 0.35
+_def_symbols_per_pulse = 1
+_def_generic_taps = numpy.empty(1)
+_def_verbose = False
+_def_log = False
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# CPM modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class cpm_mod(gr.hier_block2):
+ def __init__(self,
+ samples_per_symbol=_def_samples_per_symbol,
+ bits_per_symbol=_def_bits_per_symbol,
+ h_numerator=_def_h_numerator,
+ h_denominator=_def_h_denominator,
+ cpm_type=_def_cpm_type,
+ bt=_def_bt,
+ symbols_per_pulse=_def_symbols_per_pulse,
+ generic_taps=_def_generic_taps,
+ verbose=_def_verbose,
+ log=_def_log):
+ """
+ Hierarchical block for Continuous Phase
+ modulation.
+
+ The input is a byte stream (unsigned char)
+ representing packed bits and the
+ output is the complex modulated signal at baseband.
+
+ See Proakis for definition of generic CPM signals:
+ s(t)=exp(j phi(t))
+ phi(t)= 2 pi h int_0^t f(t') dt'
+ f(t)=sum_k a_k g(t-kT)
+ (normalizing assumption: int_0^infty g(t) dt = 1/2)
+
+ @param samples_per_symbol: samples per baud >= 2
+ @type samples_per_symbol: integer
+ @param bits_per_symbol: bits per symbol
+ @type bits_per_symbol: integer
+ @param h_numerator: numerator of modulation index
+ @type h_numerator: integer
+ @param h_denominator: denominator of modulation index (numerator and denominator must be relative primes)
+ @type h_denominator: integer
+ @param cpm_type: supported types are: 0=CPFSK, 1=GMSK, 2=RC, 3=GENERAL
+ @type cpm_type: integer
+ @param bt: bandwidth symbol time product for GMSK
+ @type bt: float
+ @param symbols_per_pulse: shaping pulse duration in symbols
+ @type symbols_per_pulse: integer
+ @param generic_taps: define a generic CPM pulse shape (sum = samples_per_symbol/2)
+ @type generic_taps: array of floats
+
+ @param verbose: Print information about modulator?
+ @type verbose: bool
+ @param debug: Print modulation data to files?
+ @type debug: bool
+ """
+
+ gr.hier_block2.__init__(self, "cpm_mod",
+ gr.io_signature(1, 1, gr.sizeof_char), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
+
+ self._samples_per_symbol = samples_per_symbol
+ self._bits_per_symbol = bits_per_symbol
+ self._h_numerator = h_numerator
+ self._h_denominator = h_denominator
+ self._cpm_type = cpm_type
+ self._bt=bt
+ if cpm_type == 0 or cpm_type == 2 or cpm_type == 3: # CPFSK, RC, Generic
+ self._symbols_per_pulse = symbols_per_pulse
+ elif cpm_type == 1: # GMSK
+ self._symbols_per_pulse = 4
+ else:
+ raise TypeError, ("cpm_type must be an integer in {0,1,2,3}, is %r" % (cpm_type,))
+
+ self._generic_taps=numpy.array(generic_taps)
+
+ if samples_per_symbol < 2:
+ raise TypeError, ("samples_per_symbol must be >= 2, is %r" % (samples_per_symbol,))
+
+ self.nsymbols = 2**bits_per_symbol
+ self.sym_alphabet = numpy.arange(-(self.nsymbols-1),self.nsymbols,2).tolist()
+
+
+ self.ntaps = int(self._symbols_per_pulse * samples_per_symbol)
+ sensitivity = 2 * pi * h_numerator / h_denominator / samples_per_symbol
+
+ # Unpack Bytes into bits_per_symbol groups
+ self.B2s = gr.packed_to_unpacked_bb(bits_per_symbol,gr.GR_MSB_FIRST)
+
+
+ # Turn it into symmetric PAM data.
+ self.pam = gr.chunks_to_symbols_bf(self.sym_alphabet,1)
+
+ # Generate pulse (sum of taps = samples_per_symbol/2)
+ if cpm_type == 0: # CPFSK
+ self.taps= (1.0/self._symbols_per_pulse/2,) * self.ntaps
+ elif cpm_type == 1: # GMSK
+ gaussian_taps = gr.firdes.gaussian(
+ 1.0/2, # gain
+ samples_per_symbol, # symbol_rate
+ bt, # bandwidth * symbol time
+ self.ntaps # number of taps
+ )
+ sqwave = (1,) * samples_per_symbol # rectangular window
+ self.taps = numpy.convolve(numpy.array(gaussian_taps),numpy.array(sqwave))
+ elif cpm_type == 2: # Raised Cosine
+ # generalize it for arbitrary roll-off factor
+ self.taps = (1-numpy.cos(2*pi*numpy.arange(0,self.ntaps)/samples_per_symbol/self._symbols_per_pulse))/(2*self._symbols_per_pulse)
+ elif cpm_type == 3: # Generic CPM
+ self.taps = generic_taps
+ else:
+ raise TypeError, ("cpm_type must be an integer in {0,1,2,3}, is %r" % (cpm_type,))
+
+ self.filter = blks2.pfb_arb_resampler_fff(samples_per_symbol, self.taps)
+
+ # FM modulation
+ self.fmmod = gr.frequency_modulator_fc(sensitivity)
+
+ if verbose:
+ self._print_verbage()
+
+ if log:
+ self._setup_logging()
+
+ # Connect
+ self.connect(self, self.B2s, self.pam, self.filter, self.fmmod, self)
+
+ def samples_per_symbol(self):
+ return self._samples_per_symbol
+
+ def bits_per_symbol(self):
+ return self._bits_per_symbol
+
+ def h_numerator(self):
+ return self._h_numerator
+
+ def h_denominator(self):
+ return self._h_denominator
+
+ def cpm_type(self):
+ return self._cpm_type
+
+ def bt(self):
+ return self._bt
+
+ def symbols_per_pulse(self):
+ return self._symbols_per_pulse
+
+
+ def _print_verbage(self):
+ print "Samples per symbol = %d" % self._samples_per_symbol
+ print "Bits per symbol = %d" % self._bits_per_symbol
+ print "h = " , self._h_numerator , " / " , self._h_denominator
+ print "Symbol alphabet = " , self.sym_alphabet
+ print "Symbols per pulse = %d" % self._symbols_per_pulse
+ print "taps = " , self.taps
+
+ print "CPM type = %d" % self._cpm_type
+ if self._cpm_type == 1:
+ print "Gaussian filter BT = %.2f" % self._bt
+
+
+ def _setup_logging(self):
+ print "Modulation logging turned on."
+ self.connect(self.B2s,
+ gr.file_sink(gr.sizeof_float, "symbols.dat"))
+ self.connect(self.pam,
+ gr.file_sink(gr.sizeof_float, "pam.dat"))
+ self.connect(self.filter,
+ gr.file_sink(gr.sizeof_float, "filter.dat"))
+ self.connect(self.fmmod,
+ gr.file_sink(gr.sizeof_gr_complex, "fmmod.dat"))
+
+
+ def add_options(parser):
+ """
+ Adds CPM modulation-specific options to the standard parser
+ """
+ parser.add_option("", "--bt", type="float", default=_def_bt,
+ help="set bandwidth-time product [default=%default] (GMSK)")
+ add_options=staticmethod(add_options)
+
+
+ def extract_kwargs_from_options(options):
+ """
+ Given command line options, create dictionary suitable for passing to __init__
+ """
+ return modulation_utils.extract_kwargs_from_options(cpm_mod.__init__,
+ ('self',), options)
+ extract_kwargs_from_options=staticmethod(extract_kwargs_from_options)
+
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# CPM demodulator
+# /////////////////////////////////////////////////////////////////////////////
+#
+# Not yet implemented
+#
+
+#
+# Add these to the mod/demod registry
+#
+modulation_utils.add_type_1_mod('cpm', cpm_mod)
+#modulation_utils.add_type_1_demod('cpm', cpm_demod)
diff --git a/gr-digital/python/crc.py b/gr-digital/python/crc.py
new file mode 100644
index 000000000..198ab059f
--- /dev/null
+++ b/gr-digital/python/crc.py
@@ -0,0 +1,38 @@
+#
+# Copyright 2005,2007,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gru
+import digital_swig
+import struct
+
+def gen_and_append_crc32(s):
+ crc = digital_swig.crc32(s)
+ return s + struct.pack(">I", gru.hexint(crc) & 0xFFFFFFFF)
+
+def check_crc32(s):
+ if len(s) < 4:
+ return (False, '')
+ msg = s[:-4]
+ #print "msg = '%s'" % (msg,)
+ actual = digital_swig.crc32(msg)
+ (expected,) = struct.unpack(">I", s[-4:])
+ # print "actual =", hex(actual), "expected =", hex(expected)
+ return (actual == expected, msg)
diff --git a/gr-digital/python/generic_mod_demod.py b/gr-digital/python/generic_mod_demod.py
new file mode 100644
index 000000000..ae876e108
--- /dev/null
+++ b/gr-digital/python/generic_mod_demod.py
@@ -0,0 +1,387 @@
+#
+# Copyright 2005,2006,2007,2009,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+# See gnuradio-examples/python/digital for examples
+
+"""
+Generic modulation and demodulation.
+"""
+
+from gnuradio import gr
+from modulation_utils import extract_kwargs_from_options_for_class
+from utils import mod_codes
+import digital_swig
+import math
+
+# default values (used in __init__ and add_options)
+_def_samples_per_symbol = 2
+_def_excess_bw = 0.35
+_def_verbose = False
+_def_log = False
+
+# Frequency correction
+_def_freq_bw = 2*math.pi/100.0
+# Symbol timing recovery
+_def_timing_bw = 2*math.pi/100.0
+_def_timing_max_dev = 1.5
+# Fine frequency / Phase correction
+_def_phase_bw = 2*math.pi/100.0
+# Number of points in constellation
+_def_constellation_points = 16
+# Whether differential coding is used.
+_def_differential = False
+
+def add_common_options(parser):
+ """
+ Sets options common to both modulator and demodulator.
+ """
+ parser.add_option("-p", "--constellation-points", type="int", default=_def_constellation_points,
+ help="set the number of constellation points (must be a power of 2 for psk, power of 4 for QAM) [default=%default]")
+ parser.add_option("", "--non-differential", action="store_false",
+ dest="differential",
+ help="do not use differential encoding [default=False]")
+ parser.add_option("", "--differential", action="store_true",
+ dest="differential", default=True,
+ help="use differential encoding [default=%default]")
+ parser.add_option("", "--mod-code", type="choice", choices=mod_codes.codes,
+ default=mod_codes.NO_CODE,
+ help="Select modulation code from: %s [default=%%default]"
+ % (', '.join(mod_codes.codes),))
+ parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw,
+ help="set RRC excess bandwith factor [default=%default]")
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# Generic modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class generic_mod(gr.hier_block2):
+
+ def __init__(self, constellation,
+ samples_per_symbol=_def_samples_per_symbol,
+ differential=_def_differential,
+ excess_bw=_def_excess_bw,
+ gray_coded=True,
+ verbose=_def_verbose,
+ log=_def_log):
+ """
+ Hierarchical block for RRC-filtered differential generic modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ @param constellation: determines the modulation type
+ @type constellation: gnuradio.digital.gr_constellation
+ @param samples_per_symbol: samples per baud >= 2
+ @type samples_per_symbol: float
+ @param excess_bw: Root-raised cosine filter excess bandwidth
+ @type excess_bw: float
+ @param gray_coded: turn gray coding on/off
+ @type gray_coded: bool
+ @param verbose: Print information about modulator?
+ @type verbose: bool
+ @param log: Log modulation data to files?
+ @type log: bool
+ """
+
+ gr.hier_block2.__init__(self, "generic_mod",
+ gr.io_signature(1, 1, gr.sizeof_char), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
+
+ self._constellation = constellation.base()
+ self._samples_per_symbol = samples_per_symbol
+ self._excess_bw = excess_bw
+ self._differential = differential
+
+ if self._samples_per_symbol < 2:
+ raise TypeError, ("sbp must be >= 2, is %f" % self._samples_per_symbol)
+
+ arity = pow(2,self.bits_per_symbol())
+
+ # turn bytes into k-bit vectors
+ self.bytes2chunks = \
+ gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST)
+
+ if gray_coded == True:
+ self.symbol_mapper = gr.map_bb(self._constellation.pre_diff_code())
+
+ if differential:
+ self.diffenc = gr.diff_encoder_bb(arity)
+
+ self.chunks2symbols = gr.chunks_to_symbols_bc(self._constellation.points())
+
+ # pulse shaping filter
+ nfilts = 32
+ ntaps = nfilts * 11 * int(self._samples_per_symbol) # make nfilts filters of ntaps each
+ self.rrc_taps = gr.firdes.root_raised_cosine(
+ nfilts, # gain
+ nfilts, # sampling rate based on 32 filters in resampler
+ 1.0, # symbol rate
+ self._excess_bw, # excess bandwidth (roll-off factor)
+ ntaps)
+ self.rrc_filter = gr.pfb_arb_resampler_ccf(self._samples_per_symbol,
+ self.rrc_taps)
+
+ # Connect
+ blocks = [self, self.bytes2chunks]
+ if gray_coded == True:
+ blocks.append(self.symbol_mapper)
+ if differential:
+ blocks.append(self.diffenc)
+ blocks += [self.chunks2symbols, self.rrc_filter, self]
+ self.connect(*blocks)
+
+ if verbose:
+ self._print_verbage()
+
+ if log:
+ self._setup_logging()
+
+
+ def samples_per_symbol(self):
+ return self._samples_per_symbol
+
+ def bits_per_symbol(self): # static method that's also callable on an instance
+ return self._constellation.bits_per_symbol()
+
+ def add_options(parser):
+ """
+ Adds generic modulation options to the standard parser
+ """
+ add_common_options(parser)
+ add_options=staticmethod(add_options)
+
+ def extract_kwargs_from_options(cls, options):
+ """
+ Given command line options, create dictionary suitable for passing to __init__
+ """
+ return extract_kwargs_from_options_for_class(cls, options)
+ extract_kwargs_from_options=classmethod(extract_kwargs_from_options)
+
+
+ def _print_verbage(self):
+ print "\nModulator:"
+ print "bits per symbol: %d" % self.bits_per_symbol()
+ print "RRC roll-off factor: %.2f" % self._excess_bw
+
+ def _setup_logging(self):
+ print "Modulation logging turned on."
+ self.connect(self.bytes2chunks,
+ gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.8b"))
+ if self._constellation.apply_pre_diff_code():
+ self.connect(self.symbol_mapper,
+ gr.file_sink(gr.sizeof_char, "tx_symbol_mapper.8b"))
+ if self._differential:
+ self.connect(self.diffenc,
+ gr.file_sink(gr.sizeof_char, "tx_diffenc.8b"))
+ self.connect(self.chunks2symbols,
+ gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.32fc"))
+ self.connect(self.rrc_filter,
+ gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.32fc"))
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# Generic demodulator
+#
+# Differentially coherent detection of differentially encoded generically
+# modulated signal.
+# /////////////////////////////////////////////////////////////////////////////
+
+class generic_demod(gr.hier_block2):
+
+ def __init__(self, constellation,
+ samples_per_symbol=_def_samples_per_symbol,
+ differential=_def_differential,
+ excess_bw=_def_excess_bw,
+ gray_coded=True,
+ freq_bw=_def_freq_bw,
+ timing_bw=_def_timing_bw,
+ phase_bw=_def_phase_bw,
+ verbose=_def_verbose,
+ log=_def_log):
+ """
+ Hierarchical block for RRC-filtered differential generic demodulation.
+
+ The input is the complex modulated signal at baseband.
+ The output is a stream of bits packed 1 bit per byte (LSB)
+
+ @param constellation: determines the modulation type
+ @type constellation: gnuradio.digital.gr_constellation
+ @param samples_per_symbol: samples per symbol >= 2
+ @type samples_per_symbol: float
+ @param excess_bw: Root-raised cosine filter excess bandwidth
+ @type excess_bw: float
+ @param gray_coded: turn gray coding on/off
+ @type gray_coded: bool
+ @param freq_bw: loop filter lock-in bandwidth
+ @type freq_bw: float
+ @param timing_bw: timing recovery loop lock-in bandwidth
+ @type timing_bw: float
+ @param phase_bw: phase recovery loop bandwidth
+ @type phase_bw: float
+ @param verbose: Print information about modulator?
+ @type verbose: bool
+ @param debug: Print modualtion data to files?
+ @type debug: bool
+ """
+
+ gr.hier_block2.__init__(self, "generic_demod",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_char)) # Output signature
+
+ self._constellation = constellation.base()
+ self._samples_per_symbol = samples_per_symbol
+ self._excess_bw = excess_bw
+ self._phase_bw = phase_bw
+ self._freq_bw = freq_bw
+ self._timing_bw = timing_bw
+ self._timing_max_dev= _def_timing_max_dev
+ self._differential = differential
+
+ if self._samples_per_symbol < 2:
+ raise TypeError, ("sbp must be >= 2, is %d" % self._samples_per_symbol)
+
+ arity = pow(2,self.bits_per_symbol())
+
+ nfilts = 32
+ ntaps = 11 * int(self._samples_per_symbol*nfilts)
+
+ # Automatic gain control
+ self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100)
+
+ # Frequency correction
+ fll_ntaps = 55
+ self.freq_recov = digital_swig.fll_band_edge_cc(self._samples_per_symbol, self._excess_bw,
+ fll_ntaps, self._freq_bw)
+
+ # symbol timing recovery with RRC data filter
+ taps = gr.firdes.root_raised_cosine(nfilts, nfilts*self._samples_per_symbol,
+ 1.0, self._excess_bw, ntaps)
+ self.time_recov = gr.pfb_clock_sync_ccf(self._samples_per_symbol,
+ self._timing_bw, taps,
+ nfilts, nfilts//2, self._timing_max_dev)
+
+ fmin = -0.25
+ fmax = 0.25
+ self.receiver = digital_swig.constellation_receiver_cb(
+ self._constellation, self._phase_bw,
+ fmin, fmax)
+
+ # Do differential decoding based on phase change of symbols
+ if differential:
+ self.diffdec = gr.diff_decoder_bb(arity)
+
+ if gray_coded:
+ self.symbol_mapper = gr.map_bb(
+ mod_codes.invert_code(self._constellation.pre_diff_code()))
+
+ # unpack the k bit vector into a stream of bits
+ self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol())
+
+ if verbose:
+ self._print_verbage()
+
+ if log:
+ self._setup_logging()
+
+ # Connect and Initialize base class
+ blocks = [self, self.agc, self.freq_recov,
+ self.time_recov, self.receiver]
+ if differential:
+ blocks.append(self.diffdec)
+ if self._constellation.apply_pre_diff_code():
+ blocks.append(self.symbol_mapper)
+ blocks += [self.unpack, self]
+ self.connect(*blocks)
+
+ def samples_per_symbol(self):
+ return self._samples_per_symbol
+
+ def bits_per_symbol(self): # staticmethod that's also callable on an instance
+ return self._constellation.bits_per_symbol()
+
+ def _print_verbage(self):
+ print "\nDemodulator:"
+ print "bits per symbol: %d" % self.bits_per_symbol()
+ print "RRC roll-off factor: %.2f" % self._excess_bw
+ print "FLL bandwidth: %.2e" % self._freq_bw
+ print "Timing bandwidth: %.2e" % self._timing_bw
+ print "Phase bandwidth: %.2e" % self._phase_bw
+
+ def _setup_logging(self):
+ print "Modulation logging turned on."
+ self.connect(self.agc,
+ gr.file_sink(gr.sizeof_gr_complex, "rx_agc.32fc"))
+ self.connect((self.freq_recov, 0),
+ gr.file_sink(gr.sizeof_gr_complex, "rx_freq_recov.32fc"))
+ self.connect((self.freq_recov, 1),
+ gr.file_sink(gr.sizeof_float, "rx_freq_recov_freq.32f"))
+ self.connect((self.freq_recov, 2),
+ gr.file_sink(gr.sizeof_float, "rx_freq_recov_phase.32f"))
+ self.connect((self.freq_recov, 3),
+ gr.file_sink(gr.sizeof_float, "rx_freq_recov_error.32f"))
+ self.connect((self.time_recov, 0),
+ gr.file_sink(gr.sizeof_gr_complex, "rx_time_recov.32fc"))
+ self.connect((self.time_recov, 1),
+ gr.file_sink(gr.sizeof_float, "rx_time_recov_error.32f"))
+ self.connect((self.time_recov, 2),
+ gr.file_sink(gr.sizeof_float, "rx_time_recov_rate.32f"))
+ self.connect((self.time_recov, 3),
+ gr.file_sink(gr.sizeof_float, "rx_time_recov_phase.32f"))
+ self.connect((self.receiver, 0),
+ gr.file_sink(gr.sizeof_char, "rx_receiver.8b"))
+ self.connect((self.receiver, 1),
+ gr.file_sink(gr.sizeof_float, "rx_receiver_error.32f"))
+ self.connect((self.receiver, 2),
+ gr.file_sink(gr.sizeof_float, "rx_receiver_phase.32f"))
+ self.connect((self.receiver, 3),
+ gr.file_sink(gr.sizeof_float, "rx_receiver_freq.32f"))
+ if self._differential:
+ self.connect(self.diffdec,
+ gr.file_sink(gr.sizeof_char, "rx_diffdec.8b"))
+ if self._constellation.apply_pre_diff_code():
+ self.connect(self.symbol_mapper,
+ gr.file_sink(gr.sizeof_char, "rx_symbol_mapper.8b"))
+ self.connect(self.unpack,
+ gr.file_sink(gr.sizeof_char, "rx_unpack.8b"))
+
+ def add_options(parser):
+ """
+ Adds generic demodulation options to the standard parser
+ """
+ # Add options shared with modulator.
+ add_common_options(parser)
+ # Add options specific to demodulator.
+ parser.add_option("", "--freq-bw", type="float", default=_def_freq_bw,
+ help="set frequency lock loop lock-in bandwidth [default=%default]")
+ parser.add_option("", "--phase-bw", type="float", default=_def_phase_bw,
+ help="set phase tracking loop lock-in bandwidth [default=%default]")
+ parser.add_option("", "--timing-bw", type="float", default=_def_timing_bw,
+ help="set timing symbol sync loop gain lock-in bandwidth [default=%default]")
+ add_options=staticmethod(add_options)
+
+ def extract_kwargs_from_options(cls, options):
+ """
+ Given command line options, create dictionary suitable for passing to __init__
+ """
+ return extract_kwargs_from_options_for_class(cls, options)
+ extract_kwargs_from_options=classmethod(extract_kwargs_from_options)
+
diff --git a/gr-digital/python/gmsk.py b/gr-digital/python/gmsk.py
new file mode 100644
index 000000000..70fa197e3
--- /dev/null
+++ b/gr-digital/python/gmsk.py
@@ -0,0 +1,276 @@
+#
+# GMSK modulation and demodulation.
+#
+#
+# Copyright 2005,2006,2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+# See gnuradio-examples/python/digital for examples
+
+from gnuradio import gr
+import digital_swig
+import modulation_utils
+
+from math import pi
+import numpy
+from pprint import pprint
+import inspect
+
+# default values (used in __init__ and add_options)
+_def_samples_per_symbol = 2
+_def_bt = 0.35
+_def_verbose = False
+_def_log = False
+
+# Symbol timing recovery
+_def_timing_bw = 2*pi/100.0
+_def_timing_max_dev = 1.5
+
+# /////////////////////////////////////////////////////////////////////////////
+# GMSK modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class gmsk_mod(gr.hier_block2):
+
+ def __init__(self,
+ samples_per_symbol=_def_samples_per_symbol,
+ bt=_def_bt,
+ verbose=_def_verbose,
+ log=_def_log):
+ """
+ Hierarchical block for Gaussian Minimum Shift Key (GMSK)
+ modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ @param samples_per_symbol: samples per baud >= 2
+ @type samples_per_symbol: integer
+ @param bt: Gaussian filter bandwidth * symbol time
+ @type bt: float
+ @param verbose: Print information about modulator?
+ @type verbose: bool
+ @param debug: Print modualtion data to files?
+ @type debug: bool
+ """
+
+ gr.hier_block2.__init__(self, "gmsk_mod",
+ gr.io_signature(1, 1, gr.sizeof_char), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
+
+ self._samples_per_symbol = samples_per_symbol
+ self._bt = bt
+
+ self._differential = False # make consistant with other modulators
+
+ if samples_per_symbol < 2:
+ raise TypeError, ("samples_per_symbol must >= 2, is %r" % \
+ (samples_per_symbol,))
+
+ ntaps = 4 * samples_per_symbol # up to 3 bits in filter at once
+ sensitivity = (pi / 2) / samples_per_symbol # phase change per bit = pi / 2
+
+ # Turn it into NRZ data.
+ self.nrz = gr.bytes_to_syms()
+
+ # Form Gaussian filter
+ # Generate Gaussian response (Needs to be convolved with window below).
+ self.gaussian_taps = gr.firdes.gaussian(
+ 1, # gain
+ samples_per_symbol, # symbol_rate
+ bt, # bandwidth * symbol time
+ int(ntaps) # number of taps
+ )
+
+ self.sqwave = (1,) * int(samples_per_symbol) # rectangular window
+ self.taps = numpy.convolve(numpy.array(self.gaussian_taps),numpy.array(self.sqwave))
+ self.gaussian_filter = gr.pfb_arb_resampler_fff(samples_per_symbol, self.taps)
+
+ # FM modulation
+ self.fmmod = gr.frequency_modulator_fc(sensitivity)
+
+ if verbose:
+ self._print_verbage()
+
+ if log:
+ self._setup_logging()
+
+ # Connect & Initialize base class
+ self.connect(self, self.nrz, self.gaussian_filter, self.fmmod, self)
+
+ def samples_per_symbol(self):
+ return self._samples_per_symbol
+
+ def bits_per_symbol(self=None): # staticmethod that's also callable on an instance
+ return 1
+ bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method.
+
+
+ def _print_verbage(self):
+ print "bits per symbol = %d" % self.bits_per_symbol()
+ print "Gaussian filter bt = %.2f" % self._bt
+
+
+ def _setup_logging(self):
+ print "Modulation logging turned on."
+ self.connect(self.nrz,
+ gr.file_sink(gr.sizeof_float, "tx_gmsk_nrz.32f"))
+ self.connect(self.gaussian_filter,
+ gr.file_sink(gr.sizeof_float, "tx_gmsk_gaussian_filter.32f"))
+ self.connect(self.fmmod,
+ gr.file_sink(gr.sizeof_gr_complex, "tx_gmsk_fmmod.32fc"))
+
+
+ def add_options(parser):
+ """
+ Adds GMSK modulation-specific options to the standard parser
+ """
+ parser.add_option("", "--bt", type="float", default=_def_bt,
+ help="set bandwidth-time product [default=%default] (GMSK)")
+ add_options=staticmethod(add_options)
+
+
+ def extract_kwargs_from_options(options):
+ """
+ Given command line options, create dictionary suitable for passing to __init__
+ """
+ return modulation_utils.extract_kwargs_from_options(gmsk_mod.__init__,
+ ('self',), options)
+ extract_kwargs_from_options=staticmethod(extract_kwargs_from_options)
+
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# GMSK demodulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class gmsk_demod(gr.hier_block2):
+
+ def __init__(self,
+ samples_per_symbol=_def_samples_per_symbol,
+ bt=_def_bt,
+ timing_bw=_def_timing_bw,
+ verbose=_def_verbose,
+ log=_def_log):
+ """
+ Hierarchical block for Gaussian Minimum Shift Key (GMSK)
+ demodulation.
+
+ The input is the complex modulated signal at baseband.
+ The output is a stream of bits packed 1 bit per byte (the LSB)
+
+ @param samples_per_symbol: samples per baud
+ @type samples_per_symbol: integer
+ @param timing_bw: timing recovery loop lock-in bandwidth
+ @type timing_bw: float
+ @param verbose: Print information about modulator?
+ @type verbose: bool
+ @param log: Print modualtion data to files?
+ @type log: bool
+ """
+
+ gr.hier_block2.__init__(self, "gmsk_demod",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_char)) # Output signature
+
+ self._samples_per_symbol = samples_per_symbol
+ self._bt = bt
+ self._timing_bw = timing_bw
+ self._timing_max_dev= _def_timing_max_dev
+
+ self._differential = False # make consistant with other modulators
+
+ if samples_per_symbol < 2:
+ raise TypeError, "samples_per_symbol >= 2, is %f" % samples_per_symbol
+
+ # Demodulate FM
+ sensitivity = (pi / 2) / samples_per_symbol
+ self.fmdemod = gr.quadrature_demod_cf(1.0 / sensitivity)
+
+ # the clock recovery block tracks the symbol clock and resamples as needed.
+ # the output of the block is a stream of soft symbols (float)
+ nfilts = 32
+ ntaps = 11 * int(self._samples_per_symbol*nfilts)
+ taps = gr.firdes.gaussian(nfilts,
+ nfilts*self._samples_per_symbol,
+ self._bt, ntaps)
+ self.clock_recovery = \
+ gr.pfb_clock_sync_fff(self._samples_per_symbol,
+ self._timing_bw, taps,
+ nfilts, nfilts//2, self._timing_max_dev)
+
+ # slice the floats at 0, outputting 1 bit (the LSB of the output byte) per sample
+ self.slicer = digital_swig.binary_slicer_fb()
+
+ if verbose:
+ self._print_verbage()
+
+ if log:
+ self._setup_logging()
+
+ # Connect & Initialize base class
+ self.connect(self, self.fmdemod, self.clock_recovery, self.slicer, self)
+
+ def samples_per_symbol(self):
+ return self._samples_per_symbol
+
+ def bits_per_symbol(self=None): # staticmethod that's also callable on an instance
+ return 1
+ bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method.
+
+
+ def _print_verbage(self):
+ print "bits per symbol: %d" % self.bits_per_symbol()
+ print "Bandwidth-Time Prod: %f" % self._bt
+ print "Timing bandwidth: %.2e" % self._timing_bw
+
+
+ def _setup_logging(self):
+ print "Demodulation logging turned on."
+ self.connect(self.fmdemod,
+ gr.file_sink(gr.sizeof_float, "rx_gmsk_fmdemod.32f"))
+ self.connect(self.clock_recovery,
+ gr.file_sink(gr.sizeof_float, "rx_gmsk_clock_recovery.32f"))
+ self.connect(self.slicer,
+ gr.file_sink(gr.sizeof_char, "rx_gmsk_slicer.8b"))
+
+ def add_options(parser):
+ """
+ Adds GMSK demodulation-specific options to the standard parser
+ """
+ parser.add_option("", "--timing-bw", type="float", default=_def_timing_bw,
+ help="set timing symbol sync loop gain lock-in bandwidth [default=%default]")
+ parser.add_option("", "--bt", type="float", default=_def_bt,
+ help="set bandwidth-time product [default=%default] (GMSK)")
+ add_options=staticmethod(add_options)
+
+ def extract_kwargs_from_options(options):
+ """
+ Given command line options, create dictionary suitable for passing to __init__
+ """
+ return modulation_utils.extract_kwargs_from_options(gmsk_demod.__init__,
+ ('self',), options)
+ extract_kwargs_from_options=staticmethod(extract_kwargs_from_options)
+
+#
+# Add these to the mod/demod registry
+#
+modulation_utils.add_type_1_mod('gmsk', gmsk_mod)
+modulation_utils.add_type_1_demod('gmsk', gmsk_demod)
diff --git a/gr-digital/python/modulation_utils.py b/gr-digital/python/modulation_utils.py
new file mode 100644
index 000000000..cb3a9812d
--- /dev/null
+++ b/gr-digital/python/modulation_utils.py
@@ -0,0 +1,102 @@
+#
+# Copyright 2010 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+"""
+Miscellaneous utilities for managing mods and demods, as well as other items
+useful in dealing with generalized handling of different modulations and demods.
+"""
+
+import inspect
+
+
+# Type 1 modulators accept a stream of bytes on their input and produce complex baseband output
+_type_1_modulators = {}
+
+def type_1_mods():
+ return _type_1_modulators
+
+def add_type_1_mod(name, mod_class):
+ _type_1_modulators[name] = mod_class
+
+
+# Type 1 demodulators accept complex baseband input and produce a stream of bits, packed
+# 1 bit / byte as their output. Their output is completely unambiguous. There is no need
+# to resolve phase or polarity ambiguities.
+_type_1_demodulators = {}
+
+def type_1_demods():
+ return _type_1_demodulators
+
+def add_type_1_demod(name, demod_class):
+ _type_1_demodulators[name] = demod_class
+
+# Also record the constellation making functions of the modulations
+_type_1_constellations = {}
+
+def type_1_constellations():
+ return _type_1_constellations
+
+def add_type_1_constellation(name, constellation):
+ _type_1_constellations[name] = constellation
+
+
+def extract_kwargs_from_options(function, excluded_args, options):
+ """
+ Given a function, a list of excluded arguments and the result of
+ parsing command line options, create a dictionary of key word
+ arguments suitable for passing to the function. The dictionary
+ will be populated with key/value pairs where the keys are those
+ that are common to the function's argument list (minus the
+ excluded_args) and the attributes in options. The values are the
+ corresponding values from options unless that value is None.
+ In that case, the corresponding dictionary entry is not populated.
+
+ (This allows different modulations that have the same parameter
+ names, but different default values to coexist. The downside is
+ that --help in the option parser will list the default as None,
+ but in that case the default provided in the __init__ argument
+ list will be used since there is no kwargs entry.)
+
+ @param function: the function whose parameter list will be examined
+ @param excluded_args: function arguments that are NOT to be added to the dictionary
+ @type excluded_args: sequence of strings
+ @param options: result of command argument parsing
+ @type options: optparse.Values
+ """
+
+ # Try this in C++ ;)
+ args, varargs, varkw, defaults = inspect.getargspec(function)
+ d = {}
+ for kw in [a for a in args if a not in excluded_args]:
+ if hasattr(options, kw):
+ if getattr(options, kw) is not None:
+ d[kw] = getattr(options, kw)
+ return d
+
+def extract_kwargs_from_options_for_class(cls, options):
+ """
+ Given command line options, create dictionary suitable for passing to __init__
+ """
+ d = extract_kwargs_from_options(
+ cls.__init__, ('self',), options)
+ for base in cls.__bases__:
+ if hasattr(base, 'extract_kwargs_from_options'):
+ d.update(base.extract_kwargs_from_options(options))
+ return d
diff --git a/gr-digital/python/ofdm.py b/gr-digital/python/ofdm.py
new file mode 100644
index 000000000..9f57920ef
--- /dev/null
+++ b/gr-digital/python/ofdm.py
@@ -0,0 +1,318 @@
+#!/usr/bin/env python
+#
+# Copyright 2006,2007,2008 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import math
+from gnuradio import gr
+import digital_swig
+import ofdm_packet_utils
+from ofdm_receiver import ofdm_receiver
+import gnuradio.gr.gr_threading as _threading
+import psk, qam
+
+# /////////////////////////////////////////////////////////////////////////////
+# mod/demod with packets as i/o
+# /////////////////////////////////////////////////////////////////////////////
+
+class ofdm_mod(gr.hier_block2):
+ """
+ Modulates an OFDM stream. Based on the options fft_length, occupied_tones, and
+ cp_length, this block creates OFDM symbols using a specified modulation option.
+
+ Send packets by calling send_pkt
+ """
+ def __init__(self, options, msgq_limit=2, pad_for_usrp=True):
+ """
+ Hierarchical block for sending packets
+
+ Packets to be sent are enqueued by calling send_pkt.
+ The output is the complex modulated signal at baseband.
+
+ @param options: pass modulation options from higher layers (fft length, occupied tones, etc.)
+ @param msgq_limit: maximum number of messages in message queue
+ @type msgq_limit: int
+ @param pad_for_usrp: If true, packets are padded such that they end up a multiple of 128 samples
+ """
+
+ gr.hier_block2.__init__(self, "ofdm_mod",
+ gr.io_signature(0, 0, 0), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
+
+ self._pad_for_usrp = pad_for_usrp
+ self._modulation = options.modulation
+ self._fft_length = options.fft_length
+ self._occupied_tones = options.occupied_tones
+ self._cp_length = options.cp_length
+
+ win = [] #[1 for i in range(self._fft_length)]
+
+ # Use freq domain to get doubled-up known symbol for correlation in time domain
+ zeros_on_left = int(math.ceil((self._fft_length - self._occupied_tones)/2.0))
+ ksfreq = known_symbols_4512_3[0:self._occupied_tones]
+ for i in range(len(ksfreq)):
+ if((zeros_on_left + i) & 1):
+ ksfreq[i] = 0
+
+ # hard-coded known symbols
+ preambles = (ksfreq,)
+
+ padded_preambles = list()
+ for pre in preambles:
+ padded = self._fft_length*[0,]
+ padded[zeros_on_left : zeros_on_left + self._occupied_tones] = pre
+ padded_preambles.append(padded)
+
+ symbol_length = options.fft_length + options.cp_length
+
+ mods = {"bpsk": 2, "qpsk": 4, "8psk": 8, "qam8": 8, "qam16": 16, "qam64": 64, "qam256": 256}
+ arity = mods[self._modulation]
+
+ rot = 1
+ if self._modulation == "qpsk":
+ rot = (0.707+0.707j)
+
+ # FIXME: pass the constellation objects instead of just the points
+ if(self._modulation.find("psk") >= 0):
+ constel = psk.psk_constellation(arity)
+ rotated_const = map(lambda pt: pt * rot, constel.points())
+ elif(self._modulation.find("qam") >= 0):
+ constel = qam.qam_constellation(arity)
+ rotated_const = map(lambda pt: pt * rot, constel.points())
+ #print rotated_const
+ self._pkt_input = digital_swig.ofdm_mapper_bcv(rotated_const,
+ msgq_limit,
+ options.occupied_tones,
+ options.fft_length)
+
+ self.preambles = digital_swig.ofdm_insert_preamble(self._fft_length,
+ padded_preambles)
+ self.ifft = gr.fft_vcc(self._fft_length, False, win, True)
+ self.cp_adder = digital_swig.ofdm_cyclic_prefixer(self._fft_length,
+ symbol_length)
+ self.scale = gr.multiply_const_cc(1.0 / math.sqrt(self._fft_length))
+
+ self.connect((self._pkt_input, 0), (self.preambles, 0))
+ self.connect((self._pkt_input, 1), (self.preambles, 1))
+ self.connect(self.preambles, self.ifft, self.cp_adder, self.scale, self)
+
+ if options.verbose:
+ self._print_verbage()
+
+ if options.log:
+ self.connect(self._pkt_input, gr.file_sink(gr.sizeof_gr_complex*options.fft_length,
+ "ofdm_mapper_c.dat"))
+ self.connect(self.preambles, gr.file_sink(gr.sizeof_gr_complex*options.fft_length,
+ "ofdm_preambles.dat"))
+ self.connect(self.ifft, gr.file_sink(gr.sizeof_gr_complex*options.fft_length,
+ "ofdm_ifft_c.dat"))
+ self.connect(self.cp_adder, gr.file_sink(gr.sizeof_gr_complex,
+ "ofdm_cp_adder_c.dat"))
+
+ def send_pkt(self, payload='', eof=False):
+ """
+ Send the payload.
+
+ @param payload: data to send
+ @type payload: string
+ """
+ if eof:
+ msg = gr.message(1) # tell self._pkt_input we're not sending any more packets
+ else:
+ # print "original_payload =", string_to_hex_list(payload)
+ pkt = ofdm_packet_utils.make_packet(payload, 1, 1,
+ self._pad_for_usrp,
+ whitening=True)
+
+ #print "pkt =", string_to_hex_list(pkt)
+ msg = gr.message_from_string(pkt)
+ self._pkt_input.msgq().insert_tail(msg)
+
+ def add_options(normal, expert):
+ """
+ Adds OFDM-specific options to the Options Parser
+ """
+ normal.add_option("-m", "--modulation", type="string", default="bpsk",
+ help="set modulation type (bpsk, qpsk, 8psk, qam{16,64}) [default=%default]")
+ expert.add_option("", "--fft-length", type="intx", default=512,
+ help="set the number of FFT bins [default=%default]")
+ expert.add_option("", "--occupied-tones", type="intx", default=200,
+ help="set the number of occupied FFT bins [default=%default]")
+ expert.add_option("", "--cp-length", type="intx", default=128,
+ help="set the number of bits in the cyclic prefix [default=%default]")
+ # Make a static method to call before instantiation
+ add_options = staticmethod(add_options)
+
+ def _print_verbage(self):
+ """
+ Prints information about the OFDM modulator
+ """
+ print "\nOFDM Modulator:"
+ print "Modulation Type: %s" % (self._modulation)
+ print "FFT length: %3d" % (self._fft_length)
+ print "Occupied Tones: %3d" % (self._occupied_tones)
+ print "CP length: %3d" % (self._cp_length)
+
+
+class ofdm_demod(gr.hier_block2):
+ """
+ Demodulates a received OFDM stream. Based on the options fft_length, occupied_tones, and
+ cp_length, this block performs synchronization, FFT, and demodulation of incoming OFDM
+ symbols and passes packets up the a higher layer.
+
+ The input is complex baseband. When packets are demodulated, they are passed to the
+ app via the callback.
+ """
+
+ def __init__(self, options, callback=None):
+ """
+ Hierarchical block for demodulating and deframing packets.
+
+ The input is the complex modulated signal at baseband.
+ Demodulated packets are sent to the handler.
+
+ @param options: pass modulation options from higher layers (fft length, occupied tones, etc.)
+ @param callback: function of two args: ok, payload
+ @type callback: ok: bool; payload: string
+ """
+ gr.hier_block2.__init__(self, "ofdm_demod",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
+
+
+ self._rcvd_pktq = gr.msg_queue() # holds packets from the PHY
+
+ self._modulation = options.modulation
+ self._fft_length = options.fft_length
+ self._occupied_tones = options.occupied_tones
+ self._cp_length = options.cp_length
+ self._snr = options.snr
+
+ # Use freq domain to get doubled-up known symbol for correlation in time domain
+ zeros_on_left = int(math.ceil((self._fft_length - self._occupied_tones)/2.0))
+ ksfreq = known_symbols_4512_3[0:self._occupied_tones]
+ for i in range(len(ksfreq)):
+ if((zeros_on_left + i) & 1):
+ ksfreq[i] = 0
+
+ # hard-coded known symbols
+ preambles = (ksfreq,)
+
+ symbol_length = self._fft_length + self._cp_length
+ self.ofdm_recv = ofdm_receiver(self._fft_length,
+ self._cp_length,
+ self._occupied_tones,
+ self._snr, preambles,
+ options.log)
+
+ mods = {"bpsk": 2, "qpsk": 4, "8psk": 8, "qam8": 8, "qam16": 16, "qam64": 64, "qam256": 256}
+ arity = mods[self._modulation]
+
+ rot = 1
+ if self._modulation == "qpsk":
+ rot = (0.707+0.707j)
+
+ # FIXME: pass the constellation objects instead of just the points
+ if(self._modulation.find("psk") >= 0):
+ constel = psk.psk_constellation(arity)
+ rotated_const = map(lambda pt: pt * rot, constel.points())
+ elif(self._modulation.find("qam") >= 0):
+ constel = qam.qam_constellation(arity)
+ rotated_const = map(lambda pt: pt * rot, constel.points())
+ #print rotated_const
+
+ phgain = 0.25
+ frgain = phgain*phgain / 4.0
+ self.ofdm_demod = digital_swig.ofdm_frame_sink(rotated_const, range(arity),
+ self._rcvd_pktq,
+ self._occupied_tones,
+ phgain, frgain)
+
+ self.connect(self, self.ofdm_recv)
+ self.connect((self.ofdm_recv, 0), (self.ofdm_demod, 0))
+ self.connect((self.ofdm_recv, 1), (self.ofdm_demod, 1))
+
+ # added output signature to work around bug, though it might not be a bad
+ # thing to export, anyway
+ self.connect(self.ofdm_recv.chan_filt, self)
+
+ if options.log:
+ self.connect(self.ofdm_demod,
+ gr.file_sink(gr.sizeof_gr_complex*self._occupied_tones,
+ "ofdm_frame_sink_c.dat"))
+ else:
+ self.connect(self.ofdm_demod,
+ gr.null_sink(gr.sizeof_gr_complex*self._occupied_tones))
+
+ if options.verbose:
+ self._print_verbage()
+
+ self._watcher = _queue_watcher_thread(self._rcvd_pktq, callback)
+
+ def add_options(normal, expert):
+ """
+ Adds OFDM-specific options to the Options Parser
+ """
+ normal.add_option("-m", "--modulation", type="string", default="bpsk",
+ help="set modulation type (bpsk or qpsk) [default=%default]")
+ expert.add_option("", "--fft-length", type="intx", default=512,
+ help="set the number of FFT bins [default=%default]")
+ expert.add_option("", "--occupied-tones", type="intx", default=200,
+ help="set the number of occupied FFT bins [default=%default]")
+ expert.add_option("", "--cp-length", type="intx", default=128,
+ help="set the number of bits in the cyclic prefix [default=%default]")
+ expert.add_option("", "--snr", type="float", default=30.0,
+ help="SNR estimate [default=%default]")
+ # Make a static method to call before instantiation
+ add_options = staticmethod(add_options)
+
+ def _print_verbage(self):
+ """
+ Prints information about the OFDM demodulator
+ """
+ print "\nOFDM Demodulator:"
+ print "Modulation Type: %s" % (self._modulation)
+ print "FFT length: %3d" % (self._fft_length)
+ print "Occupied Tones: %3d" % (self._occupied_tones)
+ print "CP length: %3d" % (self._cp_length)
+
+
+
+class _queue_watcher_thread(_threading.Thread):
+ def __init__(self, rcvd_pktq, callback):
+ _threading.Thread.__init__(self)
+ self.setDaemon(1)
+ self.rcvd_pktq = rcvd_pktq
+ self.callback = callback
+ self.keep_running = True
+ self.start()
+
+
+ def run(self):
+ while self.keep_running:
+ msg = self.rcvd_pktq.delete_head()
+ ok, payload = ofdm_packet_utils.unmake_packet(msg.to_string())
+ if self.callback:
+ self.callback(ok, payload)
+
+# Generating known symbols with:
+# i = [2*random.randint(0,1)-1 for i in range(4512)]
+
+known_symbols_4512_3 = [-1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1]
diff --git a/gr-digital/python/ofdm_packet_utils.py b/gr-digital/python/ofdm_packet_utils.py
new file mode 100644
index 000000000..d0000e6db
--- /dev/null
+++ b/gr-digital/python/ofdm_packet_utils.py
@@ -0,0 +1,454 @@
+#
+# Copyright 2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import struct
+import numpy
+from gnuradio import gru
+import crc
+
+def conv_packed_binary_string_to_1_0_string(s):
+ """
+ '\xAF' --> '10101111'
+ """
+ r = []
+ for ch in s:
+ x = ord(ch)
+ for i in range(7,-1,-1):
+ t = (x >> i) & 0x1
+ r.append(t)
+
+ return ''.join(map(lambda x: chr(x + ord('0')), r))
+
+def conv_1_0_string_to_packed_binary_string(s):
+ """
+ '10101111' -> ('\xAF', False)
+
+ Basically the inverse of conv_packed_binary_string_to_1_0_string,
+ but also returns a flag indicating if we had to pad with leading zeros
+ to get to a multiple of 8.
+ """
+ if not is_1_0_string(s):
+ raise ValueError, "Input must be a string containing only 0's and 1's"
+
+ # pad to multiple of 8
+ padded = False
+ rem = len(s) % 8
+ if rem != 0:
+ npad = 8 - rem
+ s = '0' * npad + s
+ padded = True
+
+ assert len(s) % 8 == 0
+
+ r = []
+ i = 0
+ while i < len(s):
+ t = 0
+ for j in range(8):
+ t = (t << 1) | (ord(s[i + j]) - ord('0'))
+ r.append(chr(t))
+ i += 8
+ return (''.join(r), padded)
+
+
+def is_1_0_string(s):
+ if not isinstance(s, str):
+ return False
+ for ch in s:
+ if not ch in ('0', '1'):
+ return False
+ return True
+
+def string_to_hex_list(s):
+ return map(lambda x: hex(ord(x)), s)
+
+
+def whiten(s, o):
+ sa = numpy.fromstring(s, numpy.uint8)
+ z = sa ^ random_mask_vec8[o:len(sa)+o]
+ return z.tostring()
+
+def dewhiten(s, o):
+ return whiten(s, o) # self inverse
+
+
+def make_header(payload_len, whitener_offset=0):
+ # Upper nibble is offset, lower 12 bits is len
+ val = ((whitener_offset & 0xf) << 12) | (payload_len & 0x0fff)
+ #print "offset =", whitener_offset, " len =", payload_len, " val=", val
+ return struct.pack('!HH', val, val)
+
+def make_packet(payload, samples_per_symbol, bits_per_symbol,
+ pad_for_usrp=True, whitener_offset=0, whitening=True):
+ """
+ Build a packet, given access code, payload, and whitener offset
+
+ @param payload: packet payload, len [0, 4096]
+ @param samples_per_symbol: samples per symbol (needed for padding calculation)
+ @type samples_per_symbol: int
+ @param bits_per_symbol: (needed for padding calculation)
+ @type bits_per_symbol: int
+ @param whitener_offset offset into whitener string to use [0-16)
+ @param whitening: Turn whitener on or off
+ @type whitening: bool
+
+ Packet will have access code at the beginning, followed by length, payload
+ and finally CRC-32.
+ """
+
+ if not whitener_offset >=0 and whitener_offset < 16:
+ raise ValueError, "whitener_offset must be between 0 and 15, inclusive (%i)" % (whitener_offset,)
+
+ payload_with_crc = crc.gen_and_append_crc32(payload)
+ #print "outbound crc =", string_to_hex_list(payload_with_crc[-4:])
+
+ L = len(payload_with_crc)
+ MAXLEN = len(random_mask_tuple)
+ if L > MAXLEN:
+ raise ValueError, "len(payload) must be in [0, %d]" % (MAXLEN,)
+
+ pkt_hd = make_header(L, whitener_offset)
+ pkt_dt = ''.join((payload_with_crc, '\x55'))
+ packet_length = len(pkt_hd) + len(pkt_dt)
+
+ if pad_for_usrp:
+ usrp_packing = _npadding_bytes(packet_length, samples_per_symbol, bits_per_symbol) * '\x55'
+ pkt_dt = pkt_dt + usrp_packing
+
+ if(whitening):
+ pkt = pkt_hd + whiten(pkt_dt, whitener_offset)
+ else:
+ pkt = pkt_hd + pkt_dt
+
+ #print "make_packet: len(pkt) =", len(pkt)
+
+ return pkt
+
+def _npadding_bytes(pkt_byte_len, samples_per_symbol, bits_per_symbol):
+ """
+ Generate sufficient padding such that each packet ultimately ends
+ up being a multiple of 512 bytes when sent across the USB. We
+ send 4-byte samples across the USB (16-bit I and 16-bit Q), thus
+ we want to pad so that after modulation the resulting packet
+ is a multiple of 128 samples.
+
+ @param ptk_byte_len: len in bytes of packet, not including padding.
+ @param samples_per_symbol: samples per bit (1 bit / symbolwidth GMSK)
+ @type samples_per_symbol: int
+ @param bits_per_symbol: bits per symbol (log2(modulation order))
+ @type bits_per_symbol: int
+
+ @returns number of bytes of padding to append.
+ """
+ modulus = 128
+ byte_modulus = gru.lcm(modulus/8, samples_per_symbol) * bits_per_symbol / samples_per_symbol
+ r = pkt_byte_len % byte_modulus
+ if r == 0:
+ return 0
+ return byte_modulus - r
+
+
+def unmake_packet(whitened_payload_with_crc, whitener_offset=0, dewhitening=1):
+ """
+ Return (ok, payload)
+
+ @param whitened_payload_with_crc: string
+ @param whitener_offset offset into whitener string to use [0-16)
+ @param dewhitening: Turn whitener on or off
+ @type dewhitening: bool
+ """
+
+ if dewhitening:
+ payload_with_crc = dewhiten(whitened_payload_with_crc, whitener_offset)
+ else:
+ payload_with_crc = whitened_payload_with_crc
+
+ ok, payload = crc.check_crc32(payload_with_crc)
+
+ if 0:
+ print "payload_with_crc =", string_to_hex_list(payload_with_crc)
+ print "ok = %r, len(payload) = %d" % (ok, len(payload))
+ print "payload =", string_to_hex_list(payload)
+
+ return ok, payload
+
+
+# FYI, this PN code is the output of a 15-bit LFSR
+random_mask_tuple = (
+ 255, 63, 0, 16, 0, 12, 0, 5, 192, 3, 16, 1, 204, 0, 85, 192,
+ 63, 16, 16, 12, 12, 5, 197, 195, 19, 17, 205, 204, 85, 149, 255, 47,
+ 0, 28, 0, 9, 192, 6, 208, 2, 220, 1, 153, 192, 106, 208, 47, 28,
+ 28, 9, 201, 198, 214, 210, 222, 221, 152, 89, 170, 186, 255, 51, 0, 21,
+ 192, 15, 16, 4, 12, 3, 69, 193, 243, 16, 69, 204, 51, 21, 213, 207,
+ 31, 20, 8, 15, 70, 132, 50, 227, 85, 137, 255, 38, 192, 26, 208, 11,
+ 28, 7, 73, 194, 182, 209, 182, 220, 118, 217, 230, 218, 202, 219, 23, 27,
+ 78, 139, 116, 103, 103, 106, 170, 175, 63, 60, 16, 17, 204, 12, 85, 197,
+ 255, 19, 0, 13, 192, 5, 144, 3, 44, 1, 221, 192, 89, 144, 58, 236,
+ 19, 13, 205, 197, 149, 147, 47, 45, 220, 29, 153, 201, 170, 214, 255, 30,
+ 192, 8, 80, 6, 188, 2, 241, 193, 132, 80, 99, 124, 41, 225, 222, 200,
+ 88, 86, 186, 190, 243, 48, 69, 212, 51, 31, 85, 200, 63, 22, 144, 14,
+ 236, 4, 77, 195, 117, 145, 231, 44, 74, 157, 247, 41, 134, 158, 226, 232,
+ 73, 142, 182, 228, 118, 203, 102, 215, 106, 222, 175, 24, 124, 10, 161, 199,
+ 56, 82, 146, 189, 173, 177, 189, 180, 113, 183, 100, 118, 171, 102, 255, 106,
+ 192, 47, 16, 28, 12, 9, 197, 198, 211, 18, 221, 205, 153, 149, 170, 239,
+ 63, 12, 16, 5, 204, 3, 21, 193, 207, 16, 84, 12, 63, 69, 208, 51,
+ 28, 21, 201, 207, 22, 212, 14, 223, 68, 88, 51, 122, 149, 227, 47, 9,
+ 220, 6, 217, 194, 218, 209, 155, 28, 107, 73, 239, 118, 204, 38, 213, 218,
+ 223, 27, 24, 11, 74, 135, 119, 34, 166, 153, 186, 234, 243, 15, 5, 196,
+ 3, 19, 65, 205, 240, 85, 132, 63, 35, 80, 25, 252, 10, 193, 199, 16,
+ 82, 140, 61, 165, 209, 187, 28, 115, 73, 229, 246, 203, 6, 215, 66, 222,
+ 177, 152, 116, 106, 167, 111, 58, 172, 19, 61, 205, 209, 149, 156, 111, 41,
+ 236, 30, 205, 200, 85, 150, 191, 46, 240, 28, 68, 9, 243, 70, 197, 242,
+ 211, 5, 157, 195, 41, 145, 222, 236, 88, 77, 250, 181, 131, 55, 33, 214,
+ 152, 94, 234, 184, 79, 50, 180, 21, 183, 79, 54, 180, 22, 247, 78, 198,
+ 180, 82, 247, 125, 134, 161, 162, 248, 121, 130, 162, 225, 185, 136, 114, 230,
+ 165, 138, 251, 39, 3, 90, 129, 251, 32, 67, 88, 49, 250, 148, 67, 47,
+ 113, 220, 36, 89, 219, 122, 219, 99, 27, 105, 203, 110, 215, 108, 94, 173,
+ 248, 125, 130, 161, 161, 184, 120, 114, 162, 165, 185, 187, 50, 243, 85, 133,
+ 255, 35, 0, 25, 192, 10, 208, 7, 28, 2, 137, 193, 166, 208, 122, 220,
+ 35, 25, 217, 202, 218, 215, 27, 30, 139, 72, 103, 118, 170, 166, 255, 58,
+ 192, 19, 16, 13, 204, 5, 149, 195, 47, 17, 220, 12, 89, 197, 250, 211,
+ 3, 29, 193, 201, 144, 86, 236, 62, 205, 208, 85, 156, 63, 41, 208, 30,
+ 220, 8, 89, 198, 186, 210, 243, 29, 133, 201, 163, 22, 249, 206, 194, 212,
+ 81, 159, 124, 104, 33, 238, 152, 76, 106, 181, 239, 55, 12, 22, 133, 206,
+ 227, 20, 73, 207, 118, 212, 38, 223, 90, 216, 59, 26, 147, 75, 45, 247,
+ 93, 134, 185, 162, 242, 249, 133, 130, 227, 33, 137, 216, 102, 218, 170, 219,
+ 63, 27, 80, 11, 124, 7, 97, 194, 168, 81, 190, 188, 112, 113, 228, 36,
+ 75, 91, 119, 123, 102, 163, 106, 249, 239, 2, 204, 1, 149, 192, 111, 16,
+ 44, 12, 29, 197, 201, 147, 22, 237, 206, 205, 148, 85, 175, 127, 60, 32,
+ 17, 216, 12, 90, 133, 251, 35, 3, 89, 193, 250, 208, 67, 28, 49, 201,
+ 212, 86, 223, 126, 216, 32, 90, 152, 59, 42, 147, 95, 45, 248, 29, 130,
+ 137, 161, 166, 248, 122, 194, 163, 17, 185, 204, 114, 213, 229, 159, 11, 40,
+ 7, 94, 130, 184, 97, 178, 168, 117, 190, 167, 48, 122, 148, 35, 47, 89,
+ 220, 58, 217, 211, 26, 221, 203, 25, 151, 74, 238, 183, 12, 118, 133, 230,
+ 227, 10, 201, 199, 22, 210, 142, 221, 164, 89, 187, 122, 243, 99, 5, 233,
+ 195, 14, 209, 196, 92, 83, 121, 253, 226, 193, 137, 144, 102, 236, 42, 205,
+ 223, 21, 152, 15, 42, 132, 31, 35, 72, 25, 246, 138, 198, 231, 18, 202,
+ 141, 151, 37, 174, 155, 60, 107, 81, 239, 124, 76, 33, 245, 216, 71, 26,
+ 178, 139, 53, 167, 87, 58, 190, 147, 48, 109, 212, 45, 159, 93, 168, 57,
+ 190, 146, 240, 109, 132, 45, 163, 93, 185, 249, 178, 194, 245, 145, 135, 44,
+ 98, 157, 233, 169, 142, 254, 228, 64, 75, 112, 55, 100, 22, 171, 78, 255,
+ 116, 64, 39, 112, 26, 164, 11, 59, 71, 83, 114, 189, 229, 177, 139, 52,
+ 103, 87, 106, 190, 175, 48, 124, 20, 33, 207, 88, 84, 58, 191, 83, 48,
+ 61, 212, 17, 159, 76, 104, 53, 238, 151, 12, 110, 133, 236, 99, 13, 233,
+ 197, 142, 211, 36, 93, 219, 121, 155, 98, 235, 105, 143, 110, 228, 44, 75,
+ 93, 247, 121, 134, 162, 226, 249, 137, 130, 230, 225, 138, 200, 103, 22, 170,
+ 142, 255, 36, 64, 27, 112, 11, 100, 7, 107, 66, 175, 113, 188, 36, 113,
+ 219, 100, 91, 107, 123, 111, 99, 108, 41, 237, 222, 205, 152, 85, 170, 191,
+ 63, 48, 16, 20, 12, 15, 69, 196, 51, 19, 85, 205, 255, 21, 128, 15,
+ 32, 4, 24, 3, 74, 129, 247, 32, 70, 152, 50, 234, 149, 143, 47, 36,
+ 28, 27, 73, 203, 118, 215, 102, 222, 170, 216, 127, 26, 160, 11, 56, 7,
+ 82, 130, 189, 161, 177, 184, 116, 114, 167, 101, 186, 171, 51, 63, 85, 208,
+ 63, 28, 16, 9, 204, 6, 213, 194, 223, 17, 152, 12, 106, 133, 239, 35,
+ 12, 25, 197, 202, 211, 23, 29, 206, 137, 148, 102, 239, 106, 204, 47, 21,
+ 220, 15, 25, 196, 10, 211, 71, 29, 242, 137, 133, 166, 227, 58, 201, 211,
+ 22, 221, 206, 217, 148, 90, 239, 123, 12, 35, 69, 217, 243, 26, 197, 203,
+ 19, 23, 77, 206, 181, 148, 119, 47, 102, 156, 42, 233, 223, 14, 216, 4,
+ 90, 131, 123, 33, 227, 88, 73, 250, 182, 195, 54, 209, 214, 220, 94, 217,
+ 248, 90, 194, 187, 17, 179, 76, 117, 245, 231, 7, 10, 130, 135, 33, 162,
+ 152, 121, 170, 162, 255, 57, 128, 18, 224, 13, 136, 5, 166, 131, 58, 225,
+ 211, 8, 93, 198, 185, 146, 242, 237, 133, 141, 163, 37, 185, 219, 50, 219,
+ 85, 155, 127, 43, 96, 31, 104, 8, 46, 134, 156, 98, 233, 233, 142, 206,
+ 228, 84, 75, 127, 119, 96, 38, 168, 26, 254, 139, 0, 103, 64, 42, 176,
+ 31, 52, 8, 23, 70, 142, 178, 228, 117, 139, 103, 39, 106, 154, 175, 43,
+ 60, 31, 81, 200, 60, 86, 145, 254, 236, 64, 77, 240, 53, 132, 23, 35,
+ 78, 153, 244, 106, 199, 111, 18, 172, 13, 189, 197, 177, 147, 52, 109, 215,
+ 109, 158, 173, 168, 125, 190, 161, 176, 120, 116, 34, 167, 89, 186, 186, 243,
+ 51, 5, 213, 195, 31, 17, 200, 12, 86, 133, 254, 227, 0, 73, 192, 54,
+ 208, 22, 220, 14, 217, 196, 90, 211, 123, 29, 227, 73, 137, 246, 230, 198,
+ 202, 210, 215, 29, 158, 137, 168, 102, 254, 170, 192, 127, 16, 32, 12, 24,
+ 5, 202, 131, 23, 33, 206, 152, 84, 106, 191, 111, 48, 44, 20, 29, 207,
+ 73, 148, 54, 239, 86, 204, 62, 213, 208, 95, 28, 56, 9, 210, 134, 221,
+ 162, 217, 185, 154, 242, 235, 5, 143, 67, 36, 49, 219, 84, 91, 127, 123,
+ 96, 35, 104, 25, 238, 138, 204, 103, 21, 234, 143, 15, 36, 4, 27, 67,
+ 75, 113, 247, 100, 70, 171, 114, 255, 101, 128, 43, 32, 31, 88, 8, 58,
+ 134, 147, 34, 237, 217, 141, 154, 229, 171, 11, 63, 71, 80, 50, 188, 21,
+ 177, 207, 52, 84, 23, 127, 78, 160, 52, 120, 23, 98, 142, 169, 164, 126,
+ 251, 96, 67, 104, 49, 238, 148, 76, 111, 117, 236, 39, 13, 218, 133, 155,
+ 35, 43, 89, 223, 122, 216, 35, 26, 153, 203, 42, 215, 95, 30, 184, 8,
+ 114, 134, 165, 162, 251, 57, 131, 82, 225, 253, 136, 65, 166, 176, 122, 244,
+ 35, 7, 89, 194, 186, 209, 179, 28, 117, 201, 231, 22, 202, 142, 215, 36,
+ 94, 155, 120, 107, 98, 175, 105, 188, 46, 241, 220, 68, 89, 243, 122, 197,
+ 227, 19, 9, 205, 198, 213, 146, 223, 45, 152, 29, 170, 137, 191, 38, 240,
+ 26, 196, 11, 19, 71, 77, 242, 181, 133, 183, 35, 54, 153, 214, 234, 222,
+ 207, 24, 84, 10, 191, 71, 48, 50, 148, 21, 175, 79, 60, 52, 17, 215,
+ 76, 94, 181, 248, 119, 2, 166, 129, 186, 224, 115, 8, 37, 198, 155, 18,
+ 235, 77, 143, 117, 164, 39, 59, 90, 147, 123, 45, 227, 93, 137, 249, 166,
+ 194, 250, 209, 131, 28, 97, 201, 232, 86, 206, 190, 212, 112, 95, 100, 56,
+ 43, 82, 159, 125, 168, 33, 190, 152, 112, 106, 164, 47, 59, 92, 19, 121,
+ 205, 226, 213, 137, 159, 38, 232, 26, 206, 139, 20, 103, 79, 106, 180, 47,
+ 55, 92, 22, 185, 206, 242, 212, 69, 159, 115, 40, 37, 222, 155, 24, 107,
+ 74, 175, 119, 60, 38, 145, 218, 236, 91, 13, 251, 69, 131, 115, 33, 229,
+ 216, 75, 26, 183, 75, 54, 183, 86, 246, 190, 198, 240, 82, 196, 61, 147,
+ 81, 173, 252, 125, 129, 225, 160, 72, 120, 54, 162, 150, 249, 174, 194, 252,
+ 81, 129, 252, 96, 65, 232, 48, 78, 148, 52, 111, 87, 108, 62, 173, 208,
+ 125, 156, 33, 169, 216, 126, 218, 160, 91, 56, 59, 82, 147, 125, 173, 225,
+ 189, 136, 113, 166, 164, 122, 251, 99, 3, 105, 193, 238, 208, 76, 92, 53,
+ 249, 215, 2, 222, 129, 152, 96, 106, 168, 47, 62, 156, 16, 105, 204, 46,
+ 213, 220, 95, 25, 248, 10, 194, 135, 17, 162, 140, 121, 165, 226, 251, 9,
+ 131, 70, 225, 242, 200, 69, 150, 179, 46, 245, 220, 71, 25, 242, 138, 197,
+ 167, 19, 58, 141, 211, 37, 157, 219, 41, 155, 94, 235, 120, 79, 98, 180,
+ 41, 183, 94, 246, 184, 70, 242, 178, 197, 181, 147, 55, 45, 214, 157, 158,
+ 233, 168, 78, 254, 180, 64, 119, 112, 38, 164, 26, 251, 75, 3, 119, 65,
+ 230, 176, 74, 244, 55, 7, 86, 130, 190, 225, 176, 72, 116, 54, 167, 86,
+ 250, 190, 195, 48, 81, 212, 60, 95, 81, 248, 60, 66, 145, 241, 172, 68,
+ 125, 243, 97, 133, 232, 99, 14, 169, 196, 126, 211, 96, 93, 232, 57, 142,
+ 146, 228, 109, 139, 109, 167, 109, 186, 173, 179, 61, 181, 209, 183, 28, 118,
+ 137, 230, 230, 202, 202, 215, 23, 30, 142, 136, 100, 102, 171, 106, 255, 111,
+ 0, 44, 0, 29, 192, 9, 144, 6, 236, 2, 205, 193, 149, 144, 111, 44,
+ 44, 29, 221, 201, 153, 150, 234, 238, 207, 12, 84, 5, 255, 67, 0, 49,
+ 192, 20, 80, 15, 124, 4, 33, 195, 88, 81, 250, 188, 67, 49, 241, 212,
+ 68, 95, 115, 120, 37, 226, 155, 9, 171, 70, 255, 114, 192, 37, 144, 27,
+ 44, 11, 93, 199, 121, 146, 162, 237, 185, 141, 178, 229, 181, 139, 55, 39,
+ 86, 154, 190, 235, 48, 79, 84, 52, 63, 87, 80, 62, 188, 16, 113, 204,
+ 36, 85, 219, 127, 27, 96, 11, 104, 7, 110, 130, 172, 97, 189, 232, 113,
+ 142, 164, 100, 123, 107, 99, 111, 105, 236, 46, 205, 220, 85, 153, 255, 42,
+ 192, 31, 16, 8, 12, 6, 133, 194, 227, 17, 137, 204, 102, 213, 234, 223,
+ 15, 24, 4, 10, 131, 71, 33, 242, 152, 69, 170, 179, 63, 53, 208, 23,
+ 28, 14, 137, 196, 102, 211, 106, 221, 239, 25, 140, 10, 229, 199, 11, 18,
+ 135, 77, 162, 181, 185, 183, 50, 246, 149, 134, 239, 34, 204, 25, 149, 202,
+ 239, 23, 12, 14, 133, 196, 99, 19, 105, 205, 238, 213, 140, 95, 37, 248,
+ 27, 2, 139, 65, 167, 112, 122, 164, 35, 59, 89, 211, 122, 221, 227, 25,
+ 137, 202, 230, 215, 10, 222, 135, 24, 98, 138, 169, 167, 62, 250, 144, 67,
+ 44, 49, 221, 212, 89, 159, 122, 232, 35, 14, 153, 196, 106, 211, 111, 29,
+ 236, 9, 141, 198, 229, 146, 203, 45, 151, 93, 174, 185, 188, 114, 241, 229,
+ 132, 75, 35, 119, 89, 230, 186, 202, 243, 23, 5, 206, 131, 20, 97, 207,
+ 104, 84, 46, 191, 92, 112, 57, 228, 18, 203, 77, 151, 117, 174, 167, 60,
+ 122, 145, 227, 44, 73, 221, 246, 217, 134, 218, 226, 219, 9, 155, 70, 235,
+ 114, 207, 101, 148, 43, 47, 95, 92, 56, 57, 210, 146, 221, 173, 153, 189,
+ 170, 241, 191, 4, 112, 3, 100, 1, 235, 64, 79, 112, 52, 36, 23, 91,
+ 78, 187, 116, 115, 103, 101, 234, 171, 15, 63, 68, 16, 51, 76, 21, 245,
+ 207, 7, 20, 2, 143, 65, 164, 48, 123, 84, 35, 127, 89, 224, 58, 200,
+ 19, 22, 141, 206, 229, 148, 75, 47, 119, 92, 38, 185, 218, 242, 219, 5,
+ 155, 67, 43, 113, 223, 100, 88, 43, 122, 159, 99, 40, 41, 222, 158, 216,
+ 104, 90, 174, 187, 60, 115, 81, 229, 252, 75, 1, 247, 64, 70, 176, 50,
+ 244, 21, 135, 79, 34, 180, 25, 183, 74, 246, 183, 6, 246, 130, 198, 225,
+ 146, 200, 109, 150, 173, 174, 253, 188, 65, 177, 240, 116, 68, 39, 115, 90,
+ 165, 251, 59, 3, 83, 65, 253, 240, 65, 132, 48, 99, 84, 41, 255, 94,
+ 192, 56, 80, 18, 188, 13, 177, 197, 180, 83, 55, 125, 214, 161, 158, 248,
+ 104, 66, 174, 177, 188, 116, 113, 231, 100, 74, 171, 119, 63, 102, 144, 42,
+ 236, 31, 13, 200, 5, 150, 131, 46, 225, 220, 72, 89, 246, 186, 198, 243,
+ 18, 197, 205, 147, 21, 173, 207, 61, 148, 17, 175, 76, 124, 53, 225, 215,
+ 8, 94, 134, 184, 98, 242, 169, 133, 190, 227, 48, 73, 212, 54, 223, 86,
+ 216, 62, 218, 144, 91, 44, 59, 93, 211, 121, 157, 226, 233, 137, 142, 230,
+ 228, 74, 203, 119, 23, 102, 142, 170, 228, 127, 11, 96, 7, 104, 2, 174,
+ 129, 188, 96, 113, 232, 36, 78, 155, 116, 107, 103, 111, 106, 172, 47, 61,
+ 220, 17, 153, 204, 106, 213, 239, 31, 12, 8, 5, 198, 131, 18, 225, 205,
+ 136, 85, 166, 191, 58, 240, 19, 4, 13, 195, 69, 145, 243, 44, 69, 221,
+ 243, 25, 133, 202, 227, 23, 9, 206, 134, 212, 98, 223, 105, 152, 46, 234,
+ 156, 79, 41, 244, 30, 199, 72, 82, 182, 189, 182, 241, 182, 196, 118, 211,
+ 102, 221, 234, 217, 143, 26, 228, 11, 11, 71, 71, 114, 178, 165, 181, 187,
+ 55, 51, 86, 149, 254, 239, 0, 76, 0, 53, 192, 23, 16, 14, 140, 4,
+ 101, 195, 107, 17, 239, 76, 76, 53, 245, 215, 7, 30, 130, 136, 97, 166,
+ 168, 122, 254, 163, 0, 121, 192, 34, 208, 25, 156, 10, 233, 199, 14, 210,
+ 132, 93, 163, 121, 185, 226, 242, 201, 133, 150, 227, 46, 201, 220, 86, 217,
+ 254, 218, 192, 91, 16, 59, 76, 19, 117, 205, 231, 21, 138, 143, 39, 36,
+ 26, 155, 75, 43, 119, 95, 102, 184, 42, 242, 159, 5, 168, 3, 62, 129,
+ 208, 96, 92, 40, 57, 222, 146, 216, 109, 154, 173, 171, 61, 191, 81, 176,
+ 60, 116, 17, 231, 76, 74, 181, 247, 55, 6, 150, 130, 238, 225, 140, 72,
+ 101, 246, 171, 6, 255, 66, 192, 49, 144, 20, 108, 15, 109, 196, 45, 147,
+ 93, 173, 249, 189, 130, 241, 161, 132, 120, 99, 98, 169, 233, 190, 206, 240,
+ 84, 68, 63, 115, 80, 37, 252, 27, 1, 203, 64, 87, 112, 62, 164, 16,
+ 123, 76, 35, 117, 217, 231, 26, 202, 139, 23, 39, 78, 154, 180, 107, 55,
+ 111, 86, 172, 62, 253, 208, 65, 156, 48, 105, 212, 46, 223, 92, 88, 57,
+ 250, 146, 195, 45, 145, 221, 172, 89, 189, 250, 241, 131, 4, 97, 195, 104,
+ 81, 238, 188, 76, 113, 245, 228, 71, 11, 114, 135, 101, 162, 171, 57, 191,
+ 82, 240, 61, 132, 17, 163, 76, 121, 245, 226, 199, 9, 146, 134, 237, 162,
+ 205, 185, 149, 178, 239, 53, 140, 23, 37, 206, 155, 20, 107, 79, 111, 116,
+ 44, 39, 93, 218, 185, 155, 50, 235, 85, 143, 127, 36, 32, 27, 88, 11,
+ 122, 135, 99, 34, 169, 217, 190, 218, 240, 91, 4, 59, 67, 83, 113, 253,
+ 228, 65, 139, 112, 103, 100, 42, 171, 95, 63, 120, 16, 34, 140, 25, 165,
+ 202, 251, 23, 3, 78, 129, 244, 96, 71, 104, 50, 174, 149, 188, 111, 49,
+ 236, 20, 77, 207, 117, 148, 39, 47, 90, 156, 59, 41, 211, 94, 221, 248,
+ 89, 130, 186, 225, 179, 8, 117, 198, 167, 18, 250, 141, 131, 37, 161, 219,
+ 56, 91, 82, 187, 125, 179, 97, 181, 232, 119, 14, 166, 132, 122, 227, 99,
+ 9, 233, 198, 206, 210, 212, 93, 159, 121, 168, 34, 254, 153, 128, 106, 224,
+ 47, 8, 28, 6, 137, 194, 230, 209, 138, 220, 103, 25, 234, 138, 207, 39,
+ 20, 26, 143, 75, 36, 55, 91, 86, 187, 126, 243, 96, 69, 232, 51, 14,
+ 149, 196, 111, 19, 108, 13, 237, 197, 141, 147, 37, 173, 219, 61, 155, 81,
+ 171, 124, 127, 97, 224, 40, 72, 30, 182, 136, 118, 230, 166, 202, 250, 215,
+ 3, 30, 129, 200, 96, 86, 168, 62, 254, 144, 64, 108, 48, 45, 212, 29,
+ 159, 73, 168, 54, 254, 150, 192, 110, 208, 44, 92, 29, 249, 201, 130, 214,
+ 225, 158, 200, 104, 86, 174, 190, 252, 112, 65, 228, 48, 75, 84, 55, 127,
+ 86, 160, 62, 248, 16, 66, 140, 49, 165, 212, 123, 31, 99, 72, 41, 246,
+ 158, 198, 232, 82, 206, 189, 148, 113, 175, 100, 124, 43, 97, 223, 104, 88,
+ 46, 186, 156, 115, 41, 229, 222, 203, 24, 87, 74, 190, 183, 48, 118, 148,
+ 38, 239, 90, 204, 59, 21, 211, 79, 29, 244, 9, 135, 70, 226, 178, 201,
+ 181, 150, 247, 46, 198, 156, 82, 233, 253, 142, 193, 164, 80, 123, 124, 35,
+ 97, 217, 232, 90, 206, 187, 20, 115, 79, 101, 244, 43, 7, 95, 66, 184,
+ 49, 178, 148, 117, 175, 103, 60, 42, 145, 223, 44, 88, 29, 250, 137, 131,
+ 38, 225, 218, 200, 91, 22, 187, 78, 243, 116, 69, 231, 115, 10, 165, 199,
+ 59, 18, 147, 77, 173, 245, 189, 135, 49, 162, 148, 121, 175, 98, 252, 41,
+ 129, 222, 224, 88, 72, 58, 182, 147, 54, 237, 214, 205, 158, 213, 168, 95,
+ 62, 184, 16, 114, 140, 37, 165, 219, 59, 27, 83, 75, 125, 247, 97, 134,
+ 168, 98, 254, 169, 128, 126, 224, 32, 72, 24, 54, 138, 150, 231, 46, 202,
+ 156, 87, 41, 254, 158, 192, 104, 80, 46, 188, 28, 113, 201, 228, 86, 203,
+ 126, 215, 96, 94, 168, 56, 126, 146, 160, 109, 184, 45, 178, 157, 181, 169,
+ 183, 62, 246, 144, 70, 236, 50, 205, 213, 149, 159, 47, 40, 28, 30, 137,
+ 200, 102, 214, 170, 222, 255, 24, 64, 10, 176, 7, 52, 2, 151, 65, 174,
+ 176, 124, 116, 33, 231, 88, 74, 186, 183, 51, 54, 149, 214, 239, 30, 204,
+ 8, 85, 198, 191, 18, 240, 13, 132, 5, 163, 67, 57, 241, 210, 196, 93,
+ 147, 121, 173, 226, 253, 137, 129, 166, 224, 122, 200, 35, 22, 153, 206, 234,
+ 212, 79, 31, 116, 8, 39, 70, 154, 178, 235, 53, 143, 87, 36, 62, 155,
+ 80, 107, 124, 47, 97, 220, 40, 89, 222, 186, 216, 115, 26, 165, 203, 59,
+ 23, 83, 78, 189, 244, 113, 135, 100, 98, 171, 105, 191, 110, 240, 44, 68,
+ 29, 243, 73, 133, 246, 227, 6, 201, 194, 214, 209, 158, 220, 104, 89, 238,
+ 186, 204, 115, 21, 229, 207, 11, 20, 7, 79, 66, 180, 49, 183, 84, 118,
+ 191, 102, 240, 42, 196, 31, 19, 72, 13, 246, 133, 134, 227, 34, 201, 217,
+ 150, 218, 238, 219, 12, 91, 69, 251, 115, 3, 101, 193, 235, 16, 79, 76,
+ 52, 53, 215, 87, 30, 190, 136, 112, 102, 164, 42, 251, 95, 3, 120, 1,
+ 226, 128, 73, 160, 54, 248, 22, 194, 142, 209, 164, 92, 123, 121, 227, 98,
+ 201, 233, 150, 206, 238, 212, 76, 95, 117, 248, 39, 2, 154, 129, 171, 32,
+ 127, 88, 32, 58, 152, 19, 42, 141, 223, 37, 152, 27, 42, 139, 95, 39,
+ 120, 26, 162, 139, 57, 167, 82, 250, 189, 131, 49, 161, 212, 120, 95, 98,
+ 184, 41, 178, 158, 245, 168, 71, 62, 178, 144, 117, 172, 39, 61, 218, 145,
+ 155, 44, 107, 93, 239, 121, 140, 34, 229, 217, 139, 26, 231, 75, 10, 183,
+ 71, 54, 178, 150, 245, 174, 199, 60, 82, 145, 253, 172, 65, 189, 240, 113,
+ 132, 36, 99, 91, 105, 251, 110, 195, 108, 81, 237, 252, 77, 129, 245, 160,
+ 71, 56, 50, 146, 149, 173, 175, 61, 188, 17, 177, 204, 116, 85, 231, 127,
+ 10, 160, 7, 56, 2, 146, 129, 173, 160, 125, 184, 33, 178, 152, 117, 170,
+ 167, 63, 58, 144, 19, 44, 13, 221, 197, 153, 147, 42, 237, 223, 13, 152,
+ 5, 170, 131, 63, 33, 208, 24, 92, 10, 185, 199, 50, 210, 149, 157, 175,
+ 41, 188, 30, 241, 200, 68, 86, 179, 126, 245, 224, 71, 8, 50, 134, 149,
+ 162, 239, 57, 140, 18, 229, 205, 139, 21, 167, 79, 58, 180, 19, 55, 77,
+ 214, 181, 158, 247, 40, 70, 158, 178, 232, 117, 142, 167, 36, 122, 155, 99,
+ 43, 105, 223, 110, 216, 44, 90, 157, 251, 41, 131, 94, 225, 248, 72, 66,
+ 182, 177, 182, 244, 118, 199, 102, 210, 170, 221, 191, 25, 176, 10, 244, 7,
+ 7, 66, 130, 177, 161, 180, 120, 119, 98, 166, 169, 186, 254, 243, 0, 69,
+ 192, 51, 16, 21, 204, 15, 21, 196, 15, 19, 68, 13, 243, 69, 133, 243,
+ 35, 5, 217, 195, 26, 209, 203, 28, 87, 73, 254, 182, 192, 118, 208, 38,
+ 220, 26, 217, 203, 26, 215, 75, 30, 183, 72, 118, 182, 166, 246, 250, 198,
+ 195, 18, 209, 205, 156, 85, 169, 255, 62, 192, 16, 80, 12, 60, 5, 209,
+ 195, 28, 81, 201, 252, 86, 193, 254, 208, 64, 92, 48, 57, 212, 18, 223,
+ 77, 152, 53, 170, 151, 63, 46, 144, 28, 108, 9, 237, 198, 205, 146, 213,
+ 173, 159, 61, 168, 17, 190, 140, 112, 101, 228, 43, 11, 95, 71, 120, 50,
+ 162, 149, 185, 175, 50, 252, 21, 129, 207, 32, 84, 24, 63, 74, 144, 55,
+ 44, 22, 157, 206, 233, 148, 78, 239, 116, 76, 39, 117, 218, 167, 27, 58,
+ 139, 83, 39, 125, 218, 161, 155, 56, 107, 82, 175, 125, 188, 33, 177, 216,
+ 116, 90, 167, 123, 58, 163, 83, 57, 253, 210, 193, 157, 144, 105, 172, 46,
+ 253, 220, 65, 153, 240, 106, 196, 47, 19, 92, 13, 249, 197, 130, 211, 33,
+ 157, 216, 105, 154, 174, 235, 60, 79, 81, 244, 60, 71, 81, 242, 188, 69,
+ 177, 243, 52, 69, 215, 115, 30, 165, 200, 123, 22, 163, 78, 249, 244, 66,
+ 199, 113, 146, 164, 109, 187, 109, 179, 109, 181, 237, 183, 13, 182, 133, 182,
+ 227, 54, 201, 214, 214, 222, 222, 216, 88, 90, 186, 187, 51, 51, 255, 63 )
+
+random_mask_vec8 = numpy.array(random_mask_tuple, numpy.uint8)
+
diff --git a/gr-digital/python/ofdm_receiver.py b/gr-digital/python/ofdm_receiver.py
new file mode 100644
index 000000000..9d4d6e559
--- /dev/null
+++ b/gr-digital/python/ofdm_receiver.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python
+#
+# Copyright 2006, 2007, 2008 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import math
+from numpy import fft
+from gnuradio import gr
+
+import digital_swig
+from ofdm_sync_pn import ofdm_sync_pn
+from ofdm_sync_fixed import ofdm_sync_fixed
+from ofdm_sync_pnac import ofdm_sync_pnac
+from ofdm_sync_ml import ofdm_sync_ml
+
+class ofdm_receiver(gr.hier_block2):
+ """
+ Performs receiver synchronization on OFDM symbols.
+
+ The receiver performs channel filtering as well as symbol, frequency, and phase synchronization.
+ The synchronization routines are available in three flavors: preamble correlator (Schmidl and Cox),
+ modifid preamble correlator with autocorrelation (not yet working), and cyclic prefix correlator
+ (Van de Beeks).
+ """
+
+ def __init__(self, fft_length, cp_length, occupied_tones, snr, ks, logging=False):
+ """
+ Hierarchical block for receiving OFDM symbols.
+
+ The input is the complex modulated signal at baseband.
+ Synchronized packets are sent back to the demodulator.
+
+ @param fft_length: total number of subcarriers
+ @type fft_length: int
+ @param cp_length: length of cyclic prefix as specified in subcarriers (<= fft_length)
+ @type cp_length: int
+ @param occupied_tones: number of subcarriers used for data
+ @type occupied_tones: int
+ @param snr: estimated signal to noise ratio used to guide cyclic prefix synchronizer
+ @type snr: float
+ @param ks: known symbols used as preambles to each packet
+ @type ks: list of lists
+ @param logging: turn file logging on or off
+ @type logging: bool
+ """
+
+ gr.hier_block2.__init__(self, "ofdm_receiver",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature2(2, 2, gr.sizeof_gr_complex*occupied_tones, gr.sizeof_char)) # Output signature
+
+ bw = (float(occupied_tones) / float(fft_length)) / 2.0
+ tb = bw*0.08
+ chan_coeffs = gr.firdes.low_pass (1.0, # gain
+ 1.0, # sampling rate
+ bw+tb, # midpoint of trans. band
+ tb, # width of trans. band
+ gr.firdes.WIN_HAMMING) # filter type
+ self.chan_filt = gr.fft_filter_ccc(1, chan_coeffs)
+
+ win = [1 for i in range(fft_length)]
+
+ zeros_on_left = int(math.ceil((fft_length - occupied_tones)/2.0))
+ ks0 = fft_length*[0,]
+ ks0[zeros_on_left : zeros_on_left + occupied_tones] = ks[0]
+
+ ks0 = fft.ifftshift(ks0)
+ ks0time = fft.ifft(ks0)
+ # ADD SCALING FACTOR
+ ks0time = ks0time.tolist()
+
+ SYNC = "pn"
+ if SYNC == "ml":
+ nco_sensitivity = -1.0/fft_length # correct for fine frequency
+ self.ofdm_sync = ofdm_sync_ml(fft_length,
+ cp_length,
+ snr,
+ ks0time,
+ logging)
+ elif SYNC == "pn":
+ nco_sensitivity = -2.0/fft_length # correct for fine frequency
+ self.ofdm_sync = ofdm_sync_pn(fft_length,
+ cp_length,
+ logging)
+ elif SYNC == "pnac":
+ nco_sensitivity = -2.0/fft_length # correct for fine frequency
+ self.ofdm_sync = ofdm_sync_pnac(fft_length,
+ cp_length,
+ ks0time,
+ logging)
+ # for testing only; do not user over the air
+ # remove filter and filter delay for this
+ elif SYNC == "fixed":
+ self.chan_filt = gr.multiply_const_cc(1.0)
+ nsymbols = 18 # enter the number of symbols per packet
+ freq_offset = 0.0 # if you use a frequency offset, enter it here
+ nco_sensitivity = -2.0/fft_length # correct for fine frequency
+ self.ofdm_sync = ofdm_sync_fixed(fft_length,
+ cp_length,
+ nsymbols,
+ freq_offset,
+ logging)
+
+ # Set up blocks
+
+ self.nco = gr.frequency_modulator_fc(nco_sensitivity) # generate a signal proportional to frequency error of sync block
+ self.sigmix = gr.multiply_cc()
+ self.sampler = digital_swig.ofdm_sampler(fft_length, fft_length+cp_length)
+ self.fft_demod = gr.fft_vcc(fft_length, True, win, True)
+ self.ofdm_frame_acq = digital_swig.ofdm_frame_acquisition(occupied_tones,
+ fft_length,
+ cp_length, ks[0])
+
+ self.connect(self, self.chan_filt) # filter the input channel
+ self.connect(self.chan_filt, self.ofdm_sync) # into the synchronization alg.
+ self.connect((self.ofdm_sync,0), self.nco, (self.sigmix,1)) # use sync freq. offset output to derotate input signal
+ self.connect(self.chan_filt, (self.sigmix,0)) # signal to be derotated
+ self.connect(self.sigmix, (self.sampler,0)) # sample off timing signal detected in sync alg
+ self.connect((self.ofdm_sync,1), (self.sampler,1)) # timing signal to sample at
+
+ self.connect((self.sampler,0), self.fft_demod) # send derotated sampled signal to FFT
+ self.connect(self.fft_demod, (self.ofdm_frame_acq,0)) # find frame start and equalize signal
+ self.connect((self.sampler,1), (self.ofdm_frame_acq,1)) # send timing signal to signal frame start
+ self.connect((self.ofdm_frame_acq,0), (self,0)) # finished with fine/coarse freq correction,
+ self.connect((self.ofdm_frame_acq,1), (self,1)) # frame and symbol timing, and equalization
+
+ if logging:
+ self.connect(self.chan_filt, gr.file_sink(gr.sizeof_gr_complex, "ofdm_receiver-chan_filt_c.dat"))
+ self.connect(self.fft_demod, gr.file_sink(gr.sizeof_gr_complex*fft_length, "ofdm_receiver-fft_out_c.dat"))
+ self.connect(self.ofdm_frame_acq,
+ gr.file_sink(gr.sizeof_gr_complex*occupied_tones, "ofdm_receiver-frame_acq_c.dat"))
+ self.connect((self.ofdm_frame_acq,1), gr.file_sink(1, "ofdm_receiver-found_corr_b.dat"))
+ self.connect(self.sampler, gr.file_sink(gr.sizeof_gr_complex*fft_length, "ofdm_receiver-sampler_c.dat"))
+ self.connect(self.sigmix, gr.file_sink(gr.sizeof_gr_complex, "ofdm_receiver-sigmix_c.dat"))
+ self.connect(self.nco, gr.file_sink(gr.sizeof_gr_complex, "ofdm_receiver-nco_c.dat"))
diff --git a/gr-digital/python/ofdm_sync_fixed.py b/gr-digital/python/ofdm_sync_fixed.py
new file mode 100644
index 000000000..9bac789bf
--- /dev/null
+++ b/gr-digital/python/ofdm_sync_fixed.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import math
+from gnuradio import gr
+
+class ofdm_sync_fixed(gr.hier_block2):
+ def __init__(self, fft_length, cp_length, nsymbols, freq_offset, logging=False):
+
+ gr.hier_block2.__init__(self, "ofdm_sync_fixed",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature
+
+ # Use a fixed trigger point instead of sync block
+ symbol_length = fft_length + cp_length
+ pkt_length = nsymbols*symbol_length
+ data = (pkt_length)*[0,]
+ data[(symbol_length)-1] = 1
+ self.peak_trigger = gr.vector_source_b(data, True)
+
+ # Use a pre-defined frequency offset
+ foffset = (pkt_length)*[math.pi*freq_offset,]
+ self.frequency_offset = gr.vector_source_f(foffset, True)
+
+ self.connect(self, gr.null_sink(gr.sizeof_gr_complex))
+ self.connect(self.frequency_offset, (self,0))
+ self.connect(self.peak_trigger, (self,1))
+
+ if logging:
+ self.connect(self.peak_trigger, gr.file_sink(gr.sizeof_char, "ofdm_sync_fixed-peaks_b.dat"))
+
diff --git a/gr-digital/python/ofdm_sync_ml.py b/gr-digital/python/ofdm_sync_ml.py
new file mode 100644
index 000000000..7c75d7f1d
--- /dev/null
+++ b/gr-digital/python/ofdm_sync_ml.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+#
+# Copyright 2007,2008 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import math
+from gnuradio import gr
+
+class ofdm_sync_ml(gr.hier_block2):
+ def __init__(self, fft_length, cp_length, snr, kstime, logging):
+ ''' Maximum Likelihood OFDM synchronizer:
+ J. van de Beek, M. Sandell, and P. O. Borjesson, "ML Estimation
+ of Time and Frequency Offset in OFDM Systems," IEEE Trans.
+ Signal Processing, vol. 45, no. 7, pp. 1800-1805, 1997.
+ '''
+
+ gr.hier_block2.__init__(self, "ofdm_sync_ml",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature
+
+ self.input = gr.add_const_cc(0)
+
+ SNR = 10.0**(snr/10.0)
+ rho = SNR / (SNR + 1.0)
+ symbol_length = fft_length + cp_length
+
+ # ML Sync
+
+ # Energy Detection from ML Sync
+
+ self.connect(self, self.input)
+
+ # Create a delay line
+ self.delay = gr.delay(gr.sizeof_gr_complex, fft_length)
+ self.connect(self.input, self.delay)
+
+ # magnitude squared blocks
+ self.magsqrd1 = gr.complex_to_mag_squared()
+ self.magsqrd2 = gr.complex_to_mag_squared()
+ self.adder = gr.add_ff()
+
+ moving_sum_taps = [rho/2 for i in range(cp_length)]
+ self.moving_sum_filter = gr.fir_filter_fff(1,moving_sum_taps)
+
+ self.connect(self.input,self.magsqrd1)
+ self.connect(self.delay,self.magsqrd2)
+ self.connect(self.magsqrd1,(self.adder,0))
+ self.connect(self.magsqrd2,(self.adder,1))
+ self.connect(self.adder,self.moving_sum_filter)
+
+
+ # Correlation from ML Sync
+ self.conjg = gr.conjugate_cc();
+ self.mixer = gr.multiply_cc();
+
+ movingsum2_taps = [1.0 for i in range(cp_length)]
+ self.movingsum2 = gr.fir_filter_ccf(1,movingsum2_taps)
+
+ # Correlator data handler
+ self.c2mag = gr.complex_to_mag()
+ self.angle = gr.complex_to_arg()
+ self.connect(self.input,(self.mixer,1))
+ self.connect(self.delay,self.conjg,(self.mixer,0))
+ self.connect(self.mixer,self.movingsum2,self.c2mag)
+ self.connect(self.movingsum2,self.angle)
+
+ # ML Sync output arg, need to find maximum point of this
+ self.diff = gr.sub_ff()
+ self.connect(self.c2mag,(self.diff,0))
+ self.connect(self.moving_sum_filter,(self.diff,1))
+
+ #ML measurements input to sampler block and detect
+ self.f2c = gr.float_to_complex()
+ self.pk_detect = gr.peak_detector_fb(0.2, 0.25, 30, 0.0005)
+ self.sample_and_hold = gr.sample_and_hold_ff()
+
+ # use the sync loop values to set the sampler and the NCO
+ # self.diff = theta
+ # self.angle = epsilon
+
+ self.connect(self.diff, self.pk_detect)
+
+ # The DPLL corrects for timing differences between CP correlations
+ use_dpll = 0
+ if use_dpll:
+ self.dpll = gr.dpll_bb(float(symbol_length),0.01)
+ self.connect(self.pk_detect, self.dpll)
+ self.connect(self.dpll, (self.sample_and_hold,1))
+ else:
+ self.connect(self.pk_detect, (self.sample_and_hold,1))
+
+ self.connect(self.angle, (self.sample_and_hold,0))
+
+ ################################
+ # correlate against known symbol
+ # This gives us the same timing signal as the PN sync block only on the preamble
+ # we don't use the signal generated from the CP correlation because we don't want
+ # to readjust the timing in the middle of the packet or we ruin the equalizer settings.
+ kstime = [k.conjugate() for k in kstime]
+ kstime.reverse()
+ self.kscorr = gr.fir_filter_ccc(1, kstime)
+ self.corrmag = gr.complex_to_mag_squared()
+ self.div = gr.divide_ff()
+
+ # The output signature of the correlation has a few spikes because the rest of the
+ # system uses the repeated preamble symbol. It needs to work that generically if
+ # anyone wants to use this against a WiMAX-like signal since it, too, repeats.
+ # The output theta of the correlator above is multiplied with this correlation to
+ # identify the proper peak and remove other products in this cross-correlation
+ self.threshold_factor = 0.1
+ self.slice = gr.threshold_ff(self.threshold_factor, self.threshold_factor, 0)
+ self.f2b = gr.float_to_char()
+ self.b2f = gr.char_to_float()
+ self.mul = gr.multiply_ff()
+
+ # Normalize the power of the corr output by the energy. This is not really needed
+ # and could be removed for performance, but it makes for a cleaner signal.
+ # if this is removed, the threshold value needs adjustment.
+ self.connect(self.input, self.kscorr, self.corrmag, (self.div,0))
+ self.connect(self.moving_sum_filter, (self.div,1))
+
+ self.connect(self.div, (self.mul,0))
+ self.connect(self.pk_detect, self.b2f, (self.mul,1))
+ self.connect(self.mul, self.slice)
+
+ # Set output signals
+ # Output 0: fine frequency correction value
+ # Output 1: timing signal
+ self.connect(self.sample_and_hold, (self,0))
+ self.connect(self.slice, self.f2b, (self,1))
+
+
+ if logging:
+ self.connect(self.moving_sum_filter, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-energy_f.dat"))
+ self.connect(self.diff, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-theta_f.dat"))
+ self.connect(self.angle, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-epsilon_f.dat"))
+ self.connect(self.corrmag, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-corrmag_f.dat"))
+ self.connect(self.kscorr, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_ml-kscorr_c.dat"))
+ self.connect(self.div, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-div_f.dat"))
+ self.connect(self.mul, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-mul_f.dat"))
+ self.connect(self.slice, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-slice_f.dat"))
+ self.connect(self.pk_detect, gr.file_sink(gr.sizeof_char, "ofdm_sync_ml-peaks_b.dat"))
+ if use_dpll:
+ self.connect(self.dpll, gr.file_sink(gr.sizeof_char, "ofdm_sync_ml-dpll_b.dat"))
+
+ self.connect(self.sample_and_hold, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-sample_and_hold_f.dat"))
+ self.connect(self.input, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_ml-input_c.dat"))
+
diff --git a/gr-digital/python/ofdm_sync_pn.py b/gr-digital/python/ofdm_sync_pn.py
new file mode 100644
index 000000000..05b1de2e1
--- /dev/null
+++ b/gr-digital/python/ofdm_sync_pn.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+#
+# Copyright 2007,2008 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import math
+from numpy import fft
+from gnuradio import gr
+
+class ofdm_sync_pn(gr.hier_block2):
+ def __init__(self, fft_length, cp_length, logging=False):
+ """
+ OFDM synchronization using PN Correlation:
+ T. M. Schmidl and D. C. Cox, "Robust Frequency and Timing
+ Synchonization for OFDM," IEEE Trans. Communications, vol. 45,
+ no. 12, 1997.
+ """
+
+ gr.hier_block2.__init__(self, "ofdm_sync_pn",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature
+
+ self.input = gr.add_const_cc(0)
+
+ # PN Sync
+
+ # Create a delay line
+ self.delay = gr.delay(gr.sizeof_gr_complex, fft_length/2)
+
+ # Correlation from ML Sync
+ self.conjg = gr.conjugate_cc();
+ self.corr = gr.multiply_cc();
+
+ # Create a moving sum filter for the corr output
+ if 1:
+ moving_sum_taps = [1.0 for i in range(fft_length//2)]
+ self.moving_sum_filter = gr.fir_filter_ccf(1,moving_sum_taps)
+ else:
+ moving_sum_taps = [complex(1.0,0.0) for i in range(fft_length//2)]
+ self.moving_sum_filter = gr.fft_filter_ccc(1,moving_sum_taps)
+
+ # Create a moving sum filter for the input
+ self.inputmag2 = gr.complex_to_mag_squared()
+ movingsum2_taps = [1.0 for i in range(fft_length//2)]
+
+ if 1:
+ self.inputmovingsum = gr.fir_filter_fff(1,movingsum2_taps)
+ else:
+ self.inputmovingsum = gr.fft_filter_fff(1,movingsum2_taps)
+
+ self.square = gr.multiply_ff()
+ self.normalize = gr.divide_ff()
+
+ # Get magnitude (peaks) and angle (phase/freq error)
+ self.c2mag = gr.complex_to_mag_squared()
+ self.angle = gr.complex_to_arg()
+
+ self.sample_and_hold = gr.sample_and_hold_ff()
+
+ #ML measurements input to sampler block and detect
+ self.sub1 = gr.add_const_ff(-1)
+ self.pk_detect = gr.peak_detector_fb(0.20, 0.20, 30, 0.001)
+ #self.pk_detect = gr.peak_detector2_fb(9)
+
+ self.connect(self, self.input)
+
+ # Calculate the frequency offset from the correlation of the preamble
+ self.connect(self.input, self.delay)
+ self.connect(self.input, (self.corr,0))
+ self.connect(self.delay, self.conjg)
+ self.connect(self.conjg, (self.corr,1))
+ self.connect(self.corr, self.moving_sum_filter)
+ self.connect(self.moving_sum_filter, self.c2mag)
+ self.connect(self.moving_sum_filter, self.angle)
+ self.connect(self.angle, (self.sample_and_hold,0))
+
+ # Get the power of the input signal to normalize the output of the correlation
+ self.connect(self.input, self.inputmag2, self.inputmovingsum)
+ self.connect(self.inputmovingsum, (self.square,0))
+ self.connect(self.inputmovingsum, (self.square,1))
+ self.connect(self.square, (self.normalize,1))
+ self.connect(self.c2mag, (self.normalize,0))
+
+ # Create a moving sum filter for the corr output
+ matched_filter_taps = [1.0/cp_length for i in range(cp_length)]
+ self.matched_filter = gr.fir_filter_fff(1,matched_filter_taps)
+ self.connect(self.normalize, self.matched_filter)
+
+ self.connect(self.matched_filter, self.sub1, self.pk_detect)
+ #self.connect(self.matched_filter, self.pk_detect)
+ self.connect(self.pk_detect, (self.sample_and_hold,1))
+
+ # Set output signals
+ # Output 0: fine frequency correction value
+ # Output 1: timing signal
+ self.connect(self.sample_and_hold, (self,0))
+ self.connect(self.pk_detect, (self,1))
+
+ if logging:
+ self.connect(self.matched_filter, gr.file_sink(gr.sizeof_float, "ofdm_sync_pn-mf_f.dat"))
+ self.connect(self.normalize, gr.file_sink(gr.sizeof_float, "ofdm_sync_pn-theta_f.dat"))
+ self.connect(self.angle, gr.file_sink(gr.sizeof_float, "ofdm_sync_pn-epsilon_f.dat"))
+ self.connect(self.pk_detect, gr.file_sink(gr.sizeof_char, "ofdm_sync_pn-peaks_b.dat"))
+ self.connect(self.sample_and_hold, gr.file_sink(gr.sizeof_float, "ofdm_sync_pn-sample_and_hold_f.dat"))
+ self.connect(self.input, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_pn-input_c.dat"))
+
diff --git a/gr-digital/python/ofdm_sync_pnac.py b/gr-digital/python/ofdm_sync_pnac.py
new file mode 100644
index 000000000..10a125964
--- /dev/null
+++ b/gr-digital/python/ofdm_sync_pnac.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import math
+from numpy import fft
+from gnuradio import gr
+
+class ofdm_sync_pnac(gr.hier_block2):
+ def __init__(self, fft_length, cp_length, kstime, logging=False):
+ """
+ OFDM synchronization using PN Correlation and initial cross-correlation:
+ F. Tufvesson, O. Edfors, and M. Faulkner, "Time and Frequency Synchronization for OFDM using
+ PN-Sequency Preambles," IEEE Proc. VTC, 1999, pp. 2203-2207.
+
+ This implementation is meant to be a more robust version of the Schmidl and Cox receiver design.
+ By correlating against the preamble and using that as the input to the time-delayed correlation,
+ this circuit produces a very clean timing signal at the end of the preamble. The timing is
+ more accurate and does not have the problem associated with determining the timing from the
+ plateau structure in the Schmidl and Cox.
+
+ This implementation appears to require that the signal is received with a normalized power or signal
+ scalling factor to reduce ambiguities intorduced from partial correlation of the cyclic prefix and
+ the peak detection. A better peak detection block might fix this.
+
+ Also, the cross-correlation falls apart as the frequency offset gets larger and completely fails
+ when an integer offset is introduced. Another thing to look at.
+ """
+
+ gr.hier_block2.__init__(self, "ofdm_sync_pnac",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature
+
+
+ self.input = gr.add_const_cc(0)
+
+ symbol_length = fft_length + cp_length
+
+ # PN Sync with cross-correlation input
+
+ # cross-correlate with the known symbol
+ kstime = [k.conjugate() for k in kstime[0:fft_length//2]]
+ kstime.reverse()
+ self.crosscorr_filter = gr.fir_filter_ccc(1, kstime)
+
+ # Create a delay line
+ self.delay = gr.delay(gr.sizeof_gr_complex, fft_length/2)
+
+ # Correlation from ML Sync
+ self.conjg = gr.conjugate_cc();
+ self.corr = gr.multiply_cc();
+
+ # Create a moving sum filter for the input
+ self.mag = gr.complex_to_mag_squared()
+ movingsum_taps = (fft_length//1)*[1.0,]
+ self.power = gr.fir_filter_fff(1,movingsum_taps)
+
+ # Get magnitude (peaks) and angle (phase/freq error)
+ self.c2mag = gr.complex_to_mag_squared()
+ self.angle = gr.complex_to_arg()
+ self.compare = gr.sub_ff()
+
+ self.sample_and_hold = gr.sample_and_hold_ff()
+
+ #ML measurements input to sampler block and detect
+ self.threshold = gr.threshold_ff(0,0,0) # threshold detection might need to be tweaked
+ self.peaks = gr.float_to_char()
+
+ self.connect(self, self.input)
+
+ # Cross-correlate input signal with known preamble
+ self.connect(self.input, self.crosscorr_filter)
+
+ # use the output of the cross-correlation as input time-shifted correlation
+ self.connect(self.crosscorr_filter, self.delay)
+ self.connect(self.crosscorr_filter, (self.corr,0))
+ self.connect(self.delay, self.conjg)
+ self.connect(self.conjg, (self.corr,1))
+ self.connect(self.corr, self.c2mag)
+ self.connect(self.corr, self.angle)
+ self.connect(self.angle, (self.sample_and_hold,0))
+
+ # Get the power of the input signal to compare against the correlation
+ self.connect(self.crosscorr_filter, self.mag, self.power)
+
+ # Compare the power to the correlator output to determine timing peak
+ # When the peak occurs, it peaks above zero, so the thresholder detects this
+ self.connect(self.c2mag, (self.compare,0))
+ self.connect(self.power, (self.compare,1))
+ self.connect(self.compare, self.threshold)
+ self.connect(self.threshold, self.peaks, (self.sample_and_hold,1))
+
+ # Set output signals
+ # Output 0: fine frequency correction value
+ # Output 1: timing signal
+ self.connect(self.sample_and_hold, (self,0))
+ self.connect(self.peaks, (self,1))
+
+ if logging:
+ self.connect(self.compare, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-compare_f.dat"))
+ self.connect(self.c2mag, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-theta_f.dat"))
+ self.connect(self.power, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-inputpower_f.dat"))
+ self.connect(self.angle, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-epsilon_f.dat"))
+ self.connect(self.threshold, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-threshold_f.dat"))
+ self.connect(self.peaks, gr.file_sink(gr.sizeof_char, "ofdm_sync_pnac-peaks_b.dat"))
+ self.connect(self.sample_and_hold, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-sample_and_hold_f.dat"))
+ self.connect(self.input, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_pnac-input_c.dat"))
diff --git a/gr-digital/python/packet_utils.py b/gr-digital/python/packet_utils.py
new file mode 100644
index 000000000..2e216ff50
--- /dev/null
+++ b/gr-digital/python/packet_utils.py
@@ -0,0 +1,457 @@
+#
+# Copyright 2005,2006,2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import struct
+import numpy
+from gnuradio import gru
+import crc
+
+def conv_packed_binary_string_to_1_0_string(s):
+ """
+ '\xAF' --> '10101111'
+ """
+ r = []
+ for ch in s:
+ x = ord(ch)
+ for i in range(7,-1,-1):
+ t = (x >> i) & 0x1
+ r.append(t)
+
+ return ''.join(map(lambda x: chr(x + ord('0')), r))
+
+def conv_1_0_string_to_packed_binary_string(s):
+ """
+ '10101111' -> ('\xAF', False)
+
+ Basically the inverse of conv_packed_binary_string_to_1_0_string,
+ but also returns a flag indicating if we had to pad with leading zeros
+ to get to a multiple of 8.
+ """
+ if not is_1_0_string(s):
+ raise ValueError, "Input must be a string containing only 0's and 1's"
+
+ # pad to multiple of 8
+ padded = False
+ rem = len(s) % 8
+ if rem != 0:
+ npad = 8 - rem
+ s = '0' * npad + s
+ padded = True
+
+ assert len(s) % 8 == 0
+
+ r = []
+ i = 0
+ while i < len(s):
+ t = 0
+ for j in range(8):
+ t = (t << 1) | (ord(s[i + j]) - ord('0'))
+ r.append(chr(t))
+ i += 8
+ return (''.join(r), padded)
+
+
+default_access_code = \
+ conv_packed_binary_string_to_1_0_string('\xAC\xDD\xA4\xE2\xF2\x8C\x20\xFC')
+preamble = \
+ conv_packed_binary_string_to_1_0_string('\xA4\xF2')
+
+def is_1_0_string(s):
+ if not isinstance(s, str):
+ return False
+ for ch in s:
+ if not ch in ('0', '1'):
+ return False
+ return True
+
+def string_to_hex_list(s):
+ return map(lambda x: hex(ord(x)), s)
+
+
+def whiten(s, o):
+ sa = numpy.fromstring(s, numpy.uint8)
+ z = sa ^ random_mask_vec8[o:len(sa)+o]
+ return z.tostring()
+
+def dewhiten(s, o):
+ return whiten(s, o) # self inverse
+
+
+def make_header(payload_len, whitener_offset=0):
+ # Upper nibble is offset, lower 12 bits is len
+ val = ((whitener_offset & 0xf) << 12) | (payload_len & 0x0fff)
+ #print "offset =", whitener_offset, " len =", payload_len, " val=", val
+ return struct.pack('!HH', val, val)
+
+def make_packet(payload, samples_per_symbol, bits_per_symbol,
+ access_code=default_access_code, pad_for_usrp=True,
+ whitener_offset=0, whitening=True):
+ """
+ Build a packet, given access code, payload, and whitener offset
+
+ @param payload: packet payload, len [0, 4096]
+ @param samples_per_symbol: samples per symbol (needed for padding calculation)
+ @type samples_per_symbol: int
+ @param bits_per_symbol: (needed for padding calculation)
+ @type bits_per_symbol: int
+ @param access_code: string of ascii 0's and 1's
+ @param whitener_offset offset into whitener string to use [0-16)
+
+ Packet will have access code at the beginning, followed by length, payload
+ and finally CRC-32.
+ """
+ if not is_1_0_string(access_code):
+ raise ValueError, "access_code must be a string containing only 0's and 1's (%r)" % (access_code,)
+
+ if not whitener_offset >=0 and whitener_offset < 16:
+ raise ValueError, "whitener_offset must be between 0 and 15, inclusive (%i)" % (whitener_offset,)
+
+ (packed_access_code, padded) = conv_1_0_string_to_packed_binary_string(access_code)
+ (packed_preamble, ignore) = conv_1_0_string_to_packed_binary_string(preamble)
+
+ payload_with_crc = crc.gen_and_append_crc32(payload)
+ #print "outbound crc =", string_to_hex_list(payload_with_crc[-4:])
+
+ L = len(payload_with_crc)
+ MAXLEN = len(random_mask_tuple)
+ if L > MAXLEN:
+ raise ValueError, "len(payload) must be in [0, %d]" % (MAXLEN,)
+
+ if whitening:
+ pkt = ''.join((packed_preamble, packed_access_code, make_header(L, whitener_offset),
+ whiten(payload_with_crc, whitener_offset), '\x55'))
+ else:
+ pkt = ''.join((packed_preamble, packed_access_code, make_header(L, whitener_offset),
+ (payload_with_crc), '\x55'))
+
+ if pad_for_usrp:
+ pkt = pkt + (_npadding_bytes(len(pkt), int(samples_per_symbol), bits_per_symbol) * '\x55')
+
+ #print "make_packet: len(pkt) =", len(pkt)
+ return pkt
+
+def _npadding_bytes(pkt_byte_len, samples_per_symbol, bits_per_symbol):
+ """
+ Generate sufficient padding such that each packet ultimately ends
+ up being a multiple of 512 bytes when sent across the USB. We
+ send 4-byte samples across the USB (16-bit I and 16-bit Q), thus
+ we want to pad so that after modulation the resulting packet
+ is a multiple of 128 samples.
+
+ @param ptk_byte_len: len in bytes of packet, not including padding.
+ @param samples_per_symbol: samples per bit (1 bit / symbolwidth GMSK)
+ @type samples_per_symbol: int
+ @param bits_per_symbol: bits per symbol (log2(modulation order))
+ @type bits_per_symbol: int
+
+ @returns number of bytes of padding to append.
+ """
+ modulus = 128
+ byte_modulus = gru.lcm(modulus/8, samples_per_symbol) * bits_per_symbol / samples_per_symbol
+ r = pkt_byte_len % byte_modulus
+ if r == 0:
+ return 0
+ return byte_modulus - r
+
+
+def unmake_packet(whitened_payload_with_crc, whitener_offset=0, dewhitening=True):
+ """
+ Return (ok, payload)
+
+ @param whitened_payload_with_crc: string
+ """
+
+ if dewhitening:
+ payload_with_crc = dewhiten(whitened_payload_with_crc, whitener_offset)
+ else:
+ payload_with_crc = (whitened_payload_with_crc)
+
+ ok, payload = crc.check_crc32(payload_with_crc)
+
+ if 0:
+ print "payload_with_crc =", string_to_hex_list(payload_with_crc)
+ print "ok = %r, len(payload) = %d" % (ok, len(payload))
+ print "payload =", string_to_hex_list(payload)
+
+ return ok, payload
+
+
+# FYI, this PN code is the output of a 15-bit LFSR
+random_mask_tuple = (
+ 255, 63, 0, 16, 0, 12, 0, 5, 192, 3, 16, 1, 204, 0, 85, 192,
+ 63, 16, 16, 12, 12, 5, 197, 195, 19, 17, 205, 204, 85, 149, 255, 47,
+ 0, 28, 0, 9, 192, 6, 208, 2, 220, 1, 153, 192, 106, 208, 47, 28,
+ 28, 9, 201, 198, 214, 210, 222, 221, 152, 89, 170, 186, 255, 51, 0, 21,
+ 192, 15, 16, 4, 12, 3, 69, 193, 243, 16, 69, 204, 51, 21, 213, 207,
+ 31, 20, 8, 15, 70, 132, 50, 227, 85, 137, 255, 38, 192, 26, 208, 11,
+ 28, 7, 73, 194, 182, 209, 182, 220, 118, 217, 230, 218, 202, 219, 23, 27,
+ 78, 139, 116, 103, 103, 106, 170, 175, 63, 60, 16, 17, 204, 12, 85, 197,
+ 255, 19, 0, 13, 192, 5, 144, 3, 44, 1, 221, 192, 89, 144, 58, 236,
+ 19, 13, 205, 197, 149, 147, 47, 45, 220, 29, 153, 201, 170, 214, 255, 30,
+ 192, 8, 80, 6, 188, 2, 241, 193, 132, 80, 99, 124, 41, 225, 222, 200,
+ 88, 86, 186, 190, 243, 48, 69, 212, 51, 31, 85, 200, 63, 22, 144, 14,
+ 236, 4, 77, 195, 117, 145, 231, 44, 74, 157, 247, 41, 134, 158, 226, 232,
+ 73, 142, 182, 228, 118, 203, 102, 215, 106, 222, 175, 24, 124, 10, 161, 199,
+ 56, 82, 146, 189, 173, 177, 189, 180, 113, 183, 100, 118, 171, 102, 255, 106,
+ 192, 47, 16, 28, 12, 9, 197, 198, 211, 18, 221, 205, 153, 149, 170, 239,
+ 63, 12, 16, 5, 204, 3, 21, 193, 207, 16, 84, 12, 63, 69, 208, 51,
+ 28, 21, 201, 207, 22, 212, 14, 223, 68, 88, 51, 122, 149, 227, 47, 9,
+ 220, 6, 217, 194, 218, 209, 155, 28, 107, 73, 239, 118, 204, 38, 213, 218,
+ 223, 27, 24, 11, 74, 135, 119, 34, 166, 153, 186, 234, 243, 15, 5, 196,
+ 3, 19, 65, 205, 240, 85, 132, 63, 35, 80, 25, 252, 10, 193, 199, 16,
+ 82, 140, 61, 165, 209, 187, 28, 115, 73, 229, 246, 203, 6, 215, 66, 222,
+ 177, 152, 116, 106, 167, 111, 58, 172, 19, 61, 205, 209, 149, 156, 111, 41,
+ 236, 30, 205, 200, 85, 150, 191, 46, 240, 28, 68, 9, 243, 70, 197, 242,
+ 211, 5, 157, 195, 41, 145, 222, 236, 88, 77, 250, 181, 131, 55, 33, 214,
+ 152, 94, 234, 184, 79, 50, 180, 21, 183, 79, 54, 180, 22, 247, 78, 198,
+ 180, 82, 247, 125, 134, 161, 162, 248, 121, 130, 162, 225, 185, 136, 114, 230,
+ 165, 138, 251, 39, 3, 90, 129, 251, 32, 67, 88, 49, 250, 148, 67, 47,
+ 113, 220, 36, 89, 219, 122, 219, 99, 27, 105, 203, 110, 215, 108, 94, 173,
+ 248, 125, 130, 161, 161, 184, 120, 114, 162, 165, 185, 187, 50, 243, 85, 133,
+ 255, 35, 0, 25, 192, 10, 208, 7, 28, 2, 137, 193, 166, 208, 122, 220,
+ 35, 25, 217, 202, 218, 215, 27, 30, 139, 72, 103, 118, 170, 166, 255, 58,
+ 192, 19, 16, 13, 204, 5, 149, 195, 47, 17, 220, 12, 89, 197, 250, 211,
+ 3, 29, 193, 201, 144, 86, 236, 62, 205, 208, 85, 156, 63, 41, 208, 30,
+ 220, 8, 89, 198, 186, 210, 243, 29, 133, 201, 163, 22, 249, 206, 194, 212,
+ 81, 159, 124, 104, 33, 238, 152, 76, 106, 181, 239, 55, 12, 22, 133, 206,
+ 227, 20, 73, 207, 118, 212, 38, 223, 90, 216, 59, 26, 147, 75, 45, 247,
+ 93, 134, 185, 162, 242, 249, 133, 130, 227, 33, 137, 216, 102, 218, 170, 219,
+ 63, 27, 80, 11, 124, 7, 97, 194, 168, 81, 190, 188, 112, 113, 228, 36,
+ 75, 91, 119, 123, 102, 163, 106, 249, 239, 2, 204, 1, 149, 192, 111, 16,
+ 44, 12, 29, 197, 201, 147, 22, 237, 206, 205, 148, 85, 175, 127, 60, 32,
+ 17, 216, 12, 90, 133, 251, 35, 3, 89, 193, 250, 208, 67, 28, 49, 201,
+ 212, 86, 223, 126, 216, 32, 90, 152, 59, 42, 147, 95, 45, 248, 29, 130,
+ 137, 161, 166, 248, 122, 194, 163, 17, 185, 204, 114, 213, 229, 159, 11, 40,
+ 7, 94, 130, 184, 97, 178, 168, 117, 190, 167, 48, 122, 148, 35, 47, 89,
+ 220, 58, 217, 211, 26, 221, 203, 25, 151, 74, 238, 183, 12, 118, 133, 230,
+ 227, 10, 201, 199, 22, 210, 142, 221, 164, 89, 187, 122, 243, 99, 5, 233,
+ 195, 14, 209, 196, 92, 83, 121, 253, 226, 193, 137, 144, 102, 236, 42, 205,
+ 223, 21, 152, 15, 42, 132, 31, 35, 72, 25, 246, 138, 198, 231, 18, 202,
+ 141, 151, 37, 174, 155, 60, 107, 81, 239, 124, 76, 33, 245, 216, 71, 26,
+ 178, 139, 53, 167, 87, 58, 190, 147, 48, 109, 212, 45, 159, 93, 168, 57,
+ 190, 146, 240, 109, 132, 45, 163, 93, 185, 249, 178, 194, 245, 145, 135, 44,
+ 98, 157, 233, 169, 142, 254, 228, 64, 75, 112, 55, 100, 22, 171, 78, 255,
+ 116, 64, 39, 112, 26, 164, 11, 59, 71, 83, 114, 189, 229, 177, 139, 52,
+ 103, 87, 106, 190, 175, 48, 124, 20, 33, 207, 88, 84, 58, 191, 83, 48,
+ 61, 212, 17, 159, 76, 104, 53, 238, 151, 12, 110, 133, 236, 99, 13, 233,
+ 197, 142, 211, 36, 93, 219, 121, 155, 98, 235, 105, 143, 110, 228, 44, 75,
+ 93, 247, 121, 134, 162, 226, 249, 137, 130, 230, 225, 138, 200, 103, 22, 170,
+ 142, 255, 36, 64, 27, 112, 11, 100, 7, 107, 66, 175, 113, 188, 36, 113,
+ 219, 100, 91, 107, 123, 111, 99, 108, 41, 237, 222, 205, 152, 85, 170, 191,
+ 63, 48, 16, 20, 12, 15, 69, 196, 51, 19, 85, 205, 255, 21, 128, 15,
+ 32, 4, 24, 3, 74, 129, 247, 32, 70, 152, 50, 234, 149, 143, 47, 36,
+ 28, 27, 73, 203, 118, 215, 102, 222, 170, 216, 127, 26, 160, 11, 56, 7,
+ 82, 130, 189, 161, 177, 184, 116, 114, 167, 101, 186, 171, 51, 63, 85, 208,
+ 63, 28, 16, 9, 204, 6, 213, 194, 223, 17, 152, 12, 106, 133, 239, 35,
+ 12, 25, 197, 202, 211, 23, 29, 206, 137, 148, 102, 239, 106, 204, 47, 21,
+ 220, 15, 25, 196, 10, 211, 71, 29, 242, 137, 133, 166, 227, 58, 201, 211,
+ 22, 221, 206, 217, 148, 90, 239, 123, 12, 35, 69, 217, 243, 26, 197, 203,
+ 19, 23, 77, 206, 181, 148, 119, 47, 102, 156, 42, 233, 223, 14, 216, 4,
+ 90, 131, 123, 33, 227, 88, 73, 250, 182, 195, 54, 209, 214, 220, 94, 217,
+ 248, 90, 194, 187, 17, 179, 76, 117, 245, 231, 7, 10, 130, 135, 33, 162,
+ 152, 121, 170, 162, 255, 57, 128, 18, 224, 13, 136, 5, 166, 131, 58, 225,
+ 211, 8, 93, 198, 185, 146, 242, 237, 133, 141, 163, 37, 185, 219, 50, 219,
+ 85, 155, 127, 43, 96, 31, 104, 8, 46, 134, 156, 98, 233, 233, 142, 206,
+ 228, 84, 75, 127, 119, 96, 38, 168, 26, 254, 139, 0, 103, 64, 42, 176,
+ 31, 52, 8, 23, 70, 142, 178, 228, 117, 139, 103, 39, 106, 154, 175, 43,
+ 60, 31, 81, 200, 60, 86, 145, 254, 236, 64, 77, 240, 53, 132, 23, 35,
+ 78, 153, 244, 106, 199, 111, 18, 172, 13, 189, 197, 177, 147, 52, 109, 215,
+ 109, 158, 173, 168, 125, 190, 161, 176, 120, 116, 34, 167, 89, 186, 186, 243,
+ 51, 5, 213, 195, 31, 17, 200, 12, 86, 133, 254, 227, 0, 73, 192, 54,
+ 208, 22, 220, 14, 217, 196, 90, 211, 123, 29, 227, 73, 137, 246, 230, 198,
+ 202, 210, 215, 29, 158, 137, 168, 102, 254, 170, 192, 127, 16, 32, 12, 24,
+ 5, 202, 131, 23, 33, 206, 152, 84, 106, 191, 111, 48, 44, 20, 29, 207,
+ 73, 148, 54, 239, 86, 204, 62, 213, 208, 95, 28, 56, 9, 210, 134, 221,
+ 162, 217, 185, 154, 242, 235, 5, 143, 67, 36, 49, 219, 84, 91, 127, 123,
+ 96, 35, 104, 25, 238, 138, 204, 103, 21, 234, 143, 15, 36, 4, 27, 67,
+ 75, 113, 247, 100, 70, 171, 114, 255, 101, 128, 43, 32, 31, 88, 8, 58,
+ 134, 147, 34, 237, 217, 141, 154, 229, 171, 11, 63, 71, 80, 50, 188, 21,
+ 177, 207, 52, 84, 23, 127, 78, 160, 52, 120, 23, 98, 142, 169, 164, 126,
+ 251, 96, 67, 104, 49, 238, 148, 76, 111, 117, 236, 39, 13, 218, 133, 155,
+ 35, 43, 89, 223, 122, 216, 35, 26, 153, 203, 42, 215, 95, 30, 184, 8,
+ 114, 134, 165, 162, 251, 57, 131, 82, 225, 253, 136, 65, 166, 176, 122, 244,
+ 35, 7, 89, 194, 186, 209, 179, 28, 117, 201, 231, 22, 202, 142, 215, 36,
+ 94, 155, 120, 107, 98, 175, 105, 188, 46, 241, 220, 68, 89, 243, 122, 197,
+ 227, 19, 9, 205, 198, 213, 146, 223, 45, 152, 29, 170, 137, 191, 38, 240,
+ 26, 196, 11, 19, 71, 77, 242, 181, 133, 183, 35, 54, 153, 214, 234, 222,
+ 207, 24, 84, 10, 191, 71, 48, 50, 148, 21, 175, 79, 60, 52, 17, 215,
+ 76, 94, 181, 248, 119, 2, 166, 129, 186, 224, 115, 8, 37, 198, 155, 18,
+ 235, 77, 143, 117, 164, 39, 59, 90, 147, 123, 45, 227, 93, 137, 249, 166,
+ 194, 250, 209, 131, 28, 97, 201, 232, 86, 206, 190, 212, 112, 95, 100, 56,
+ 43, 82, 159, 125, 168, 33, 190, 152, 112, 106, 164, 47, 59, 92, 19, 121,
+ 205, 226, 213, 137, 159, 38, 232, 26, 206, 139, 20, 103, 79, 106, 180, 47,
+ 55, 92, 22, 185, 206, 242, 212, 69, 159, 115, 40, 37, 222, 155, 24, 107,
+ 74, 175, 119, 60, 38, 145, 218, 236, 91, 13, 251, 69, 131, 115, 33, 229,
+ 216, 75, 26, 183, 75, 54, 183, 86, 246, 190, 198, 240, 82, 196, 61, 147,
+ 81, 173, 252, 125, 129, 225, 160, 72, 120, 54, 162, 150, 249, 174, 194, 252,
+ 81, 129, 252, 96, 65, 232, 48, 78, 148, 52, 111, 87, 108, 62, 173, 208,
+ 125, 156, 33, 169, 216, 126, 218, 160, 91, 56, 59, 82, 147, 125, 173, 225,
+ 189, 136, 113, 166, 164, 122, 251, 99, 3, 105, 193, 238, 208, 76, 92, 53,
+ 249, 215, 2, 222, 129, 152, 96, 106, 168, 47, 62, 156, 16, 105, 204, 46,
+ 213, 220, 95, 25, 248, 10, 194, 135, 17, 162, 140, 121, 165, 226, 251, 9,
+ 131, 70, 225, 242, 200, 69, 150, 179, 46, 245, 220, 71, 25, 242, 138, 197,
+ 167, 19, 58, 141, 211, 37, 157, 219, 41, 155, 94, 235, 120, 79, 98, 180,
+ 41, 183, 94, 246, 184, 70, 242, 178, 197, 181, 147, 55, 45, 214, 157, 158,
+ 233, 168, 78, 254, 180, 64, 119, 112, 38, 164, 26, 251, 75, 3, 119, 65,
+ 230, 176, 74, 244, 55, 7, 86, 130, 190, 225, 176, 72, 116, 54, 167, 86,
+ 250, 190, 195, 48, 81, 212, 60, 95, 81, 248, 60, 66, 145, 241, 172, 68,
+ 125, 243, 97, 133, 232, 99, 14, 169, 196, 126, 211, 96, 93, 232, 57, 142,
+ 146, 228, 109, 139, 109, 167, 109, 186, 173, 179, 61, 181, 209, 183, 28, 118,
+ 137, 230, 230, 202, 202, 215, 23, 30, 142, 136, 100, 102, 171, 106, 255, 111,
+ 0, 44, 0, 29, 192, 9, 144, 6, 236, 2, 205, 193, 149, 144, 111, 44,
+ 44, 29, 221, 201, 153, 150, 234, 238, 207, 12, 84, 5, 255, 67, 0, 49,
+ 192, 20, 80, 15, 124, 4, 33, 195, 88, 81, 250, 188, 67, 49, 241, 212,
+ 68, 95, 115, 120, 37, 226, 155, 9, 171, 70, 255, 114, 192, 37, 144, 27,
+ 44, 11, 93, 199, 121, 146, 162, 237, 185, 141, 178, 229, 181, 139, 55, 39,
+ 86, 154, 190, 235, 48, 79, 84, 52, 63, 87, 80, 62, 188, 16, 113, 204,
+ 36, 85, 219, 127, 27, 96, 11, 104, 7, 110, 130, 172, 97, 189, 232, 113,
+ 142, 164, 100, 123, 107, 99, 111, 105, 236, 46, 205, 220, 85, 153, 255, 42,
+ 192, 31, 16, 8, 12, 6, 133, 194, 227, 17, 137, 204, 102, 213, 234, 223,
+ 15, 24, 4, 10, 131, 71, 33, 242, 152, 69, 170, 179, 63, 53, 208, 23,
+ 28, 14, 137, 196, 102, 211, 106, 221, 239, 25, 140, 10, 229, 199, 11, 18,
+ 135, 77, 162, 181, 185, 183, 50, 246, 149, 134, 239, 34, 204, 25, 149, 202,
+ 239, 23, 12, 14, 133, 196, 99, 19, 105, 205, 238, 213, 140, 95, 37, 248,
+ 27, 2, 139, 65, 167, 112, 122, 164, 35, 59, 89, 211, 122, 221, 227, 25,
+ 137, 202, 230, 215, 10, 222, 135, 24, 98, 138, 169, 167, 62, 250, 144, 67,
+ 44, 49, 221, 212, 89, 159, 122, 232, 35, 14, 153, 196, 106, 211, 111, 29,
+ 236, 9, 141, 198, 229, 146, 203, 45, 151, 93, 174, 185, 188, 114, 241, 229,
+ 132, 75, 35, 119, 89, 230, 186, 202, 243, 23, 5, 206, 131, 20, 97, 207,
+ 104, 84, 46, 191, 92, 112, 57, 228, 18, 203, 77, 151, 117, 174, 167, 60,
+ 122, 145, 227, 44, 73, 221, 246, 217, 134, 218, 226, 219, 9, 155, 70, 235,
+ 114, 207, 101, 148, 43, 47, 95, 92, 56, 57, 210, 146, 221, 173, 153, 189,
+ 170, 241, 191, 4, 112, 3, 100, 1, 235, 64, 79, 112, 52, 36, 23, 91,
+ 78, 187, 116, 115, 103, 101, 234, 171, 15, 63, 68, 16, 51, 76, 21, 245,
+ 207, 7, 20, 2, 143, 65, 164, 48, 123, 84, 35, 127, 89, 224, 58, 200,
+ 19, 22, 141, 206, 229, 148, 75, 47, 119, 92, 38, 185, 218, 242, 219, 5,
+ 155, 67, 43, 113, 223, 100, 88, 43, 122, 159, 99, 40, 41, 222, 158, 216,
+ 104, 90, 174, 187, 60, 115, 81, 229, 252, 75, 1, 247, 64, 70, 176, 50,
+ 244, 21, 135, 79, 34, 180, 25, 183, 74, 246, 183, 6, 246, 130, 198, 225,
+ 146, 200, 109, 150, 173, 174, 253, 188, 65, 177, 240, 116, 68, 39, 115, 90,
+ 165, 251, 59, 3, 83, 65, 253, 240, 65, 132, 48, 99, 84, 41, 255, 94,
+ 192, 56, 80, 18, 188, 13, 177, 197, 180, 83, 55, 125, 214, 161, 158, 248,
+ 104, 66, 174, 177, 188, 116, 113, 231, 100, 74, 171, 119, 63, 102, 144, 42,
+ 236, 31, 13, 200, 5, 150, 131, 46, 225, 220, 72, 89, 246, 186, 198, 243,
+ 18, 197, 205, 147, 21, 173, 207, 61, 148, 17, 175, 76, 124, 53, 225, 215,
+ 8, 94, 134, 184, 98, 242, 169, 133, 190, 227, 48, 73, 212, 54, 223, 86,
+ 216, 62, 218, 144, 91, 44, 59, 93, 211, 121, 157, 226, 233, 137, 142, 230,
+ 228, 74, 203, 119, 23, 102, 142, 170, 228, 127, 11, 96, 7, 104, 2, 174,
+ 129, 188, 96, 113, 232, 36, 78, 155, 116, 107, 103, 111, 106, 172, 47, 61,
+ 220, 17, 153, 204, 106, 213, 239, 31, 12, 8, 5, 198, 131, 18, 225, 205,
+ 136, 85, 166, 191, 58, 240, 19, 4, 13, 195, 69, 145, 243, 44, 69, 221,
+ 243, 25, 133, 202, 227, 23, 9, 206, 134, 212, 98, 223, 105, 152, 46, 234,
+ 156, 79, 41, 244, 30, 199, 72, 82, 182, 189, 182, 241, 182, 196, 118, 211,
+ 102, 221, 234, 217, 143, 26, 228, 11, 11, 71, 71, 114, 178, 165, 181, 187,
+ 55, 51, 86, 149, 254, 239, 0, 76, 0, 53, 192, 23, 16, 14, 140, 4,
+ 101, 195, 107, 17, 239, 76, 76, 53, 245, 215, 7, 30, 130, 136, 97, 166,
+ 168, 122, 254, 163, 0, 121, 192, 34, 208, 25, 156, 10, 233, 199, 14, 210,
+ 132, 93, 163, 121, 185, 226, 242, 201, 133, 150, 227, 46, 201, 220, 86, 217,
+ 254, 218, 192, 91, 16, 59, 76, 19, 117, 205, 231, 21, 138, 143, 39, 36,
+ 26, 155, 75, 43, 119, 95, 102, 184, 42, 242, 159, 5, 168, 3, 62, 129,
+ 208, 96, 92, 40, 57, 222, 146, 216, 109, 154, 173, 171, 61, 191, 81, 176,
+ 60, 116, 17, 231, 76, 74, 181, 247, 55, 6, 150, 130, 238, 225, 140, 72,
+ 101, 246, 171, 6, 255, 66, 192, 49, 144, 20, 108, 15, 109, 196, 45, 147,
+ 93, 173, 249, 189, 130, 241, 161, 132, 120, 99, 98, 169, 233, 190, 206, 240,
+ 84, 68, 63, 115, 80, 37, 252, 27, 1, 203, 64, 87, 112, 62, 164, 16,
+ 123, 76, 35, 117, 217, 231, 26, 202, 139, 23, 39, 78, 154, 180, 107, 55,
+ 111, 86, 172, 62, 253, 208, 65, 156, 48, 105, 212, 46, 223, 92, 88, 57,
+ 250, 146, 195, 45, 145, 221, 172, 89, 189, 250, 241, 131, 4, 97, 195, 104,
+ 81, 238, 188, 76, 113, 245, 228, 71, 11, 114, 135, 101, 162, 171, 57, 191,
+ 82, 240, 61, 132, 17, 163, 76, 121, 245, 226, 199, 9, 146, 134, 237, 162,
+ 205, 185, 149, 178, 239, 53, 140, 23, 37, 206, 155, 20, 107, 79, 111, 116,
+ 44, 39, 93, 218, 185, 155, 50, 235, 85, 143, 127, 36, 32, 27, 88, 11,
+ 122, 135, 99, 34, 169, 217, 190, 218, 240, 91, 4, 59, 67, 83, 113, 253,
+ 228, 65, 139, 112, 103, 100, 42, 171, 95, 63, 120, 16, 34, 140, 25, 165,
+ 202, 251, 23, 3, 78, 129, 244, 96, 71, 104, 50, 174, 149, 188, 111, 49,
+ 236, 20, 77, 207, 117, 148, 39, 47, 90, 156, 59, 41, 211, 94, 221, 248,
+ 89, 130, 186, 225, 179, 8, 117, 198, 167, 18, 250, 141, 131, 37, 161, 219,
+ 56, 91, 82, 187, 125, 179, 97, 181, 232, 119, 14, 166, 132, 122, 227, 99,
+ 9, 233, 198, 206, 210, 212, 93, 159, 121, 168, 34, 254, 153, 128, 106, 224,
+ 47, 8, 28, 6, 137, 194, 230, 209, 138, 220, 103, 25, 234, 138, 207, 39,
+ 20, 26, 143, 75, 36, 55, 91, 86, 187, 126, 243, 96, 69, 232, 51, 14,
+ 149, 196, 111, 19, 108, 13, 237, 197, 141, 147, 37, 173, 219, 61, 155, 81,
+ 171, 124, 127, 97, 224, 40, 72, 30, 182, 136, 118, 230, 166, 202, 250, 215,
+ 3, 30, 129, 200, 96, 86, 168, 62, 254, 144, 64, 108, 48, 45, 212, 29,
+ 159, 73, 168, 54, 254, 150, 192, 110, 208, 44, 92, 29, 249, 201, 130, 214,
+ 225, 158, 200, 104, 86, 174, 190, 252, 112, 65, 228, 48, 75, 84, 55, 127,
+ 86, 160, 62, 248, 16, 66, 140, 49, 165, 212, 123, 31, 99, 72, 41, 246,
+ 158, 198, 232, 82, 206, 189, 148, 113, 175, 100, 124, 43, 97, 223, 104, 88,
+ 46, 186, 156, 115, 41, 229, 222, 203, 24, 87, 74, 190, 183, 48, 118, 148,
+ 38, 239, 90, 204, 59, 21, 211, 79, 29, 244, 9, 135, 70, 226, 178, 201,
+ 181, 150, 247, 46, 198, 156, 82, 233, 253, 142, 193, 164, 80, 123, 124, 35,
+ 97, 217, 232, 90, 206, 187, 20, 115, 79, 101, 244, 43, 7, 95, 66, 184,
+ 49, 178, 148, 117, 175, 103, 60, 42, 145, 223, 44, 88, 29, 250, 137, 131,
+ 38, 225, 218, 200, 91, 22, 187, 78, 243, 116, 69, 231, 115, 10, 165, 199,
+ 59, 18, 147, 77, 173, 245, 189, 135, 49, 162, 148, 121, 175, 98, 252, 41,
+ 129, 222, 224, 88, 72, 58, 182, 147, 54, 237, 214, 205, 158, 213, 168, 95,
+ 62, 184, 16, 114, 140, 37, 165, 219, 59, 27, 83, 75, 125, 247, 97, 134,
+ 168, 98, 254, 169, 128, 126, 224, 32, 72, 24, 54, 138, 150, 231, 46, 202,
+ 156, 87, 41, 254, 158, 192, 104, 80, 46, 188, 28, 113, 201, 228, 86, 203,
+ 126, 215, 96, 94, 168, 56, 126, 146, 160, 109, 184, 45, 178, 157, 181, 169,
+ 183, 62, 246, 144, 70, 236, 50, 205, 213, 149, 159, 47, 40, 28, 30, 137,
+ 200, 102, 214, 170, 222, 255, 24, 64, 10, 176, 7, 52, 2, 151, 65, 174,
+ 176, 124, 116, 33, 231, 88, 74, 186, 183, 51, 54, 149, 214, 239, 30, 204,
+ 8, 85, 198, 191, 18, 240, 13, 132, 5, 163, 67, 57, 241, 210, 196, 93,
+ 147, 121, 173, 226, 253, 137, 129, 166, 224, 122, 200, 35, 22, 153, 206, 234,
+ 212, 79, 31, 116, 8, 39, 70, 154, 178, 235, 53, 143, 87, 36, 62, 155,
+ 80, 107, 124, 47, 97, 220, 40, 89, 222, 186, 216, 115, 26, 165, 203, 59,
+ 23, 83, 78, 189, 244, 113, 135, 100, 98, 171, 105, 191, 110, 240, 44, 68,
+ 29, 243, 73, 133, 246, 227, 6, 201, 194, 214, 209, 158, 220, 104, 89, 238,
+ 186, 204, 115, 21, 229, 207, 11, 20, 7, 79, 66, 180, 49, 183, 84, 118,
+ 191, 102, 240, 42, 196, 31, 19, 72, 13, 246, 133, 134, 227, 34, 201, 217,
+ 150, 218, 238, 219, 12, 91, 69, 251, 115, 3, 101, 193, 235, 16, 79, 76,
+ 52, 53, 215, 87, 30, 190, 136, 112, 102, 164, 42, 251, 95, 3, 120, 1,
+ 226, 128, 73, 160, 54, 248, 22, 194, 142, 209, 164, 92, 123, 121, 227, 98,
+ 201, 233, 150, 206, 238, 212, 76, 95, 117, 248, 39, 2, 154, 129, 171, 32,
+ 127, 88, 32, 58, 152, 19, 42, 141, 223, 37, 152, 27, 42, 139, 95, 39,
+ 120, 26, 162, 139, 57, 167, 82, 250, 189, 131, 49, 161, 212, 120, 95, 98,
+ 184, 41, 178, 158, 245, 168, 71, 62, 178, 144, 117, 172, 39, 61, 218, 145,
+ 155, 44, 107, 93, 239, 121, 140, 34, 229, 217, 139, 26, 231, 75, 10, 183,
+ 71, 54, 178, 150, 245, 174, 199, 60, 82, 145, 253, 172, 65, 189, 240, 113,
+ 132, 36, 99, 91, 105, 251, 110, 195, 108, 81, 237, 252, 77, 129, 245, 160,
+ 71, 56, 50, 146, 149, 173, 175, 61, 188, 17, 177, 204, 116, 85, 231, 127,
+ 10, 160, 7, 56, 2, 146, 129, 173, 160, 125, 184, 33, 178, 152, 117, 170,
+ 167, 63, 58, 144, 19, 44, 13, 221, 197, 153, 147, 42, 237, 223, 13, 152,
+ 5, 170, 131, 63, 33, 208, 24, 92, 10, 185, 199, 50, 210, 149, 157, 175,
+ 41, 188, 30, 241, 200, 68, 86, 179, 126, 245, 224, 71, 8, 50, 134, 149,
+ 162, 239, 57, 140, 18, 229, 205, 139, 21, 167, 79, 58, 180, 19, 55, 77,
+ 214, 181, 158, 247, 40, 70, 158, 178, 232, 117, 142, 167, 36, 122, 155, 99,
+ 43, 105, 223, 110, 216, 44, 90, 157, 251, 41, 131, 94, 225, 248, 72, 66,
+ 182, 177, 182, 244, 118, 199, 102, 210, 170, 221, 191, 25, 176, 10, 244, 7,
+ 7, 66, 130, 177, 161, 180, 120, 119, 98, 166, 169, 186, 254, 243, 0, 69,
+ 192, 51, 16, 21, 204, 15, 21, 196, 15, 19, 68, 13, 243, 69, 133, 243,
+ 35, 5, 217, 195, 26, 209, 203, 28, 87, 73, 254, 182, 192, 118, 208, 38,
+ 220, 26, 217, 203, 26, 215, 75, 30, 183, 72, 118, 182, 166, 246, 250, 198,
+ 195, 18, 209, 205, 156, 85, 169, 255, 62, 192, 16, 80, 12, 60, 5, 209,
+ 195, 28, 81, 201, 252, 86, 193, 254, 208, 64, 92, 48, 57, 212, 18, 223,
+ 77, 152, 53, 170, 151, 63, 46, 144, 28, 108, 9, 237, 198, 205, 146, 213,
+ 173, 159, 61, 168, 17, 190, 140, 112, 101, 228, 43, 11, 95, 71, 120, 50,
+ 162, 149, 185, 175, 50, 252, 21, 129, 207, 32, 84, 24, 63, 74, 144, 55,
+ 44, 22, 157, 206, 233, 148, 78, 239, 116, 76, 39, 117, 218, 167, 27, 58,
+ 139, 83, 39, 125, 218, 161, 155, 56, 107, 82, 175, 125, 188, 33, 177, 216,
+ 116, 90, 167, 123, 58, 163, 83, 57, 253, 210, 193, 157, 144, 105, 172, 46,
+ 253, 220, 65, 153, 240, 106, 196, 47, 19, 92, 13, 249, 197, 130, 211, 33,
+ 157, 216, 105, 154, 174, 235, 60, 79, 81, 244, 60, 71, 81, 242, 188, 69,
+ 177, 243, 52, 69, 215, 115, 30, 165, 200, 123, 22, 163, 78, 249, 244, 66,
+ 199, 113, 146, 164, 109, 187, 109, 179, 109, 181, 237, 183, 13, 182, 133, 182,
+ 227, 54, 201, 214, 214, 222, 222, 216, 88, 90, 186, 187, 51, 51, 255, 63 )
+
+random_mask_vec8 = numpy.array(random_mask_tuple, numpy.uint8)
+
diff --git a/gr-digital/python/pkt.py b/gr-digital/python/pkt.py
new file mode 100644
index 000000000..8650bdbb0
--- /dev/null
+++ b/gr-digital/python/pkt.py
@@ -0,0 +1,167 @@
+#
+# Copyright 2005, 2006, 2007 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from math import pi
+from gnuradio import gr
+import gnuradio.gr.gr_threading as _threading
+import packet_utils
+import digital_swig
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# mod/demod with packets as i/o
+# /////////////////////////////////////////////////////////////////////////////
+
+class mod_pkts(gr.hier_block2):
+ """
+ Wrap an arbitrary digital modulator in our packet handling framework.
+
+ Send packets by calling send_pkt
+ """
+ def __init__(self, modulator, access_code=None, msgq_limit=2, pad_for_usrp=True,
+ use_whitener_offset=False, modulate=True):
+ """
+ Hierarchical block for sending packets
+
+ Packets to be sent are enqueued by calling send_pkt.
+ The output is the complex modulated signal at baseband.
+
+ @param modulator: instance of modulator class (gr_block or hier_block2)
+ @type modulator: complex baseband out
+ @param access_code: AKA sync vector
+ @type access_code: string of 1's and 0's between 1 and 64 long
+ @param msgq_limit: maximum number of messages in message queue
+ @type msgq_limit: int
+ @param pad_for_usrp: If true, packets are padded such that they end up a multiple of 128 samples
+ @param use_whitener_offset: If true, start of whitener XOR string is incremented each packet
+
+ See gmsk_mod for remaining parameters
+ """
+
+ gr.hier_block2.__init__(self, "mod_pkts",
+ gr.io_signature(0, 0, 0), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
+
+ self._modulator = modulator
+ self._pad_for_usrp = pad_for_usrp
+ self._use_whitener_offset = use_whitener_offset
+ self._whitener_offset = 0
+
+ if access_code is None:
+ access_code = packet_utils.default_access_code
+ if not packet_utils.is_1_0_string(access_code):
+ raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,)
+ self._access_code = access_code
+
+ # accepts messages from the outside world
+ self._pkt_input = gr.message_source(gr.sizeof_char, msgq_limit)
+ self.connect(self._pkt_input, self._modulator, self)
+
+ def send_pkt(self, payload='', eof=False):
+ """
+ Send the payload.
+
+ @param payload: data to send
+ @type payload: string
+ """
+ if eof:
+ msg = gr.message(1) # tell self._pkt_input we're not sending any more packets
+ else:
+ # print "original_payload =", string_to_hex_list(payload)
+ pkt = packet_utils.make_packet(payload,
+ self._modulator.samples_per_symbol(),
+ self._modulator.bits_per_symbol(),
+ self._access_code,
+ self._pad_for_usrp,
+ self._whitener_offset)
+ #print "pkt =", string_to_hex_list(pkt)
+ msg = gr.message_from_string(pkt)
+ if self._use_whitener_offset is True:
+ self._whitener_offset = (self._whitener_offset + 1) % 16
+
+ self._pkt_input.msgq().insert_tail(msg)
+
+
+
+class demod_pkts(gr.hier_block2):
+ """
+ Wrap an arbitrary digital demodulator in our packet handling framework.
+
+ The input is complex baseband. When packets are demodulated, they are passed to the
+ app via the callback.
+ """
+
+ def __init__(self, demodulator, access_code=None, callback=None, threshold=-1):
+ """
+ Hierarchical block for demodulating and deframing packets.
+
+ The input is the complex modulated signal at baseband.
+ Demodulated packets are sent to the handler.
+
+ @param demodulator: instance of demodulator class (gr_block or hier_block2)
+ @type demodulator: complex baseband in
+ @param access_code: AKA sync vector
+ @type access_code: string of 1's and 0's
+ @param callback: function of two args: ok, payload
+ @type callback: ok: bool; payload: string
+ @param threshold: detect access_code with up to threshold bits wrong (-1 -> use default)
+ @type threshold: int
+ """
+
+ gr.hier_block2.__init__(self, "demod_pkts",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+ gr.io_signature(0, 0, 0)) # Output signature
+
+ self._demodulator = demodulator
+ if access_code is None:
+ access_code = packet_utils.default_access_code
+ if not packet_utils.is_1_0_string(access_code):
+ raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,)
+ self._access_code = access_code
+
+ if threshold == -1:
+ threshold = 12 # FIXME raise exception
+
+ self._rcvd_pktq = gr.msg_queue() # holds packets from the PHY
+ self.correlator = digital_swig.correlate_access_code_bb(access_code, threshold)
+
+ self.framer_sink = gr.framer_sink_1(self._rcvd_pktq)
+ self.connect(self, self._demodulator, self.correlator, self.framer_sink)
+
+ self._watcher = _queue_watcher_thread(self._rcvd_pktq, callback)
+
+
+class _queue_watcher_thread(_threading.Thread):
+ def __init__(self, rcvd_pktq, callback):
+ _threading.Thread.__init__(self)
+ self.setDaemon(1)
+ self.rcvd_pktq = rcvd_pktq
+ self.callback = callback
+ self.keep_running = True
+ self.start()
+
+
+ def run(self):
+ while self.keep_running:
+ msg = self.rcvd_pktq.delete_head()
+ ok, payload = packet_utils.unmake_packet(msg.to_string(), int(msg.arg1()))
+ if self.callback:
+ self.callback(ok, payload)
diff --git a/gr-digital/python/psk.py b/gr-digital/python/psk.py
new file mode 100644
index 000000000..58f6787f0
--- /dev/null
+++ b/gr-digital/python/psk.py
@@ -0,0 +1,122 @@
+#
+# Copyright 2005,2006,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+"""
+PSK modulation and demodulation.
+"""
+
+from math import pi, log
+from cmath import exp
+
+import digital_swig
+import modulation_utils
+from utils import mod_codes, gray_code
+from generic_mod_demod import generic_mod, generic_demod
+
+# Default number of points in constellation.
+_def_constellation_points = 4
+# The default encoding (e.g. gray-code, set-partition)
+_def_mod_code = mod_codes.GRAY_CODE
+
+def create_encodings(mod_code, arity):
+ post_diff_code = None
+ if mod_code not in mod_codes.codes:
+ raise ValueError('That modulation code does not exist.')
+ if mod_code == mod_codes.GRAY_CODE:
+ pre_diff_code = gray_code.gray_code(arity)
+ elif mod_code == mod_codes.SET_PARTITION_CODE:
+ pre_diff_code = set_partition_code.set_partition_code(arity)
+ elif mod_code == mod_codes.NO_CODE:
+ pre_diff_code = []
+ else:
+ raise ValueError('That modulation code is not implemented for this constellation.')
+ return (pre_diff_code, post_diff_code)
+
+# /////////////////////////////////////////////////////////////////////////////
+# PSK constellation
+# /////////////////////////////////////////////////////////////////////////////
+
+def psk_constellation(m=_def_constellation_points, mod_code=_def_mod_code):
+ """
+ Creates a PSK constellation object.
+ """
+ k = log(m) / log(2.0)
+ if (k != int(k)):
+ raise StandardError('Number of constellation points must be a power of two.')
+ points = [exp(2*pi*(0+1j)*i/m) for i in range(0,m)]
+ pre_diff_code, post_diff_code = create_encodings(mod_code, m)
+ if post_diff_code is not None:
+ inverse_post_diff_code = mod_codes.invert_code(post_diff_code)
+ points = [points[x] for x in inverse_post_diff_code]
+ constellation = digital_swig.constellation_psk(points, pre_diff_code, m)
+ return constellation
+
+# /////////////////////////////////////////////////////////////////////////////
+# PSK modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class psk_mod(generic_mod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ mod_code=_def_mod_code,
+ *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered PSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_mod block for list of parameters.
+ """
+
+ constellation = psk_constellation(constellation_points, mod_code)
+ super(psk_mod, self).__init__(constellation, *args, **kwargs)
+
+# /////////////////////////////////////////////////////////////////////////////
+# PSK demodulator
+#
+# /////////////////////////////////////////////////////////////////////////////
+
+class psk_demod(generic_demod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ mod_code=_def_mod_code,
+ *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered PSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_demod block for list of parameters.
+ """
+
+ constellation = psk_constellation(constellation_points, mod_code)
+ super(psk_demod, self).__init__(constellation, *args, **kwargs)
+
+#
+# Add these to the mod/demod registry
+#
+modulation_utils.add_type_1_mod('psk', psk_mod)
+modulation_utils.add_type_1_demod('psk', psk_demod)
+modulation_utils.add_type_1_constellation('psk', psk_constellation)
diff --git a/gr-digital/python/qa_binary_slicer_fb.py b/gr-digital/python/qa_binary_slicer_fb.py
new file mode 100755
index 000000000..944dc1b5a
--- /dev/null
+++ b/gr-digital/python/qa_binary_slicer_fb.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+import math, random
+
+class test_constellation_decoder (gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test_binary_slicer_fb (self):
+ expected_result = ( 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1)
+ src_data = (-1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1)
+ src_data = [s + (1 - random.random()) for s in src_data] # add some noise
+ src = gr.vector_source_f (src_data)
+ op = digital_swig.binary_slicer_fb ()
+ dst = gr.vector_sink_b ()
+
+ self.tb.connect (src, op)
+ self.tb.connect (op, dst)
+ self.tb.run () # run the graph and wait for it to finish
+
+ actual_result = dst.data () # fetch the contents of the sink
+ #print "actual result", actual_result
+ #print "expected result", expected_result
+ self.assertFloatTuplesAlmostEqual (expected_result, actual_result)
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_constellation_decoder, "test_constellation_decoder.xml")
+
diff --git a/gr-digital/python/qa_clock_recovery_mm.py b/gr-digital/python/qa_clock_recovery_mm.py
new file mode 100755
index 000000000..f4c345b03
--- /dev/null
+++ b/gr-digital/python/qa_clock_recovery_mm.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+import random, cmath
+
+class test_clock_recovery_mm(gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test01 (self):
+ # Test complex/complex version
+ omega = 2
+ gain_omega = 0.001
+ mu = 0.5
+ gain_mu = 0.01
+ omega_rel_lim = 0.001
+
+ self.test = digital_swig.clock_recovery_mm_cc(omega, gain_omega,
+ mu, gain_mu,
+ omega_rel_lim)
+
+ data = 100*[complex(1, 1),]
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = 100*[complex(0.99972, 0.99972)] # doesn't quite get to 1.0
+ dst_data = self.snk.data()
+
+ # Only compare last Ncmp samples
+ Ncmp = 30
+ len_e = len(expected_result)
+ len_d = len(dst_data)
+ expected_result = expected_result[len_e - Ncmp:]
+ dst_data = dst_data[len_d - Ncmp:]
+
+ #print expected_result
+ #print dst_data
+
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 5)
+
+
+ def test02 (self):
+ # Test float/float version
+ omega = 2
+ gain_omega = 0.01
+ mu = 0.5
+ gain_mu = 0.01
+ omega_rel_lim = 0.001
+
+ self.test = digital_swig.clock_recovery_mm_ff(omega, gain_omega,
+ mu, gain_mu,
+ omega_rel_lim)
+
+ data = 100*[1,]
+ self.src = gr.vector_source_f(data, False)
+ self.snk = gr.vector_sink_f()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = 100*[0.99972, ] # doesn't quite get to 1.0
+ dst_data = self.snk.data()
+
+ # Only compare last Ncmp samples
+ Ncmp = 30
+ len_e = len(expected_result)
+ len_d = len(dst_data)
+ expected_result = expected_result[len_e - Ncmp:]
+ dst_data = dst_data[len_d - Ncmp:]
+
+ #print expected_result
+ #print dst_data
+
+ self.assertFloatTuplesAlmostEqual (expected_result, dst_data, 5)
+
+
+ def test03 (self):
+ # Test complex/complex version with varying input
+ omega = 2
+ gain_omega = 0.01
+ mu = 0.25
+ gain_mu = 0.1
+ omega_rel_lim = 0.0001
+
+ self.test = digital_swig.clock_recovery_mm_cc(omega, gain_omega,
+ mu, gain_mu,
+ omega_rel_lim)
+
+ data = 1000*[complex(1, 1), complex(1, 1), complex(-1, -1), complex(-1, -1)]
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = 1000*[complex(-1.2, -1.2), complex(1.2, 1.2)]
+ dst_data = self.snk.data()
+
+ # Only compare last Ncmp samples
+ Ncmp = 100
+ len_e = len(expected_result)
+ len_d = len(dst_data)
+ expected_result = expected_result[len_e - Ncmp:]
+ dst_data = dst_data[len_d - Ncmp:]
+
+ #print expected_result
+ #print dst_data
+
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 1)
+
+
+ def test04 (self):
+ # Test float/float version
+ omega = 2
+ gain_omega = 0.01
+ mu = 0.25
+ gain_mu = 0.1
+ omega_rel_lim = 0.001
+
+ self.test = digital_swig.clock_recovery_mm_ff(omega, gain_omega,
+ mu, gain_mu,
+ omega_rel_lim)
+
+ data = 1000*[1, 1, -1, -1]
+ self.src = gr.vector_source_f(data, False)
+ self.snk = gr.vector_sink_f()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = 1000*[-1.31, 1.31]
+ dst_data = self.snk.data()
+
+ # Only compare last Ncmp samples
+ Ncmp = 100
+ len_e = len(expected_result)
+ len_d = len(dst_data)
+ expected_result = expected_result[len_e - Ncmp:]
+ dst_data = dst_data[len_d - Ncmp:]
+
+ #print expected_result
+ #print dst_data
+
+ self.assertFloatTuplesAlmostEqual (expected_result, dst_data, 1)
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_clock_recovery_mm, "test_clock_recovery_mm.xml")
diff --git a/gr-digital/python/qa_cma_equalizer.py b/gr-digital/python/qa_cma_equalizer.py
new file mode 100755
index 000000000..75fb0f05e
--- /dev/null
+++ b/gr-digital/python/qa_cma_equalizer.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+#
+# Copyright 2006,2007,2010,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+
+class test_cma_equalizer_fir(gr_unittest.TestCase):
+
+ def setUp(self):
+ self.tb = gr.top_block()
+
+ def tearDown(self):
+ self.tb = None
+
+ def transform(self, src_data):
+ SRC = gr.vector_source_c(src_data, False)
+ EQU = digital_swig.cma_equalizer_cc(4, 1.0, .001, 1)
+ DST = gr.vector_sink_c()
+ self.tb.connect(SRC, EQU, DST)
+ self.tb.run()
+ return DST.data()
+
+ def test_001_identity(self):
+ # Constant modulus signal so no adjustments
+ src_data = (1+0j, 0+1j, -1+0j, 0-1j)*1000
+ expected_data = src_data
+ result = self.transform(src_data)
+ self.assertComplexTuplesAlmostEqual(expected_data, result)
+
+if __name__ == "__main__":
+ gr_unittest.run(test_cma_equalizer_fir, "test_cma_equalizer_fir.xml")
diff --git a/gr-digital/python/qa_constellation.py b/gr-digital/python/qa_constellation.py
new file mode 100755
index 000000000..b17d2a0fc
--- /dev/null
+++ b/gr-digital/python/qa_constellation.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import random
+from cmath import exp, pi, log
+
+from gnuradio import gr, gr_unittest, blks2
+from utils import mod_codes
+import digital_swig
+
+# import from local folder
+import psk
+import qam
+
+tested_mod_codes = (mod_codes.NO_CODE, mod_codes.GRAY_CODE)
+
+# A list of the constellations to test.
+# Each constellation is given by a 3-tuple.
+# First item is a function to generate the constellation
+# Second item is a dictionary of arguments for function with lists of
+# possible values.
+# Third item is whether differential encoding should be tested.
+# Fourth item is the name of the argument to constructor that specifices
+# whether differential encoding is used.
+
+def twod_constell():
+ """
+
+ """
+ points = ((1+0j), (0+1j),
+ (-1+0j), (0-1j))
+ rot_sym = 2
+ dim = 2
+ return digital_swig.constellation_calcdist(points, [], rot_sym, dim)
+
+def threed_constell():
+ oned_points = ((1+0j), (0+1j), (-1+0j), (0-1j))
+ points = []
+ r4 = range(0, 4)
+ for ia in r4:
+ for ib in r4:
+ for ic in r4:
+ points += [oned_points[ia], oned_points[ib], oned_points[ic]]
+ rot_sym = 4
+ dim = 3
+ return digital_swig.constellation_calcdist(points, [], rot_sym, dim)
+
+tested_constellation_info = (
+ (psk.psk_constellation,
+ {'m': (2, 4, 8, 16, 32, 64),
+ 'mod_code': tested_mod_codes, },
+ True, None),
+ (digital_swig.constellation_bpsk, {}, True, None),
+ (digital_swig.constellation_qpsk, {}, False, None),
+ (digital_swig.constellation_dqpsk, {}, True, None),
+ (digital_swig.constellation_8psk, {}, False, None),
+ (twod_constell, {}, True, None),
+ (threed_constell, {}, True, None),
+ )
+
+def tested_constellations():
+ """
+ Generator to produce (constellation, differential) tuples for testing purposes.
+ """
+ for constructor, poss_args, differential, diff_argname in tested_constellation_info:
+ if differential:
+ diff_poss = (True, False)
+ else:
+ diff_poss = (False,)
+ poss_args = [[argname, argvalues, 0] for argname, argvalues in poss_args.items()]
+ for current_diff in diff_poss:
+ # Add an index into args to keep track of current position in argvalues
+ while True:
+ current_args = dict([(argname, argvalues[argindex])
+ for argname, argvalues, argindex in poss_args])
+ if diff_argname is not None:
+ current_args[diff_argname] = current_diff
+ constellation = constructor(**current_args)
+ yield (constellation, current_diff)
+ for this_poss_arg in poss_args:
+ argname, argvalues, argindex = this_poss_arg
+ if argindex < len(argvalues) - 1:
+ this_poss_arg[2] += 1
+ break
+ else:
+ this_poss_arg[2] = 0
+ if sum([argindex for argname, argvalues, argindex in poss_args]) == 0:
+ break
+
+
+class test_constellation (gr_unittest.TestCase):
+
+ src_length = 256
+
+ def setUp(self):
+ # Generate a list of random bits.
+ self.src_data = tuple([random.randint(0,1) for i in range(0, self.src_length)])
+
+ def tearDown(self):
+ pass
+
+ def test_hard_decision(self):
+ for constellation, differential in tested_constellations():
+ if differential:
+ rs = constellation.rotational_symmetry()
+ rotations = [exp(i*2*pi*(0+1j)/rs) for i in range(0, rs)]
+ else:
+ rotations = [None]
+ for rotation in rotations:
+ src = gr.vector_source_b(self.src_data)
+ content = mod_demod(constellation, differential, rotation)
+ dst = gr.vector_sink_b()
+ self.tb = gr.top_block()
+ self.tb.connect(src, content, dst)
+ self.tb.run()
+ data = dst.data()
+ # Don't worry about cut off data for now.
+ first = constellation.bits_per_symbol()
+ self.assertEqual (self.src_data[first:len(data)], data[first:])
+
+
+class mod_demod(gr.hier_block2):
+ def __init__(self, constellation, differential, rotation):
+ if constellation.arity() > 256:
+ # If this becomes limiting some of the blocks should be generalised so
+ # that they can work with shorts and ints as well as chars.
+ raise ValueError("Constellation cannot contain more than 256 points.")
+
+ gr.hier_block2.__init__(self, "mod_demod",
+ gr.io_signature(1, 1, gr.sizeof_char), # Input signature
+ gr.io_signature(1, 1, gr.sizeof_char)) # Output signature
+
+ arity = constellation.arity()
+
+ # TX
+ self.constellation = constellation
+ self.differential = differential
+ self.blocks = [self]
+ # We expect a stream of unpacked bits.
+ # First step is to pack them.
+ self.blocks.append(
+ gr.unpacked_to_packed_bb(1, gr.GR_MSB_FIRST))
+ # Second step we unpack them such that we have k bits in each byte where
+ # each constellation symbol hold k bits.
+ self.blocks.append(
+ gr.packed_to_unpacked_bb(self.constellation.bits_per_symbol(),
+ gr.GR_MSB_FIRST))
+ # Apply any pre-differential coding
+ # Gray-coding is done here if we're also using differential coding.
+ if self.constellation.apply_pre_diff_code():
+ self.blocks.append(gr.map_bb(self.constellation.pre_diff_code()))
+ # Differential encoding.
+ if self.differential:
+ self.blocks.append(gr.diff_encoder_bb(arity))
+ # Convert to constellation symbols.
+ self.blocks.append(gr.chunks_to_symbols_bc(self.constellation.points(),
+ self.constellation.dimensionality()))
+ # CHANNEL
+ # Channel just consists of a rotation to check differential coding.
+ if rotation is not None:
+ self.blocks.append(gr.multiply_const_cc(rotation))
+
+ # RX
+ # Convert the constellation symbols back to binary values.
+ self.blocks.append(digital_swig.constellation_decoder_cb(self.constellation.base()))
+ # Differential decoding.
+ if self.differential:
+ self.blocks.append(gr.diff_decoder_bb(arity))
+ # Decode any pre-differential coding.
+ if self.constellation.apply_pre_diff_code():
+ self.blocks.append(gr.map_bb(
+ mod_codes.invert_code(self.constellation.pre_diff_code())))
+ # unpack the k bit vector into a stream of bits
+ self.blocks.append(gr.unpack_k_bits_bb(
+ self.constellation.bits_per_symbol()))
+ # connect to block output
+ check_index = len(self.blocks)
+ self.blocks = self.blocks[:check_index]
+ self.blocks.append(self)
+
+ self.connect(*self.blocks)
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_constellation, "test_constellation.xml")
diff --git a/gr-digital/python/qa_constellation_decoder_cb.py b/gr-digital/python/qa_constellation_decoder_cb.py
new file mode 100755
index 000000000..5401a07fc
--- /dev/null
+++ b/gr-digital/python/qa_constellation_decoder_cb.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+#
+# Copyright 2004,2007,2010,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+import math
+
+class test_constellation_decoder (gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test_constellation_decoder_cb_bpsk (self):
+ cnst = digital_swig.constellation_bpsk()
+ src_data = (0.5 + 0.5j, 0.1 - 1.2j, -0.8 - 0.1j, -0.45 + 0.8j,
+ 0.8 + 1.0j, -0.5 + 0.1j, 0.1 - 1.2j)
+ expected_result = ( 1, 1, 0, 0,
+ 1, 0, 1)
+ src = gr.vector_source_c (src_data)
+ op = digital_swig.constellation_decoder_cb (cnst.base())
+ dst = gr.vector_sink_b ()
+
+ self.tb.connect (src, op)
+ self.tb.connect (op, dst)
+ self.tb.run () # run the graph and wait for it to finish
+
+ actual_result = dst.data () # fetch the contents of the sink
+ #print "actual result", actual_result
+ #print "expected result", expected_result
+ self.assertFloatTuplesAlmostEqual (expected_result, actual_result)
+
+ def test_constellation_decoder_cb_qpsk (self):
+ cnst = digital_swig.constellation_qpsk()
+ src_data = (0.5 + 0.5j, 0.1 - 1.2j, -0.8 - 0.1j, -0.45 + 0.8j,
+ 0.8 + 1.0j, -0.5 + 0.1j, 0.1 - 1.2j)
+ expected_result = ( 3, 1, 0, 2,
+ 3, 2, 1)
+ src = gr.vector_source_c (src_data)
+ op = digital_swig.constellation_decoder_cb (cnst.base())
+ dst = gr.vector_sink_b ()
+
+ self.tb.connect (src, op)
+ self.tb.connect (op, dst)
+ self.tb.run () # run the graph and wait for it to finish
+
+ actual_result = dst.data () # fetch the contents of the sink
+ #print "actual result", actual_result
+ #print "expected result", expected_result
+ self.assertFloatTuplesAlmostEqual (expected_result, actual_result)
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_constellation_decoder, "test_constellation_decoder.xml")
+
diff --git a/gr-digital/python/qa_constellation_receiver.py b/gr-digital/python/qa_constellation_receiver.py
new file mode 100755
index 000000000..25107e4a7
--- /dev/null
+++ b/gr-digital/python/qa_constellation_receiver.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import random
+
+from gnuradio import gr, blks2, gr_unittest
+from utils import mod_codes, alignment
+import digital_swig, packet_utils
+from generic_mod_demod import generic_mod, generic_demod
+
+from qa_constellation import tested_constellations, twod_constell
+
+import math
+
+# Set a seed so that if errors turn up they are reproducible.
+# 1234 fails
+random.seed(1239)
+
+# TESTING PARAMETERS
+# The number of symbols to test with.
+# We need this many to let the frequency recovery block converge.
+DATA_LENGTH = 10000
+# Test fails if fraction of output that is correct is less than this.
+REQ_CORRECT = 0.8
+
+# CHANNEL PARAMETERS
+NOISE_VOLTAGE = 0.01
+FREQUENCY_OFFSET = 0.01
+TIMING_OFFSET = 1.0
+
+# RECEIVER PARAMETERS
+FREQ_BW = 2*math.pi/100.0
+PHASE_BW = 2*math.pi/100.0
+
+
+class test_constellation_receiver (gr_unittest.TestCase):
+
+ # We ignore the first half of the output data since often it takes
+ # a while for the receiver to lock on.
+ ignore_fraction = 0.8
+ seed = 1234
+ max_data_length = DATA_LENGTH * 6
+ max_num_samples = 1000
+
+ def test_basic(self):
+ """
+ Tests a bunch of different constellations by using generic
+ modulation, a channel, and generic demodulation. The generic
+ demodulation uses constellation_receiver which is what
+ we're really trying to test.
+ """
+
+ # Assumes not more than 64 points in a constellation
+ # Generates some random input data to use.
+ self.src_data = tuple(
+ [random.randint(0,1) for i in range(0, self.max_data_length)])
+ # Generates some random indices to use for comparing input and
+ # output data (a full comparison is too slow in python).
+ self.indices = alignment.random_sample(
+ self.max_data_length, self.max_num_samples, self.seed)
+
+ for constellation, differential in tested_constellations():
+ # The constellation_receiver doesn't work for constellations
+ # of multple dimensions (i.e. multiple complex numbers to a
+ # single symbol).
+ # That is not implemented since the receiver has no way of
+ # knowing where the beginning of a symbol is.
+ # It also doesn't work for non-differential modulation.
+ if constellation.dimensionality() != 1 or not differential:
+ continue
+ data_length = DATA_LENGTH * constellation.bits_per_symbol()
+ tb = rec_test_tb(constellation, differential,
+ src_data=self.src_data[:data_length])
+ tb.run()
+ data = tb.dst.data()
+ d1 = tb.src_data[:int(len(tb.src_data)*self.ignore_fraction)]
+ d2 = data[:int(len(data)*self.ignore_fraction)]
+ correct, overlap, offset, indices = alignment.align_sequences(
+ d1, d2, indices=self.indices)
+ self.assertTrue(correct > REQ_CORRECT)
+
+
+class rec_test_tb (gr.top_block):
+ """
+ Takes a constellation an runs a generic modulation, channel,
+ and generic demodulation.
+ """
+ def __init__(self, constellation, differential,
+ data_length=None, src_data=None):
+ """
+ constellation -- a constellation object
+ differential -- whether differential encoding is used
+ data_length -- the number of bits of data to use
+ src_data -- a list of the bits to use
+ """
+ super(rec_test_tb, self).__init__()
+ # Transmission Blocks
+ if src_data is None:
+ self.src_data = tuple([random.randint(0,1) for i in range(0, data_length)])
+ else:
+ self.src_data = src_data
+ packer = gr.unpacked_to_packed_bb(1, gr.GR_MSB_FIRST)
+ src = gr.vector_source_b(self.src_data)
+ mod = generic_mod(constellation, differential=differential)
+ # Channel
+ channel = gr.channel_model(NOISE_VOLTAGE, FREQUENCY_OFFSET, TIMING_OFFSET)
+ # Receiver Blocks
+ demod = generic_demod(constellation, differential=differential,
+ freq_bw=FREQ_BW,
+ phase_bw=PHASE_BW)
+ self.dst = gr.vector_sink_b()
+ self.connect(src, packer, mod, channel, demod, self.dst)
+
+if __name__ == '__main__':
+ gr_unittest.run(test_constellation_receiver, "test_constellation_receiver.xml")
diff --git a/gr-digital/python/qa_correlate_access_code.py b/gr-digital/python/qa_correlate_access_code.py
new file mode 100755
index 000000000..6b6f25051
--- /dev/null
+++ b/gr-digital/python/qa_correlate_access_code.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+#
+# Copyright 2006,2007,2010,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+import math
+
+default_access_code = '\xAC\xDD\xA4\xE2\xF2\x8C\x20\xFC'
+
+def string_to_1_0_list(s):
+ r = []
+ for ch in s:
+ x = ord(ch)
+ for i in range(8):
+ t = (x >> i) & 0x1
+ r.append(t)
+
+ return r
+
+def to_1_0_string(L):
+ return ''.join(map(lambda x: chr(x + ord('0')), L))
+
+class test_correlate_access_code(gr_unittest.TestCase):
+
+ def setUp(self):
+ self.tb = gr.top_block()
+
+ def tearDown(self):
+ self.tb = None
+
+ def test_001(self):
+ pad = (0,) * 64
+ # 0 0 0 1 0 0 0 1
+ src_data = (1, 0, 1, 1, 1, 1, 0, 1, 1) + pad + (0,) * 7
+ expected_result = pad + (1, 0, 1, 1, 3, 1, 0, 1, 1, 2) + (0,) * 6
+ src = gr.vector_source_b (src_data)
+ op = digital_swig.correlate_access_code_bb("1011", 0)
+ dst = gr.vector_sink_b ()
+ self.tb.connect (src, op, dst)
+ self.tb.run ()
+ result_data = dst.data ()
+ self.assertEqual (expected_result, result_data)
+
+
+ def test_002(self):
+ code = tuple(string_to_1_0_list(default_access_code))
+ access_code = to_1_0_string(code)
+ pad = (0,) * 64
+ #print code
+ #print access_code
+ src_data = code + (1, 0, 1, 1) + pad
+ expected_result = pad + code + (3, 0, 1, 1)
+ src = gr.vector_source_b (src_data)
+ op = digital_swig.correlate_access_code_bb(access_code, 0)
+ dst = gr.vector_sink_b ()
+ self.tb.connect (src, op, dst)
+ self.tb.run ()
+ result_data = dst.data ()
+ self.assertEqual (expected_result, result_data)
+
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_correlate_access_code, "test_correlate_access_code.xml")
+
diff --git a/gr-digital/python/qa_costas_loop_cc.py b/gr-digital/python/qa_costas_loop_cc.py
new file mode 100755
index 000000000..75fdbc2f8
--- /dev/null
+++ b/gr-digital/python/qa_costas_loop_cc.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig, psk
+import random, cmath
+
+class test_costas_loop_cc(gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test01 (self):
+ # test basic functionality by setting all gains to 0
+ natfreq = 0.0
+ order = 2
+ self.test = digital_swig.costas_loop_cc(natfreq, order)
+
+ data = 100*[complex(1,0),]
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = data
+ dst_data = self.snk.data()
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 5)
+
+ def test02 (self):
+ # Make sure it doesn't diverge given perfect data
+ natfreq = 0.25
+ order = 2
+ self.test = digital_swig.costas_loop_cc(natfreq, order)
+
+ data = [complex(2*random.randint(0,1)-1, 0) for i in xrange(100)]
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = data
+ dst_data = self.snk.data()
+
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 5)
+
+ def test03 (self):
+ # BPSK Convergence test with static rotation
+ natfreq = 0.25
+ order = 2
+ self.test = digital_swig.costas_loop_cc(natfreq, order)
+
+ rot = cmath.exp(0.2j) # some small rotation
+ data = [complex(2*random.randint(0,1)-1, 0) for i in xrange(100)]
+
+ N = 40 # settling time
+ expected_result = data[N:]
+ data = [rot*d for d in data]
+
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ dst_data = self.snk.data()[N:]
+
+ # generously compare results; the loop will converge near to, but
+ # not exactly on, the target data
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 2)
+
+ def test04 (self):
+ # QPSK Convergence test with static rotation
+ natfreq = 0.25
+ order = 4
+ self.test = digital_swig.costas_loop_cc(natfreq, order)
+
+ rot = cmath.exp(0.2j) # some small rotation
+ data = [complex(2*random.randint(0,1)-1, 2*random.randint(0,1)-1)
+ for i in xrange(100)]
+
+ N = 40 # settling time
+ expected_result = data[N:]
+ data = [rot*d for d in data]
+
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ dst_data = self.snk.data()[N:]
+
+ # generously compare results; the loop will converge near to, but
+ # not exactly on, the target data
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 2)
+
+ def test05 (self):
+ # 8PSK Convergence test with static rotation
+ natfreq = 0.25
+ order = 8
+ self.test = digital_swig.costas_loop_cc(natfreq, order)
+
+ rot = cmath.exp(-cmath.pi/8.0j) # rotate to match Costas rotation
+ const = psk.psk_constellation(order)
+ data = [random.randint(0,7) for i in xrange(100)]
+ data = [2*rot*const.points()[d] for d in data]
+
+ N = 40 # settling time
+ expected_result = data[N:]
+
+ rot = cmath.exp(0.1j) # some small rotation
+ data = [rot*d for d in data]
+
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ dst_data = self.snk.data()[N:]
+
+ # generously compare results; the loop will converge near to, but
+ # not exactly on, the target data
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 2)
+
+if __name__ == '__main__':
+ gr_unittest.run(test_costas_loop_cc, "test_costas_loop_cc.xml")
diff --git a/gr-digital/python/qa_cpm.py b/gr-digital/python/qa_cpm.py
new file mode 100755
index 000000000..12a84c76c
--- /dev/null
+++ b/gr-digital/python/qa_cpm.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+import numpy
+
+class test_cpm(gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def do_check_phase_shift(self, type, name):
+ sps = 2
+ L = 1
+ in_bits = (1,) * 20
+ src = gr.vector_source_b(in_bits, False)
+ cpm = digital_swig.cpmmod_bc(type, 0.5, sps, L)
+ arg = gr.complex_to_arg()
+ sink = gr.vector_sink_f()
+
+ self.tb.connect(src, cpm, arg, sink)
+ self.tb.run()
+
+ symbol_phases = numpy.array(sink.data()[sps*L-1::sps])
+ phase_diff = numpy.mod(numpy.subtract(symbol_phases[1:], symbol_phases[:-1]),
+ (2*numpy.pi,) * (len(symbol_phases)-1))
+ self.assertFloatTuplesAlmostEqual(tuple(phase_diff), (0.5 * numpy.pi,) * len(phase_diff), 5,
+ msg="Phase shift was not correct for CPM method " + name)
+
+ def test_001_lrec(self):
+ self.do_check_phase_shift(gr.cpm.LRC, 'LREC')
+
+ def test_001_lrc(self):
+ self.do_check_phase_shift(gr.cpm.LRC, 'LRC')
+
+ def test_001_lsrc(self):
+ self.do_check_phase_shift(gr.cpm.LSRC, 'LSRC')
+
+ def test_001_ltfm(self):
+ self.do_check_phase_shift(gr.cpm.TFM, 'TFM')
+
+ def test_001_lgmsk(self):
+ sps = 2
+ L = 5
+ bt = 0.3
+ in_bits = (1,) * 20
+ src = gr.vector_source_b(in_bits, False)
+ gmsk = digital_swig.gmskmod_bc(sps, bt, L)
+ arg = gr.complex_to_arg()
+ sink = gr.vector_sink_f()
+
+ self.tb.connect(src, gmsk, arg, sink)
+ self.tb.run()
+
+ symbol_phases = numpy.array(sink.data()[sps*L-1::sps])
+ phase_diff = numpy.mod(numpy.subtract(symbol_phases[1:], symbol_phases[:-1]),
+ (2*numpy.pi,) * (len(symbol_phases)-1))
+ self.assertFloatTuplesAlmostEqual(tuple(phase_diff), (0.5 * numpy.pi,) * len(phase_diff), 5,
+ msg="Phase shift was not correct for GMSK")
+
+ def test_phase_response(self):
+ phase_response = gr.cpm.phase_response(gr.cpm.LREC, 2, 4)
+ self.assertAlmostEqual(numpy.sum(phase_response), 1)
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_cpm, "test_cpm.xml")
+
diff --git a/gr-digital/python/qa_crc32.py b/gr-digital/python/qa_crc32.py
new file mode 100755
index 000000000..f86813f3f
--- /dev/null
+++ b/gr-digital/python/qa_crc32.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+import random, cmath
+
+class test_crc32(gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test01 (self):
+ data = 100*"0"
+ expected_result = 2943744955
+ result = digital_swig.crc32(data)
+ #print hex(result)
+
+ self.assertEqual (expected_result, result)
+
+ def test02 (self):
+ data = 100*"1"
+ expected_result = 2326594156
+ result = digital_swig.crc32(data)
+ #print hex(result)
+
+ self.assertEqual (expected_result, result)
+
+ def test03 (self):
+ data = 10*"0123456789"
+ expected_result = 3774345973
+ result = digital_swig.crc32(data)
+ #print hex(result)
+
+ self.assertEqual (expected_result, result)
+
+if __name__ == '__main__':
+ gr_unittest.run(test_crc32, "test_crc32.xml")
diff --git a/gr-digital/python/qa_digital.py b/gr-digital/python/qa_digital.py
new file mode 100755
index 000000000..97e35da56
--- /dev/null
+++ b/gr-digital/python/qa_digital.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+
+class test_digital(gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+if __name__ == '__main__':
+ gr_unittest.run(test_digital, "test_digital.xml")
diff --git a/gr-digital/python/qa_fll_band_edge.py b/gr-digital/python/qa_fll_band_edge.py
new file mode 100755
index 000000000..088eb2b68
--- /dev/null
+++ b/gr-digital/python/qa_fll_band_edge.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+import random, math
+
+class test_fll_band_edge_cc(gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test01 (self):
+ sps = 4
+ rolloff = 0.35
+ bw = 2*math.pi/100.0
+ ntaps = 45
+
+ # Create pulse shape filter
+ rrc_taps = gr.firdes.root_raised_cosine(
+ sps, sps, 1.0, rolloff, ntaps)
+
+ # The frequency offset to correct
+ foffset = 0.2 / (2.0*math.pi)
+
+ # Create a set of 1's and -1's, pulse shape and interpolate to sps
+ data = [2.0*random.randint(0, 2) - 1.0 for i in xrange(200)]
+ self.src = gr.vector_source_c(data, False)
+ self.rrc = gr.interp_fir_filter_ccf(sps, rrc_taps)
+
+ # Mix symbols with a complex sinusoid to spin them
+ self.nco = gr.sig_source_c(1, gr.GR_SIN_WAVE, foffset, 1)
+ self.mix = gr.multiply_cc()
+
+ # FLL will despin the symbols to an arbitrary phase
+ self.fll = digital_swig.fll_band_edge_cc(sps, rolloff, ntaps, bw)
+
+ # Create sinks for all outputs of the FLL
+ # we will only care about the freq and error outputs
+ self.vsnk_frq = gr.vector_sink_f()
+ self.nsnk_fll = gr.null_sink(gr.sizeof_gr_complex)
+ self.nsnk_phs = gr.null_sink(gr.sizeof_float)
+ self.nsnk_err = gr.null_sink(gr.sizeof_float)
+
+ # Connect the blocks
+ self.tb.connect(self.nco, (self.mix,1))
+ self.tb.connect(self.src, self.rrc, (self.mix,0))
+ self.tb.connect(self.mix, self.fll, self.nsnk_fll)
+ self.tb.connect((self.fll,1), self.vsnk_frq)
+ self.tb.connect((self.fll,2), self.nsnk_phs)
+ self.tb.connect((self.fll,3), self.nsnk_err)
+ self.tb.run()
+
+ N = 700
+ dst_data = self.vsnk_frq.data()[N:]
+
+ expected_result = len(dst_data)* [-0.20,]
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 4)
+
+if __name__ == '__main__':
+ gr_unittest.run(test_fll_band_edge_cc, "test_fll_band_edge_cc.xml")
diff --git a/gr-digital/python/qa_lms_equalizer.py b/gr-digital/python/qa_lms_equalizer.py
new file mode 100755
index 000000000..025c785aa
--- /dev/null
+++ b/gr-digital/python/qa_lms_equalizer.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+#
+# Copyright 2006,2007,2010,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+
+class test_lms_dd_equalizer(gr_unittest.TestCase):
+
+ def setUp(self):
+ self.tb = gr.top_block()
+
+ def tearDown(self):
+ self.tb = None
+
+ def transform(self, src_data, gain, const):
+ SRC = gr.vector_source_c(src_data, False)
+ EQU = digital_swig.lms_dd_equalizer_cc(4, gain, 1, const.base())
+ DST = gr.vector_sink_c()
+ self.tb.connect(SRC, EQU, DST)
+ self.tb.run()
+ return DST.data()
+
+ def test_001_identity(self):
+ # Constant modulus signal so no adjustments
+ const = digital_swig.constellation_qpsk()
+ src_data = const.points()*1000
+
+ N = 100 # settling time
+ expected_data = src_data[N:]
+ result = self.transform(src_data, 0.1, const)[N:]
+ self.assertComplexTuplesAlmostEqual(expected_data, result, 5)
+
+if __name__ == "__main__":
+ gr_unittest.run(test_lms_dd_equalizer, "test_lms_dd_equalizer.xml")
diff --git a/gr-digital/python/qa_mpsk_receiver.py b/gr-digital/python/qa_mpsk_receiver.py
new file mode 100755
index 000000000..e1f16ee67
--- /dev/null
+++ b/gr-digital/python/qa_mpsk_receiver.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig
+import random, cmath
+
+class test_mpsk_receiver(gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test01 (self):
+ # Test BPSK sync
+ M = 2
+ theta = 0
+ loop_bw = cmath.pi/100.0
+ fmin = -0.5
+ fmax = 0.5
+ mu = 0.25
+ gain_mu = 0.01
+ omega = 2
+ gain_omega = 0.001
+ omega_rel = 0.001
+
+ self.test = digital_swig.mpsk_receiver_cc(M, theta, loop_bw,
+ fmin, fmax, mu, gain_mu,
+ omega, gain_omega,
+ omega_rel)
+
+ data = 1000*[complex(1,0), complex(1,0), complex(-1,0), complex(-1,0)]
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = 1000*[complex(-0.5,0), complex(0.5,0)]
+ dst_data = self.snk.data()
+
+ # Only compare last Ncmp samples
+ Ncmp = 100
+ len_e = len(expected_result)
+ len_d = len(dst_data)
+ expected_result = expected_result[len_e - Ncmp:]
+ dst_data = dst_data[len_d - Ncmp:]
+
+ #for e,d in zip(expected_result, dst_data):
+ # print e, d
+
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 1)
+
+
+ def test02 (self):
+ # Test QPSK sync
+ M = 4
+ theta = 0
+ loop_bw = 2*cmath.pi/100.0
+ fmin = -0.5
+ fmax = 0.5
+ mu = 0.25
+ gain_mu = 0.01
+ omega = 2
+ gain_omega = 0.001
+ omega_rel = 0.001
+
+ self.test = digital_swig.mpsk_receiver_cc(M, theta, loop_bw,
+ fmin, fmax, mu, gain_mu,
+ omega, gain_omega,
+ omega_rel)
+
+ data = 1000*[complex( 0.707, 0.707), complex( 0.707, 0.707),
+ complex(-0.707, 0.707), complex(-0.707, 0.707),
+ complex(-0.707, -0.707), complex(-0.707, -0.707),
+ complex( 0.707, -0.707), complex( 0.707, -0.707)]
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = 1000*[complex(0, -1.0), complex(1.0, 0),
+ complex(0, 1.0), complex(-1.0, 0)]
+ dst_data = self.snk.data()
+
+ # Only compare last Ncmp samples
+ Ncmp = 100
+ len_e = len(expected_result)
+ len_d = len(dst_data)
+ expected_result = expected_result[len_e - Ncmp:]
+ dst_data = dst_data[len_d - Ncmp:]
+
+ #for e,d in zip(expected_result, dst_data):
+ # print e, d
+
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 1)
+
+if __name__ == '__main__':
+ gr_unittest.run(test_mpsk_receiver, "test_mpsk_receiver.xml")
diff --git a/gr-digital/python/qa_ofdm_insert_preamble.py b/gr-digital/python/qa_ofdm_insert_preamble.py
new file mode 100755
index 000000000..c45893fa3
--- /dev/null
+++ b/gr-digital/python/qa_ofdm_insert_preamble.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+#
+# Copyright 2007,2010,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from gnuradio import gr, gr_unittest
+from pprint import pprint
+import digital_swig
+
+class test_ofdm_insert_preamble (gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def helper(self, v0, v1, fft_length, preamble):
+ tb = self.tb
+ src0 = gr.vector_source_c(v0)
+ src1 = gr.vector_source_b(v1)
+
+ s2v = gr.stream_to_vector(gr.sizeof_gr_complex, fft_length)
+
+ # print "len(v) = %d" % (len(v))
+
+ op = digital_swig.ofdm_insert_preamble(fft_length, preamble)
+
+ v2s = gr.vector_to_stream(gr.sizeof_gr_complex, fft_length)
+ dst0 = gr.vector_sink_c()
+ dst1 = gr.vector_sink_b()
+
+ tb.connect(src0, s2v, (op, 0))
+ tb.connect(src1, (op, 1))
+ tb.connect((op, 0), v2s, dst0)
+ tb.connect((op, 1), dst1)
+
+ tb.run()
+ r0 = dst0.data()
+ r0v = []
+ for i in range(len(r0)//fft_length):
+ r0v.append(r0[i*fft_length:(i+1)*fft_length])
+
+ r1 = dst1.data()
+ self.assertEqual(len(r0v), len(r1))
+ return (r1, r0v)
+
+ def check_match(self, actual, expected_list):
+ lst = []
+ map(lambda x: lst.append(x), expected_list)
+ self.assertEqual(actual, lst)
+
+
+ # ----------------------------------------------------------------
+
+ def test_000(self):
+ # no preamble, 1 symbol payloads
+
+ preamble = ()
+ fft_length = 8
+ npayloads = 8
+ v = []
+ p = []
+ for i in range(npayloads):
+ t = fft_length*[(i + i*1j)]
+ p.append(tuple(t))
+ v += t
+
+ p = tuple(p)
+
+ r = self.helper(v, npayloads*[1], fft_length, preamble)
+ # pprint(r)
+
+ self.assertEqual(r[0], tuple(npayloads*[1]))
+ self.check_match(r[1], (p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]))
+
+
+ def test_001(self):
+ # 1 symbol preamble, 1 symbol payloads
+ preamble = ((100, 101, 102, 103, 104, 105, 106, 107),)
+ p0 = preamble[0]
+ fft_length = 8
+ npayloads = 8
+ v = []
+ p = []
+ for i in range(npayloads):
+ t = fft_length*[(i + i*1j)]
+ p.append(tuple(t))
+ v += t
+
+
+ r = self.helper(v, npayloads*[1], fft_length, preamble)
+
+ self.assertEqual(r[0], tuple(npayloads*[1, 0]))
+ self.check_match(r[1], (p0, p[0],
+ p0, p[1],
+ p0, p[2],
+ p0, p[3],
+ p0, p[4],
+ p0, p[5],
+ p0, p[6],
+ p0, p[7]))
+
+ def test_002(self):
+ # 2 symbol preamble, 1 symbol payloads
+ preamble = ((100, 101, 102, 103, 104, 105, 106, 107),
+ (200, 201, 202, 203, 204, 205, 206, 207))
+ p0 = preamble[0]
+ p1 = preamble[1]
+
+ fft_length = 8
+ npayloads = 8
+ v = []
+ p = []
+ for i in range(npayloads):
+ t = fft_length*[(i + i*1j)]
+ p.append(tuple(t))
+ v += t
+
+ r = self.helper(v, npayloads*[1], fft_length, preamble)
+
+ self.assertEqual(r[0], tuple(npayloads*[1, 0, 0]))
+ self.check_match(r[1], (p0, p1, p[0],
+ p0, p1, p[1],
+ p0, p1, p[2],
+ p0, p1, p[3],
+ p0, p1, p[4],
+ p0, p1, p[5],
+ p0, p1, p[6],
+ p0, p1, p[7]))
+
+
+ def xtest_003_preamble(self):
+ # 2 symbol preamble, 2 symbol payloads
+ preamble = ((100, 101, 102, 103, 104, 105, 106, 107),
+ (200, 201, 202, 203, 204, 205, 206, 207))
+ p0 = preamble[0]
+ p1 = preamble[1]
+
+ fft_length = 8
+ npayloads = 8
+ v = []
+ p = []
+ for i in range(npayloads * 2):
+ t = fft_length*[(i + i*1j)]
+ p.append(tuple(t))
+ v += t
+
+ r = self.helper(v, npayloads*[1, 0], fft_length, preamble)
+
+ self.assertEqual(r[0], tuple(npayloads*[1, 0, 0, 0]))
+ self.check_match(r[1], (p0, p1, p[0], p[1],
+ p0, p1, p[2], p[3],
+ p0, p1, p[4], p[5],
+ p0, p1, p[6], p[7],
+ p0, p1, p[8], p[9],
+ p0, p1, p[10], p[11],
+ p0, p1, p[12], p[13],
+ p0, p1, p[14], p[15]))
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_ofdm_insert_preamble, "test_ofdm_insert_preamble.xml")
diff --git a/gr-digital/python/qam.py b/gr-digital/python/qam.py
new file mode 100644
index 000000000..5b1f7683b
--- /dev/null
+++ b/gr-digital/python/qam.py
@@ -0,0 +1,229 @@
+#
+# Copyright 2005,2006,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+"""
+QAM modulation and demodulation.
+"""
+
+from math import pi, sqrt, log
+
+from gnuradio import gr
+from generic_mod_demod import generic_mod, generic_demod
+from utils.gray_code import gray_code
+from utils import mod_codes
+import modulation_utils
+import digital_swig
+
+# Default number of points in constellation.
+_def_constellation_points = 16
+# Whether the quadrant bits are coded differentially.
+_def_differential = True
+# Whether gray coding is used. If differential is True then gray
+# coding is used within but not between each quadrant.
+_def_mod_code = mod_codes.NO_CODE
+
+def is_power_of_four(x):
+ v = log(x)/log(4)
+ return int(v) == v
+
+def get_bit(x, n):
+ """ Get the n'th bit of integer x (from little end)."""
+ return (x&(0x01 << n)) >> n
+
+def get_bits(x, n, k):
+ """ Get the k bits of integer x starting at bit n(from little end)."""
+ # Remove the n smallest bits
+ v = x >> n
+ # Remove all bits bigger than n+k-1
+ return v % pow(2, k)
+
+def make_differential_constellation(m, gray_coded):
+ """
+ Create a constellation with m possible symbols where m must be
+ a power of 4.
+
+ Points are laid out in a square grid.
+
+ Bits referring to the quadrant are differentilly encoded,
+ remaining bits are gray coded.
+
+ """
+ sqrtm = pow(m, 0.5)
+ if (not isinstance(m, int) or m < 4 or not is_power_of_four(m)):
+ raise ValueError("m must be a power of 4 integer.")
+ # Each symbol holds k bits.
+ k = int(log(m) / log(2.0))
+ # First create a constellation for one quadrant containing m/4 points.
+ # The quadrant has 'side' points along each side of a quadrant.
+ side = int(sqrtm/2)
+ if gray_coded:
+ # Number rows and columns using gray codes.
+ gcs = gray_code(side)
+ # Get inverse gray codes.
+ i_gcs = dict([(v, key) for key, v in enumerate(gcs)])
+ else:
+ i_gcs = dict([(i, i) for i in range(0, side)])
+ # The distance between points is found.
+ step = 1/(side-0.5)
+
+ gc_to_x = [(i_gcs[gc]+0.5)*step for gc in range(0, side)]
+
+ # Takes the (x, y) location of the point with the quadrant along
+ # with the quadrant number. (x, y) are integers referring to which
+ # point within the quadrant it is.
+ # A complex number representing this location of this point is returned.
+ def get_c(gc_x, gc_y, quad):
+ if quad == 0:
+ return complex(gc_to_x[gc_x], gc_to_x[gc_y])
+ if quad == 1:
+ return complex(-gc_to_x[gc_y], gc_to_x[gc_x])
+ if quad == 2:
+ return complex(-gc_to_x[gc_x], -gc_to_x[gc_y])
+ if quad == 3:
+ return complex(gc_to_x[gc_y], -gc_to_x[gc_x])
+ raise StandardError("Impossible!")
+
+ # First two bits determine quadrant.
+ # Next (k-2)/2 bits determine x position.
+ # Following (k-2)/2 bits determine y position.
+ # How x and y relate to real and imag depends on quadrant (see get_c function).
+ const_map = []
+ for i in range(m):
+ y = get_bits(i, 0, (k-2)/2)
+ x = get_bits(i, (k-2)/2, (k-2)/2)
+ quad = get_bits(i, k-2, 2)
+ const_map.append(get_c(x, y, quad))
+
+ return const_map
+
+def make_non_differential_constellation(m, gray_coded):
+ side = int(pow(m, 0.5))
+ if (not isinstance(m, int) or m < 4 or not is_power_of_four(m)):
+ raise ValueError("m must be a power of 4 integer.")
+ # Each symbol holds k bits.
+ k = int(log(m) / log(2.0))
+ if gray_coded:
+ # Number rows and columns using gray codes.
+ gcs = gray_code(side)
+ # Get inverse gray codes.
+ i_gcs = mod_codes.invert_code(gcs)
+ else:
+ i_gcs = range(0, side)
+ # The distance between points is found.
+ step = 2.0/(side-1)
+
+ gc_to_x = [-1 + i_gcs[gc]*step for gc in range(0, side)]
+ # First k/2 bits determine x position.
+ # Following k/2 bits determine y position.
+ const_map = []
+ for i in range(m):
+ y = gc_to_x[get_bits(i, 0, k/2)]
+ x = gc_to_x[get_bits(i, k/2, k/2)]
+ const_map.append(complex(x,y))
+ return const_map
+
+# /////////////////////////////////////////////////////////////////////////////
+# QAM constellation
+# /////////////////////////////////////////////////////////////////////////////
+
+def qam_constellation(constellation_points=_def_constellation_points,
+ differential=_def_differential,
+ mod_code=_def_mod_code):
+ """
+ Creates a QAM constellation object.
+ """
+ if mod_code == mod_codes.GRAY_CODE:
+ gray_coded = True
+ elif mod_code == mod_codes.NO_CODE:
+ gray_coded = False
+ else:
+ raise ValueError("Mod code is not implemented for QAM")
+ if differential:
+ points = make_differential_constellation(constellation_points, gray_coded)
+ else:
+ points = make_non_differential_constellation(constellation_points, gray_coded)
+ side = int(sqrt(constellation_points))
+ width = 2.0/(side-1)
+ # No pre-diff code
+ # Should add one so that we can gray-code the quadrant bits too.
+ pre_diff_code = []
+ constellation = digital_swig.constellation_rect(points, pre_diff_code, 4,
+ side, side, width, width)
+ return constellation
+
+# /////////////////////////////////////////////////////////////////////////////
+# QAM modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class qam_mod(generic_mod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ differential=_def_differential,
+ mod_code=_def_mod_code,
+ *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered QAM modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_mod block for list of parameters.
+ """
+
+ constellation = qam_constellation(constellation_points, differential, mod_code)
+ # We take care of the gray coding in the constellation generation so it doesn't
+ # need to be done in the block.
+ super(qam_mod, self).__init__(constellation, differential=differential,
+ *args, **kwargs)
+
+# /////////////////////////////////////////////////////////////////////////////
+# QAM demodulator
+#
+# /////////////////////////////////////////////////////////////////////////////
+
+class qam_demod(generic_demod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ differential=_def_differential,
+ mod_code=_def_mod_code,
+ *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered QAM modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_demod block for list of parameters.
+ """
+ constellation = qam_constellation(constellation_points, differential, mod_code)
+ # We take care of the gray coding in the constellation generation so it doesn't
+ # need to be done in the block.
+ super(qam_demod, self).__init__(constellation, differential=differential,
+ *args, **kwargs)
+
+#
+# Add these to the mod/demod registry
+#
+modulation_utils.add_type_1_mod('qam', qam_mod)
+modulation_utils.add_type_1_demod('qam', qam_demod)
+modulation_utils.add_type_1_constellation('qam', qam_constellation)
diff --git a/gr-digital/python/qpsk.py b/gr-digital/python/qpsk.py
new file mode 100644
index 000000000..be21fd76f
--- /dev/null
+++ b/gr-digital/python/qpsk.py
@@ -0,0 +1,176 @@
+#
+# Copyright 2005,2006,2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+"""
+QPSK modulation.
+
+Demodulation is not included since the generic_mod_demod
+"""
+
+from gnuradio import gr
+from gnuradio.digital.generic_mod_demod import generic_mod, generic_demod
+import digital_swig
+import modulation_utils
+
+# Default number of points in constellation.
+_def_constellation_points = 4
+# Whether gray coding is used.
+_def_gray_coded = True
+
+# /////////////////////////////////////////////////////////////////////////////
+# QPSK constellation
+# /////////////////////////////////////////////////////////////////////////////
+
+def qpsk_constellation(m=_def_constellation_points):
+ if m != _def_constellation_points:
+ raise ValueError("QPSK can only have 4 constellation points.")
+ return digital_swig.constellation_qpsk()
+
+# /////////////////////////////////////////////////////////////////////////////
+# QPSK modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class qpsk_mod(generic_mod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ gray_coded=_def_gray_coded,
+ *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered QPSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_mod block for list of parameters.
+ """
+
+ constellation_points = _def_constellation_points
+ constellation = digital_swig.constellation_qpsk()
+ if constellation_points != 4:
+ raise ValueError("QPSK can only have 4 constellation points.")
+ if not gray_coded:
+ raise ValueError("This QPSK mod/demod works only for gray-coded constellations.")
+ super(qpsk_mod, self).__init__(constellation=constellation,
+ gray_coded=gray_coded,
+ *args, **kwargs)
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# QPSK demodulator
+#
+# /////////////////////////////////////////////////////////////////////////////
+
+class qpsk_demod(generic_demod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered QPSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_demod block for list of parameters.
+ """
+
+ constellation_points = _def_constellation_points
+ constellation = digital_swig.constellation_qpsk()
+ if constellation_points != 4:
+ raise ValueError('Number of constellation points must be 4 for QPSK.')
+ super(qpsk_demod, self).__init__(constellation=constellation,
+ *args, **kwargs)
+
+
+
+# /////////////////////////////////////////////////////////////////////////////
+# DQPSK constellation
+# /////////////////////////////////////////////////////////////////////////////
+
+def dqpsk_constellation(m=_def_constellation_points):
+ if m != _def_constellation_points:
+ raise ValueError("DQPSK can only have 4 constellation points.")
+ return digital_swig.constellation_dqpsk()
+
+# /////////////////////////////////////////////////////////////////////////////
+# DQPSK modulator
+# /////////////////////////////////////////////////////////////////////////////
+
+class dqpsk_mod(generic_mod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ gray_coded=_def_gray_coded,
+ differential=True, *args, **kwargs):
+ """
+ Hierarchical block for RRC-filtered DQPSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_mod block for list of parameters.
+ """
+
+ constellation_points = _def_constellation_points
+ constellation = digital_swig.constellation_dqpsk()
+ if constellation_points != 4:
+ raise ValueError('Number of constellation points must be 4 for DQPSK.')
+ super(dqpsk_mod, self).__init__(constellation=constellation,
+ gray_coded=gray_coded,
+ differential=True,
+ *args, **kwargs)
+
+# /////////////////////////////////////////////////////////////////////////////
+# DQPSK demodulator
+#
+# /////////////////////////////////////////////////////////////////////////////
+
+class dqpsk_demod(generic_demod):
+
+ def __init__(self, constellation_points=_def_constellation_points,
+ differential=True, *args, **kwargs):
+
+ """
+ Hierarchical block for RRC-filtered DQPSK modulation.
+
+ The input is a byte stream (unsigned char) and the
+ output is the complex modulated signal at baseband.
+
+ See generic_demod block for list of parameters.
+ """
+ constellation_points = _def_constellation_points
+ constellation = digital_swig.constellation_dqpsk()
+ if constellation_points != 4:
+ raise ValueError('Number of constellation points must be 4 for DQPSK.')
+ super(dqpsk_demod, self).__init__(constellation=constellation,
+ differential=True,
+ *args, **kwargs)
+
+#
+# Add these to the mod/demod registry
+#
+modulation_utils.add_type_1_mod('qpsk', qpsk_mod)
+modulation_utils.add_type_1_demod('qpsk', qpsk_demod)
+modulation_utils.add_type_1_constellation('qpsk', qpsk_constellation)
+modulation_utils.add_type_1_mod('dqpsk', dqpsk_mod)
+modulation_utils.add_type_1_demod('dqpsk', dqpsk_demod)
+modulation_utils.add_type_1_constellation('dqpsk', dqpsk_constellation)
+
diff --git a/gr-digital/python/run_tests.in b/gr-digital/python/run_tests.in
new file mode 100644
index 000000000..b39e7e847
--- /dev/null
+++ b/gr-digital/python/run_tests.in
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# 1st parameter is absolute path to component source directory
+# 2nd parameter is absolute path to component build directory
+# 3rd parameter is path to Python QA directory
+
+@top_builddir@/run_tests.sh \
+ @abs_top_srcdir@/gr-digital \
+ @abs_top_builddir@/gr-digital \
+ @srcdir@
diff --git a/gr-digital/python/utils/.gitignore b/gr-digital/python/utils/.gitignore
new file mode 100644
index 000000000..60c81fdce
--- /dev/null
+++ b/gr-digital/python/utils/.gitignore
@@ -0,0 +1,3 @@
+run_tests
+Makefile
+Makefile.in
diff --git a/gr-digital/python/utils/Makefile.am b/gr-digital/python/utils/Makefile.am
new file mode 100644
index 000000000..6da4d61dd
--- /dev/null
+++ b/gr-digital/python/utils/Makefile.am
@@ -0,0 +1,34 @@
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+include $(top_srcdir)/Makefile.common
+
+if PYTHON
+utilspythondir = $(grpythondir)/digital/utils
+
+TESTS =
+
+utilspython_PYTHON = \
+ __init__.py \
+ gray_code.py \
+ mod_codes.py \
+ alignment.py
+endif \ No newline at end of file
diff --git a/gr-digital/python/utils/__init__.py b/gr-digital/python/utils/__init__.py
new file mode 100644
index 000000000..b3e997f9f
--- /dev/null
+++ b/gr-digital/python/utils/__init__.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
diff --git a/gr-digital/python/utils/alignment.py b/gr-digital/python/utils/alignment.py
new file mode 100644
index 000000000..d32365866
--- /dev/null
+++ b/gr-digital/python/utils/alignment.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+"""
+This module contains functions for aligning sequences.
+
+>>> import random
+>>> random.seed(1234)
+>>> ran_seq = [random.randint(0,1) for i in range(0, 100)]
+>>> offset_seq = [0] * 20 + ran_seq
+>>> correct, overlap, offset = align_sequences(ran_seq, offset_seq)
+>>> print(correct, overlap, offset)
+(1.0, 100, -20)
+>>> offset_err_seq = []
+>>> for bit in offset_seq:
+... if random.randint(0,4) == 4:
+... offset_err_seq.append(random.randint(0,1))
+... else:
+... offset_err_seq.append(bit)
+>>> correct, overlap, offset = align_sequences(ran_seq, offset_err_seq)
+>>> print(overlap, offset)
+(100, -20)
+
+"""
+
+import random
+
+# DEFAULT PARAMETERS
+# If the fraction of matching bits between two sequences is greater than
+# this the sequences are assumed to be aligned.
+def_correct_cutoff = 0.9
+# The maximum offset to test during sequence alignment.
+def_max_offset = 500
+# The maximum number of samples to take from two sequences to check alignment.
+def_num_samples = 1000
+
+def compare_sequences(d1, d2, offset, sample_indices=None):
+ """
+ Takes two binary sequences and an offset and returns the number of
+ matching entries and the number of compared entries.
+ d1 & d2 -- sequences
+ offset -- offset of d2 relative to d1
+ sample_indices -- a list of indices to use for the comparison
+ """
+ max_index = min(len(d1), len(d2)+offset)
+ if sample_indices is None:
+ sample_indices = range(0, max_index)
+ correct = 0
+ total = 0
+ for i in sample_indices:
+ if i >= max_index:
+ break
+ if d1[i] == d2[i-offset]:
+ correct += 1
+ total += 1
+ return (correct, total)
+
+def random_sample(size, num_samples=def_num_samples, seed=None):
+ """
+ Returns a set of random integers between 0 and (size-1).
+ The set contains no more than num_samples integers.
+ """
+ random.seed(seed)
+ if num_samples > size:
+ indices = set(range(0, size))
+ else:
+ if num_samples > size/2:
+ num_samples = num_samples/2
+ indices = set([])
+ while len(indices) < num_samples:
+ index = random.randint(0, size-1)
+ indices.add(index)
+ indices = list(indices)
+ indices.sort()
+ return indices
+
+def align_sequences(d1, d2,
+ num_samples=def_num_samples,
+ max_offset=def_max_offset,
+ correct_cutoff=def_correct_cutoff,
+ seed=None,
+ indices=None):
+ """
+ Takes two sequences and finds the offset and which the two sequences best
+ match. It returns the fraction correct, the number of entries compared,
+ the offset.
+ d1 & d2 -- sequences to compare
+ num_samples -- the maximum number of entries to compare
+ max_offset -- the maximum offset between the sequences that is checked
+ correct_cutoff -- If the fraction of bits correct is greater than this then
+ the offset is assumed to optimum.
+ seed -- a random number seed
+ indices -- an explicit list of the indices used to compare the two sequences
+ """
+ max_overlap = max(len(d1), len(d2))
+ if indices is None:
+ indices = random_sample(max_overlap, num_samples, seed)
+ max_frac_correct = 0
+ best_offset = None
+ best_compared = None
+ best_correct = None
+ pos_range = range(0, min(len(d1), max_offset))
+ neg_range = range(-1, -min(len(d2), max_offset), -1)
+ # Interleave the positive and negative offsets.
+ int_range = [item for items in zip(pos_range, neg_range) for item in items]
+ for offset in int_range:
+ correct, compared = compare_sequences(d1, d2, offset, indices)
+ frac_correct = 1.0*correct/compared
+ if frac_correct > max_frac_correct:
+ max_frac_correct = frac_correct
+ best_offset = offset
+ best_compared = compared
+ best_correct = correct
+ if frac_correct > correct_cutoff:
+ break
+ return max_frac_correct, best_compared, best_offset, indices
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
+
diff --git a/gr-digital/python/utils/gray_code.py b/gr-digital/python/utils/gray_code.py
new file mode 100644
index 000000000..926a1ded1
--- /dev/null
+++ b/gr-digital/python/utils/gray_code.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+class GrayCodeGenerator(object):
+ """
+ Generates and caches gray codes.
+ """
+
+ def __init__(self):
+ self.gcs = [0, 1]
+ # The last power of two passed through.
+ self.lp2 = 2
+ # The next power of two that will be passed through.
+ self.np2 = 4
+ # Curent index
+ self.i = 2
+
+ def get_gray_code(self, length):
+ """
+ Returns a list of gray code of given length.
+ """
+ if len(self.gcs) < length:
+ self.generate_new_gray_code(length)
+ return self.gcs[:length]
+
+ def generate_new_gray_code(self, length):
+ """
+ Generates new gray code and places into cache.
+ """
+ while len(self.gcs) < length:
+ if self.i == self.lp2:
+ # if i is a power of two then gray number is of form 1100000...
+ result = self.i + self.i/2
+ else:
+ # if not we take advantage of the symmetry of all but the last bit
+ # around a power of two.
+ result = self.gcs[2*self.lp2-1-self.i] + self.lp2
+ self.gcs.append(result)
+ self.i += 1
+ if self.i == self.np2:
+ self.lp2 = self.i
+ self.np2 = self.i*2
+
+_gray_code_generator = GrayCodeGenerator()
+
+gray_code = _gray_code_generator.get_gray_code
+
diff --git a/gr-digital/python/utils/mod_codes.py b/gr-digital/python/utils/mod_codes.py
new file mode 100644
index 000000000..caacda5cc
--- /dev/null
+++ b/gr-digital/python/utils/mod_codes.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+GRAY_CODE = 'gray'
+SET_PARTITION_CODE = 'set-partition'
+NO_CODE = 'none'
+
+codes = (GRAY_CODE, SET_PARTITION_CODE, NO_CODE)
+
+def invert_code(code):
+ c = enumerate(code)
+ ic = [(b, a) for (a, b) in c]
+ ic.sort()
+ return [a for (b, a) in ic]