diff options
Diffstat (limited to 'tools/gr-usrptest/python')
18 files changed, 1253 insertions, 0 deletions
diff --git a/tools/gr-usrptest/python/CMakeLists.txt b/tools/gr-usrptest/python/CMakeLists.txt new file mode 100644 index 000000000..7bb812b0c --- /dev/null +++ b/tools/gr-usrptest/python/CMakeLists.txt @@ -0,0 +1,49 @@ +# 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 python install macros +######################################################################## +include(GrPython) +if(NOT PYTHONINTERP_FOUND) + return() +endif() + +######################################################################## +# Install python sources +######################################################################## +GR_PYTHON_INSTALL( + FILES + __init__.py + functions.py + phase_calc_ccf.py DESTINATION ${GR_PYTHON_DIR}/usrptest +) + +add_subdirectory(flowgraphs) +add_subdirectory(labview_control) +add_subdirectory(rts_tests) + +######################################################################## +# Handle the unit tests +######################################################################## +include(GrTest) + +set(GR_TEST_TARGET_DEPS gnuradio-usrptest) +set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig) +GR_ADD_TEST(qa_measurement_sink_f ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_measurement_sink_f.py) diff --git a/tools/gr-usrptest/python/__init__.py b/tools/gr-usrptest/python/__init__.py new file mode 100644 index 000000000..f4265974a --- /dev/null +++ b/tools/gr-usrptest/python/__init__.py @@ -0,0 +1,35 @@ +# +# Copyright 2008,2009 Free Software Foundation, Inc. +# +# This application 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. +# +# This application 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. +# + +# The presence of this file turns this directory into a Python package + +''' +This is the GNU Radio USRPTEST module. Place your Python package +description here (python/__init__.py). +''' + +# import swig generated symbols into the usrptest namespace +try: + # this might fail if the module is python-only + from usrptest_swig import * +except ImportError: + pass + +# import any pure python here +from phase_calc_ccf import phase_calc_ccf +# diff --git a/tools/gr-usrptest/python/build_utils.py b/tools/gr-usrptest/python/build_utils.py new file mode 100644 index 000000000..cf58a9763 --- /dev/null +++ b/tools/gr-usrptest/python/build_utils.py @@ -0,0 +1,226 @@ +# +# Copyright 2004,2009,2012 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. +# + +"""Misc utilities used at build time +""" + +import re, os, os.path +from build_utils_codes import * + + +# set srcdir to the directory that contains Makefile.am +try: + srcdir = os.environ['srcdir'] +except KeyError, e: + srcdir = "." +srcdir = srcdir + '/' + +# set do_makefile to either true or false dependeing on the environment +try: + if os.environ['do_makefile'] == '0': + do_makefile = False + else: + do_makefile = True +except KeyError, e: + do_makefile = False + +# set do_sources to either true or false dependeing on the environment +try: + if os.environ['do_sources'] == '0': + do_sources = False + else: + do_sources = True +except KeyError, e: + do_sources = True + +name_dict = {} + +def log_output_name (name): + (base, ext) = os.path.splitext (name) + ext = ext[1:] # drop the leading '.' + + entry = name_dict.setdefault (ext, []) + entry.append (name) + +def open_and_log_name (name, dir): + global do_sources + if do_sources: + f = open (name, dir) + else: + f = None + log_output_name (name) + return f + +def expand_template (d, template_filename, extra = ""): + '''Given a dictionary D and a TEMPLATE_FILENAME, expand template into output file + ''' + global do_sources + output_extension = extract_extension (template_filename) + template = open_src (template_filename, 'r') + output_name = d['NAME'] + extra + '.' + output_extension + log_output_name (output_name) + if do_sources: + output = open (output_name, 'w') + do_substitution (d, template, output) + output.close () + template.close () + +def output_glue (dirname): + output_makefile_fragment () + output_ifile_include (dirname) + +def output_makefile_fragment (): + global do_makefile + if not do_makefile: + return +# overwrite the source, which must be writable; this should have been +# checked for beforehand in the top-level Makefile.gen.gen . + f = open (os.path.join (os.environ.get('gendir', os.environ.get('srcdir', '.')), 'Makefile.gen'), 'w') + f.write ('#\n# This file is machine generated. All edits will be overwritten\n#\n') + output_subfrag (f, 'h') + output_subfrag (f, 'i') + output_subfrag (f, 'cc') + f.close () + +def output_ifile_include (dirname): + global do_sources + if do_sources: + f = open ('%s_generated.i' % (dirname,), 'w') + f.write ('//\n// This file is machine generated. All edits will be overwritten\n//\n') + files = name_dict.setdefault ('i', []) + files.sort () + f.write ('%{\n') + for file in files: + f.write ('#include <%s>\n' % (file[0:-1] + 'h',)) + f.write ('%}\n\n') + for file in files: + f.write ('%%include <%s>\n' % (file,)) + +def output_subfrag (f, ext): + files = name_dict.setdefault (ext, []) + files.sort () + f.write ("GENERATED_%s =" % (ext.upper ())) + for file in files: + f.write (" \\\n\t%s" % (file,)) + f.write ("\n\n") + +def extract_extension (template_name): + # template name is something like: GrFIRfilterXXX.h.t + # we return everything between the penultimate . and .t + mo = re.search (r'\.([a-z]+)\.t$', template_name) + if not mo: + raise ValueError, "Incorrectly formed template_name '%s'" % (template_name,) + return mo.group (1) + +def open_src (name, mode): + global srcdir + return open (os.path.join (srcdir, name), mode) + +def do_substitution (d, in_file, out_file): + def repl (match_obj): + key = match_obj.group (1) + # print key + return d[key] + + inp = in_file.read () + out = re.sub (r"@([a-zA-Z0-9_]+)@", repl, inp) + out_file.write (out) + + + +copyright = '''/* -*- c++ -*- */ +/* + * Copyright 2003,2004 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. + */ +''' + +def is_complex (code3): + if i_code (code3) == 'c' or o_code (code3) == 'c': + return '1' + else: + return '0' + + +def standard_dict (name, code3, package='gr'): + d = {} + d['NAME'] = name + d['NAME_IMPL'] = name+'_impl' + d['GUARD_NAME'] = 'INCLUDED_%s_%s_H' % (package.upper(), name.upper()) + d['GUARD_NAME_IMPL'] = 'INCLUDED_%s_%s_IMPL_H' % (package.upper(), name.upper()) + d['BASE_NAME'] = re.sub ('^' + package + '_', '', name) + d['SPTR_NAME'] = '%s_sptr' % name + d['WARNING'] = 'WARNING: this file is machine generated. Edits will be overwritten' + d['COPYRIGHT'] = copyright + d['TYPE'] = i_type (code3) + d['I_TYPE'] = i_type (code3) + d['O_TYPE'] = o_type (code3) + d['TAP_TYPE'] = tap_type (code3) + d['IS_COMPLEX'] = is_complex (code3) + return d + + +def standard_dict2 (name, code3, package): + d = {} + d['NAME'] = name + d['BASE_NAME'] = name + d['GUARD_NAME'] = 'INCLUDED_%s_%s_H' % (package.upper(), name.upper()) + d['WARNING'] = 'WARNING: this file is machine generated. Edits will be overwritten' + d['COPYRIGHT'] = copyright + d['TYPE'] = i_type (code3) + d['I_TYPE'] = i_type (code3) + d['O_TYPE'] = o_type (code3) + d['TAP_TYPE'] = tap_type (code3) + d['IS_COMPLEX'] = is_complex (code3) + return d + +def standard_impl_dict2 (name, code3, package): + d = {} + d['NAME'] = name + d['IMPL_NAME'] = name + d['BASE_NAME'] = name.rstrip("impl").rstrip("_") + d['GUARD_NAME'] = 'INCLUDED_%s_%s_H' % (package.upper(), name.upper()) + d['WARNING'] = 'WARNING: this file is machine generated. Edits will be overwritten' + d['COPYRIGHT'] = copyright + d['FIR_TYPE'] = "fir_filter_" + code3 + d['CFIR_TYPE'] = "fir_filter_" + code3[0:2] + 'c' + d['TYPE'] = i_type (code3) + d['I_TYPE'] = i_type (code3) + d['O_TYPE'] = o_type (code3) + d['TAP_TYPE'] = tap_type (code3) + d['IS_COMPLEX'] = is_complex (code3) + return d diff --git a/tools/gr-usrptest/python/build_utils_codes.py b/tools/gr-usrptest/python/build_utils_codes.py new file mode 100644 index 000000000..9ea96baae --- /dev/null +++ b/tools/gr-usrptest/python/build_utils_codes.py @@ -0,0 +1,52 @@ +# +# Copyright 2004 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. +# + +def i_code (code3): + return code3[0] + +def o_code (code3): + if len (code3) >= 2: + return code3[1] + else: + return code3[0] + +def tap_code (code3): + if len (code3) >= 3: + return code3[2] + else: + return code3[0] + +def i_type (code3): + return char_to_type[i_code (code3)] + +def o_type (code3): + return char_to_type[o_code (code3)] + +def tap_type (code3): + return char_to_type[tap_code (code3)] + + +char_to_type = {} +char_to_type['s'] = 'short' +char_to_type['i'] = 'int' +char_to_type['f'] = 'float' +char_to_type['c'] = 'gr_complex' +char_to_type['b'] = 'unsigned char' diff --git a/tools/gr-usrptest/python/flowgraphs/CMakeLists.txt b/tools/gr-usrptest/python/flowgraphs/CMakeLists.txt new file mode 100644 index 000000000..7ea94b505 --- /dev/null +++ b/tools/gr-usrptest/python/flowgraphs/CMakeLists.txt @@ -0,0 +1,28 @@ +# 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. + +######################################################################## +# Install python sources +######################################################################## +GR_PYTHON_INSTALL( + FILES + __init__.py + selftest_fg.py + phasealignment_fg.py DESTINATION ${GR_PYTHON_DIR}/usrptest/flowgraphs +) diff --git a/tools/gr-usrptest/python/flowgraphs/__init__.py b/tools/gr-usrptest/python/flowgraphs/__init__.py new file mode 100644 index 000000000..048e5638a --- /dev/null +++ b/tools/gr-usrptest/python/flowgraphs/__init__.py @@ -0,0 +1,14 @@ +""" +usrptest.flowgraphs +====================================== + +Contents +-------- + +Subpackages +----------- +:: + +The existance of the file turns the folder into a Python module. + +""" diff --git a/tools/gr-usrptest/python/flowgraphs/phasealignment_fg.py b/tools/gr-usrptest/python/flowgraphs/phasealignment_fg.py new file mode 100644 index 000000000..f120e2421 --- /dev/null +++ b/tools/gr-usrptest/python/flowgraphs/phasealignment_fg.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python2 + +from gnuradio import gr +from gnuradio import uhd +from gnuradio import analog +from usrptest import phase_calc_ccf, measurement_sink_f +from usrptest.functions import log_level +from random import uniform +from ast import literal_eval +import copy +import logging +import sys + + +class phasealignment_fg(gr.top_block): + def __init__(self, uhd_app): + gr.top_block.__init__(self, "Calculate dphi for all USRPs (Rx only)") + ############################## + # Block dicts + ############################## + self.log = logging.getLogger(__name__) + [self.log.removeHandler(h) for h in self.log.handlers] + self.log.addHandler(logging.StreamHandler(sys.stdout)) + self.log.setLevel(log_level(uhd_app.args.log_level)) + self.rx_streams = list() + self.phase_diff_calc = list() + self.measurement_sink = list() + self.uhd_app = copy.copy(uhd_app) + self.tx_app = copy.copy(uhd_app) + self.samp_rate = uhd_app.args.samp_rate + # Create all devices specified in --receiver + # Create all remaining blocks and connect devices to the first port and + # sink + self.uhd_app.args.num_chan = len(self.uhd_app.args.channels) + self.log.info('setting up usrp....') + ############################## + # Setup RX + ############################## + self.log.debug("RX-Setup with args: {}".format(self.uhd_app.args)) + self.uhd_app.setup_usrp(uhd.usrp_source, self.uhd_app.args) + if self.uhd_app.args.measurement_setup is not None: + self.measurement_channels = self.uhd_app.args.measurement_setup.strip( + ).split(',') + # make sure every channels is listed in measurement_channels at least once + if len(set( + self.measurement_channels)) != self.uhd_apps.args.num_chan: + self.uhd_app.vprint( + "[{prefix}] Number of measurement channels has to be the number of used channels." + ) + self.uhd_app.exit(1) + self.measurement_channels = [self.uhd_app.args.channels.index(m) for m in self.measurement_channels] + else: + self.measurement_channels = range(self.uhd_app.args.num_chan) + + self.measurement_channels_names = list() + for chan in self.measurement_channels: + usrp_info = self.uhd_app.usrp.get_usrp_info(chan) + self.measurement_channels_names.append("_".join( + [usrp_info['mboard_serial'], usrp_info['rx_serial']])) + + #Connect channels to first port of d_phi_calc_block and to measurement_sink + for num, chan in enumerate(self.measurement_channels[:-1]): + self.phase_diff_calc.append(phase_calc_ccf()) + self.measurement_sink.append( + measurement_sink_f( + int(self.uhd_app.args.samp_rate * + self.uhd_app.args.duration), self.uhd_app.args.runs)) + self.connect((self.uhd_app.usrp, chan), + (self.phase_diff_calc[num], 0)) + self.connect((self.phase_diff_calc[num], 0), + (self.measurement_sink[num], 0)) + # Connect devices to second port of d_phi_block + for num, chan in enumerate(self.measurement_channels[1:]): + self.connect((self.uhd_app.usrp, chan), + (self.phase_diff_calc[num], 1)) + ############################## + # Setup TX + ############################## + if self.uhd_app.args.tx_channels is not None: + self.tx_app.args.antenna = self.tx_app.args.tx_antenna + self.tx_app.args.channels = [ + int(chan.strip()) + for chan in self.tx_app.args.tx_channels.split(',') + ] + self.tx_app.usrp = None + self.log.debug("TX-Setup with args: {}".format(self.tx_app.args)) + self.tx_app.setup_usrp(uhd.usrp_sink, self.tx_app.args) + self.siggen = analog.sig_source_c(self.samp_rate, + analog.GR_COS_WAVE, + self.tx_app.args.tx_offset, 1.0) + for chan in range(len(self.tx_app.channels)): + self.connect((self.siggen, 0), (self.tx_app.usrp, chan)) + + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + + def retune_frequency(self, band_num=1, bands=1): + ref_chan = self.uhd_app.channels[0] + freq_range = literal_eval( + self.uhd_app.usrp.get_freq_range(ref_chan).__str__( + )) # returns tuple with (start_freq, end_freq, step) + + if bands > 1: + bw = (freq_range[1] - freq_range[0])/bands + freq_range = list(freq_range) + freq_range[0] = freq_range[0] + ((band_num-1) % bands)*bw + freq_range[1] = freq_range[0] + bw + + retune_freq = uniform(freq_range[0], freq_range[1]) + self.log.info('tune all channels to: {:f} MHz'.format(retune_freq / + 1e6)) + self.uhd_app.set_freq(retune_freq) + self.log.info('tune all channels to: {:f} MHz'.format( + self.uhd_app.args.freq / 1e6)) + self.uhd_app.set_freq(self.uhd_app.args.freq) diff --git a/tools/gr-usrptest/python/flowgraphs/selftest_fg.py b/tools/gr-usrptest/python/flowgraphs/selftest_fg.py new file mode 100644 index 000000000..cdfc35e74 --- /dev/null +++ b/tools/gr-usrptest/python/flowgraphs/selftest_fg.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +from gnuradio import blocks +from gnuradio import gr +from gnuradio import uhd +from usrptest import phase_calc_ccf +from gnuradio.uhd.uhd_app import UHDApp +import numpy as np + +class selftest_fg(gr.top_block): + + def __init__(self, freq, samp_rate, dphase, devices=list()): + gr.top_block.__init__(self, "Generate Signal extract phase") + + ################################################## + # Variables + ################################################## + self.samp_rate = samp_rate + self.freq = 10e3 + self.devices = devices + self.dphase = dphase + self.tx_gain = 50 + self.rx_gain = 50 + self.center_freq = freq + self.omega = 2*np.pi*self.freq + self.steps = np.arange( + self.samp_rate)*float(self.omega)/float(self.samp_rate) + self.reference = self.complex_sine(self.steps) + self.test_signal = self.complex_sine(self.steps+0.5*np.pi) + self.device_test = False + + ################################################## + # Block dicts + ################################################## + self.rx_devices = dict() + self.tx_devices = dict() + self.sink_dict = dict() + self.phase_dict = dict() + self.reference_source = blocks.vector_source_c(self.reference) + + if len(self.devices): + ################################################## + # Devices + ################################################## + self.device_test = True + #To reuse existing setup_usrp() command + for device in self.devices: + # Create and configure all devices + self.rx_devices[device] = uhd.usrp_source( + device, uhd.stream_args( + cpu_format="fc32", channel=range(1))) + self.tx_devices[device] = uhd.usrp_sink( + device, uhd.stream_args( + cpu_format="fc32", channel=range(1))) + self.rx_devices[device].set_samp_rate(self.samp_rate) + self.rx_devices[device].set_center_freq(self.center_freq, 0) + self.rx_devices[device].set_gain(self.rx_gain, 0) + self.tx_devices[device].set_samp_rate(self.samp_rate) + self.tx_devices[device].set_center_freq(self.center_freq, 0) + self.tx_devices[device].set_gain(self.tx_gain, 0) + self.sink_dict[device] = blocks.vector_sink_f() + self.phase_dict[device] = phase_calc_ccf( + self.samp_rate, self.freq) + for device in self.tx_devices.values(): + self.connect((self.reference_source, 0), (device, 0)) + + for device_key in self.rx_devices.keys(): + self.connect( + (self.rx_devices[device_key], 0), (self.phase_dict[device_key], 0)) + self.connect((self.reference_source, 0), + (self.phase_dict[device_key], 1)) + self.connect( + (self.phase_dict[device_key], 0), (self.sink_dict[device_key], 0)) + # Debug options + # self.sink_list.append(blocks.vector_sink_c()) + #self.connect((device, 0), (self.sink_list[-1], 0)) + # self.sink_list.append(blocks.vector_sink_c()) + #self.connect((self.reference_source, 0), (self.sink_list[-1], 0)) + else: + ################################################## + # Blocks + ################################################## + self.result = blocks.vector_sink_f(1) + self.test_source = blocks.vector_source_c(self.test_signal) + self.block_phase_calc = phase_calc_ccf( + self.samp_rate, self.freq) + + ################################################## + # Connections + ################################################## + self.connect((self.reference_source, 0), (self.block_phase_calc, 1)) + self.connect((self.test_source, 0), (self.block_phase_calc, 0)) + self.connect((self.block_phase_calc, 0), (self.result, 0)) + def complex_sine(self, steps): + return np.exp(1j*steps) + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + + def run(self): + self.start() + self.wait() + if self.device_test: + data = dict() + for device_key in self.sink_dict.keys(): + curr_data = self.sink_dict[device_key].data() + curr_data = curr_data[int(0.2*self.samp_rate):-int(0.2*self.samp_rate)] + phase_avg = np.average(curr_data) + if (np.max(curr_data) < phase_avg+self.dphase*0.5) and (np.min(curr_data) > phase_avg-self.dphase*0.5): + data[device_key] = phase_avg + else: + print("Error phase not settled") + + #Debug + # plt.ylim(-1, 1) + # plt.xlim(self.samp_rate/2.), (self.samp_rate/2.)+1000) + #for key in data: + # plt.plot(data[key]) + return data diff --git a/tools/gr-usrptest/python/functions.py b/tools/gr-usrptest/python/functions.py new file mode 100644 index 000000000..2ce2ee451 --- /dev/null +++ b/tools/gr-usrptest/python/functions.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python2 +import numpy as np +import time +import copy +import logging + + +def setup_phase_alignment_parser(parser): + test_group = parser.add_argument_group( + 'Phase alignment specific arguments') + test_group.add_argument( + '--runs', + default=10, + type=int, + help='Number of times to retune and measure d_phi') + test_group.add_argument( + '--duration', + default=5.0, + type=float, + help='Duration of a measurement run') + test_group.add_argument( + '--measurement-setup', + type=str, + help='Comma-seperated list of channel ids. Phase difference will be calculated between consecutive channels. default=(0,1,2,..,M-1) M: num_chan' + ) + test_group.add_argument( + '--log-level', + type=str, + choices=["critical", "error", "warning", "info", "debug"], + default="info") + test_group.add_argument( + '--freq-bands', + type=int, + help="Number of frequency bands in daughterboard range to randomly retune to", + default=1) + return parser + + +def setup_tx_phase_alignment_parser(parser): + tx_group = parser.add_argument_group( + 'TX Phase alignment specific arguments.') + tx_group.add_argument( + '--tx-channels', type=str, help='which channels to use') + tx_group.add_argument( + '--tx-antenna', + type=str, + help='comma-separated list of channel antennas for tx') + tx_group.add_argument( + '--tx-offset', + type=float, + help='frequency offset in Hz which should be added to center frequency for transmission' + ) + return parser + + +def setup_rts_phase_alignment_parser(parser): + rts_group = parser.add_argument_group( + 'RTS Phase alignment specific arguments') + rts_group.add_argument( + '-pd', + '--phasedev', + type=float, + default=1.0, + help='maximum phase standard deviation of dphi in a run which is considered settled (in deg)' + ) + rts_group.add_argument( + '-dp', + '--dphi', + type=float, + default=2.0, + help='maximum allowed d_phase deviation between runs (in deg)') + rts_group.add_argument( + '--freqlist', + type=str, + help='comma-separated list of frequencies to test') + rts_group.add_argument( + '--lv-host', + type=str, + help='specify this argument if running tests with vst/switch') + rts_group.add_argument('--lv-vst-name', type=str, help='vst device name') + rts_group.add_argument( + '--lv-switch-name', type=str, help='executive switch name') + rts_group.add_argument( + '--lv-basepath', + type=str, + help='basepath for LabVIEW VIs on Windows') + rts_group.add_argument( + '--tx-offset', + type=float, + help='transmitter frequency offset in VST') + rts_group.add_argument( + '--lv-switch-ports', type=str, help='comma-separated switch-port pair') + return parser + +def setup_manual_phase_alignment_parser(parser): + manual_group = parser.add_argument_group( + 'Manual Phase alignment specific arguments') + manual_group.add_argument( + '--plot', + dest='plot', + action='store_true', + help='Set this argument to enable plotting results with matplotlib' + ) + manual_group.add_argument( + '--auto', + action='store_true', + help='Set this argument to enable automatic selection of test frequencies' + ) + manual_group.add_argument( + '--start-freq', + type=float, + default=0.0, + help='Start frequency for automatic selection' + ), + manual_group.add_argument( + '--stop-freq', + type=float, + default=0.0, + help='Stop frequency for automatic selection') + + parser.set_defaults(plot=False,auto=False) + return parser + + +def process_measurement_sinks(top_block): + data = list() + curr_data = dict() + for num, chan in enumerate(top_block.measurement_channels[:-1]): + curr_data['avg'] = list(top_block.measurement_sink[num].get_avg()) + curr_data['stddev'] = list(top_block.measurement_sink[num].get_stddev( + )) + curr_data['first'] = top_block.measurement_channels_names[num] + curr_data['second'] = top_block.measurement_channels_names[num + 1] + data.append(copy.copy(curr_data)) + return data + + +def run_test(top_block, ntimes): + results = dict() + num_sinks = len(top_block.measurement_sink) + for i in xrange(ntimes): + #tune frequency to random position and back to specified frequency + top_block.retune_frequency(bands=top_block.uhd_app.args.freq_bands,band_num=i+1) + time.sleep(2) + #trigger start in all measurement_sinks + for sink in top_block.measurement_sink: + sink.start_run() + #wait until every measurement_sink is ready with the current run + while (sum([ms.get_run() for ms in top_block.measurement_sink]) < ( + (i + 1) * num_sinks)): + time.sleep(1) + results = process_measurement_sinks(top_block) + return results + + +def log_level(string): + return getattr(logging, string.upper()) diff --git a/tools/gr-usrptest/python/labview_control/CMakeLists.txt b/tools/gr-usrptest/python/labview_control/CMakeLists.txt new file mode 100644 index 000000000..924df5479 --- /dev/null +++ b/tools/gr-usrptest/python/labview_control/CMakeLists.txt @@ -0,0 +1,28 @@ +# 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. + +######################################################################## +# Install python sources +######################################################################## +GR_PYTHON_INSTALL( + FILES + __init__.py + lv_control.py DESTINATION ${GR_PYTHON_DIR}/usrptest/labview_control +) + diff --git a/tools/gr-usrptest/python/labview_control/__init__.py b/tools/gr-usrptest/python/labview_control/__init__.py new file mode 100644 index 000000000..28d2fe03b --- /dev/null +++ b/tools/gr-usrptest/python/labview_control/__init__.py @@ -0,0 +1,14 @@ +""" +usrptest.labview_control +====================================== + +Contents +-------- + +Subpackages +----------- +:: + +The existance of the file turns the folder into a Python module. + +""" diff --git a/tools/gr-usrptest/python/labview_control/lv_control.py b/tools/gr-usrptest/python/labview_control/lv_control.py new file mode 100644 index 000000000..27407a07c --- /dev/null +++ b/tools/gr-usrptest/python/labview_control/lv_control.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +from labview_automation.client import LabVIEWClient + +class vst_siggen: + def __init__(self,host,vi_base_path,rio_device): + self._host = host + self._path = vi_base_path + self._rio = rio_device + self._caller = lv_caller(host,2552,vi_base_path) + + def __del__(self): + self.disconnect() + + def set_freq(self, freq): + self._caller.vst_set_freq(self._rio,freq) + + def disconnect(self): + try: + self._caller.vst_disconnect(self._rio) + except: + pass + + +class executive_switch: + def __init__(self,host,vi_base_path,device_name): + self._host = host + self._path = vi_base_path + self._device = device_name + self._caller = lv_caller(host,2552,vi_base_path) + + def __del__(self): + self.disconnect_all() + + def connect_ports(self, port0, port1): + self._caller.switch_connect_ports(self._device,port0,port1) + + def disconnect_all(self): + try: + self._caller.switch_disconnect_all(self._device) + except: + pass + + + +class lv_caller: + def __init__(self, host, port, vi_base_path): + self._host = host + self._port = port + self._client = LabVIEWClient(host, port) + self._path = vi_base_path + + def vst_disconnect(self, rio_device): + with self._client as c: + control_values = { + "rio_device": rio_device, + } + result = c.run_vi_synchronous("".join([self._path,"vst_disconnect.vi"]),control_values) + return result + + def vst_set_freq(self, rio_device, freq): + with self._client as c: + control_values = { + "rio_device": rio_device, + "cw_freq": freq, + "power": 0, + } + result = c.run_vi_synchronous("".join([self._path,"vst_set_freq.vi"]),control_values) + return result + + def switch_connect_ports(self, switch_device, port0, port1): + with self._client as c: + control_values = { + "virtual_switch": switch_device, + "chan0": port0, + "chan1": port1, + } + result = c.run_vi_synchronous("".join([self._path,"switch_connect_ports.vi"]),control_values) + return result + + + def switch_disconnect_all(self, switch_device): + with self._client as c: + control_values = { + "virtual_switch": switch_device, + } + result = c.run_vi_synchronous("".join([self._path,"switch_disconnect.vi"]),control_values) + return result diff --git a/tools/gr-usrptest/python/phase_calc_ccf.py b/tools/gr-usrptest/python/phase_calc_ccf.py new file mode 100644 index 000000000..fe3cf55a8 --- /dev/null +++ b/tools/gr-usrptest/python/phase_calc_ccf.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2016 Ettus Research LLC. +# +# This 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. +# +# This software 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 software; 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 +from gnuradio import blocks +import numpy as np + + +class phase_calc_ccf(gr.hier_block2): + """ + docstring for block phase_calc_ccf + """ + + def __init__(self): + gr.hier_block2.__init__( + self, + "phase_calc_ccf", + gr.io_signature(2, 2, gr.sizeof_gr_complex), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Output signature + self.block = dict() + self.block['mult_conj'] = blocks.multiply_conjugate_cc() + self.block['arg'] = blocks.complex_to_arg() + self.block['mult_const'] = blocks.multiply_const_ff(180.0 / np.pi) + + self.connect((self, 0), (self.block['mult_conj'], 0)) + self.connect((self, 1), (self.block['mult_conj'], 1)) + self.connect((self.block['mult_conj'], 0), (self.block['arg'], 0)) + self.connect((self.block['arg'], 0), (self.block['mult_const'], 0)) + self.connect((self.block['mult_const'], 0), (self, 0)) diff --git a/tools/gr-usrptest/python/qa_measurement_sink_f.py b/tools/gr-usrptest/python/qa_measurement_sink_f.py new file mode 100755 index 000000000..ceb9913ae --- /dev/null +++ b/tools/gr-usrptest/python/qa_measurement_sink_f.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2016 Ettus Research LLC. +# +# This 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. +# +# This software 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 software; 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 gnuradio import blocks +import usrptest_swig as usrptest + +class qa_measurement_sink_f (gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test_001_t (self): + # set up fg + self.tb.run () + # check data + + +if __name__ == '__main__': + gr_unittest.run(qa_measurement_sink_f, "qa_measurement_sink_f.xml") diff --git a/tools/gr-usrptest/python/rts_tests/CMakeLists.txt b/tools/gr-usrptest/python/rts_tests/CMakeLists.txt new file mode 100644 index 000000000..03111a9bb --- /dev/null +++ b/tools/gr-usrptest/python/rts_tests/CMakeLists.txt @@ -0,0 +1,33 @@ +# 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. + +######################################################################## +# Install python sources +######################################################################## +GR_PYTHON_INSTALL( + FILES + __init__.py + test_phasealignment.py DESTINATION ${GR_PYTHON_DIR}/usrptest/rts_tests +) + +GR_PYTHON_INSTALL( + PROGRAMS + test_phasealignment.py + DESTINATION bin +) diff --git a/tools/gr-usrptest/python/rts_tests/__init__.py b/tools/gr-usrptest/python/rts_tests/__init__.py new file mode 100644 index 000000000..048e5638a --- /dev/null +++ b/tools/gr-usrptest/python/rts_tests/__init__.py @@ -0,0 +1,14 @@ +""" +usrptest.flowgraphs +====================================== + +Contents +-------- + +Subpackages +----------- +:: + +The existance of the file turns the folder into a Python module. + +""" diff --git a/tools/gr-usrptest/python/rts_tests/test_phasealignment.py b/tools/gr-usrptest/python/rts_tests/test_phasealignment.py new file mode 100755 index 000000000..12856bbdb --- /dev/null +++ b/tools/gr-usrptest/python/rts_tests/test_phasealignment.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2016 Ettus Research LLC. +# +# This 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. +# +# This software 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 software; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# +import unittest +from tinydb import TinyDB, Query +from usrptest.flowgraphs import phasealignment_fg +from usrptest.functions import setup_phase_alignment_parser, setup_tx_phase_alignment_parser, setup_rts_phase_alignment_parser, run_test, log_level +from usrptest.labview_control import lv_control +from gnuradio.uhd.uhd_app import UHDApp +import logging +import sys +import numpy as np +import argparse +import time +import copy + + +class gr_usrp_test(unittest.TestCase): + def __init__(self, methodName='runTest', args=None): + super(gr_usrp_test, self).__init__(methodName) + self.args = args + + +class qa_phasealignment(gr_usrp_test): + def setUp(self): + time.sleep(15) #Wait for devices to settle, just in case + if self.args.lv_host is not None: + self.siggen = lv_control.vst_siggen(self.args.lv_host, + self.args.lv_basepath, + self.args.lv_vst_name) + self.switch = lv_control.executive_switch(self.args.lv_host, + self.args.lv_basepath, + self.args.lv_switch_name) + self.siggen.set_freq(self.args.freq + self.args.tx_offset) + self.switch.connect_ports( + *self.args.lv_switch_ports.strip().split(',')) + + self.uhd_app = UHDApp(args=self.args) + self.log = logging.getLogger("test_phasealignment") + self.tb = phasealignment_fg.phasealignment_fg(self.uhd_app) + self.db = TinyDB('phase_db.json') + + def tearDown(self): + self.uhd_app = None + self.tb = None + if args.lv_host is not None: + self.siggen.disconnect() + self.switch.disconnect_all() + + def test_001(self): + self.tb.start() + time.sleep(2) + results = run_test( + self.tb, + self.args.runs) # dict key:dev, value: dict{dphase:[],stddev:[]} + time.sleep(1) + #self.tb.stop() + #self.tb.wait() + self.time_stamp = time.strftime('%Y%m%d%H%M') + self.passed = True + for result in results: + fdev = result['first'] + sdev = result['second'] + self.log.info('Comparing values for phase difference between {} and {}'. + format(fdev, sdev)) + dphase_list = result['avg'] + dev_list = result['stddev'] + dphase = np.average(dphase_list) + dev = np.average(dev_list) + ref_meas = get_reference_meas(self.db, fdev, sdev, self.args.freq) + for dphase_i in dphase_list: + passed = True + if abs(dphase_i - dphase) > self.args.dphi and passed: + self.log.info( + '\t dPhase of a measurement_run differs from average dhpase. dphase_run: {}, dphase_avg: {}'. + format(dphase_i, dphase)) + passed = False + if dev > self.args.phasedev: + self.log.info('\t dPhase deviates during measurement. stddev: {}'. + format(dev)) + passed = False + if ref_meas: + if abs(ref_meas['dphase'] - dphase) > self.args.dphi: + self.log.info( + '\t dPhase differs from reference measurement. Now: {}, reference: {}'. + format(dphase, ref_meas['dphase'])) + if not passed: + self.passed = False + else: + self.db.insert({ + 'dev1': fdev, + 'dev2': sdev, + 'timestamp': self.time_stamp, + 'dphase': dphase, + 'dphase_dev': dev, + 'freq': self.args.freq + }) + self.tb.stop() + self.tb.wait() + self.assertTrue(self.passed) + + +def get_previous_meas(db, dev1, dev2, freq): + meas = Query() + results = db.search((meas.dev1 == dev1) & (meas.dev2 == dev2) & (meas.freq + == freq)) + prev_result = dict() + if results: + prev_result = results[0] + for result in results: + if result['timestamp'] > prev_result['timestamp']: + prev_result = result + return prev_result + + +def get_reference_meas(db, dev1, dev2, freq): + meas = Query() + results = db.search((meas.dev1 == dev1) & (meas.dev2 == dev2) & (meas.freq + == freq)) + ref_result = dict() + if results: + ref_result = results[0] + for result in results: + if result['timestamp'] < ref_result['timestamp']: + ref_result = result + return ref_result + + +if __name__ == '__main__': + # parse all arguments + parser = argparse.ArgumentParser(conflict_handler='resolve') + parser = setup_phase_alignment_parser(parser) + parser = setup_tx_phase_alignment_parser(parser) + parser = setup_rts_phase_alignment_parser(parser) + logging.basicConfig(stream=sys.stdout) + UHDApp.setup_argparser(parser=parser) + args = parser.parse_args() + logging.getLogger("test_phasealignment").setLevel(log_level(args.log_level)) + + freqlist = args.freqlist.strip().split(',') + + def make_suite(testcase_class, freqlist): + testloader = unittest.TestLoader() + testnames = testloader.getTestCaseNames(testcase_class) + suite = unittest.TestSuite() + for name in testnames: + for freq in freqlist: + test_args = copy.deepcopy(args) + test_args.freq = float(freq) + suite.addTest(testcase_class(name, args=test_args)) + return suite + + # Add tests. + alltests = unittest.TestSuite() + alltests.addTest(make_suite(qa_phasealignment, freqlist)) + result = unittest.TextTestRunner(verbosity=2).run(alltests) # Run tests. + sys.exit(not result.wasSuccessful()) diff --git a/tools/gr-usrptest/python/setup.py b/tools/gr-usrptest/python/setup.py new file mode 100644 index 000000000..6969f413d --- /dev/null +++ b/tools/gr-usrptest/python/setup.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + + +setup(name='urptest_automation', + version='0.0.1', + description='usrptest integration into RTS and Labview', + packages=['usrptest_automation'], + install_requires=['labview-automation>=15.0.0.dev1','hoplite>=15.0.0.dev1'] + ) |